Skip to content

Commit 0037ef1

Browse files
committed
text: Add proportionalLetterSpacing
To confirm that [TextStyle.letterSpacing] is taken literally as logical pixels, as its dartdoc suggests it is, I rendered the text "||" with an extreme [TextStyle.letterSpacing] value of 40. Then it was easy to see that the letter spacing -- the horizontal distance between the two "|" characters -- wasn't changing as I adjusted the text-size slider in my iPhone settings. In the tests, the `textScaleFactors` list is copied from emoji_reaction_test. Perhaps there's a convenient place to define it centrally.
1 parent 4ba596f commit 0037ef1

File tree

2 files changed

+72
-1
lines changed

2 files changed

+72
-1
lines changed

lib/widgets/text.dart

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,3 +280,22 @@ FontWeight clampVariableFontWeight(double wght) {
280280
/// font's own custom-defined "wght" axis. But it's a great guess,
281281
/// at least without knowledge of the particular font.
282282
double wghtFromFontWeight(FontWeight fontWeight) => fontWeight.value.toDouble();
283+
284+
/// A [TextStyle.letterSpacing] value from a given proportion of the font size.
285+
///
286+
/// Returns [baseFontSize] scaled by the ambient [MediaQueryData.textScaler],
287+
/// multiplied by [proportion] (e.g., 0.01).
288+
///
289+
/// Using [MediaQueryData.textScaler] ensures that [proportion] is still
290+
/// respected when the device font size setting is adjusted.
291+
/// To opt out of this behavior, pass [TextScaler.noScaling] or some other value
292+
/// for [textScaler].
293+
double proportionalLetterSpacing(
294+
BuildContext context,
295+
double proportion, {
296+
required double baseFontSize,
297+
TextScaler? textScaler,
298+
}) {
299+
final effectiveTextScaler = textScaler ?? MediaQuery.textScalerOf(context);
300+
return effectiveTextScaler.scale(baseFontSize) * proportion;
301+
}

test/widgets/text_test.dart

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
import 'package:checks/checks.dart';
32
import 'package:flutter/material.dart';
43
import 'package:flutter_test/flutter_test.dart';
@@ -215,4 +214,57 @@ void main() {
215214
check(clampVariableFontWeight(999)) .equals(FontWeight.w900);
216215
check(clampVariableFontWeight(1000)) .equals(FontWeight.w900);
217216
});
217+
218+
group('proportionalLetterSpacing', () {
219+
Future<void> testLetterSpacing(
220+
String description, {
221+
required double Function(BuildContext context) getValue,
222+
double? ambientTextScaleFactor,
223+
required double expected,
224+
}) async {
225+
testWidgets(description, (WidgetTester tester) async {
226+
if (ambientTextScaleFactor != null) {
227+
tester.platformDispatcher.textScaleFactorTestValue = ambientTextScaleFactor;
228+
addTearDown(tester.platformDispatcher.clearTextScaleFactorTestValue);
229+
}
230+
await tester.pumpWidget(
231+
MaterialApp(
232+
home: Builder(builder: (context) => Text('',
233+
style: TextStyle(letterSpacing: getValue(context))))));
234+
235+
final TextStyle? style = tester.widget<Text>(find.byType(Text)).style;
236+
final actualLetterSpacing = style!.letterSpacing!;
237+
check((actualLetterSpacing - expected).abs()).isLessThan(0.0001);
238+
});
239+
}
240+
241+
testLetterSpacing('smoke 1',
242+
getValue: (context) => proportionalLetterSpacing(context, 0.01, baseFontSize: 14),
243+
expected: 0.14);
244+
245+
testLetterSpacing('smoke 2',
246+
getValue: (context) => proportionalLetterSpacing(context, 0.02, baseFontSize: 16),
247+
expected: 0.32);
248+
249+
for (final textScaleFactor in kTextScaleFactors) {
250+
testLetterSpacing('ambient text scale factor $textScaleFactor, no override',
251+
ambientTextScaleFactor: textScaleFactor,
252+
getValue: (context) => proportionalLetterSpacing(context, 0.01, baseFontSize: 14),
253+
expected: 0.14 * textScaleFactor);
254+
255+
testLetterSpacing('ambient text scale factor $textScaleFactor, override with no scaling',
256+
ambientTextScaleFactor: textScaleFactor,
257+
getValue: (context) => proportionalLetterSpacing(context,
258+
0.01, baseFontSize: 14, textScaler: TextScaler.noScaling),
259+
expected: 0.14);
260+
261+
final clampingTextScaler = TextScaler.linear(textScaleFactor)
262+
.clamp(minScaleFactor: 0.9, maxScaleFactor: 1.1);
263+
testLetterSpacing('ambient text scale factor $textScaleFactor, override with clamping',
264+
ambientTextScaleFactor: textScaleFactor,
265+
getValue: (context) => proportionalLetterSpacing(context,
266+
0.01, baseFontSize: 14, textScaler: clampingTextScaler),
267+
expected: clampingTextScaler.scale(14) * 0.01);
268+
}
269+
});
218270
}

0 commit comments

Comments
 (0)