From 51955453ef889a4ef1a1c9cee63856c8e8bf59b8 Mon Sep 17 00:00:00 2001 From: attila Date: Tue, 21 May 2024 18:04:25 +0200 Subject: [PATCH] Remove TextLayout::createNativeLayout() --- BREAKING_CHANGES.md | 32 ++ .../juce_graphics/fonts/juce_TextLayout.cpp | 3 +- modules/juce_graphics/fonts/juce_TextLayout.h | 1 - .../juce_DirectWriteTypeLayout_windows.cpp | 519 ------------------ .../juce_DirectWriteTypeface_windows.cpp | 7 - .../native/juce_Fonts_android.cpp | 5 - .../juce_graphics/native/juce_Fonts_linux.cpp | 5 - .../juce_graphics/native/juce_Fonts_mac.mm | 442 --------------- 8 files changed, 33 insertions(+), 981 deletions(-) delete mode 100644 modules/juce_graphics/native/juce_DirectWriteTypeLayout_windows.cpp diff --git a/BREAKING_CHANGES.md b/BREAKING_CHANGES.md index 86fac94e50..cb7cdc8e88 100644 --- a/BREAKING_CHANGES.md +++ b/BREAKING_CHANGES.md @@ -2,6 +2,38 @@ # Version 8.0.0 +## Change + +As part of the Unicode upgrades TextLayout codepaths have been unified across +all platforms. As a consequence the behaviour of TextLayout on Apple platforms +will now be different in two regards: +- With certain fonts, line spacing will now be different. +- The AttributedString option WordWrap::byChar will no longer have an effect, + just like it didn't have an effect on non-Apple platforms previously. Wrapping + will now always happen on word boundaries. + +Furthermore, the JUCE_USE_DIRECTWRITE compiler flag will no longer have any +effect. + +**Possible Issues** + +User interfaces using TextLayout and the WordWrap::byChar option will have their +appearance altered on Apple platforms. The line spacing will be different for +certain fonts. + +**Workaround** + +There is no workaround. + +**Rationale** + +The new, unified codepath has better support for Unicode text in general. The +font fallback mechanism, which previously was only available using the removed +codepaths is now an integral part of the new approach. By removing the +alternative codepaths, text layout and line spacing has become more consistent +across the platforms. + + ## Change As part of the Unicode upgrades the vertical alignment logic of TextLayout has diff --git a/modules/juce_graphics/fonts/juce_TextLayout.cpp b/modules/juce_graphics/fonts/juce_TextLayout.cpp index 166c5a8e27..2ca1a81c43 100644 --- a/modules/juce_graphics/fonts/juce_TextLayout.cpp +++ b/modules/juce_graphics/fonts/juce_TextLayout.cpp @@ -273,8 +273,7 @@ void TextLayout::createLayout (const AttributedString& text, float maxWidth, flo height = maxHeight; justification = text.getJustification(); - if (! createNativeLayout (text)) - createStandardLayout (text); + createStandardLayout (text); recalculateSize(); } diff --git a/modules/juce_graphics/fonts/juce_TextLayout.h b/modules/juce_graphics/fonts/juce_TextLayout.h index 9bdd797555..dcea6d9ec5 100644 --- a/modules/juce_graphics/fonts/juce_TextLayout.h +++ b/modules/juce_graphics/fonts/juce_TextLayout.h @@ -271,7 +271,6 @@ private: Justification justification; void createStandardLayout (const AttributedString&); - bool createNativeLayout (const AttributedString&); JUCE_LEAK_DETECTOR (TextLayout) }; diff --git a/modules/juce_graphics/native/juce_DirectWriteTypeLayout_windows.cpp b/modules/juce_graphics/native/juce_DirectWriteTypeLayout_windows.cpp deleted file mode 100644 index 4451743bee..0000000000 --- a/modules/juce_graphics/native/juce_DirectWriteTypeLayout_windows.cpp +++ /dev/null @@ -1,519 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE framework. - Copyright (c) Raw Material Software Limited - - JUCE is an open source framework subject to commercial or open source - licensing. - - By downloading, installing, or using the JUCE framework, or combining the - JUCE framework with any other source code, object code, content or any other - copyrightable work, you agree to the terms of the JUCE End User Licence - Agreement, and all incorporated terms including the JUCE Privacy Policy and - the JUCE Website Terms of Service, as applicable, which will bind you. If you - do not agree to the terms of these agreements, we will not license the JUCE - framework to you, and you must discontinue the installation or download - process and cease use of the JUCE framework. - - JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ - JUCE Privacy Policy: https://juce.com/juce-privacy-policy - JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ - - Or: - - You may also use this code under the terms of the AGPLv3: - https://www.gnu.org/licenses/agpl-3.0.en.html - - THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL - WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF - MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -#if JUCE_USE_DIRECTWRITE -namespace DirectWriteTypeLayout -{ - class CustomDirectWriteTextRenderer final : public ComBaseClassHelper - { - public: - CustomDirectWriteTextRenderer (IDWriteFontCollection& fonts, const AttributedString& as) - : attributedString (as), - fontCollection (fonts) - { - } - - JUCE_COMRESULT QueryInterface (REFIID refId, void** result) override - { - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token") - - if (refId == __uuidof (IDWritePixelSnapping)) - return castToType (result); - - return ComBaseClassHelper::QueryInterface (refId, result); - - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - } - - JUCE_COMRESULT IsPixelSnappingDisabled (void* /*clientDrawingContext*/, BOOL* isDisabled) noexcept override - { - *isDisabled = FALSE; - return S_OK; - } - - JUCE_COMRESULT GetCurrentTransform (void*, DWRITE_MATRIX* matrix) noexcept override - { - matrix->m11 = 1.0f; matrix->m12 = 0.0f; - matrix->m21 = 0.0f; matrix->m22 = 1.0f; - matrix->dx = 0.0f; matrix->dy = 0.0f; - return S_OK; - } - - JUCE_COMRESULT GetPixelsPerDip (void*, FLOAT* pixelsPerDip) noexcept override - { - *pixelsPerDip = 1.0f; - return S_OK; - } - - JUCE_COMRESULT DrawUnderline (void*, FLOAT, FLOAT, DWRITE_UNDERLINE const*, IUnknown*) noexcept override - { - return E_NOTIMPL; - } - - JUCE_COMRESULT DrawStrikethrough (void*, FLOAT, FLOAT, DWRITE_STRIKETHROUGH const*, IUnknown*) noexcept override - { - return E_NOTIMPL; - } - - JUCE_COMRESULT DrawInlineObject (void*, FLOAT, FLOAT, IDWriteInlineObject*, BOOL, BOOL, IUnknown*) noexcept override - { - return E_NOTIMPL; - } - - JUCE_COMRESULT DrawGlyphRun (void* clientDrawingContext, FLOAT baselineOriginX, FLOAT baselineOriginY, DWRITE_MEASURING_MODE, - DWRITE_GLYPH_RUN const* glyphRun, DWRITE_GLYPH_RUN_DESCRIPTION const* runDescription, - IUnknown* clientDrawingEffect) noexcept override - { - const auto containsTextOrNewLines = [runDescription] - { - const String runString (runDescription->string, runDescription->stringLength); - - if (runString.containsNonWhitespaceChars() || runString.containsAnyOf ("\n\r")) - return true; - - return false; - }(); - - if (! containsTextOrNewLines) - return S_OK; - - auto layout = static_cast (clientDrawingContext); - - if (! (baselineOriginY >= -1.0e10f && baselineOriginY <= 1.0e10f)) - baselineOriginY = 0; // DirectWrite sometimes sends NaNs in this parameter - - if (! approximatelyEqual (baselineOriginY, lastOriginY)) - { - lastOriginY = baselineOriginY; - ++currentLine; - - if (currentLine >= layout->getNumLines()) - { - jassert (currentLine == layout->getNumLines()); - auto line = std::make_unique(); - line->lineOrigin = Point (baselineOriginX, baselineOriginY); - - layout->addLine (std::move (line)); - } - } - - auto& glyphLine = layout->getLine (currentLine); - - DWRITE_FONT_METRICS dwFontMetrics; - glyphRun->fontFace->GetMetrics (&dwFontMetrics); - - glyphLine.ascent = jmax (glyphLine.ascent, scaledFontSize (dwFontMetrics.ascent, dwFontMetrics, *glyphRun)); - glyphLine.descent = jmax (glyphLine.descent, scaledFontSize (dwFontMetrics.descent, dwFontMetrics, *glyphRun)); - - auto glyphRunLayout = new TextLayout::Run (Range ((int) runDescription->textPosition, - (int) (runDescription->textPosition + runDescription->stringLength)), - (int) glyphRun->glyphCount); - glyphLine.runs.add (glyphRunLayout); - - glyphRun->fontFace->GetMetrics (&dwFontMetrics); - auto totalHeight = std::abs ((float) dwFontMetrics.ascent) + std::abs ((float) dwFontMetrics.descent); - auto fontHeightToEmSizeFactor = (float) dwFontMetrics.designUnitsPerEm / totalHeight; - - glyphRunLayout->font = getFontForRun (*glyphRun, glyphRun->fontEmSize / fontHeightToEmSizeFactor); - glyphRunLayout->colour = getColourOf (static_cast (clientDrawingEffect)); - - auto lineOrigin = layout->getLine (currentLine).lineOrigin; - auto x = baselineOriginX - lineOrigin.x; - - auto extraKerning = glyphRunLayout->font.getExtraKerningFactor() - * glyphRunLayout->font.getHeight(); - - for (UINT32 i = 0; i < glyphRun->glyphCount; ++i) - { - auto advance = glyphRun->glyphAdvances[i]; - - if ((glyphRun->bidiLevel & 1) != 0) - x -= advance + extraKerning; // RTL text - - glyphRunLayout->glyphs.add (TextLayout::Glyph (glyphRun->glyphIndices[i], - Point (x, baselineOriginY - lineOrigin.y), - advance)); - - if ((glyphRun->bidiLevel & 1) == 0) - x += advance + extraKerning; // LTR text - } - - return S_OK; - } - - private: - const AttributedString& attributedString; - IDWriteFontCollection& fontCollection; - int currentLine = -1; - float lastOriginY = -10000.0f; - - static float scaledFontSize (int n, const DWRITE_FONT_METRICS& metrics, const DWRITE_GLYPH_RUN& glyphRun) noexcept - { - return (std::abs ((float) n) / (float) metrics.designUnitsPerEm) * glyphRun.fontEmSize; - } - - static Colour getColourOf (ID2D1SolidColorBrush* d2dBrush) noexcept - { - if (d2dBrush == nullptr) - return Colours::black; - - const D2D1_COLOR_F colour (d2dBrush->GetColor()); - return Colour::fromFloatRGBA (colour.r, colour.g, colour.b, colour.a); - } - - Font getFontForRun (const DWRITE_GLYPH_RUN& glyphRun, float fontHeight) - { - for (int i = 0; i < attributedString.getNumAttributes(); ++i) - { - auto& font = attributedString.getAttribute (i).font; - auto typeface = font.getTypefacePtr(); - - if (auto* wt = dynamic_cast (typeface.get())) - if (wt->getIDWriteFontFace() == glyphRun.fontFace) - return font.withHeight (fontHeight); - } - - ComSmartPtr dwFont; - [[maybe_unused]] auto hr = fontCollection.GetFontFromFontFace (glyphRun.fontFace, dwFont.resetAndGetPointerAddress()); - jassert (dwFont != nullptr); - - ComSmartPtr dwFontFamily; - hr = dwFont->GetFontFamily (dwFontFamily.resetAndGetPointerAddress()); - - return Font (getFontFamilyName (dwFontFamily), getFontFaceName (dwFont), fontHeight); - } - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomDirectWriteTextRenderer) - }; - - //============================================================================== - static float getFontHeightToEmSizeFactor (IDWriteFont& dwFont) - { - ComSmartPtr dwFontFace; - dwFont.CreateFontFace (dwFontFace.resetAndGetPointerAddress()); - - if (dwFontFace == nullptr) - return 1.0f; - - DWRITE_FONT_METRICS dwFontMetrics; - dwFontFace->GetMetrics (&dwFontMetrics); - - const float totalHeight = (float) (dwFontMetrics.ascent + dwFontMetrics.descent); - return dwFontMetrics.designUnitsPerEm / totalHeight; - } - - static void setTextFormatProperties (const AttributedString& text, IDWriteTextFormat& format) - { - DWRITE_TEXT_ALIGNMENT alignment = DWRITE_TEXT_ALIGNMENT_LEADING; - DWRITE_WORD_WRAPPING wrapType = DWRITE_WORD_WRAPPING_WRAP; - - switch (text.getJustification().getOnlyHorizontalFlags()) - { - case 0: - case Justification::left: break; - case Justification::right: alignment = DWRITE_TEXT_ALIGNMENT_TRAILING; break; - case Justification::horizontallyCentred: alignment = DWRITE_TEXT_ALIGNMENT_CENTER; break; - case Justification::horizontallyJustified: break; // DirectWrite cannot justify text, default to left alignment - default: jassertfalse; break; // Illegal justification flags - } - - switch (text.getWordWrap()) - { - case AttributedString::none: wrapType = DWRITE_WORD_WRAPPING_NO_WRAP; break; - case AttributedString::byWord: break; - case AttributedString::byChar: break; // DirectWrite doesn't support wrapping by character, default to word-wrap - default: jassertfalse; break; // Illegal flags! - } - - // DirectWrite does not automatically set reading direction - // This must be set correctly and manually when using RTL Scripts (Hebrew, Arabic) - if (text.getReadingDirection() == AttributedString::rightToLeft) - { - format.SetReadingDirection (DWRITE_READING_DIRECTION_RIGHT_TO_LEFT); - - switch (text.getJustification().getOnlyHorizontalFlags()) - { - case 0: - case Justification::left: alignment = DWRITE_TEXT_ALIGNMENT_TRAILING; break; - case Justification::right: alignment = DWRITE_TEXT_ALIGNMENT_LEADING; break; - default: break; - } - } - - format.SetTextAlignment (alignment); - format.SetWordWrapping (wrapType); - } - - static void addAttributedRange (const AttributedString::Attribute& attr, - IDWriteTextLayout& textLayout, - CharPointer_UTF16 begin, - CharPointer_UTF16 textPointer, - const UINT32 textLen, - ID2D1RenderTarget& renderTarget, - IDWriteFontCollection& fontCollection) - { - DWRITE_TEXT_RANGE range; - range.startPosition = (UINT32) (textPointer.getAddress() - begin.getAddress()); - - if (textLen <= range.startPosition) - return; - - const auto wordEnd = jmin (textLen, (UINT32) ((textPointer + attr.range.getLength()).getAddress() - begin.getAddress())); - range.length = wordEnd - range.startPosition; - - { - auto familyName = FontStyleHelpers::getConcreteFamilyName (attr.font); - - BOOL fontFound = false; - uint32 fontIndex; - fontCollection.FindFamilyName (familyName.toWideCharPointer(), &fontIndex, &fontFound); - - if (! fontFound) - fontIndex = 0; - - ComSmartPtr fontFamily; - [[maybe_unused]] auto hr = fontCollection.GetFontFamily (fontIndex, fontFamily.resetAndGetPointerAddress()); - - ComSmartPtr dwFont; - uint32 fontFacesCount = 0; - fontFacesCount = fontFamily->GetFontCount(); - - for (int i = (int) fontFacesCount; --i >= 0;) - { - hr = fontFamily->GetFont ((UINT32) i, dwFont.resetAndGetPointerAddress()); - - if (attr.font.getTypefaceStyle() == getFontFaceName (dwFont)) - break; - } - - textLayout.SetFontFamilyName (familyName.toWideCharPointer(), range); - textLayout.SetFontWeight (dwFont->GetWeight(), range); - textLayout.SetFontStretch (dwFont->GetStretch(), range); - textLayout.SetFontStyle (dwFont->GetStyle(), range); - - auto fontHeightToEmSizeFactor = getFontHeightToEmSizeFactor (*dwFont); - textLayout.SetFontSize (attr.font.getHeight() * fontHeightToEmSizeFactor, range); - } - - { - auto col = attr.colour; - ComSmartPtr d2dBrush; - renderTarget.CreateSolidColorBrush (D2D1::ColorF (col.getFloatRed(), - col.getFloatGreen(), - col.getFloatBlue(), - col.getFloatAlpha()), - d2dBrush.resetAndGetPointerAddress()); - - // We need to call SetDrawingEffect with a legitimate brush to get DirectWrite to break text based on colours - textLayout.SetDrawingEffect (d2dBrush, range); - } - } - - static bool setupLayout (const AttributedString& text, - float maxWidth, - float maxHeight, - ID2D1RenderTarget& renderTarget, - IDWriteFactory& directWriteFactory, - IDWriteFontCollection& fontCollection, - ComSmartPtr& textLayout) - { - // To add color to text, we need to create a D2D render target - // Since we are not actually rendering to a D2D context we create a temporary GDI render target - - Font defaultFont; - BOOL fontFound = false; - uint32 fontIndex; - fontCollection.FindFamilyName (defaultFont.getTypefacePtr()->getName().toWideCharPointer(), &fontIndex, &fontFound); - - if (! fontFound) - fontIndex = 0; - - ComSmartPtr dwFontFamily; - auto hr = fontCollection.GetFontFamily (fontIndex, dwFontFamily.resetAndGetPointerAddress()); - - ComSmartPtr dwFont; - hr = dwFontFamily->GetFirstMatchingFont (DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STRETCH_NORMAL, DWRITE_FONT_STYLE_NORMAL, - dwFont.resetAndGetPointerAddress()); - jassert (dwFont != nullptr); - - auto defaultFontHeightToEmSizeFactor = getFontHeightToEmSizeFactor (*dwFont); - - ComSmartPtr dwTextFormat; - hr = directWriteFactory.CreateTextFormat (defaultFont.getTypefaceName().toWideCharPointer(), &fontCollection, - DWRITE_FONT_WEIGHT_REGULAR, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, - defaultFont.getHeight() * defaultFontHeightToEmSizeFactor, - L"en-us", dwTextFormat.resetAndGetPointerAddress()); - - setTextFormatProperties (text, *dwTextFormat); - - { - DWRITE_TRIMMING trimming = { DWRITE_TRIMMING_GRANULARITY_CHARACTER, 0, 0 }; - ComSmartPtr trimmingSign; - hr = directWriteFactory.CreateEllipsisTrimmingSign (dwTextFormat, trimmingSign.resetAndGetPointerAddress()); - hr = dwTextFormat->SetTrimming (&trimming, trimmingSign); - } - - const auto beginPtr = text.getText().toUTF16(); - const auto textLen = (UINT32) (beginPtr.findTerminatingNull().getAddress() - beginPtr.getAddress()); - - hr = directWriteFactory.CreateTextLayout (beginPtr.getAddress(), - textLen, - dwTextFormat, - maxWidth, - maxHeight, - textLayout.resetAndGetPointerAddress()); - - if (FAILED (hr) || textLayout == nullptr) - return false; - - const auto numAttributes = text.getNumAttributes(); - auto rangePointer = beginPtr; - - for (int i = 0; i < numAttributes; ++i) - { - const auto attribute = text.getAttribute (i); - addAttributedRange (attribute, *textLayout, beginPtr, rangePointer, textLen, renderTarget, fontCollection); - rangePointer += attribute.range.getLength(); - } - - return true; - } - - static void createLayout (TextLayout& layout, - const AttributedString& text, - IDWriteFactory& directWriteFactory, - IDWriteFontCollection& fontCollection, - ID2D1DCRenderTarget& renderTarget) - { - ComSmartPtr dwTextLayout; - - if (! setupLayout (text, layout.getWidth(), layout.getHeight(), renderTarget, - directWriteFactory, fontCollection, dwTextLayout)) - return; - - UINT32 actualLineCount = 0; - [[maybe_unused]] auto hr = dwTextLayout->GetLineMetrics (nullptr, 0, &actualLineCount); - - layout.ensureStorageAllocated ((int) actualLineCount); - - { - auto textRenderer = becomeComSmartPtrOwner (new CustomDirectWriteTextRenderer (fontCollection, text)); - hr = dwTextLayout->Draw (&layout, textRenderer, 0, 0); - } - - HeapBlock dwLineMetrics (actualLineCount); - hr = dwTextLayout->GetLineMetrics (dwLineMetrics, actualLineCount, &actualLineCount); - int lastLocation = 0; - auto numLines = jmin ((int) actualLineCount, layout.getNumLines()); - float yAdjustment = 0; - auto extraLineSpacing = text.getLineSpacing(); - - for (int i = 0; i < numLines; ++i) - { - auto& line = layout.getLine (i); - line.stringRange = Range (lastLocation, lastLocation + (int) dwLineMetrics[i].length); - line.lineOrigin.y += yAdjustment; - yAdjustment += extraLineSpacing; - lastLocation += (int) dwLineMetrics[i].length; - } - } - - static inline void drawToD2DContext (const AttributedString& text, - const Rectangle& area, - ID2D1RenderTarget& renderTarget, - IDWriteFactory& directWriteFactory, - IDWriteFontCollection& fontCollection) - { - ComSmartPtr dwTextLayout; - - if (setupLayout (text, area.getWidth(), area.getHeight(), renderTarget, - directWriteFactory, fontCollection, dwTextLayout)) - { - ComSmartPtr d2dBrush; - renderTarget.CreateSolidColorBrush (D2D1::ColorF (0.0f, 0.0f, 0.0f, 1.0f), - d2dBrush.resetAndGetPointerAddress()); - - renderTarget.DrawTextLayout (D2D1::Point2F ((float) area.getX(), (float) area.getY()), - dwTextLayout, d2dBrush, D2D1_DRAW_TEXT_OPTIONS_CLIP); - } - } -} - -static bool canAllTypefacesAndFontsBeUsedInLayout (const AttributedString& text) -{ - auto numCharacterAttributes = text.getNumAttributes(); - - for (int i = 0; i < numCharacterAttributes; ++i) - { - const auto& font = text.getAttribute (i).font; - auto typeface = font.getTypefacePtr(); - - if (font.getHorizontalScale() != 1.0f || dynamic_cast (typeface.get()) == nullptr) - return false; - } - - return true; -} - -#endif - -bool TextLayout::createNativeLayout ([[maybe_unused]] const AttributedString& text) -{ - #if JUCE_USE_DIRECTWRITE - if (! canAllTypefacesAndFontsBeUsedInLayout (text)) - return false; - - SharedResourcePointer factories; - - if (factories->d2dFactory != nullptr - && factories->directWriteFactory != nullptr - && factories->systemFonts != nullptr - && factories->directWriteRenderTarget != nullptr) - { - DirectWriteTypeLayout::createLayout (*this, text, - *factories->directWriteFactory, - *factories->systemFonts, - *factories->directWriteRenderTarget); - - return true; - } - #endif - - return false; -} - -} // namespace juce diff --git a/modules/juce_graphics/native/juce_DirectWriteTypeface_windows.cpp b/modules/juce_graphics/native/juce_DirectWriteTypeface_windows.cpp index fe3b9faf43..c975b7fcdf 100644 --- a/modules/juce_graphics/native/juce_DirectWriteTypeface_windows.cpp +++ b/modules/juce_graphics/native/juce_DirectWriteTypeface_windows.cpp @@ -855,11 +855,4 @@ void Typeface::scanFolderForFonts (const File&) // TODO(reuk) } -//============================================================================== -bool TextLayout::createNativeLayout ([[maybe_unused]] const AttributedString& text) -{ - // TODO(reuk) Currently unimplemented - return false; -} - } // namespace juce diff --git a/modules/juce_graphics/native/juce_Fonts_android.cpp b/modules/juce_graphics/native/juce_Fonts_android.cpp index 0050c9a2c6..64dac9f2de 100644 --- a/modules/juce_graphics/native/juce_Fonts_android.cpp +++ b/modules/juce_graphics/native/juce_Fonts_android.cpp @@ -633,9 +633,4 @@ void Typeface::scanFolderForFonts (const File&) jassertfalse; // not currently available } -bool TextLayout::createNativeLayout (const AttributedString&) -{ - return false; -} - } // namespace juce diff --git a/modules/juce_graphics/native/juce_Fonts_linux.cpp b/modules/juce_graphics/native/juce_Fonts_linux.cpp index f785daabca..59dfc6cb6f 100644 --- a/modules/juce_graphics/native/juce_Fonts_linux.cpp +++ b/modules/juce_graphics/native/juce_Fonts_linux.cpp @@ -104,11 +104,6 @@ StringArray Font::findAllTypefaceStyles (const String& family) return FTTypefaceList::getInstance()->findAllTypefaceStyles (family); } -bool TextLayout::createNativeLayout (const AttributedString&) -{ - return false; -} - //============================================================================== struct DefaultFontInfo { diff --git a/modules/juce_graphics/native/juce_Fonts_mac.mm b/modules/juce_graphics/native/juce_Fonts_mac.mm index 7acf49dbbb..f0f3ab58d4 100644 --- a/modules/juce_graphics/native/juce_Fonts_mac.mm +++ b/modules/juce_graphics/native/juce_Fonts_mac.mm @@ -35,432 +35,6 @@ namespace juce { -static constexpr float referenceFontSize = 1024.0f; - -CTFontRef getCTFontFromTypeface (const Font& f); - -namespace CoreTextTypeLayout -{ - static float getFontTotalHeight (CTFontRef font) - { - return std::abs ((float) CTFontGetAscent (font)) - + std::abs ((float) CTFontGetDescent (font)); - } - - static float getHeightToPointsFactor (CTFontRef font) - { - return (float) CTFontGetSize (font) / (float) getFontTotalHeight (font); - } - - static CFUniquePtr getFontWithPointSize (CTFontRef font, float pointSize) - { - return CFUniquePtr (CTFontCreateCopyWithAttributes (font, pointSize, nullptr, nullptr)); - } - - //============================================================================== - struct Advances - { - Advances (CTRunRef run, CFIndex numGlyphs) : advances (CTRunGetAdvancesPtr (run)) - { - if (advances == nullptr) - { - local.malloc (numGlyphs); - CTRunGetAdvances (run, CFRangeMake (0, 0), local); - advances = local; - } - } - - const CGSize* advances; - HeapBlock local; - }; - - struct Glyphs - { - Glyphs (CTRunRef run, size_t numGlyphs) : glyphs (CTRunGetGlyphsPtr (run)) - { - if (glyphs == nullptr) - { - local.malloc (numGlyphs); - CTRunGetGlyphs (run, CFRangeMake (0, 0), local); - glyphs = local; - } - } - - const CGGlyph* glyphs; - HeapBlock local; - }; - - struct Positions - { - Positions (CTRunRef run, size_t numGlyphs) : points (CTRunGetPositionsPtr (run)) - { - if (points == nullptr) - { - local.malloc (numGlyphs); - CTRunGetPositions (run, CFRangeMake (0, 0), local); - points = local; - } - } - - const CGPoint* points; - HeapBlock local; - }; - - struct LineInfo - { - LineInfo (CTFrameRef frame, CTLineRef line, CFIndex lineIndex) - { - CTFrameGetLineOrigins (frame, CFRangeMake (lineIndex, 1), &origin); - CTLineGetTypographicBounds (line, &ascent, &descent, &leading); - } - - CGPoint origin; - CGFloat ascent, descent, leading; - }; - - static CFUniquePtr getOrCreateFont (const Font& f) - { - if (auto ctf = getCTFontFromTypeface (f)) - { - CFRetain (ctf); - return CFUniquePtr (ctf); - } - - return nullptr; - } - - //============================================================================== - static CTTextAlignment getTextAlignment (const AttributedString& text) - { - const auto flags = text.getJustification().getOnlyHorizontalFlags(); - - if (@available (macOS 10.8, *)) - { - switch (flags) - { - case Justification::right: return kCTTextAlignmentRight; - case Justification::horizontallyCentred: return kCTTextAlignmentCenter; - case Justification::horizontallyJustified: return kCTTextAlignmentJustified; - default: return kCTTextAlignmentLeft; - } - } - - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") - - switch (flags) - { - case Justification::right: return kCTRightTextAlignment; - case Justification::horizontallyCentred: return kCTCenterTextAlignment; - case Justification::horizontallyJustified: return kCTJustifiedTextAlignment; - default: return kCTLeftTextAlignment; - } - - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - } - - static CTLineBreakMode getLineBreakMode (const AttributedString& text) - { - switch (text.getWordWrap()) - { - case AttributedString::none: return kCTLineBreakByClipping; - case AttributedString::byChar: return kCTLineBreakByCharWrapping; - case AttributedString::byWord: - default: return kCTLineBreakByWordWrapping; - } - } - - static CTWritingDirection getWritingDirection (const AttributedString& text) - { - switch (text.getReadingDirection()) - { - case AttributedString::rightToLeft: return kCTWritingDirectionRightToLeft; - case AttributedString::leftToRight: return kCTWritingDirectionLeftToRight; - case AttributedString::natural: - default: return kCTWritingDirectionNatural; - } - } - - //============================================================================== - // A flatmap that properly retains/releases font refs - class FontMap - { - public: - void emplace (CTFontRef ctFontRef, Font value) - { - pairs.emplace (std::lower_bound (pairs.begin(), pairs.end(), ctFontRef), ctFontRef, std::move (value)); - } - - const Font* find (CTFontRef ctFontRef) const - { - const auto iter = std::lower_bound (pairs.begin(), pairs.end(), ctFontRef); - - if (iter == pairs.end()) - return nullptr; - - if (iter->key.get() != ctFontRef) - return nullptr; - - return &iter->value; - } - - private: - struct Pair - { - Pair (CTFontRef ref, Font font) : key (ref), value (std::move (font)) { CFRetain (ref); } - - bool operator< (CTFontRef other) const { return key.get() < other; } - - CFUniquePtr key; - Font value; - }; - - std::vector pairs; - }; - - struct AttributedStringAndFontMap - { - CFUniquePtr string; - FontMap fontMap; - }; - - static AttributedStringAndFontMap createCFAttributedString (const AttributedString& text) - { - FontMap fontMap; - - const detail::ColorSpacePtr rgbColourSpace { CGColorSpaceCreateWithName (kCGColorSpaceSRGB) }; - - auto attribString = CFAttributedStringCreateMutable (kCFAllocatorDefault, 0); - CFUniquePtr cfText (text.getText().toCFString()); - - CFAttributedStringReplaceString (attribString, CFRangeMake (0, 0), cfText.get()); - - const auto numCharacterAttributes = text.getNumAttributes(); - const auto attribStringLen = CFAttributedStringGetLength (attribString); - const auto beginPtr = text.getText().toUTF16(); - auto currentPosition = beginPtr; - - for (int i = 0; i < numCharacterAttributes; currentPosition += text.getAttribute (i).range.getLength(), ++i) - { - const auto& attr = text.getAttribute (i); - const auto wordBegin = currentPosition.getAddress() - beginPtr.getAddress(); - - if (attribStringLen <= wordBegin) - continue; - - const auto wordEndAddress = (currentPosition + attr.range.getLength()).getAddress(); - const auto wordEnd = jmin (attribStringLen, (CFIndex) (wordEndAddress - beginPtr.getAddress())); - const auto range = CFRangeMake (wordBegin, wordEnd - wordBegin); - - if (auto ctFontRef = getOrCreateFont (attr.font)) - { - ctFontRef = getFontWithPointSize (ctFontRef.get(), attr.font.getHeight() * getHeightToPointsFactor (ctFontRef.get())); - fontMap.emplace (ctFontRef.get(), attr.font); - - CFAttributedStringSetAttribute (attribString, range, kCTFontAttributeName, ctFontRef.get()); - - if (attr.font.isUnderlined()) - { - auto underline = kCTUnderlineStyleSingle; - - CFUniquePtr numberRef (CFNumberCreate (nullptr, kCFNumberIntType, &underline)); - CFAttributedStringSetAttribute (attribString, range, kCTUnderlineStyleAttributeName, numberRef.get()); - } - - auto extraKerning = attr.font.getExtraKerningFactor(); - - if (! approximatelyEqual (extraKerning, 0.0f)) - { - extraKerning *= attr.font.getHeight(); - - CFUniquePtr numberRef (CFNumberCreate (nullptr, kCFNumberFloatType, &extraKerning)); - CFAttributedStringSetAttribute (attribString, range, kCTKernAttributeName, numberRef.get()); - } - } - - { - auto col = attr.colour; - - const CGFloat components[] = { col.getFloatRed(), - col.getFloatGreen(), - col.getFloatBlue(), - col.getFloatAlpha() }; - auto colour = CGColorCreate (rgbColourSpace.get(), components); - - CFAttributedStringSetAttribute (attribString, range, kCTForegroundColorAttributeName, colour); - CGColorRelease (colour); - } - } - - // Paragraph Attributes - auto ctTextAlignment = getTextAlignment (text); - auto ctLineBreakMode = getLineBreakMode (text); - auto ctWritingDirection = getWritingDirection (text); - CGFloat ctLineSpacing = text.getLineSpacing(); - - CTParagraphStyleSetting settings[] = - { - { kCTParagraphStyleSpecifierAlignment, sizeof (CTTextAlignment), &ctTextAlignment }, - { kCTParagraphStyleSpecifierLineBreakMode, sizeof (CTLineBreakMode), &ctLineBreakMode }, - { kCTParagraphStyleSpecifierBaseWritingDirection, sizeof (CTWritingDirection), &ctWritingDirection}, - { kCTParagraphStyleSpecifierLineSpacingAdjustment, sizeof (CGFloat), &ctLineSpacing } - }; - - CFUniquePtr ctParagraphStyleRef (CTParagraphStyleCreate (settings, (size_t) numElementsInArray (settings))); - CFAttributedStringSetAttribute (attribString, CFRangeMake (0, CFAttributedStringGetLength (attribString)), - kCTParagraphStyleAttributeName, ctParagraphStyleRef.get()); - return { CFUniquePtr (attribString), std::move (fontMap) }; - } - - struct FramesetterAndFontMap - { - CFUniquePtr framesetter; - FontMap fontMap; - }; - - static FramesetterAndFontMap createCTFramesetter (const AttributedString& text) - { - auto attribStringAndMap = createCFAttributedString (text); - return { CFUniquePtr (CTFramesetterCreateWithAttributedString (attribStringAndMap.string.get())), - std::move (attribStringAndMap.fontMap) }; - } - - static CFUniquePtr createCTFrame (CTFramesetterRef framesetter, CGRect bounds) - { - auto path = CGPathCreateMutable(); - CGPathAddRect (path, nullptr, bounds); - - CFUniquePtr frame (CTFramesetterCreateFrame (framesetter, CFRangeMake (0, 0), path, nullptr)); - CGPathRelease (path); - - return frame; - } - - struct FrameAndFontMap - { - CFUniquePtr frame; - FontMap fontMap; - }; - - static FrameAndFontMap createCTFrame (const AttributedString& text, CGRect bounds) - { - auto framesetterAndMap = createCTFramesetter (text); - return { createCTFrame (framesetterAndMap.framesetter.get(), bounds), - std::move (framesetterAndMap.fontMap) }; - } - - static void createLayout (TextLayout& glyphLayout, const AttributedString& text) - { - auto boundsHeight = glyphLayout.getHeight(); - auto frameAndMap = createCTFrame (text, CGRectMake (0, 0, glyphLayout.getWidth(), boundsHeight)); - auto lines = CTFrameGetLines (frameAndMap.frame.get()); - auto numLines = CFArrayGetCount (lines); - - glyphLayout.ensureStorageAllocated ((int) numLines); - - for (CFIndex i = 0; i < numLines; ++i) - { - auto line = (CTLineRef) CFArrayGetValueAtIndex (lines, i); - auto runs = CTLineGetGlyphRuns (line); - auto numRuns = CFArrayGetCount (runs); - - auto cfrlineStringRange = CTLineGetStringRange (line); - auto lineStringEnd = cfrlineStringRange.location + cfrlineStringRange.length; - Range lineStringRange ((int) cfrlineStringRange.location, (int) lineStringEnd); - - LineInfo lineInfo (frameAndMap.frame.get(), line, i); - - 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) - { - auto run = (CTRunRef) CFArrayGetValueAtIndex (runs, j); - auto numGlyphs = CTRunGetGlyphCount (run); - auto runStringRange = CTRunGetStringRange (run); - - auto glyphRun = new TextLayout::Run (Range ((int) runStringRange.location, - (int) (runStringRange.location + runStringRange.length - 1)), - (int) numGlyphs); - glyphLine->runs.add (glyphRun); - - CFDictionaryRef runAttributes = CTRunGetAttributes (run); - - CTFontRef ctRunFont; - if (CFDictionaryGetValueIfPresent (runAttributes, kCTFontAttributeName, (const void**) &ctRunFont)) - { - glyphRun->font = [&] - { - if (auto* it = frameAndMap.fontMap.find (ctRunFont)) - return *it; - - CFUniquePtr cfsFontName (CTFontCopyPostScriptName (ctRunFont)); - CFUniquePtr ctFontRef (CTFontCreateWithName (cfsFontName.get(), referenceFontSize, nullptr)); - - auto fontHeightToPointsFactor = getHeightToPointsFactor (ctFontRef.get()); - - CFUniquePtr cfsFontFamily ((CFStringRef) CTFontCopyAttribute (ctRunFont, kCTFontFamilyNameAttribute)); - CFUniquePtr cfsFontStyle ((CFStringRef) CTFontCopyAttribute (ctRunFont, kCTFontStyleNameAttribute)); - - Font result (FontOptions { String::fromCFString (cfsFontFamily.get()), - String::fromCFString (cfsFontStyle.get()), - (float) (CTFontGetSize (ctRunFont) / fontHeightToPointsFactor) }); - - auto isUnderlined = [&] - { - CFNumberRef underlineStyle; - - if (CFDictionaryGetValueIfPresent (runAttributes, kCTUnderlineStyleAttributeName, (const void**) &underlineStyle)) - { - if (CFGetTypeID (underlineStyle) == CFNumberGetTypeID()) - { - int value = 0; - CFNumberGetValue (underlineStyle, kCFNumberLongType, (void*) &value); - - return value != 0; - } - } - - return false; - }(); - - result.setUnderline (isUnderlined); - return result; - }(); - } - - CGColorRef cgRunColor; - - if (CFDictionaryGetValueIfPresent (runAttributes, kCTForegroundColorAttributeName, (const void**) &cgRunColor) - && CGColorGetNumberOfComponents (cgRunColor) == 4) - { - auto* components = CGColorGetComponents (cgRunColor); - - glyphRun->colour = Colour::fromFloatRGBA ((float) components[0], - (float) components[1], - (float) components[2], - (float) components[3]); - } - - const Glyphs glyphs (run, (size_t) numGlyphs); - const Advances advances (run, numGlyphs); - const Positions positions (run, (size_t) numGlyphs); - - for (CFIndex k = 0; k < numGlyphs; ++k) - glyphRun->glyphs.add (TextLayout::Glyph (glyphs.glyphs[k], - convertToPointFloat (positions.points[k]), - (float) advances.advances[k].width)); - } - - glyphLayout.addLine (std::move (glyphLine)); - } - } -} - // This symbol is available on all the platforms we support, but not declared in the CoreText headers on older platforms. extern "C" CTFontRef CTFontCreateForStringWithLanguage (CTFontRef currentFont, CFStringRef string, @@ -729,16 +303,6 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreTextTypeface) }; -CTFontRef getCTFontFromTypeface (const Font& f) -{ - const auto typeface = f.getTypefacePtr(); - - if (auto* tf = dynamic_cast (typeface.get())) - return tf->getFontRef(); - - return {}; -} - //============================================================================== Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font) { @@ -849,10 +413,4 @@ Typeface::Ptr Font::Native::getDefaultPlatformTypefaceForFont (const Font& font) return Typeface::createSystemTypefaceFor (newFont); } -bool TextLayout::createNativeLayout (const AttributedString& text) -{ - CoreTextTypeLayout::createLayout (*this, text); - return true; -} - } // namespace juce