void GlyphArrangement::addFittedText (const Font& f, const String& text, const float x, const float y, const float width, const float height, Justification layout, int maximumLines, float minimumHorizontalScale) { if (minimumHorizontalScale == 0.0f) minimumHorizontalScale = Font::getDefaultMinimumHorizontalScaleFactor(); // doesn't make much sense if this is outside a sensible range of 0.5 to 1.0 jassert (minimumHorizontalScale > 0 && minimumHorizontalScale <= 1.0f); if (text.containsAnyOf ("\r\n")) { addLinesWithLineBreaks (text, f, x, y, width, height, layout); } else { const int startIndex = glyphs.size(); const String trimmed (text.trim()); addLineOfText (f, trimmed, x, y); const int numGlyphs = glyphs.size() - startIndex; if (numGlyphs > 0) { const float lineWidth = glyphs.getReference (glyphs.size() - 1).getRight() - glyphs.getReference (startIndex).getLeft(); if (lineWidth > 0) { if (lineWidth * minimumHorizontalScale < width) { if (lineWidth > width) stretchRangeOfGlyphs (startIndex, numGlyphs, width / lineWidth); justifyGlyphs (startIndex, numGlyphs, x, y, width, height, layout); } else if (maximumLines <= 1) { fitLineIntoSpace (startIndex, numGlyphs, x, y, width, height, f, layout, minimumHorizontalScale); } else { splitLines (trimmed, f, startIndex, x, y, width, height, maximumLines, lineWidth, layout, minimumHorizontalScale); } } } } }
void GlyphArrangement::splitLines (const String& text, Font font, int startIndex, float x, float y, float width, float height, int maximumLines, float lineWidth, Justification layout, float minimumHorizontalScale) { const int length = text.length(); const int originalStartIndex = startIndex; int numLines = 1; if (length <= 12 && ! text.containsAnyOf (" -\t\r\n")) maximumLines = 1; maximumLines = jmin (maximumLines, length); while (numLines < maximumLines) { ++numLines; const float newFontHeight = height / (float) numLines; if (newFontHeight < font.getHeight()) { font.setHeight (jmax (8.0f, newFontHeight)); removeRangeOfGlyphs (startIndex, -1); addLineOfText (font, text, x, y); lineWidth = glyphs.getReference (glyphs.size() - 1).getRight() - glyphs.getReference (startIndex).getLeft(); } // Try to estimate the point at which there are enough lines to fit the text, // allowing for unevenness in the lengths due to differently sized words. const float lineLengthUnevennessAllowance = 80.0f; if (numLines > (lineWidth + lineLengthUnevennessAllowance) / width || newFontHeight < 8.0f) break; } if (numLines < 1) numLines = 1; float lineY = y; float widthPerLine = lineWidth / numLines; for (int line = 0; line < numLines; ++line) { int i = startIndex; float lineStartX = glyphs.getReference (startIndex).getLeft(); if (line == numLines - 1) { widthPerLine = width; i = glyphs.size(); } else { while (i < glyphs.size()) { lineWidth = (glyphs.getReference (i).getRight() - lineStartX); if (lineWidth > widthPerLine) { // got to a point where the line's too long, so skip forward to find a // good place to break it.. const int searchStartIndex = i; while (i < glyphs.size()) { if ((glyphs.getReference (i).getRight() - lineStartX) * minimumHorizontalScale < width) { if (glyphs.getReference (i).isWhitespace() || glyphs.getReference (i).getCharacter() == '-') { ++i; break; } } else { // can't find a suitable break, so try looking backwards.. i = searchStartIndex; for (int back = 1; back < jmin (7, i - startIndex - 1); ++back) { if (glyphs.getReference (i - back).isWhitespace() || glyphs.getReference (i - back).getCharacter() == '-') { i -= back - 1; break; } } break; } ++i; } break; } ++i; } int wsStart = i; while (wsStart > 0 && glyphs.getReference (wsStart - 1).isWhitespace()) --wsStart; int wsEnd = i; while (wsEnd < glyphs.size() && glyphs.getReference (wsEnd).isWhitespace()) ++wsEnd; removeRangeOfGlyphs (wsStart, wsEnd - wsStart); i = jmax (wsStart, startIndex + 1); } i -= fitLineIntoSpace (startIndex, i - startIndex, x, lineY, width, font.getHeight(), font, layout.getOnlyHorizontalFlags() | Justification::verticallyCentred, minimumHorizontalScale); startIndex = i; lineY += font.getHeight(); if (startIndex >= glyphs.size()) break; } justifyGlyphs (originalStartIndex, glyphs.size() - originalStartIndex, x, y, width, height, layout.getFlags() & ~Justification::horizontallyJustified); }
void GlyphArrangement::addJustifiedText (const Font& font, const String& text, float x, float y, const float maxLineWidth, Justification horizontalLayout) { int lineStartIndex = glyphs.size(); addLineOfText (font, text, x, y); const float originalY = y; while (lineStartIndex < glyphs.size()) { int i = lineStartIndex; if (glyphs.getReference(i).getCharacter() != '\n' && glyphs.getReference(i).getCharacter() != '\r') ++i; const float lineMaxX = glyphs.getReference (lineStartIndex).getLeft() + maxLineWidth; int lastWordBreakIndex = -1; while (i < glyphs.size()) { const PositionedGlyph& pg = glyphs.getReference (i); const juce_wchar c = pg.getCharacter(); if (c == '\r' || c == '\n') { ++i; if (c == '\r' && i < glyphs.size() && glyphs.getReference(i).getCharacter() == '\n') ++i; break; } if (pg.isWhitespace()) { lastWordBreakIndex = i + 1; } else if (pg.getRight() - 0.0001f >= lineMaxX) { if (lastWordBreakIndex >= 0) i = lastWordBreakIndex; break; } ++i; } const float currentLineStartX = glyphs.getReference (lineStartIndex).getLeft(); float currentLineEndX = currentLineStartX; for (int j = i; --j >= lineStartIndex;) { if (! glyphs.getReference (j).isWhitespace()) { currentLineEndX = glyphs.getReference (j).getRight(); break; } } float deltaX = 0.0f; if (horizontalLayout.testFlags (Justification::horizontallyJustified)) spreadOutLine (lineStartIndex, i - lineStartIndex, maxLineWidth); else if (horizontalLayout.testFlags (Justification::horizontallyCentred)) deltaX = (maxLineWidth - (currentLineEndX - currentLineStartX)) * 0.5f; else if (horizontalLayout.testFlags (Justification::right)) deltaX = maxLineWidth - (currentLineEndX - currentLineStartX); moveRangeOfGlyphs (lineStartIndex, i - lineStartIndex, x + deltaX - currentLineStartX, y - originalY); lineStartIndex = i; y += font.getHeight(); } }
void GlyphArrangement::addFittedText (const Font& f, const String& text, const float x, const float y, const float width, const float height, const Justification& layout, int maximumLines, const float minimumHorizontalScale) { // doesn't make much sense if this is outside a sensible range of 0.5 to 1.0 jassert (minimumHorizontalScale > 0 && minimumHorizontalScale <= 1.0f); if (text.containsAnyOf ("\r\n")) { GlyphArrangement ga; ga.addJustifiedText (f, text, x, y, width, layout); const Rectangle<float> bb (ga.getBoundingBox (0, -1, false)); float dy = y - bb.getY(); if (layout.testFlags (Justification::verticallyCentred)) dy += (height - bb.getHeight()) * 0.5f; else if (layout.testFlags (Justification::bottom)) dy += height - bb.getHeight(); ga.moveRangeOfGlyphs (0, -1, 0.0f, dy); glyphs.ensureStorageAllocated (glyphs.size() + ga.glyphs.size()); for (int i = 0; i < ga.glyphs.size(); ++i) glyphs.add (ga.glyphs.getUnchecked (i)); ga.glyphs.clear (false); return; } int startIndex = glyphs.size(); addLineOfText (f, text.trim(), x, y); if (glyphs.size() > startIndex) { float lineWidth = glyphs.getUnchecked (glyphs.size() - 1)->getRight() - glyphs.getUnchecked (startIndex)->getLeft(); if (lineWidth <= 0) return; if (lineWidth * minimumHorizontalScale < width) { if (lineWidth > width) stretchRangeOfGlyphs (startIndex, glyphs.size() - startIndex, width / lineWidth); justifyGlyphs (startIndex, glyphs.size() - startIndex, x, y, width, height, layout); } else if (maximumLines <= 1) { fitLineIntoSpace (startIndex, glyphs.size() - startIndex, x, y, width, height, f, layout, minimumHorizontalScale); } else { Font font (f); String txt (text.trim()); const int length = txt.length(); const int originalStartIndex = startIndex; int numLines = 1; if (length <= 12 && ! txt.containsAnyOf (" -\t\r\n")) maximumLines = 1; maximumLines = jmin (maximumLines, length); while (numLines < maximumLines) { ++numLines; const float newFontHeight = height / (float) numLines; if (newFontHeight < font.getHeight()) { font.setHeight (jmax (8.0f, newFontHeight)); removeRangeOfGlyphs (startIndex, -1); addLineOfText (font, txt, x, y); lineWidth = glyphs.getUnchecked (glyphs.size() - 1)->getRight() - glyphs.getUnchecked (startIndex)->getLeft(); } if (numLines > lineWidth / width || newFontHeight < 8.0f) break; } if (numLines < 1) numLines = 1; float lineY = y; float widthPerLine = lineWidth / numLines; int lastLineStartIndex = 0; for (int line = 0; line < numLines; ++line) { int i = startIndex; lastLineStartIndex = i; float lineStartX = glyphs.getUnchecked (startIndex)->getLeft(); if (line == numLines - 1) { widthPerLine = width; i = glyphs.size(); } else { while (i < glyphs.size()) { lineWidth = (glyphs.getUnchecked (i)->getRight() - lineStartX); if (lineWidth > widthPerLine) { // got to a point where the line's too long, so skip forward to find a // good place to break it.. const int searchStartIndex = i; while (i < glyphs.size()) { if ((glyphs.getUnchecked (i)->getRight() - lineStartX) * minimumHorizontalScale < width) { if (glyphs.getUnchecked (i)->isWhitespace() || glyphs.getUnchecked (i)->getCharacter() == '-') { ++i; break; } } else { // can't find a suitable break, so try looking backwards.. i = searchStartIndex; for (int back = 1; back < jmin (5, i - startIndex - 1); ++back) { if (glyphs.getUnchecked (i - back)->isWhitespace() || glyphs.getUnchecked (i - back)->getCharacter() == '-') { i -= back - 1; break; } } break; } ++i; } break; } ++i; } int wsStart = i; while (wsStart > 0 && glyphs.getUnchecked (wsStart - 1)->isWhitespace()) --wsStart; int wsEnd = i; while (wsEnd < glyphs.size() && glyphs.getUnchecked (wsEnd)->isWhitespace()) ++wsEnd; removeRangeOfGlyphs (wsStart, wsEnd - wsStart); i = jmax (wsStart, startIndex + 1); } i -= fitLineIntoSpace (startIndex, i - startIndex, x, lineY, width, font.getHeight(), font, layout.getOnlyHorizontalFlags() | Justification::verticallyCentred, minimumHorizontalScale); startIndex = i; lineY += font.getHeight(); if (startIndex >= glyphs.size()) break; } justifyGlyphs (originalStartIndex, glyphs.size() - originalStartIndex, x, y, width, height, layout.getFlags() & ~Justification::horizontallyJustified); } } }