From a07098d479aa344c2d0f9d347edff64bbdb65bb5 Mon Sep 17 00:00:00 2001 From: attila Date: Wed, 29 Jan 2025 17:54:20 +0100 Subject: [PATCH] Add ShapedText::getHeight() --- .../detail/juce_JustifiedText.cpp | 46 ++++++++++++------- .../juce_graphics/detail/juce_JustifiedText.h | 42 +++++++++++++---- .../juce_graphics/detail/juce_ShapedText.cpp | 10 ++++ .../juce_graphics/detail/juce_ShapedText.h | 2 + .../juce_graphics/fonts/juce_TextLayout.cpp | 6 +-- 5 files changed, 78 insertions(+), 28 deletions(-) diff --git a/modules/juce_graphics/detail/juce_JustifiedText.cpp b/modules/juce_graphics/detail/juce_JustifiedText.cpp index a22adbf9d9..5d7571e04e 100644 --- a/modules/juce_graphics/detail/juce_JustifiedText.cpp +++ b/modules/juce_graphics/detail/juce_JustifiedText.cpp @@ -270,18 +270,26 @@ JustifiedText::JustifiedText (const SimpleShapedText* t, const ShapedTextOptions : lineLength.withoutTrailingWhitespaces); } - auto y = options.isBaselineAtZero() ? 0.0f - : getCrossAxisStartingAnchor (options.getJustification(), - lineInfos, - options.getHeight(), - leading); + auto baseline = options.isBaselineAtZero() ? 0.0f + : getCrossAxisStartingAnchor (options.getJustification(), + lineInfos, + options.getHeight(), + leading); for (const auto [lineIndex, lineInfo] : enumerate (lineInfos)) { - const auto range = shapedText.getLineNumbers().getItem ((size_t) lineIndex).range; + const auto lineNumber = shapedText.getLineNumbers().getItem ((size_t) lineIndex); + const auto range = lineNumber.range; - lineAnchors.set (range, - { lineInfo.mainAxisLineAlignment.anchor, y }); + const auto maxDescent = lineInfo.lineHeight - lineInfo.maxAscent; + const auto nextLineTop = baseline + (1.0f + leading) * maxDescent + options.getAdditiveLineSpacing(); + + linesMetrics.set (range, + { lineNumber.value, + { lineInfo.mainAxisLineAlignment.anchor, baseline }, + lineInfo.maxAscent, + lineInfo.lineHeight - lineInfo.maxAscent, + nextLineTop }); whitespaceStretch.set (range, 0.0f); const auto stretchRange = lineInfo.mainAxisLineAlignment.stretchableWhitespaces + range.getStart(); @@ -289,10 +297,8 @@ JustifiedText::JustifiedText (const SimpleShapedText* t, const ShapedTextOptions whitespaceStretch.set (stretchRange, lineInfo.mainAxisLineAlignment.extraWhitespaceAdvance); - const auto maxDescent = lineInfo.lineHeight - lineInfo.maxAscent; const auto nextLineMaxAscent = lineIndex < (int) lineInfos.size() - 1 ? lineInfos[(size_t) lineIndex + 1].maxAscent : 0.0f; - - y += (1.0f + leading) * (maxDescent + nextLineMaxAscent) + options.getAdditiveLineSpacing(); + baseline = nextLineTop + (1.0f + leading) * nextLineMaxAscent; } rangesToDraw.set ({ 0, (int64) shapedText.getGlyphs().size() }, DrawType::normal); @@ -303,11 +309,11 @@ JustifiedText::JustifiedText (const SimpleShapedText* t, const ShapedTextOptions // The remaining logic below is for supporting // GlyphArrangement::addFittedText() when the maximum number of lines is // constrained. - if (lineAnchors.isEmpty()) + if (linesMetrics.isEmpty()) return; - const auto lastLineAlignment = lineAnchors.back(); - const auto lastLineGlyphRange = lastLineAlignment.range; + const auto lastLineMetrics = linesMetrics.back(); + const auto lastLineGlyphRange = lastLineMetrics.range; const auto lastLineGlyphs = shapedText.getGlyphs (lastLineGlyphRange); const auto lastLineLengths = getMainAxisLineLength (lastLineGlyphs); @@ -318,7 +324,7 @@ JustifiedText::JustifiedText (const SimpleShapedText* t, const ShapedTextOptions || effectiveLength <= *options.getMaxWidth() + maxWidthTolerance) return; - const auto cutoffAtFront = lastLineAlignment.value.getX() < 0.0f - maxWidthTolerance; + const auto cutoffAtFront = lastLineMetrics.value.anchor.getX() < 0.0f - maxWidthTolerance; const auto getLastLineVisibleRange = [&] (float ellipsisLength) { @@ -437,12 +443,20 @@ JustifiedText::JustifiedText (const SimpleShapedText* t, const ShapedTextOptions options.getTrailingWhitespacesShouldFit()); }(); - lastLineAlignment.value.setX (realign.anchor); + lastLineMetrics.value.anchor.setX (realign.anchor); whitespaceStretch.set (lastLineGlyphRange, 0.0f); whitespaceStretch.set (realign.stretchableWhitespaces + lastLineVisibleRange.getStart(), realign.extraWhitespaceAdvance); } +float JustifiedText::getHeight() const +{ + if (linesMetrics.isEmpty()) + return 0.0f; + + return linesMetrics.back().value.nextLineTop; +} + void drawJustifiedText (const JustifiedText& text, const Graphics& g, AffineTransform transform) { auto& context = g.getInternalContext(); diff --git a/modules/juce_graphics/detail/juce_JustifiedText.h b/modules/juce_graphics/detail/juce_JustifiedText.h index 073d277118..57a6c9acaa 100644 --- a/modules/juce_graphics/detail/juce_JustifiedText.h +++ b/modules/juce_graphics/detail/juce_JustifiedText.h @@ -47,6 +47,21 @@ constexpr auto partiallyUnpack (Tuple&& tuple) return partiallyUnpackImpl (std::forward (tuple), std::make_index_sequence{}); } +//============================================================================== +struct LineMetrics +{ + int64 lineNumber; + Point anchor; + float maxAscent; + float maxDescent; + + /* This will be below the current line's visual bottom if non-default leading or additive + line spacing is used. + */ + float nextLineTop; +}; + +//============================================================================== class JustifiedText { private: @@ -83,18 +98,17 @@ public: std::optional lastLine; Point anchor {}; - for (const auto item : makeIntersectingRangedValues (&shapedText.getLineNumbers(), - &shapedText.getResolvedFonts(), - &lineAnchors, + for (const auto item : makeIntersectingRangedValues (&shapedText.getResolvedFonts(), + &linesMetrics, &rangesToDraw, &whitespaceStretch, (&rangedValues)...)) { - const auto& [range, line, font, lineAnchor, drawType, stretch] = partiallyUnpack<0, 6> (item); - const auto& rest = partiallyUnpack<6, std::tuple_size_v - 6> (item); + const auto& [range, font, lineMetrics, drawType, stretch] = partiallyUnpack<0, 5> (item); + const auto& rest = partiallyUnpack<5, std::tuple_size_v - 5> (item); - if (std::exchange (lastLine, line) != line) - anchor = lineAnchor; + if (std::exchange (lastLine, lineMetrics.lineNumber) != lineMetrics.lineNumber) + anchor = lineMetrics.anchor; const auto glyphs = [this, r = range, dt = drawType]() -> Span { @@ -124,7 +138,7 @@ public: const auto callbackFont = drawType == DrawType::ellipsis ? ellipsis->getResolvedFonts().front().value : font; const auto callbackParameters = - std::tuple_cat (std::tie (glyphs, positions, callbackFont, range, line), rest); + std::tuple_cat (std::tie (glyphs, positions, callbackFont, range, lineMetrics), rest); const auto invokeNullChecked = [&] (auto&... params) { NullCheckedInvocation::invoke (callback, params...); }; @@ -139,9 +153,19 @@ public: */ auto& getMinimumRequiredWidthForLines() const { return minimumRequiredWidthsForLine; } + /* Returns the vertical distance from the baseline of the first line to the bottom of the last + plus any additional line spacing that follows from the leading and additiveLineSpacing + members of the ShapedTextOptions object. + + This guarantees that if ShapedText object1 is drawn at y = 0 and object2 is drawn at + y = object1.getHeight(), then the two texts will be spaced exactly as if they were a single + ShapedText object containing both texts. + */ + float getHeight() const; + private: const SimpleShapedText& shapedText; - detail::RangedValues> lineAnchors; + detail::RangedValues linesMetrics; std::optional ellipsis; detail::RangedValues rangesToDraw; detail::RangedValues whitespaceStretch; diff --git a/modules/juce_graphics/detail/juce_ShapedText.cpp b/modules/juce_graphics/detail/juce_ShapedText.cpp index 2e6edfa061..627275fac8 100644 --- a/modules/juce_graphics/detail/juce_ShapedText.cpp +++ b/modules/juce_graphics/detail/juce_ShapedText.cpp @@ -49,6 +49,11 @@ public: drawJustifiedText (justifiedText, g, transform); } + float getHeight() const + { + return justifiedText.getHeight(); + } + auto& getText() const { return text; @@ -94,6 +99,11 @@ void ShapedText::draw (const Graphics& g, AffineTransform transform) const impl->draw (g, transform); } +float ShapedText::getHeight() const +{ + return impl->getHeight(); +} + const String& ShapedText::getText() const { return impl->getText(); diff --git a/modules/juce_graphics/detail/juce_ShapedText.h b/modules/juce_graphics/detail/juce_ShapedText.h index 3b9a3e72e6..5f964d163b 100644 --- a/modules/juce_graphics/detail/juce_ShapedText.h +++ b/modules/juce_graphics/detail/juce_ShapedText.h @@ -79,6 +79,8 @@ public: /* Draws the text. */ void draw (const Graphics& g, AffineTransform transform) const; + float getHeight() const; + /* @internal */ const JustifiedText& getJustifiedText() const; diff --git a/modules/juce_graphics/fonts/juce_TextLayout.cpp b/modules/juce_graphics/fonts/juce_TextLayout.cpp index 50b42300ea..844f1dd049 100644 --- a/modules/juce_graphics/fonts/juce_TextLayout.cpp +++ b/modules/juce_graphics/fonts/juce_TextLayout.cpp @@ -427,10 +427,10 @@ void TextLayout::createStandardLayout (const AttributedString& text) Span> positions, Font font, Range glyphRange, - int64 lineNumber, + LineMetrics lineMetrics, Colour colour) { - if (std::exchange (lastLineNumber, lineNumber) != lineNumber) + if (std::exchange (lastLineNumber, lineMetrics.lineNumber) != lineMetrics.lineNumber) { if (line != nullptr) addLine (std::move (line)); @@ -438,7 +438,7 @@ void TextLayout::createStandardLayout (const AttributedString& text) const auto ascentAndDescent = getMaxFontAscentAndDescentInEnclosingLine (st, glyphRange); - line = std::make_unique (castTo (getLineInputRange (st, lineNumber)), + line = std::make_unique (castTo (getLineInputRange (st, lineMetrics.lineNumber)), positions[0], ascentAndDescent.ascent, ascentAndDescent.descent,