Breaker { static LineBreakResult doBreakCharacters( String currentString, FSTextBreaker lineIterator, FSTextBreaker charIterator, LineBreakContext context, int avail, float letterSpacing, ToIntFunction<String> measurer) { int nextWordBreak = lineIterator.next(); if (nextWordBreak == 0) { nextWordBreak = lineIterator.next(); } if (nextWordBreak < 0) { nextWordBreak = currentString.length(); } int nextCharBreak = charIterator.next(); if (nextCharBreak < 0) { nextCharBreak = nextWordBreak; } int graphicsLength = 0; int left = 0; int lastGoodWrap = 0; int lastGoodGraphicsLength = 0; while (nextCharBreak >= 0 && nextCharBreak <= nextWordBreak && graphicsLength < avail) { String subString = currentString.substring(left, nextCharBreak); float extraSpacing = (nextCharBreak - left) * letterSpacing; int splitWidth = (int) (measurer.applyAsInt(subString) + extraSpacing); lastGoodWrap = left; left = nextCharBreak; lastGoodGraphicsLength = graphicsLength; graphicsLength += splitWidth; nextCharBreak = charIterator.next(); } if (graphicsLength == avail && graphicsLength > 0) { boolean needNewLine = currentString.length() > left; context.setNeedsNewLine(needNewLine); context.setEnd(left + context.getStart()); context.setWidth(graphicsLength); if (left >= currentString.length()) { return LineBreakResult.CHAR_BREAKING_FINISHED; } else if (left >= nextWordBreak) { return LineBreakResult.CHAR_BREAKING_FOUND_WORD_BREAK; } else { return LineBreakResult.CHAR_BREAKING_NEED_NEW_LINE; } } if (nextCharBreak < 0) { nextCharBreak = nextWordBreak; } if (graphicsLength < avail) { lastGoodWrap = nextCharBreak; lastGoodGraphicsLength = graphicsLength; nextCharBreak = nextWordBreak; float extraSpacing = (nextCharBreak - left) * letterSpacing; int splitWidth = (int) (measurer.applyAsInt(currentString.substring(left, nextCharBreak)) + extraSpacing); graphicsLength += splitWidth; } if (graphicsLength <= avail && graphicsLength > 0) { context.setWidth(graphicsLength); context.setEnd(nextCharBreak + context.getStart()); context.setEndsOnWordBreak(nextCharBreak == nextWordBreak); if (nextCharBreak >= currentString.length()) { return LineBreakResult.CHAR_BREAKING_FINISHED; } else if (nextCharBreak >= nextWordBreak) { return LineBreakResult.CHAR_BREAKING_FOUND_WORD_BREAK; } else { return LineBreakResult.CHAR_BREAKING_NEED_NEW_LINE; } } context.setNeedsNewLine(true); if (lastGoodWrap != 0) { context.setWidth(lastGoodGraphicsLength); context.setEnd(lastGoodWrap + context.getStart()); context.setEndsOnWordBreak(lastGoodWrap == nextWordBreak); if (lastGoodWrap >= currentString.length()) { return LineBreakResult.CHAR_BREAKING_FINISHED; } else if (lastGoodWrap >= nextWordBreak) { return LineBreakResult.CHAR_BREAKING_FOUND_WORD_BREAK; } else { return LineBreakResult.CHAR_BREAKING_NEED_NEW_LINE; } } else if (!currentString.isEmpty()) { int end = 1; float extraSpacing = letterSpacing; int splitWidth = (int) (measurer.applyAsInt(currentString.substring(0, end)) + extraSpacing); context.setUnbreakable(true); context.setEnd(end + context.getStart()); context.setEndsOnWordBreak(end == nextWordBreak); context.setWidth(splitWidth); return LineBreakResult.CHAR_BREAKING_UNBREAKABLE; } else { context.setEnd(context.getStart()); context.setWidth(0); context.setNeedsNewLine(false); return LineBreakResult.CHAR_BREAKING_FINISHED; } }  static void breakFirstLetter(LayoutContext c, LineBreakContext context,
            int avail, CalculatedStyle style); static LineBreakResult breakText(LayoutContext c,
            LineBreakContext context, int avail,
            CalculatedStyle style, boolean tryToBreakAnywhere, int lineWidth); static LineBreakResult doBreakText(
            LayoutContext c,
            LineBreakContext context,
            int avail,
            CalculatedStyle style,
            TextBreakerSupplier lineBreaker); static FSTextBreaker getCharacterBreakStream(String currentString, SharedContext sharedContext); static FSTextBreaker getLineBreakStream(String s, SharedContext shared); static int getTextWidthWithLetterSpacing(CssContext c, FSFont font, String text, float letterSpacing); static final char SOFT_HYPHEN; static final TextBreakerSupplier STANDARD_CHARACTER_BREAKER; static final TextBreakerSupplier STANDARD_LINE_BREAKER; }
