diff --git a/modules/juce_graphics/fonts/juce_TextLayout.cpp b/modules/juce_graphics/fonts/juce_TextLayout.cpp index 20da553bdf..1591490bc1 100644 --- a/modules/juce_graphics/fonts/juce_TextLayout.cpp +++ b/modules/juce_graphics/fonts/juce_TextLayout.cpp @@ -27,6 +27,11 @@ namespace juce { +static String substring (const String& text, Range range) +{ + return text.substring (range.getStart(), range.getEnd()); +} + TextLayout::Glyph::Glyph (int glyph, Point anch, float w) noexcept : glyphCode (glyph), anchor (anch), width (w) { @@ -207,9 +212,9 @@ void TextLayout::ensureStorageAllocated (int numLinesNeeded) lines.ensureStorageAllocated (numLinesNeeded); } -void TextLayout::addLine (Line* line) +void TextLayout::addLine (std::unique_ptr line) { - lines.add (line); + lines.add (line.release()); } void TextLayout::draw (Graphics& g, Rectangle area) const @@ -221,9 +226,9 @@ void TextLayout::draw (Graphics& g, Rectangle area) const auto clipTop = clip.getY() - origin.y; auto clipBottom = clip.getBottom() - origin.y; - for (auto* line : lines) + for (auto& line : *this) { - auto lineRangeY = line->getLineBoundsY(); + auto lineRangeY = line.getLineBoundsY(); if (lineRangeY.getEnd() < clipTop) continue; @@ -231,9 +236,9 @@ void TextLayout::draw (Graphics& g, Rectangle area) const if (lineRangeY.getStart() > clipBottom) break; - auto lineOrigin = origin + line->lineOrigin; + auto lineOrigin = origin + line.lineOrigin; - for (auto* run : line->runs) + for (auto* run : line.runs) { context.setFont (run->font); context.setFill (run->colour); @@ -363,8 +368,8 @@ namespace TextLayoutHelpers Array xOffsets; t.font.getGlyphPositions (getTrimmedEndIfNotAllWhitespace (t.text), newGlyphs, xOffsets); - if (currentRun == nullptr) currentRun .reset (new TextLayout::Run()); - if (currentLine == nullptr) currentLine.reset (new TextLayout::Line()); + if (currentRun == nullptr) currentRun = std::make_unique(); + if (currentLine == nullptr) currentLine = std::make_unique(); if (newGlyphs.size() > 0) { @@ -389,9 +394,10 @@ namespace TextLayoutHelpers charPosition += newGlyphs.size(); } - - if (t.isWhitespace || t.isNewLine) + else if (t.isWhitespace || t.isNewLine) + { ++charPosition; + } if (auto* nextToken = tokens[i + 1]) { @@ -404,13 +410,13 @@ namespace TextLayoutHelpers if (t.line != nextToken->line) { if (currentRun == nullptr) - currentRun.reset (new TextLayout::Run()); + currentRun = std::make_unique(); addRun (*currentLine, currentRun.release(), t, runStartPosition, charPosition); currentLine->stringRange = { lineStartPosition, charPosition }; if (! needToSetLineOrigin) - layout.addLine (currentLine.release()); + layout.addLine (std::move (currentLine)); runStartPosition = charPosition; lineStartPosition = charPosition; @@ -423,7 +429,7 @@ namespace TextLayoutHelpers currentLine->stringRange = { lineStartPosition, charPosition }; if (! needToSetLineOrigin) - layout.addLine (currentLine.release()); + layout.addLine (std::move (currentLine)); needToSetLineOrigin = true; } @@ -434,14 +440,14 @@ namespace TextLayoutHelpers auto totalW = layout.getWidth(); bool isCentred = (text.getJustification().getFlags() & Justification::horizontallyCentred) != 0; - for (int i = 0; i < layout.getNumLines(); ++i) + for (auto& line : layout) { - auto dx = totalW - layout.getLine(i).getLineBoundsX().getLength(); + auto dx = totalW - line.getLineBoundsX().getLength(); if (isCentred) dx /= 2.0f; - layout.getLine(i).lineOrigin.x += dx; + line.lineOrigin.x += dx; } } } @@ -560,7 +566,7 @@ namespace TextLayoutHelpers { auto& attr = text.getAttribute (i); - appendText (text.getText().substring (attr.range.getStart(), attr.range.getEnd()), + appendText (substring (text.getText(), attr.range), attr.font, attr.colour); } } @@ -611,4 +617,66 @@ void TextLayout::recalculateSize() } } +//============================================================================== +#if JUCE_UNIT_TESTS + +struct TextLayoutTests : public UnitTest +{ + TextLayoutTests() + : UnitTest ("Text Layout", UnitTestCategories::text) + {} + + static TextLayout createLayout (StringRef text, float width) + { + Font fontToUse (12.0f); + + AttributedString string (text); + string.setFont (std::move (fontToUse)); + + TextLayout layout; + layout.createLayout (std::move (string), width); + + return layout; + } + + void testLineBreaks (const String& line, float width, const StringArray& expected) + { + const auto layout = createLayout (line, width); + + beginTest ("A line is split into expected pieces"); + { + expectEquals (layout.getNumLines(), expected.size()); + + const auto limit = jmin (layout.getNumLines(), expected.size()); + + for (int i = 0; i != limit; ++i) + expectEquals (substring (line, layout.getLine (i).stringRange), expected[i]); + } + } + + void runTest() override + { + const String shortLine ("hello world"); + testLineBreaks (shortLine, 1.0e7f, { shortLine }); + + testLineBreaks ("this line should be split", + 60.0f, + { "this line ", + "should be ", + "split" }); + + testLineBreaks ("these\nlines \nhave\n weird \n spacing ", + 80.0f, + { "these\n", + "lines \n", + "have\n", + " weird \n", + " spacing " }); + } +}; + +static TextLayoutTests textLayoutTests; + +#endif + } // namespace juce diff --git a/modules/juce_graphics/fonts/juce_TextLayout.h b/modules/juce_graphics/fonts/juce_TextLayout.h index c8b253e57a..c1eef2eee1 100644 --- a/modules/juce_graphics/fonts/juce_TextLayout.h +++ b/modules/juce_graphics/fonts/juce_TextLayout.h @@ -40,6 +40,59 @@ namespace juce */ class JUCE_API TextLayout final { +private: + template + class DereferencingIterator + { + public: + using value_type = typename std::remove_reference())>::type; + using difference_type = typename std::iterator_traits::difference_type; + using pointer = value_type*; + using reference = value_type&; + using iterator_category = typename std::iterator_traits::iterator_category; + + explicit DereferencingIterator (Iterator in) : iterator (std::move (in)) {} + + DereferencingIterator& operator+= (difference_type distance) + { + iterator += distance; + return *this; + } + + friend DereferencingIterator operator+ (DereferencingIterator i, difference_type d) { return i += d; } + friend DereferencingIterator operator+ (difference_type d, DereferencingIterator i) { return i += d; } + + DereferencingIterator& operator-= (difference_type distance) + { + iterator -= distance; + return *this; + } + + friend DereferencingIterator operator- (DereferencingIterator i, difference_type d) { return i -= d; } + + friend difference_type operator- (DereferencingIterator a, DereferencingIterator b) { return a.iterator - b.iterator; } + + reference operator[] (difference_type d) const { return *iterator[d]; } + + friend bool operator< (DereferencingIterator a, DereferencingIterator b) { return a.iterator < b.iterator; } + friend bool operator<= (DereferencingIterator a, DereferencingIterator b) { return a.iterator <= b.iterator; } + friend bool operator> (DereferencingIterator a, DereferencingIterator b) { return a.iterator > b.iterator; } + friend bool operator>= (DereferencingIterator a, DereferencingIterator b) { return a.iterator >= b.iterator; } + friend bool operator== (DereferencingIterator a, DereferencingIterator b) { return a.iterator == b.iterator; } + friend bool operator!= (DereferencingIterator a, DereferencingIterator b) { return a.iterator != b.iterator; } + + DereferencingIterator& operator++() { ++iterator; return *this; } + DereferencingIterator& operator--() { --iterator; return *this; } + DereferencingIterator operator++ (int) const { DereferencingIterator copy (*this); ++(*this); return copy; } + DereferencingIterator operator-- (int) const { DereferencingIterator copy (*this); --(*this); return copy; } + + reference operator* () const { return **iterator; } + pointer operator->() const { return *iterator; } + + private: + Iterator iterator; + }; + public: /** Creates an empty layout. Having created a TextLayout, you can populate it using createLayout() or @@ -180,11 +233,29 @@ public: /** Adds a line to the layout. The layout will take ownership of this line object and will delete it when it is no longer needed. */ - void addLine (Line*); + void addLine (std::unique_ptr); /** Pre-allocates space for the specified number of lines. */ void ensureStorageAllocated (int numLinesNeeded); + using iterator = DereferencingIterator< Line* const*>; + using const_iterator = DereferencingIterator; + + /** Returns an iterator over the lines of content */ + iterator begin() { return iterator (lines.begin()); } + const_iterator begin() const { return const_iterator (lines.begin()); } + const_iterator cbegin() const { return const_iterator (lines.begin()); } + + /** Returns an iterator over the lines of content */ + iterator end() { return iterator (lines.end()); } + const_iterator end() const { return const_iterator (lines.end()); } + const_iterator cend() const { return const_iterator (lines.end()); } + + /** If you modify the TextLayout after creating it, call this to compute + the new dimensions of the content. + */ + void recalculateSize(); + private: OwnedArray lines; float width, height; @@ -192,7 +263,6 @@ private: void createStandardLayout (const AttributedString&); bool createNativeLayout (const AttributedString&); - void recalculateSize(); JUCE_LEAK_DETECTOR (TextLayout) }; diff --git a/modules/juce_graphics/native/juce_mac_Fonts.mm b/modules/juce_graphics/native/juce_mac_Fonts.mm index aa5cc8e78c..74bbfa497c 100644 --- a/modules/juce_graphics/native/juce_mac_Fonts.mm +++ b/modules/juce_graphics/native/juce_mac_Fonts.mm @@ -387,19 +387,18 @@ namespace CoreTextTypeLayout auto numRuns = CFArrayGetCount (runs); auto cfrlineStringRange = CTLineGetStringRange (line); - auto lineStringEnd = cfrlineStringRange.location + cfrlineStringRange.length - 1; + auto lineStringEnd = cfrlineStringRange.location + cfrlineStringRange.length; Range lineStringRange ((int) cfrlineStringRange.location, (int) lineStringEnd); LineInfo lineInfo (frame, line, i); - auto glyphLine = new TextLayout::Line (lineStringRange, - Point ((float) lineInfo.origin.x, - (float) (boundsHeight - lineInfo.origin.y)), - (float) lineInfo.ascent, - (float) lineInfo.descent, - (float) lineInfo.leading, - (int) numRuns); - glyphLayout.addLine (glyphLine); + auto glyphLine = std::make_unique (lineStringRange, + Point ((float) lineInfo.origin.x, + (float) (boundsHeight - lineInfo.origin.y)), + (float) lineInfo.ascent, + (float) lineInfo.descent, + (float) lineInfo.leading, + (int) numRuns); for (CFIndex j = 0; j < numRuns; ++j) { @@ -457,6 +456,8 @@ namespace CoreTextTypeLayout (float) positions.points[k].y), (float) advances.advances[k].width)); } + + glyphLayout.addLine (std::move (glyphLine)); } CFRelease (frame); diff --git a/modules/juce_graphics/native/juce_win32_DirectWriteTypeLayout.cpp b/modules/juce_graphics/native/juce_win32_DirectWriteTypeLayout.cpp index ae2e284fc1..ea2c67169e 100644 --- a/modules/juce_graphics/native/juce_win32_DirectWriteTypeLayout.cpp +++ b/modules/juce_graphics/native/juce_win32_DirectWriteTypeLayout.cpp @@ -100,10 +100,10 @@ namespace DirectWriteTypeLayout if (currentLine >= layout->getNumLines()) { jassert (currentLine == layout->getNumLines()); - auto line = new TextLayout::Line(); - layout->addLine (line); - + auto line = std::make_unique(); line->lineOrigin = Point (baselineOriginX, baselineOriginY); + + layout->addLine (std::move (line)); } }