diff --git a/BREAKING_CHANGES.md b/BREAKING_CHANGES.md index 9c3ea9570a..f304f467e1 100644 --- a/BREAKING_CHANGES.md +++ b/BREAKING_CHANGES.md @@ -2,6 +2,27 @@ # Version 8.0.0 +## Change + +The virtual functions LowLevelGraphicsContext::drawGlyph() and drawTextLayout() +have been removed. + +**Possible Issues** + +Classes overriding these functions will fail to compile. + +**Workaround** + +Replace drawGlyph() with drawGlyphs(), which draws several glyphs at once. +Remove implementations of drawTextLayout(). + +**Rationale** + +On Windows and macOS, drawing several glyphs at once is faster than drawing +glyphs one-at-a-time. The new API is more general, and allows for more +performant text rendering. + + ## Change JUCE widgets now query the LookAndFeel to determine the TypefaceMetricsKind to diff --git a/modules/juce_graphics/contexts/juce_LowLevelGraphicsContext.h b/modules/juce_graphics/contexts/juce_LowLevelGraphicsContext.h index b333858b67..132eaf7459 100644 --- a/modules/juce_graphics/contexts/juce_LowLevelGraphicsContext.h +++ b/modules/juce_graphics/contexts/juce_LowLevelGraphicsContext.h @@ -105,8 +105,11 @@ public: virtual void setFont (const Font&) = 0; virtual const Font& getFont() = 0; - virtual void drawGlyph (int glyphNumber, const AffineTransform&) = 0; - virtual bool drawTextLayout (const AttributedString&, const Rectangle&) { return false; } + + /** Uses the current font to draw the provided glyph numbers. */ + virtual void drawGlyphs (Span, + Span>, + const AffineTransform&) = 0; }; } // namespace juce diff --git a/modules/juce_graphics/contexts/juce_LowLevelGraphicsPostScriptRenderer.cpp b/modules/juce_graphics/contexts/juce_LowLevelGraphicsPostScriptRenderer.cpp index bd0b6ab7cc..be7e39e215 100644 --- a/modules/juce_graphics/contexts/juce_LowLevelGraphicsPostScriptRenderer.cpp +++ b/modules/juce_graphics/contexts/juce_LowLevelGraphicsPostScriptRenderer.cpp @@ -511,7 +511,7 @@ void LowLevelGraphicsPostScriptRenderer::drawImage (const Image& sourceImage, co //============================================================================== -void LowLevelGraphicsPostScriptRenderer::drawLine (const Line & line) +void LowLevelGraphicsPostScriptRenderer::drawLine (const Line& line) { Path p; p.addLineSegment (line, 1.0f); @@ -529,12 +529,24 @@ const Font& LowLevelGraphicsPostScriptRenderer::getFont() return stateStack.getLast()->font; } -void LowLevelGraphicsPostScriptRenderer::drawGlyph (int glyphNumber, const AffineTransform& transform) +void LowLevelGraphicsPostScriptRenderer::drawGlyphs (Span glyphs, + Span> positions, + const AffineTransform& transform) { - Path p; - Font& font = stateStack.getLast()->font; - font.getTypefacePtr()->getOutlineForGlyph (font.getMetricsKind(), glyphNumber, p); - fillPath (p, AffineTransform::scale (font.getHeight() * font.getHorizontalScale(), font.getHeight()).followedBy (transform)); + jassert (glyphs.size() == positions.size()); + + const auto& font = stateStack.getLast()->font; + + for (const auto [index, glyph] : enumerate (glyphs, size_t{})) + { + Path p; + font.getTypefacePtr()->getOutlineForGlyph (font.getMetricsKind(), glyph, p); + + const auto fullTransform = AffineTransform::scale (font.getHeight() * font.getHorizontalScale(), font.getHeight()) + .translated (positions[index]) + .followedBy (transform); + fillPath (p, fullTransform); + } } } // namespace juce diff --git a/modules/juce_graphics/contexts/juce_LowLevelGraphicsPostScriptRenderer.h b/modules/juce_graphics/contexts/juce_LowLevelGraphicsPostScriptRenderer.h index 9697cc6fe2..8782d0e088 100644 --- a/modules/juce_graphics/contexts/juce_LowLevelGraphicsPostScriptRenderer.h +++ b/modules/juce_graphics/contexts/juce_LowLevelGraphicsPostScriptRenderer.h @@ -89,7 +89,9 @@ public: //============================================================================== const Font& getFont() override; void setFont (const Font&) override; - void drawGlyph (int glyphNumber, const AffineTransform&) override; + void drawGlyphs (Span glyphs, + Span> positions, + const AffineTransform&) override; protected: //============================================================================== diff --git a/modules/juce_graphics/fonts/juce_AttributedString.cpp b/modules/juce_graphics/fonts/juce_AttributedString.cpp index 2c3df7b15b..a0b54c43ca 100644 --- a/modules/juce_graphics/fonts/juce_AttributedString.cpp +++ b/modules/juce_graphics/fonts/juce_AttributedString.cpp @@ -279,12 +279,9 @@ void AttributedString::draw (Graphics& g, const Rectangle& area) const { jassert (text.length() == getLength (attributes)); - if (! g.getInternalContext().drawTextLayout (*this, area)) - { - TextLayout layout; - layout.createLayout (*this, area.getWidth()); - layout.draw (g, area); - } + TextLayout layout; + layout.createLayout (*this, area.getWidth()); + layout.draw (g, area); } } diff --git a/modules/juce_graphics/fonts/juce_GlyphArrangement.cpp b/modules/juce_graphics/fonts/juce_GlyphArrangement.cpp index 0b6417cd79..9029f807a4 100644 --- a/modules/juce_graphics/fonts/juce_GlyphArrangement.cpp +++ b/modules/juce_graphics/fonts/juce_GlyphArrangement.cpp @@ -55,23 +55,21 @@ PositionedGlyph::PositionedGlyph (const Font& font_, juce_wchar character_, int { } -static void drawGlyphWithFont (Graphics& g, int glyph, const Font& font, AffineTransform t) -{ - auto& context = g.getInternalContext(); - context.setFont (font); - context.drawGlyph (glyph, t); -} - void PositionedGlyph::draw (Graphics& g) const { - if (! isWhitespace()) - drawGlyphWithFont (g, glyph, font, AffineTransform::translation (x, y)); + draw (g, {}); } void PositionedGlyph::draw (Graphics& g, AffineTransform transform) const { - if (! isWhitespace()) - drawGlyphWithFont (g, glyph, font, AffineTransform::translation (x, y).followedBy (transform)); + if (isWhitespace()) + return; + + auto& context = g.getInternalContext(); + context.setFont (font); + const uint16_t glyphs[] { static_cast (glyph) }; + const Point positions[] { Point { x, y } }; + context.drawGlyphs (glyphs, positions, transform); } void PositionedGlyph::createPath (Path& path) const @@ -698,10 +696,17 @@ void GlyphArrangement::splitLines (const String& text, Font font, int startIndex } //============================================================================== -void GlyphArrangement::drawGlyphUnderline (const Graphics& g, const PositionedGlyph& pg, - int i, AffineTransform transform) const +void GlyphArrangement::drawGlyphUnderline (const Graphics& g, + int i, + AffineTransform transform) const { - auto lineThickness = (pg.font.getDescent()) * 0.3f; + const auto pg = glyphs.getReference (i); + + if (! pg.font.isUnderlined()) + return; + + const auto lineThickness = (pg.font.getDescent()) * 0.3f; + auto nextX = pg.x + pg.w; if (i < glyphs.size() - 1 && approximatelyEqual (glyphs.getReference (i + 1).y, pg.y)) @@ -719,39 +724,42 @@ void GlyphArrangement::draw (const Graphics& g) const void GlyphArrangement::draw (const Graphics& g, AffineTransform transform) const { - auto& context = g.getInternalContext(); - auto lastFont = context.getFont(); - bool needToRestore = false; + std::vector glyphNumbers; + std::vector> positions; - for (int i = 0; i < glyphs.size(); ++i) + glyphNumbers.reserve (static_cast (glyphs.size())); + positions.reserve (static_cast (glyphs.size())); + + for (auto it = glyphs.begin(), end = glyphs.end(); it != end;) { - auto& pg = glyphs.getReference (i); - - if (pg.font.isUnderlined()) - drawGlyphUnderline (g, pg, i, transform); - - if (! pg.isWhitespace()) + const auto adjacent = std::adjacent_find (it, end, [] (const auto& a, const auto& b) { - if (lastFont != pg.font) - { - lastFont = pg.font; + return a.font != b.font; + }); - if (! needToRestore) - { - needToRestore = true; - context.saveState(); - } + const auto next = adjacent + (adjacent == end ? 0 : 1); - context.setFont (lastFont); - } + glyphNumbers.clear(); + std::transform (it, next, std::back_inserter (glyphNumbers), [] (const PositionedGlyph& x) + { + return (uint16_t) x.glyph; + }); - context.drawGlyph (pg.glyph, AffineTransform::translation (pg.x, pg.y) - .followedBy (transform)); - } + positions.clear(); + std::transform (it, next, std::back_inserter (positions), [] (const PositionedGlyph& x) + { + return Point { x.x, x.y }; + }); + + auto& ctx = g.getInternalContext(); + ctx.setFont (it->font); + ctx.drawGlyphs (glyphNumbers, positions, transform); + + it = next; } - if (needToRestore) - context.restoreState(); + for (const auto pair : enumerate (glyphs)) + drawGlyphUnderline (g, static_cast (pair.index), transform); } void GlyphArrangement::createPath (Path& path) const diff --git a/modules/juce_graphics/fonts/juce_GlyphArrangement.h b/modules/juce_graphics/fonts/juce_GlyphArrangement.h index f733b965f8..f7e3b1d938 100644 --- a/modules/juce_graphics/fonts/juce_GlyphArrangement.h +++ b/modules/juce_graphics/fonts/juce_GlyphArrangement.h @@ -322,7 +322,7 @@ private: void splitLines (const String&, Font, int start, float x, float y, float w, float h, int maxLines, float lineWidth, Justification, float minimumHorizontalScale); void addLinesWithLineBreaks (const String&, const Font&, float x, float y, float width, float height, Justification); - void drawGlyphUnderline (const Graphics&, const PositionedGlyph&, int, AffineTransform) const; + void drawGlyphUnderline (const Graphics&, int, AffineTransform) const; JUCE_LEAK_DETECTOR (GlyphArrangement) }; diff --git a/modules/juce_graphics/fonts/juce_TextLayout.cpp b/modules/juce_graphics/fonts/juce_TextLayout.cpp index c2776e7710..8cedae2e3a 100644 --- a/modules/juce_graphics/fonts/juce_TextLayout.cpp +++ b/modules/juce_graphics/fonts/juce_TextLayout.cpp @@ -214,6 +214,9 @@ void TextLayout::draw (Graphics& g, Rectangle area) const auto clipTop = (float) clip.getY() - origin.y; auto clipBottom = (float) clip.getBottom() - origin.y; + std::vector glyphNumbers; + std::vector> positions; + for (auto& line : *this) { auto lineRangeY = line.getLineBoundsY(); @@ -231,17 +234,31 @@ void TextLayout::draw (Graphics& g, Rectangle area) const context.setFont (run->font); context.setFill (run->colour); - for (auto& glyph : run->glyphs) - context.drawGlyph (glyph.glyphCode, AffineTransform::translation (lineOrigin.x + glyph.anchor.x, - lineOrigin.y + glyph.anchor.y)); + const auto& glyphs = run->glyphs; + + glyphNumbers.resize ((size_t) glyphs.size()); + std::transform (glyphs.begin(), glyphs.end(), glyphNumbers.begin(), [&] (const Glyph& x) + { + return (uint16_t) x.glyphCode; + }); + + positions.resize ((size_t) glyphs.size()); + std::transform (glyphs.begin(), glyphs.end(), positions.begin(), [&] (const Glyph& x) + { + return x.anchor; + }); + + context.drawGlyphs (glyphNumbers, positions, AffineTransform::translation (lineOrigin)); if (run->font.isUnderlined()) { - auto runExtent = run->getRunBoundsX(); - auto lineThickness = run->font.getDescent() * 0.3f; + const auto runExtent = run->getRunBoundsX(); + const auto lineThickness = run->font.getDescent() * 0.3f; - context.fillRect ({ runExtent.getStart() + lineOrigin.x, lineOrigin.y + lineThickness * 2.0f, - runExtent.getLength(), lineThickness }); + context.fillRect ({ runExtent.getStart() + lineOrigin.x, + lineOrigin.y + lineThickness * 2.0f, + runExtent.getLength(), + lineThickness }); } } } diff --git a/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.h b/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.h index 2d262a6a53..7113ceac24 100644 --- a/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.h +++ b/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.h @@ -121,7 +121,9 @@ public: void drawLine (const Line&) override; void setFont (const Font&) override; const Font& getFont() override; - void drawGlyph (int glyphNumber, const AffineTransform& transform) override; + void drawGlyphs (Span, + Span>, + const AffineTransform&) override; private: //============================================================================== diff --git a/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.mm b/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.mm index 336abca799..aede46f7f6 100644 --- a/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.mm +++ b/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.mm @@ -659,17 +659,6 @@ void CoreGraphicsContext::setFont (const Font& newFont) { state->font = newFont; state->fontRef = nullptr; - - const auto hbFont = state->font.getNativeDetails().font; - - state->fontRef.reset (hb_coretext_font_get_ct_font (hbFont.get())); - CFRetain (state->fontRef.get()); - - const auto slant = hb_font_get_synthetic_slant (hbFont.get()); - - state->textMatrix = CGAffineTransformMake (state->font.getHorizontalScale(), 0, slant * state->font.getHorizontalScale(), 1.0f, 0, 0); - CGContextSetTextMatrix (context.get(), state->textMatrix); - state->inverseTextMatrix = CGAffineTransformInvert (state->textMatrix); } } @@ -678,28 +667,48 @@ const Font& CoreGraphicsContext::getFont() return state->font; } -void CoreGraphicsContext::drawGlyph (int glyphNumber, const AffineTransform& transform) +void CoreGraphicsContext::drawGlyphs (Span glyphs, + Span> positions, + const AffineTransform& transform) { - if (state->fontRef != nullptr && state->fillType.isColour()) + jassert (glyphs.size() == positions.size()); + + if (state->fillType.isColour()) { - const CGGlyph glyphs[] { (CGGlyph) glyphNumber }; + const auto scale = state->font.getHorizontalScale(); + + if (state->fontRef == nullptr) + { + const auto hbFont = state->font.getNativeDetails().font; + state->fontRef.reset (hb_coretext_font_get_ct_font (hbFont.get())); + CFRetain (state->fontRef.get()); + + const auto slant = hb_font_get_synthetic_slant (hbFont.get()); + + state->textMatrix = CGAffineTransformMake (scale, 0, slant * scale, 1.0f, 0, 0); + CGContextSetTextMatrix (context.get(), state->textMatrix); + state->inverseTextMatrix = CGAffineTransformInvert (state->textMatrix); + } ScopedCGContextState scopedState (context.get()); - flip(); applyTransform (AffineTransform::scale (1.0f, -1.0f).followedBy (transform)); - const CGPoint positions[] { { 0.0f, 0.0f } }; - CTFontDrawGlyphs (state->fontRef.get(), glyphs, positions, std::size (glyphs), context.get()); + std::vector pos (glyphs.size()); + std::transform (positions.begin(), positions.end(), pos.begin(), [scale] (const auto& p) { return CGPointMake (p.x / scale, -p.y); }); + + CTFontDrawGlyphs (state->fontRef.get(), glyphs.data(), pos.data(), glyphs.size(), context.get()); } else { - Path p; - auto& f = state->font; - f.getTypefacePtr()->getOutlineForGlyph (f.getMetricsKind(), glyphNumber, p); - const auto scale = f.getHeight(); - - fillPath (p, AffineTransform::scale (scale * f.getHorizontalScale(), scale).followedBy (transform)); + for (const auto [index, glyph] : enumerate (glyphs, size_t{})) + { + Path p; + auto& f = state->font; + f.getTypefacePtr()->getOutlineForGlyph (f.getMetricsKind(), glyph, p); + const auto scale = f.getHeight(); + fillPath (p, AffineTransform::scale (scale * f.getHorizontalScale(), scale).translated (positions[index]).followedBy (transform)); + } } } diff --git a/modules/juce_graphics/native/juce_RenderingHelpers.h b/modules/juce_graphics/native/juce_RenderingHelpers.h index d1372d4166..b0439bfe99 100644 --- a/modules/juce_graphics/native/juce_RenderingHelpers.h +++ b/modules/juce_graphics/native/juce_RenderingHelpers.h @@ -2596,7 +2596,18 @@ public: void setFont (const Font& newFont) override { stack->font = newFont; } const Font& getFont() override { return stack->font; } - void drawGlyph (int i, const AffineTransform& t) override + void drawGlyphs (Span glyphs, + Span> positions, + const AffineTransform& t) override + { + jassert (glyphs.size() == positions.size()); + + for (const auto [index, glyph] : enumerate (glyphs)) + drawGlyph (glyph, AffineTransform::translation (positions[(size_t) index]).followedBy (t)); + } + +protected: + void drawGlyph (uint16_t i, const AffineTransform& t) { if (stack->clip == nullptr) return; @@ -2660,8 +2671,7 @@ public: } } -protected: - StackBasedLowLevelGraphicsContext (SavedStateType* initialState) : stack (initialState) {} + explicit StackBasedLowLevelGraphicsContext (SavedStateType* initialState) : stack (initialState) {} StackBasedLowLevelGraphicsContext() = default; RenderingHelpers::SavedStateStack stack;