Skip to content

Commit

Permalink
#615 Force output of text rather than throw upon infinite loop.
Browse files Browse the repository at this point in the history
Also move sanity checks for LineBreakContext to class and make them asserts.
  • Loading branch information
danfickle committed Dec 11, 2020
1 parent d7fdf63 commit 7400d52
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ public static BreakTextResult breakText(
int avail,
CalculatedStyle style,
boolean tryToBreakAnywhere,
int lineWidth) {
int lineWidth,
boolean forceOutput) {

FSFont font = style.getFSFont(c);
IdentValue whitespace = style.getWhitespace();
Expand All @@ -132,21 +133,24 @@ public static BreakTextResult breakText(
// ====== handle nowrap
if (whitespace == IdentValue.NOWRAP) {
int width = Breaker.getTextWidthWithLetterSpacing(c, font, context.getMaster(), letterSpacing);
if (width <= avail) {
if (width <= avail || forceOutput) {
c.setLineBreakedBecauseOfNoWrap(false);
context.setEnd(context.getLast());
context.setWidth(width);
context.setNeedsNewLine(false);
return BreakTextResult.FINISHED;
} else if (!c.isLineBreakedBecauseOfNoWrap()) {
c.setLineBreakedBecauseOfNoWrap(true);
context.setEnd(context.getStart());
context.setWidth(0);
context.setNeedsNewLine(true);
context.setUnbreakable(true);
return BreakTextResult.DANGER_RECONSUME_WORD_ON_NL;
} else {
c.setLineBreakedBecauseOfNoWrap(false);
context.setEnd(context.getLast());
context.setWidth(width);
context.setNeedsNewLine(false);
return BreakTextResult.FINISHED;
}
}
Expand All @@ -164,6 +168,7 @@ public static BreakTextResult breakText(
} else if (whitespace == IdentValue.PRE) {
context.setEnd(context.getLast());
context.setWidth(Breaker.getTextWidthWithLetterSpacing(c, font, context.getCalculatedSubstring(), letterSpacing));
context.setNeedsNewLine(false);
}
}

Expand Down Expand Up @@ -208,8 +213,8 @@ public static BreakTextResult breakText(
break LOOP;

case CHAR_BREAKING_UNBREAKABLE:
if (totalWidth == 0 &&
avail == lineWidth) {
if ((totalWidth == 0 && avail == lineWidth) ||
forceOutput) {
// We are at the start of the line but could not fit a single character!
totalWidth += context.getWidth();
breakResult = BreakTextResult.CHAR_UNBREAKABLE_BUT_CONSUMED;
Expand All @@ -219,6 +224,7 @@ public static BreakTextResult breakText(
// FIXME: This is very dangerous and has led to infinite
// loops. Needs review.
context.setEnd(savedEnd);
context.setNeedsNewLine(true);
breakResult = BreakTextResult.DANGER_RECONSUME_CHAR_ON_NL;
break LOOP;
}
Expand All @@ -243,7 +249,8 @@ public static BreakTextResult breakText(
}
case WORD_BREAKING_UNBREAKABLE: {
if (context.getWidth() >= lineWidth ||
context.isFirstCharInLine()) {
context.isFirstCharInLine() ||
forceOutput) {
// If the word is too long to fit on a line by itself or
// if we are at the start of a line,
// retry in character breaking mode.
Expand Down Expand Up @@ -295,7 +302,7 @@ private static BreakTextResult toBreakTextResult(LineBreakResult res) {
case CHAR_BREAKING_NEED_NEW_LINE: // Fall-thru
case CHAR_BREAKING_UNBREAKABLE: // Fall-thru
default:
throw new RuntimeException(); // TODO: Log
throw new RuntimeException("PROGRAMMER ERROR: Unexpected LineBreakResult from word wrap");
}
}

Expand All @@ -320,12 +327,12 @@ private static LineBreakResult doBreakText(

String currentString = context.getStartSubstring();
FSTextBreaker lineIterator = STANDARD_LINE_BREAKER.getBreaker(currentString, c.getSharedContext());
FSTextBreaker charIterator = STANDARD_CHARACTER_BREAKER.getBreaker(currentString, c.getSharedContext());
FSTextBreaker charIterator = STANDARD_CHARACTER_BREAKER.getBreaker(currentString, c.getSharedContext());

return doBreakCharacters(currentString, lineIterator, charIterator, context, avail, letterSpacing, measurer);
}
}

/**
* Breaks at most one word (until the next word break) going character by character to see
* what will fit in.
Expand Down Expand Up @@ -393,11 +400,12 @@ static LineBreakResult doBreakCharacters(
graphicsLength > 0) {
// Exact fit..
boolean needNewLine = currentString.length() > left;

context.setNeedsNewLine(needNewLine);
context.setEnd(left + context.getStart());
context.setWidth(graphicsLength);

context.setUnbreakable(false);

if (left >= currentString.length()) {
return LineBreakResult.CHAR_BREAKING_FINISHED;
} else if (left >= nextWordBreak) {
Expand Down Expand Up @@ -430,6 +438,7 @@ static LineBreakResult doBreakCharacters(
context.setWidth(graphicsLength);
context.setEnd(nextCharBreak + context.getStart());
context.setEndsOnWordBreak(nextCharBreak == nextWordBreak);
context.setUnbreakable(false);

if (nextCharBreak >= currentString.length()) {
return LineBreakResult.CHAR_BREAKING_FINISHED;
Expand All @@ -448,8 +457,10 @@ static LineBreakResult doBreakCharacters(
context.setWidth(lastGoodGraphicsLength);
context.setEnd(lastGoodWrap + context.getStart());
context.setEndsOnWordBreak(lastGoodWrap == nextWordBreak);
context.setUnbreakable(false);

if (lastGoodWrap >= currentString.length()) {
context.setNeedsNewLine(false);
return LineBreakResult.CHAR_BREAKING_FINISHED;
} else if (lastGoodWrap >= nextWordBreak) {
return LineBreakResult.CHAR_BREAKING_FOUND_WORD_BREAK;
Expand All @@ -466,13 +477,15 @@ static LineBreakResult doBreakCharacters(
context.setEnd(end + context.getStart());
context.setEndsOnWordBreak(end == nextWordBreak);
context.setWidth(splitWidth);

context.setNeedsNewLine(end < currentString.length());

return LineBreakResult.CHAR_BREAKING_UNBREAKABLE;
} else {
// Empty string.
context.setEnd(context.getStart());
context.setWidth(0);
context.setNeedsNewLine(false);
context.setUnbreakable(false);

return LineBreakResult.CHAR_BREAKING_FINISHED;
}
Expand Down Expand Up @@ -588,6 +601,7 @@ static LineBreakResult doBreakTextWords(
if (current.graphicsLength <= avail) {
context.setWidth(current.graphicsLength);
context.setEnd(context.getMaster().length());
context.setNeedsNewLine(false);
// It all fit!
return LineBreakResult.WORD_BREAKING_FINISHED;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,6 @@ public static void layoutContent(LayoutContext c, BlockBox box, int initialY, in
}

boolean inCharBreakingMode = false;
int troublesomeStartPosition = -1;
int troublesomeAttemptCount = 0;

do {
lbContext.reset();
Expand Down Expand Up @@ -191,34 +189,24 @@ public static void layoutContent(LayoutContext c, BlockBox box, int initialY, in
needFirstLetter = false;
} else {
if (style.getWordWrap() != IdentValue.BREAK_WORD) {
StartInlineTextResult result = startInlineText(c, lbContext, inlineBox, space, current, fit, trimmedLeadingSpace, false);
StartInlineTextResult result = startInlineText(c, lbContext, inlineBox, space, current, fit, trimmedLeadingSpace, false, lbContext.possibleEndlessLoop());
if (result == StartInlineTextResult.RECONSUME_BELOW_FLOATS) {
lbContext.newLine();
continue;
}
} else {
StartInlineTextResult result = startInlineText(c, lbContext, inlineBox, space, current, fit, trimmedLeadingSpace, inCharBreakingMode);
StartInlineTextResult result = startInlineText(c, lbContext, inlineBox, space, current, fit, trimmedLeadingSpace, inCharBreakingMode, lbContext.possibleEndlessLoop());
inCharBreakingMode = lbContext.isFinishedInCharBreakingMode();

if (result == StartInlineTextResult.RECONSUME_BELOW_FLOATS) {
lbContext.newLine();
continue;
} else if (result == StartInlineTextResult.RECONSUME_UNBREAKABLE_ON_NEW_LINE) {
if (troublesomeStartPosition == lbContext.getStart()) {
troublesomeAttemptCount++;
} else {
troublesomeStartPosition = lbContext.getStart();
troublesomeAttemptCount = 1;
}

if (troublesomeAttemptCount > 5) {
XRLog.log(Level.SEVERE, LogMessageId.LogMessageId2Param.GENERAL_FATAL_INFINITE_LOOP_BUG_IN_LINE_BREAKING_ALGO,
lbContext.getStartSubstring(), lbContext.getEnd());
throw new RuntimeException("Infinite loop bug in break-word line breaking algorithm!");
}
}
}
} }
}

if (lbContext.isNeedsNewLine()) {
lbContext.newLine();

startNewInlineLine(c, box, breakAtLine, blockLayoutDirection, space, current, previous,
contentStart, openInlineBoxes, iBMap, minimumLineHeight, markerData, pendingFloats,
hasFirstLinePEs, pendingInlineLayers, lineOffset, inlineBox, lbContext);
Expand Down Expand Up @@ -402,26 +390,34 @@ private enum StartInlineTextResult {
* layout box.
* Otherwise, if there are floats and the current line is otherwise empty, moves below float and trys again.
* Otherwise, trys again on a new line.
* @return true if the line is finished, false if we must continue
*/
private static StartInlineTextResult startInlineText(
LayoutContext c, LineBreakContext lbContext, InlineBox inlineBox,
SpaceVariables space, StateVariables current, int fit,
boolean trimmedLeadingSpace, boolean tryToBreakAnywhere) {
LayoutContext c,
LineBreakContext lbContext,
InlineBox inlineBox,
SpaceVariables space,
StateVariables current,
int fit,
boolean trimmedLeadingSpace,
boolean tryToBreakAnywhere,
boolean forceOutput) {

lbContext.saveEnd();
CalculatedStyle style = inlineBox.getStyle();

// Layout the text into the remaining width on this line. Will only go to the end of the line (at most)
// and will produce one InlineText object.
InlineText inlineText = layoutText(
c, style, space.remainingWidth - fit, lbContext, false, inlineBox.getTextDirection(), tryToBreakAnywhere, space.maxAvailableWidth - fit);
c, style, space.remainingWidth - fit, lbContext, false, inlineBox.getTextDirection(), tryToBreakAnywhere, space.maxAvailableWidth - fit, forceOutput);

if (style.hasLetterSpacing()) {
inlineText.setLetterSpacing(style.getFloatPropertyProportionalWidth(CSSName.LETTER_SPACING, 0, c));
}

if (lbContext.isUnbreakable() && !current.line.isContainsContent()) {

if (lbContext.isUnbreakable() &&
!current.line.isContainsContent() &&
!forceOutput) {

int delta = c.getBlockFormattingContext().getNextLineBoxDelta(c, current.line, space.maxAvailableWidth);

if (delta > 0) {
Expand All @@ -444,7 +440,13 @@ private static StartInlineTextResult startInlineText(
if (!lbContext.isUnbreakable() ||
(lbContext.isUnbreakable() &&
!current.line.isContainsContent() &&
lbContext.getEnd() > lbContext.getStart())) {
lbContext.getEnd() > lbContext.getStart()) ||
forceOutput) {

if (forceOutput) {
XRLog.log(Level.SEVERE, LogMessageId.LogMessageId1Param.GENERAL_FORCED_OUTPUT_TO_AVOID_INFINITE_LOOP, lbContext.getCalculatedSubstring());
}

// We can use the inline text by adding it to the current inline layout box.
// We also mark the text as consumed by the line break context and reduce the width
// we have remaining on this line.
Expand Down Expand Up @@ -556,8 +558,8 @@ private static InlineLayoutBox addFirstLetterBox(LayoutContext c, LineBox curren
currentIB.addInlineChild(c, iB);
current.setContainsContent(true);

InlineText text = layoutText(c, iB.getStyle(), remainingWidth, lbContext, true, textDirection, true, maxAvailableWidth);
InlineText text = layoutText(c, iB.getStyle(), remainingWidth, lbContext, true, textDirection, true, maxAvailableWidth, false);

if (iB.getStyle().hasLetterSpacing()) {
text.setLetterSpacing(iB.getStyle().getFloatPropertyProportionalWidth(CSSName.LETTER_SPACING, 0, c));
}
Expand Down Expand Up @@ -1109,7 +1111,8 @@ private static InlineText layoutText(
boolean needFirstLetter,
byte textDirection,
boolean tryToBreakAnywhere,
int lineWidth) {
int lineWidth,
boolean forceOutput) {

InlineText result = new InlineText();
String masterText = lbContext.getMaster();
Expand All @@ -1120,8 +1123,8 @@ private static InlineText layoutText(
Breaker.breakFirstLetter(c, lbContext, remainingWidth, style);
} else {
BreakTextResult breakResult =
Breaker.breakText(c, lbContext, remainingWidth, style, tryToBreakAnywhere, lineWidth);
checkBreakResult(breakResult, lbContext, tryToBreakAnywhere);
Breaker.breakText(c, lbContext, remainingWidth, style, tryToBreakAnywhere, lineWidth, forceOutput);
lbContext.checkConsistency(breakResult);
}

result.setMasterText(masterText);
Expand All @@ -1133,65 +1136,6 @@ private static InlineText layoutText(
return result;
}

private static void checkBreakResult(
BreakTextResult breakResult,
LineBreakContext lbContext,
boolean tryToBreakAnywhere) {

switch (breakResult) {
case CONTINUE_CHAR_BREAKING_ON_NL:
if (lbContext.getStart() >= lbContext.getEnd() ||
!lbContext.isNeedsNewLine()) {
fail(breakResult, lbContext);
}
break;
case CONTINUE_WORD_BREAKING_ON_NL:
if (lbContext.getStart() >= lbContext.getEnd() ||
lbContext.isUnbreakable() ||
!lbContext.isNeedsNewLine()) {
fail(breakResult, lbContext);
}
break;
case DANGER_RECONSUME_CHAR_ON_NL:
if (lbContext.getStart() > lbContext.getEnd()) {
fail(breakResult, lbContext);
}
break;
case DANGER_RECONSUME_WORD_ON_NL:
if (lbContext.getStart() > lbContext.getEnd()) {
fail(breakResult, lbContext);
}
break;
case FINISHED:
if (!lbContext.isFinished() ||
lbContext.getStart() >= lbContext.getEnd() ||
lbContext.isUnbreakable() ||
lbContext.isNeedsNewLine()) {
fail(breakResult, lbContext);
}
break;
case WORD_UNBREAKABLE_BUT_CONSUMED:
if (!lbContext.isUnbreakable() ||
lbContext.getStart() >= lbContext.getEnd()) {
fail(breakResult, lbContext);
}
break;
case CHAR_UNBREAKABLE_BUT_CONSUMED:
if (!lbContext.isUnbreakable() ||
lbContext.getStart() >= lbContext.getEnd()) {
fail(breakResult, lbContext);
}
break;
}
}

private static void fail(
BreakTextResult breakResult,
LineBreakContext lbContext) {
// TODO: Log
throw new RuntimeException();
}

private static int processOutOfFlowContent(
LayoutContext c, LineBox current, BlockBox block,
int available, List<FloatLayoutResult> pendingFloats) {
Expand Down
Loading

0 comments on commit 7400d52

Please sign in to comment.