@@ -78,36 +78,40 @@ class TextLayoutService {
7878 final Spanometer spanometer = Spanometer (paragraph, context);
7979
8080 int spanIndex = 0 ;
81- ParagraphSpan span = paragraph.spans[0 ];
8281 LineBuilder currentLine =
8382 LineBuilder .first (paragraph, spanometer, maxWidth: constraints.width);
8483
8584 // The only way to exit this while loop is by hitting one of the `break;`
8685 // statements (e.g. when we reach `endOfText`, when ellipsis has been
8786 // appended).
8887 while (true ) {
89- // *********************************************** //
90- // *** HANDLE HARD LINE BREAKS AND END OF TEXT *** //
91- // *********************************************** //
92-
93- if (currentLine.end.isHard) {
94- if (currentLine.isNotEmpty) {
88+ // ************************** //
89+ // *** HANDLE END OF TEXT *** //
90+ // ************************** //
91+
92+ // All spans have been consumed.
93+ final bool reachedEnd = spanIndex == spanCount;
94+ if (reachedEnd) {
95+ // In some cases, we need to extend the line to the end of text and
96+ // build it:
97+ //
98+ // 1. Line is not empty. This could happen when the last span is a
99+ // placeholder.
100+ //
101+ // 2. We haven't reached `LineBreakType.endOfText` yet. This could
102+ // happen when the last character is a new line.
103+ if (currentLine.isNotEmpty || currentLine.end.type != LineBreakType .endOfText) {
104+ currentLine.extendToEndOfText ();
95105 lines.add (currentLine.build ());
96- if (currentLine.end.type != LineBreakType .endOfText) {
97- currentLine = currentLine.nextLine ();
98- }
99- }
100-
101- if (currentLine.end.type == LineBreakType .endOfText) {
102- break ;
103106 }
107+ break ;
104108 }
105109
106110 // ********************************* //
107111 // *** THE MAIN MEASUREMENT PART *** //
108112 // ********************************* //
109113
110- final isLastSpan = spanIndex == spanCount - 1 ;
114+ final ParagraphSpan span = paragraph.spans[spanIndex] ;
111115
112116 if (span is PlaceholderSpan ) {
113117 if (currentLine.widthIncludingSpace + span.width <= constraints.width) {
@@ -121,11 +125,7 @@ class TextLayoutService {
121125 }
122126 currentLine.addPlaceholder (span);
123127 }
124-
125- if (isLastSpan) {
126- lines.add (currentLine.build ());
127- break ;
128- }
128+ spanIndex++ ;
129129 } else if (span is FlatTextSpan ) {
130130 spanometer.currentSpan = span;
131131 final LineBreakResult nextBreak = currentLine.findNextBreak (span.end);
@@ -138,6 +138,10 @@ class TextLayoutService {
138138
139139 // The line can extend to `nextBreak` without overflowing.
140140 currentLine.extendTo (nextBreak);
141+ if (nextBreak.type == LineBreakType .mandatory) {
142+ lines.add (currentLine.build ());
143+ currentLine = currentLine.nextLine ();
144+ }
141145 } else {
142146 // The chunk of text can't fit into the current line.
143147 final bool isLastLine =
@@ -165,23 +169,19 @@ class TextLayoutService {
165169 currentLine = currentLine.nextLine ();
166170 }
167171 }
172+
173+ // Only go to the next span if we've reached the end of this span.
174+ if (currentLine.end.index >= span.end) {
175+ currentLine.createBox ();
176+ ++ spanIndex;
177+ }
168178 } else {
169179 throw UnimplementedError ('Unknown span type: ${span .runtimeType }' );
170180 }
171181
172182 if (lines.length == maxLines) {
173183 break ;
174184 }
175-
176- // ********************************************* //
177- // *** ADVANCE TO THE NEXT SPAN IF NECESSARY *** //
178- // ********************************************* //
179-
180- // Only go to the next span if we've reached the end of this span.
181- if (currentLine.end.index >= span.end && spanIndex < spanCount - 1 ) {
182- currentLine.createBox ();
183- span = paragraph.spans[++ spanIndex];
184- }
185185 }
186186
187187 // ************************************************** //
@@ -205,20 +205,27 @@ class TextLayoutService {
205205 // ******************************** //
206206
207207 spanIndex = 0 ;
208- span = paragraph.spans[0 ];
209208 currentLine =
210209 LineBuilder .first (paragraph, spanometer, maxWidth: constraints.width);
211210
212- while (currentLine.end.type != LineBreakType .endOfText) {
211+ while (spanIndex < spanCount) {
212+ final ParagraphSpan span = paragraph.spans[spanIndex];
213+
213214 if (span is PlaceholderSpan ) {
214215 currentLine.addPlaceholder (span);
216+ spanIndex++ ;
215217 } else if (span is FlatTextSpan ) {
216218 spanometer.currentSpan = span;
217219 final LineBreakResult nextBreak = currentLine.findNextBreak (span.end);
218220
219221 // For the purpose of max intrinsic width, we don't care if the line
220222 // fits within the constraints or not. So we always extend it.
221223 currentLine.extendTo (nextBreak);
224+
225+ // Only go to the next span if we've reached the end of this span.
226+ if (currentLine.end.index >= span.end) {
227+ spanIndex++ ;
228+ }
222229 }
223230
224231 final double widthOfLastSegment = currentLine.lastSegment.width;
@@ -231,19 +238,9 @@ class TextLayoutService {
231238 maxIntrinsicWidth = currentLine.widthIncludingSpace;
232239 }
233240
234- if (currentLine.end.isHard ) {
241+ if (currentLine.end.type == LineBreakType .mandatory ) {
235242 currentLine = currentLine.nextLine ();
236243 }
237-
238- // Only go to the next span if we've reached the end of this span.
239- if (currentLine.end.index >= span.end) {
240- if (spanIndex < spanCount - 1 ) {
241- span = paragraph.spans[++ spanIndex];
242- } else {
243- // We reached the end of the last span in the paragraph.
244- break ;
245- }
246- }
247244 }
248245 }
249246
@@ -776,6 +773,23 @@ class LineBuilder {
776773 _addSegment (_createSegment (newEnd));
777774 }
778775
776+ void extendToEndOfText () {
777+ final LineBreakResult endOfText = LineBreakResult .sameIndex (
778+ paragraph.toPlainText ().length,
779+ LineBreakType .endOfText,
780+ );
781+
782+ // The spanometer may not be ready in some cases. E.g. when the paragraph
783+ // is made up of only placeholders and no text.
784+ if (spanometer.isReady) {
785+ ascent = math.max (ascent, spanometer.ascent);
786+ descent = math.max (descent, spanometer.descent);
787+ _addSegment (_createSegment (endOfText));
788+ } else {
789+ end = endOfText;
790+ }
791+ }
792+
779793 void addPlaceholder (PlaceholderSpan placeholder) {
780794 // Increase the line's height to fit the placeholder, if necessary.
781795 final double ascent, descent;
@@ -1024,7 +1038,7 @@ class LineBuilder {
10241038 final LineBreakResult boxEnd = end;
10251039 // Avoid creating empty boxes. This could happen when the end of a span
10261040 // coincides with the end of a line. In this case, `createBox` is called twice.
1027- if (boxStart == boxEnd) {
1041+ if (boxStart.index == boxEnd.index ) {
10281042 return ;
10291043 }
10301044
@@ -1150,6 +1164,9 @@ class Spanometer {
11501164 }
11511165 }
11521166
1167+ /// Whether the spanometer is ready to take measurements.
1168+ bool get isReady => _currentSpan != null ;
1169+
11531170 /// The distance from the top of the current span to the alphabetic baseline.
11541171 double get ascent => _currentRuler! .alphabeticBaseline;
11551172
0 commit comments