@@ -16,6 +16,16 @@ import 'paragraph.dart';
1616import 'ruler.dart' ;
1717import 'text_direction.dart' ;
1818
19+ /// A single canvas2d context to use for all text measurements.
20+ @visibleForTesting
21+ final DomCanvasRenderingContext2D textContext =
22+ // We don't use this canvas to draw anything, so let's make it as small as
23+ // possible to save memory.
24+ createDomCanvasElement (width: 0 , height: 0 ).context2D;
25+
26+ /// The last font used in the [textContext] .
27+ String ? _lastContextFont;
28+
1929/// Performs layout on a [CanvasParagraph] .
2030///
2131/// It uses a [DomCanvasElement] to measure text.
@@ -24,9 +34,6 @@ class TextLayoutService {
2434
2535 final CanvasParagraph paragraph;
2636
27- final DomCanvasRenderingContext2D context =
28- createDomCanvasElement ().context2D;
29-
3037 // *** Results of layout *** //
3138
3239 // Look at the Paragraph class for documentation of the following properties.
@@ -53,7 +60,7 @@ class TextLayoutService {
5360 ui.Rect get paintBounds => _paintBounds;
5461 ui.Rect _paintBounds = ui.Rect .zero;
5562
56- late final Spanometer spanometer = Spanometer (paragraph, context );
63+ late final Spanometer spanometer = Spanometer (paragraph);
5764
5865 late final LayoutFragmenter layoutFragmenter =
5966 LayoutFragmenter (paragraph.plainText, paragraph.spans);
@@ -882,10 +889,9 @@ class LineBuilder {
882889/// it's set, the [Spanometer] updates the underlying [context] so that
883890/// subsequent measurements use the correct styles.
884891class Spanometer {
885- Spanometer (this .paragraph, this .context );
892+ Spanometer (this .paragraph);
886893
887894 final CanvasParagraph paragraph;
888- final DomCanvasRenderingContext2D context;
889895
890896 static final RulerHost _rulerHost = RulerHost ();
891897
@@ -904,21 +910,31 @@ class Spanometer {
904910 _rulers.clear ();
905911 }
906912
907- String _cssFontString = '' ;
908-
909913 double ? get letterSpacing => currentSpan.style.letterSpacing;
910914
911915 TextHeightRuler ? _currentRuler;
912916 ParagraphSpan ? _currentSpan;
913917
914918 ParagraphSpan get currentSpan => _currentSpan! ;
915919 set currentSpan (ParagraphSpan ? span) {
920+ // Update the font string if it's different from the last applied font
921+ // string.
922+ //
923+ // Also, we need to update the font string even if the span isn't changing.
924+ // That's because `textContext` is shared across all spanometers.
925+ if (span != null ) {
926+ final String newCssFontString = span.style.cssFontString;
927+ if (_lastContextFont != newCssFontString) {
928+ _lastContextFont = newCssFontString;
929+ textContext.font = newCssFontString;
930+ }
931+ }
932+
916933 if (span == _currentSpan) {
917934 return ;
918935 }
919936 _currentSpan = span;
920937
921- // No need to update css font string when `span` is null.
922938 if (span == null ) {
923939 _currentRuler = null ;
924940 return ;
@@ -933,13 +949,6 @@ class Spanometer {
933949 _rulers[heightStyle] = ruler;
934950 }
935951 _currentRuler = ruler;
936-
937- // Update the font string if it's different from the previous span.
938- final String cssFontString = span.style.cssFontString;
939- if (_cssFontString != cssFontString) {
940- _cssFontString = cssFontString;
941- context.font = cssFontString;
942- }
943952 }
944953
945954 /// Whether the spanometer is ready to take measurements.
@@ -955,7 +964,7 @@ class Spanometer {
955964 double get height => _currentRuler! .height;
956965
957966 double measureText (String text) {
958- return measureSubstring (context , text, 0 , text.length);
967+ return measureSubstring (textContext , text, 0 , text.length);
959968 }
960969
961970 double measureRange (int start, int end) {
@@ -1047,7 +1056,7 @@ class Spanometer {
10471056 assert (end >= currentSpan.start && end <= currentSpan.end);
10481057
10491058 return measureSubstring (
1050- context ,
1059+ textContext ,
10511060 paragraph.plainText,
10521061 start,
10531062 end,
0 commit comments