From 02460bb95b7b7dfb174b293d9fdbb002f0bbe444 Mon Sep 17 00:00:00 2001 From: jules Date: Sun, 29 Jun 2014 14:34:26 +0100 Subject: [PATCH] A bit of refactoring inside GlyphArrangement, to avoid som edge-cases where the wrong number of lines is chosen. --- modules/juce_graphics/fonts/juce_Font.cpp | 6 +- .../fonts/juce_GlyphArrangement.cpp | 352 ++++++++++-------- .../fonts/juce_GlyphArrangement.h | 11 +- 3 files changed, 208 insertions(+), 161 deletions(-) diff --git a/modules/juce_graphics/fonts/juce_Font.cpp b/modules/juce_graphics/fonts/juce_Font.cpp index bc3f850e7b..1b4fc04abf 100644 --- a/modules/juce_graphics/fonts/juce_Font.cpp +++ b/modules/juce_graphics/fonts/juce_Font.cpp @@ -278,13 +278,13 @@ Font& Font::operator= (const Font& other) noexcept #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS Font::Font (Font&& other) noexcept - : font (static_cast &&> (other.font)) + : font (static_cast&&> (other.font)) { } Font& Font::operator= (Font&& other) noexcept { - font = static_cast &&> (other.font); + font = static_cast&&> (other.font); return *this; } #endif @@ -640,7 +640,7 @@ float Font::getStringWidthFloat (const String& text) const return w * font->height * font->horizontalScale; } -void Font::getGlyphPositions (const String& text, Array & glyphs, Array & xOffsets) const +void Font::getGlyphPositions (const String& text, Array& glyphs, Array& xOffsets) const { getTypeface()->getGlyphPositions (text, glyphs, xOffsets); diff --git a/modules/juce_graphics/fonts/juce_GlyphArrangement.cpp b/modules/juce_graphics/fonts/juce_GlyphArrangement.cpp index d2d9ff7b82..9bcd766011 100644 --- a/modules/juce_graphics/fonts/juce_GlyphArrangement.cpp +++ b/modules/juce_graphics/fonts/juce_GlyphArrangement.cpp @@ -40,6 +40,27 @@ PositionedGlyph::PositionedGlyph (const PositionedGlyph& other) { } +#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS +PositionedGlyph::PositionedGlyph (PositionedGlyph&& other) noexcept + : font (static_cast (other.font)), + character (other.character), glyph (other.glyph), + x (other.x), y (other.y), w (other.w), whitespace (other.whitespace) +{ +} + +PositionedGlyph& PositionedGlyph::operator= (PositionedGlyph&& other) noexcept +{ + font = static_cast (other.font); + character = other.character; + glyph = other.glyph; + x = other.x; + y = other.y; + w = other.w; + whitespace = other.whitespace; + return *this; +} +#endif + PositionedGlyph::~PositionedGlyph() {} PositionedGlyph& PositionedGlyph::operator= (const PositionedGlyph& other) @@ -182,8 +203,8 @@ void GlyphArrangement::addCurtailedLineOfText (const Font& font, { if (text.isNotEmpty()) { - Array newGlyphs; - Array xOffsets; + Array newGlyphs; + Array xOffsets; font.getGlyphPositions (text, newGlyphs, xOffsets); const int textLen = newGlyphs.size(); glyphs.ensureStorageAllocated (glyphs.size() + textLen); @@ -353,174 +374,40 @@ void GlyphArrangement::addFittedText (const Font& f, if (text.containsAnyOf ("\r\n")) { - GlyphArrangement ga; - ga.addJustifiedText (f, text, x, y, width, layout); - - const Rectangle 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.addArray (ga.glyphs); - return; + addLinesWithLineBreaks (text, f, x, y, width, height, layout); } - - int startIndex = glyphs.size(); - addLineOfText (f, text.trim(), x, y); - - if (glyphs.size() > startIndex) + else { - float lineWidth = glyphs.getReference (glyphs.size() - 1).getRight() - - glyphs.getReference (startIndex).getLeft(); + const int startIndex = glyphs.size(); + const String trimmed (text.trim()); + addLineOfText (f, trimmed, x, y); + const int numGlyphs = glyphs.size() - startIndex; - if (lineWidth <= 0) - return; - - if (lineWidth * minimumHorizontalScale < width) + if (numGlyphs > 0) { - if (lineWidth > width) - stretchRangeOfGlyphs (startIndex, glyphs.size() - startIndex, - width / lineWidth); + const float lineWidth = glyphs.getReference (glyphs.size() - 1).getRight() + - glyphs.getReference (startIndex).getLeft(); - 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) + if (lineWidth > 0) { - ++numLines; - - const float newFontHeight = height / (float) numLines; - - if (newFontHeight < font.getHeight()) + if (lineWidth * minimumHorizontalScale < width) { - font.setHeight (jmax (8.0f, newFontHeight)); + if (lineWidth > width) + stretchRangeOfGlyphs (startIndex, numGlyphs, width / lineWidth); - removeRangeOfGlyphs (startIndex, -1); - addLineOfText (font, txt, x, y); - - lineWidth = glyphs.getReference (glyphs.size() - 1).getRight() - - glyphs.getReference (startIndex).getLeft(); + justifyGlyphs (startIndex, numGlyphs, x, y, width, height, layout); } - - if (numLines > lineWidth / 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) + else if (maximumLines <= 1) { - widthPerLine = width; - i = glyphs.size(); + fitLineIntoSpace (startIndex, numGlyphs, x, y, width, height, + f, layout, minimumHorizontalScale); } 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); + splitLines (trimmed, f, startIndex, x, y, width, height, + maximumLines, lineWidth, layout, minimumHorizontalScale); } - - 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); } } } @@ -540,6 +427,24 @@ void GlyphArrangement::moveRangeOfGlyphs (int startIndex, int num, const float d } } +void GlyphArrangement::addLinesWithLineBreaks (const String& text, const Font& f, + int x, int y, int width, int height, Justification layout) +{ + GlyphArrangement ga; + ga.addJustifiedText (f, text, x, y, width, layout); + + const Rectangle 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.addArray (ga.glyphs); +} + int GlyphArrangement::fitLineIntoSpace (int start, int numGlyphs, float x, float y, float w, float h, const Font& font, Justification justification, float minimumHorizontalScale) { @@ -702,9 +607,142 @@ void GlyphArrangement::spreadOutLine (const int start, const int num, const floa } } + +void GlyphArrangement::splitLines (const String& text, Font font, int startIndex, + int x, int y, int width, int 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); +} + //============================================================================== -inline void GlyphArrangement::drawGlyphUnderline (const Graphics& g, const PositionedGlyph& pg, - const int i, const AffineTransform& transform) const +void GlyphArrangement::drawGlyphUnderline (const Graphics& g, const PositionedGlyph& pg, + const int i, const AffineTransform& transform) const { const float lineThickness = (pg.font.getDescent()) * 0.3f; diff --git a/modules/juce_graphics/fonts/juce_GlyphArrangement.h b/modules/juce_graphics/fonts/juce_GlyphArrangement.h index f11894d50c..15dbf526e2 100644 --- a/modules/juce_graphics/fonts/juce_GlyphArrangement.h +++ b/modules/juce_graphics/fonts/juce_GlyphArrangement.h @@ -46,6 +46,12 @@ public: PositionedGlyph (const PositionedGlyph&); PositionedGlyph& operator= (const PositionedGlyph&); + + #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS + PositionedGlyph (PositionedGlyph&&) noexcept; + PositionedGlyph& operator= (PositionedGlyph&&) noexcept; + #endif + ~PositionedGlyph(); /** Returns the character the glyph represents. */ @@ -295,12 +301,15 @@ public: private: //============================================================================== - Array glyphs; + Array glyphs; int insertEllipsis (const Font&, float maxXPos, int startIndex, int endIndex); int fitLineIntoSpace (int start, int numGlyphs, float x, float y, float w, float h, const Font&, Justification, float minimumHorizontalScale); void spreadOutLine (int start, int numGlyphs, float targetWidth); + void splitLines (const String&, Font, int start, int x, int y, int w, int h, int maxLines, + float lineWidth, Justification, float minimumHorizontalScale); + void addLinesWithLineBreaks (const String&, const Font&, int x, int y, int width, int height, Justification); void drawGlyphUnderline (const Graphics&, const PositionedGlyph&, int, const AffineTransform&) const; JUCE_LEAK_DETECTOR (GlyphArrangement)