Skip to content

Commit cd943a7

Browse files
author
Gilles Debunne
committed
Fixed text rendering issue with spans.
Bug 6598784 The algorithm uses three imbricated loops: - paragraphs - span regions (called "blocks" in that description) in these - characters in these We can ignore the paragraphs and assume paraStart==0. The span region loop cuts the text into blocks of text which share the same set of MetricAffectingSpan spans applied to them. Note that spanStart and spanEnd represent such a range, and not necessarily an actual span range. The third loop then iterates over the characters of these blocks, and creates a new line (calling out() as soon as the width has been reached. The core of the problem comes from the 'nextSpanStart' variable. It is used to restart the block loop from a previous position in case a line has been created that does not intersect with the current block. However, in case the current block is larger than the width of the text, the character loop is going to create other lines of text before we exit the j-loop. Going back to the block loop, we reset spanStart to the nextSpanStart, which may be too far back in the text. As a result, the same range of characters is measured again. The (spanStart == spanEnd) test was used to handle the case where nextSpanStart was indeed assigned to a value different than spanEnd. This fix simplifies this logic and removes the nextSpanStart variable: When the created line ends before the current block (here < spanStart), we immediately exit the character loop, re-starting the block loop from the current position. Patch 4: added a fix in measured to handle overlapping character ranges. Change-Id: Ie71b3cf4018b332e335ea916fef08acb43a6679e
1 parent 913bf80 commit cd943a7

File tree

2 files changed

+32
-33
lines changed

2 files changed

+32
-33
lines changed

core/java/android/text/MeasuredText.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ static MeasuredText recycle(MeasuredText mt) {
8282
return null;
8383
}
8484

85+
void setPos(int pos) {
86+
mPos = pos;
87+
}
88+
8589
/**
8690
* Analyzes text for bidirectional runs. Allocates working buffers.
8791
*/
@@ -113,7 +117,7 @@ void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textD
113117
if (startInPara < 0) startInPara = 0;
114118
if (endInPara > len) endInPara = len;
115119
for (int j = startInPara; j < endInPara; j++) {
116-
mChars[j] = '\uFFFC';
120+
mChars[j] = '\uFFFC'; // object replacement character
117121
}
118122
}
119123
}

core/java/android/text/StaticLayout.java

Lines changed: 27 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -246,12 +246,17 @@ public StaticLayout(CharSequence source, int bufstart, int bufend,
246246
int width = firstWidth;
247247

248248
float w = 0;
249+
// here is the offset of the starting character of the line we are currently measuring
249250
int here = paraStart;
250251

252+
// ok is a character offset located after a word separator (space, tab, number...) where
253+
// we would prefer to cut the current line. Equals to here when no such break was found.
251254
int ok = paraStart;
252255
float okWidth = w;
253256
int okAscent = 0, okDescent = 0, okTop = 0, okBottom = 0;
254257

258+
// fit is a character offset such that the [here, fit[ range fits in the allowed width.
259+
// We will cut the line there if no ok position is found.
255260
int fit = paraStart;
256261
float fitWidth = w;
257262
int fitAscent = 0, fitDescent = 0, fitTop = 0, fitBottom = 0;
@@ -260,30 +265,22 @@ public StaticLayout(CharSequence source, int bufstart, int bufend,
260265
boolean hasTab = false;
261266
TabStops tabStops = null;
262267

263-
for (int spanStart = paraStart, spanEnd = spanStart, nextSpanStart;
264-
spanStart < paraEnd; spanStart = nextSpanStart) {
265-
266-
if (spanStart == spanEnd) {
267-
if (spanned == null)
268-
spanEnd = paraEnd;
269-
else
270-
spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
271-
MetricAffectingSpan.class);
268+
for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
272269

270+
if (spanned == null) {
271+
spanEnd = paraEnd;
273272
int spanLen = spanEnd - spanStart;
274-
if (spanned == null) {
275-
measured.addStyleRun(paint, spanLen, fm);
276-
} else {
277-
MetricAffectingSpan[] spans =
273+
measured.addStyleRun(paint, spanLen, fm);
274+
} else {
275+
spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
276+
MetricAffectingSpan.class);
277+
int spanLen = spanEnd - spanStart;
278+
MetricAffectingSpan[] spans =
278279
spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
279-
spans = TextUtils.removeEmptySpans(spans, spanned,
280-
MetricAffectingSpan.class);
281-
measured.addStyleRun(paint, spans, spanLen, fm);
282-
}
280+
spans = TextUtils.removeEmptySpans(spans, spanned, MetricAffectingSpan.class);
281+
measured.addStyleRun(paint, spans, spanLen, fm);
283282
}
284283

285-
nextSpanStart = spanEnd;
286-
287284
int fmTop = fm.top;
288285
int fmBottom = fm.bottom;
289286
int fmAscent = fm.ascent;
@@ -343,8 +340,6 @@ public StaticLayout(CharSequence source, int bufstart, int bufend,
343340
w += widths[j - paraStart];
344341
}
345342

346-
// Log.e("text", "was " + before + " now " + w + " after " + c + " within " + width);
347-
348343
if (w <= width) {
349344
fitWidth = w;
350345
fit = j + 1;
@@ -373,7 +368,6 @@ public StaticLayout(CharSequence source, int bufstart, int bufend,
373368
* except for NS (non-starters), which can be broken
374369
* after but not before.
375370
*/
376-
377371
if (c == CHAR_SPACE || c == CHAR_TAB ||
378372
((c == CHAR_DOT || c == CHAR_COMMA ||
379373
c == CHAR_COLON || c == CHAR_SEMICOLON) &&
@@ -437,17 +431,9 @@ public StaticLayout(CharSequence source, int bufstart, int bufend,
437431
needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
438432
chs, widths, paraStart, ellipsize, ellipsizedWidth,
439433
currentTextWidth, paint, moreChars);
440-
here = endPos;
441-
442-
if (here < spanStart) {
443-
// didn't output all the text for this span
444-
// we've measured the raw widths, though, so
445-
// just reset the start point
446-
j = nextSpanStart = here;
447-
} else {
448-
j = here - 1; // continue looping
449-
}
450434

435+
here = endPos;
436+
j = here - 1; // restart j-span loop from here, compensating for the j++
451437
ok = fit = here;
452438
w = 0;
453439
fitAscent = fitDescent = fitTop = fitBottom = 0;
@@ -456,7 +442,16 @@ public StaticLayout(CharSequence source, int bufstart, int bufend,
456442
if (--firstWidthLineLimit <= 0) {
457443
width = restWidth;
458444
}
445+
446+
if (here < spanStart) {
447+
// The text was cut before the beginning of the current span range.
448+
// Exit the span loop, and get spanStart to start over from here.
449+
measured.setPos(here);
450+
spanEnd = here;
451+
break;
452+
}
459453
}
454+
// FIXME This should be moved in the above else block which changes mLineCount
460455
if (mLineCount >= mMaximumVisibleLineCount) {
461456
break;
462457
}

0 commit comments

Comments
 (0)