Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/src/auto_size_builder/auto_size.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class _AutoSize extends RenderObjectWidget {
required this.stepGranularity,
required this.presetFontSizes,
this.onMaxPossibleFontSizeChanged,
this.placeholderDimensionsBuilder,
}) {
_validateProperties();
}
Expand All @@ -45,6 +46,7 @@ class _AutoSize extends RenderObjectWidget {
final List<double>? presetFontSizes;
final double? groupMaxFontSize;
final void Function(double)? onMaxPossibleFontSizeChanged;
final AutoSizePlaceholderDimensionsBuilder? placeholderDimensionsBuilder;

TextFitter _buildFitter() {
return TextFitter(
Expand All @@ -63,6 +65,7 @@ class _AutoSize extends RenderObjectWidget {
maxFontSize: maxFontSize,
stepGranularity: stepGranularity,
presetFontSizes: presetFontSizes,
placeholderDimensionsBuilder: placeholderDimensionsBuilder,
);
}

Expand Down
4 changes: 4 additions & 0 deletions lib/src/auto_size_builder/auto_size_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class _AutoSizeBuilder extends StatefulWidget {
this.stepGranularity,
this.presetFontSizes,
this.group,
this.placeholderDimensionsBuilder,
});

final _AutoSizeTextBuilder builder;
Expand Down Expand Up @@ -81,6 +82,8 @@ class _AutoSizeBuilder extends StatefulWidget {
/// {@macro auto_size_text.group}
final AutoSizeGroup? group;

final AutoSizePlaceholderDimensionsBuilder? placeholderDimensionsBuilder;

@override
State<_AutoSizeBuilder> createState() => _AutoSizeBuilderState();
}
Expand Down Expand Up @@ -148,6 +151,7 @@ class _AutoSizeBuilderState extends State<_AutoSizeBuilder> {
widget.group?._updateFontSize(this, maxPossibleFontSize);
}
: null,
placeholderDimensionsBuilder: widget.placeholderDimensionsBuilder,
);
}

Expand Down
30 changes: 30 additions & 0 deletions lib/src/auto_size_text.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
part of '../flutter_auto_size_text.dart';

/// Builds placeholder dimensions for [PlaceholderSpan]s while calculating the
/// font size.
///
/// If no builder is provided, or if the builder returns null for a placeholder,
/// that placeholder defaults to a square using the current candidate font size.
///
/// This may be called multiple times while [AutoSizeText] searches for the
/// largest fitting font size.
typedef AutoSizePlaceholderDimensionsBuilder = PlaceholderDimensions? Function(
PlaceholderSpan span,
int index,
double fontSize,
);

/// Flutter widget that automatically resizes text to fit perfectly within its
/// bounds.
///
Expand Down Expand Up @@ -160,6 +174,19 @@ class AutoSizeText extends StatelessWidget {
/// {@endtemplate}
final Function(bool overflow)? overflowCallback;

/// Builds dimensions for placeholders while calculating the font size.
///
/// This is useful for [TextSpan] trees with [WidgetSpan]s whose rendered
/// sizes are larger than the surrounding text. If omitted, placeholders are
/// measured as a square using the current candidate font size.
///
/// Return null to use the default dimensions for a placeholder.
///
/// The builder may be called multiple times while searching for the largest
/// fitting font size, so it should return stable dimensions for the same
/// inputs.
final AutoSizePlaceholderDimensionsBuilder? placeholderDimensionsBuilder;

/// Creates a [AutoSizeText]
///
/// If the [style] argument is null, the text will use the style from the
Expand Down Expand Up @@ -188,6 +215,7 @@ class AutoSizeText extends StatelessWidget {
this.wrapWords,
this.overflowReplacement,
this.overflowCallback,
this.placeholderDimensionsBuilder,
}) : textSpan = null;

/// Creates a [AutoSizeText] widget with a [TextSpan].
Expand Down Expand Up @@ -215,6 +243,7 @@ class AutoSizeText extends StatelessWidget {
this.wrapWords,
this.overflowReplacement,
this.overflowCallback,
this.placeholderDimensionsBuilder,
}) : data = null;

@override
Expand Down Expand Up @@ -262,6 +291,7 @@ class AutoSizeText extends StatelessWidget {
maxLines: maxLines,
textWidthBasis: textWidthBasis,
textHeightBehavior: textHeightBehavior,
placeholderDimensionsBuilder: placeholderDimensionsBuilder,
);
}
}
38 changes: 37 additions & 1 deletion lib/src/text_fitter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class TextFitter {
required this.maxFontSize,
this.stepGranularity = 1.0,
this.presetFontSizes,
this.placeholderDimensionsBuilder,
});