@Test public void testCharacterBreakerSingleChar() { String whole = "A"; int avail = 5; float letterSpacing = 0; LineBreakContext context = createContext(whole); Breaker.doBreakCharacters(whole, createLine(whole), createChar(whole), context, avail, letterSpacing, MEASURER); assertFalse(context.isUnbreakable()); assertFalse(context.isNeedsNewLine()); assertThat(context.getWidth(), equalTo(1)); assertThat(context.getEnd(), equalTo(1)); }
@Test public void testCharacterBreakerEmptyString() { String whole = ""; int avail = 5; float letterSpacing = 0; LineBreakContext context = createContext(whole); Breaker.doBreakCharacters(whole, createLine(whole), createChar(whole), context, avail, letterSpacing, MEASURER); assertFalse(context.isUnbreakable()); assertFalse(context.isNeedsNewLine()); assertThat(context.getWidth(), equalTo(0)); assertThat(context.getEnd(), equalTo(0)); }
@Test public void testCharacterBreakerUntilWord() { String whole = "ABCD WORD"; int avail = 15; float letterSpacing = 0; LineBreakContext context = createContext(whole); Breaker.doBreakCharacters(whole, createLine(whole), createChar(whole), context, avail, letterSpacing, MEASURER); assertFalse(context.isUnbreakable()); assertFalse(context.isNeedsNewLine()); assertThat(context.getWidth(), equalTo(5)); assertThat(context.getEnd(), equalTo(5)); }
@Test public void testCharacterBreakerNoFit() { String whole = "ABCDEF"; int avail = 4; float letterSpacing = 0; LineBreakContext context = createContext(whole); Breaker.doBreakCharacters(whole, createLine(whole), createChar(whole), context, avail, letterSpacing, MEASURER); assertFalse(context.isUnbreakable()); assertTrue(context.isNeedsNewLine()); assertThat(context.getWidth(), equalTo(4)); assertThat(context.getEnd(), equalTo(4)); assertThat(whole.substring(context.getEnd()), equalTo("EF")); }
@Test public void testCharacterBreakerExactFit() { String whole = "ABCD"; int avail = 4; float letterSpacing = 0; LineBreakContext context = createContext(whole); Breaker.doBreakCharacters(whole, createLine(whole), createChar(whole), context, avail, letterSpacing, MEASURER); assertFalse(context.isUnbreakable()); assertFalse(context.isNeedsNewLine()); assertThat(context.getWidth(), equalTo(4)); assertThat(context.getEnd(), equalTo(4)); assertThat(whole.substring(context.getEnd()), equalTo("")); }
@Test public void testCharacterBreakerPartialFit() { String whole = "ABCDEF"; int avail = 13; float letterSpacing = 0; LineBreakContext context = createContext(whole); Breaker.doBreakCharacters(whole, createLine(whole), createChar(whole), context, avail, letterSpacing, MEASURER3); assertFalse(context.isUnbreakable()); assertTrue(context.isNeedsNewLine()); assertThat(context.getWidth(), equalTo(12)); assertThat(context.getEnd(), equalTo(4)); assertThat(whole.substring(context.getEnd()), equalTo("EF")); }
@Test public void testCharacterBreakerNoFit2() { String whole = "ABCDEF"; int avail = 2; float letterSpacing = 0; LineBreakContext context = createContext(whole); Breaker.doBreakCharacters(whole, createLine(whole), createChar(whole), context, avail, letterSpacing, MEASURER3); assertTrue(context.isUnbreakable()); assertTrue(context.isNeedsNewLine()); assertThat(context.getWidth(), equalTo(3)); assertThat(context.getEnd(), equalTo(1)); assertThat(whole.substring(context.getEnd()), equalTo("BCDEF")); }
@Test public void testCharacterBreakerWordBreakAtStart() { String whole = " ABCDEF"; int avail = 20; float letterSpacing = 0; LineBreakContext context = createContext(whole); Breaker.doBreakCharacters(whole, createLine(whole), createChar(whole), context, avail, letterSpacing, MEASURER); assertFalse(context.isUnbreakable()); assertFalse(context.isNeedsNewLine()); assertThat(context.getWidth(), equalTo(1)); assertThat(context.getEnd(), equalTo(1)); assertThat(whole.substring(context.getEnd()), equalTo(" ABCDEF")); }