final TextSpan text;
Expand All @@ -36,6 +37,7 @@ class TextFitter {
final double maxFontSize;
final double stepGranularity;
final List<double>? presetFontSizes;
final AutoSizePlaceholderDimensionsBuilder? placeholderDimensionsBuilder;

double? _longestWordWidth;

Expand Down Expand Up @@ -131,6 +133,7 @@ class TextFitter {
textHeightBehavior: textHeightBehavior,
);

textPainter.setPlaceholderDimensions(_placeholderDimensions(scale));
textPainter.layout(maxWidth: constraints.maxWidth);

final overflow = textPainter.didExceedMaxLines ||
Expand Down Expand Up @@ -163,6 +166,37 @@ class TextFitter {
return result;
}

List<PlaceholderDimensions>? _placeholderDimensions(double scale) {
final dimensions = <PlaceholderDimensions>[];
final fontSize = (text.style?.fontSize ?? _kDefaultFontSize) * scale;
var index = 0;

text.visitChildren((span) {
if (span is PlaceholderSpan) {
final placeholderDimensions = placeholderDimensionsBuilder?.call(
span,
index,
fontSize,
);

dimensions.add(
placeholderDimensions ??
PlaceholderDimensions(
size: Size(fontSize, fontSize),
alignment: span.alignment,
baseline: span.baseline,
baselineOffset: fontSize * 0.8,
),
);
index += 1;
}

return true;
});

return dimensions.isEmpty ? null : dimensions;
}

List<TextSpan> _getWordSpans() {
final wordRegex = RegExp('\\s+');
Iterable<TextSpan> splitSpan(TextSpan span, TextStyle? style) sync* {
Expand Down Expand Up @@ -203,7 +237,8 @@ class TextFitter {
other.minFontSize == minFontSize &&
other.maxFontSize == maxFontSize &&
other.stepGranularity == stepGranularity &&
other.presetFontSizes == presetFontSizes;
other.presetFontSizes == presetFontSizes &&
other.placeholderDimensionsBuilder == placeholderDimensionsBuilder;
}

@override
Expand All @@ -223,6 +258,7 @@ class TextFitter {
maxFontSize,
stepGranularity,
presetFontSizes,
placeholderDimensionsBuilder,
);
}
}
Expand Down
115 changes: 115 additions & 0 deletions test/basic_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,119 @@ void main() {
);
expect(text.key, textKey);
});
testWidgets('Uses placeholderDimensionsBuilder for WidgetSpans',
(tester) async {
final indexes = <int>[];
final spans = <PlaceholderSpan>[];
final fontSizes = <double>[];

await pump(
tester: tester,
widget: SizedBox(
width: 1000,
height: 1000,
child: AutoSizeText.rich(
const TextSpan(
children: [
TextSpan(text: 'One '),
WidgetSpan(child: SizedBox(width: 12, height: 12)),
TextSpan(text: ' two '),
WidgetSpan(child: SizedBox(width: 16, height: 16)),
],
),
style: const TextStyle(fontSize: 20),
placeholderDimensionsBuilder: (span, index, fontSize) {
indexes.add(index);
spans.add(span);
fontSizes.add(fontSize);

return PlaceholderDimensions(
size: Size(fontSize, fontSize),
alignment: span.alignment,
baseline: span.baseline,
baselineOffset: fontSize * 0.8,
);
},
),
),
);

expect(indexes, [0, 1]);
expect(spans, everyElement(isA<WidgetSpan>()));
expect(fontSizes, [20, 20]);
});

testWidgets('Uses font-size placeholder dimensions by default',
(tester) async {
await pump(
tester: tester,
widget: const AutoSizeText.rich(
TextSpan(
children: [
TextSpan(text: 'Inline '),
WidgetSpan(child: Icon(Icons.add, size: 14)),
],
),
style: TextStyle(fontSize: 20),
),
);

expect(tester.takeException(), isNull);
});

testWidgets('Uses default placeholder dimensions when builder returns null',
(tester) async {
final text = await pumpAndGet<RichText>(
tester: tester,
widget: SizedBox(
width: 50,
height: 100,
child: AutoSizeText.rich(
const TextSpan(
children: [
TextSpan(text: 'A'),
WidgetSpan(child: SizedBox(width: 12, height: 12)),
],
),
maxLines: 1,
minFontSize: 10,
style: const TextStyle(fontSize: 40),
placeholderDimensionsBuilder: (span, index, fontSize) => null,
),
),
);

expect(effectiveFontSize(text), lessThan(40));
});

testWidgets('Placeholder dimensions influence font size', (tester) async {
final text = await pumpAndGet<RichText>(
tester: tester,
widget: SizedBox(
width: 130,
height: 100,
child: AutoSizeText.rich(
const TextSpan(
children: [
TextSpan(text: 'A'),
WidgetSpan(child: SizedBox(width: 12, height: 12)),
],
),
maxLines: 1,
minFontSize: 10,
style: const TextStyle(fontSize: 40),
placeholderDimensionsBuilder: (span, index, fontSize) {
return PlaceholderDimensions(
size: Size(120, fontSize),
alignment: span.alignment,
baseline: span.baseline,
baselineOffset: fontSize * 0.8,
);
},
),
),
);

expect(effectiveFontSize(text), lessThan(40));
});
}