1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-09 23:34:20 +00:00

Remove TextLayout::createNativeLayout()

This commit is contained in:
attila 2024-05-21 18:04:25 +02:00 committed by Oliver James
parent 38f299a054
commit 51955453ef
8 changed files with 33 additions and 981 deletions

View file

@ -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

View file

@ -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();
}

View file

@ -271,7 +271,6 @@ private:
Justification justification;
void createStandardLayout (const AttributedString&);
bool createNativeLayout (const AttributedString&);
JUCE_LEAK_DETECTOR (TextLayout)
};

View file

@ -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<IDWriteTextRenderer>
{
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<IDWritePixelSnapping> (result);
return ComBaseClassHelper<IDWriteTextRenderer>::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<TextLayout*> (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<TextLayout::Line>();
line->lineOrigin = Point<float> (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> ((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<ID2D1SolidColorBrush*> (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<float> (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<WindowsDirectWriteTypeface*> (typeface.get()))
if (wt->getIDWriteFontFace() == glyphRun.fontFace)
return font.withHeight (fontHeight);
}
ComSmartPtr<IDWriteFont> dwFont;
[[maybe_unused]] auto hr = fontCollection.GetFontFromFontFace (glyphRun.fontFace, dwFont.resetAndGetPointerAddress());
jassert (dwFont != nullptr);
ComSmartPtr<IDWriteFontFamily> 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<IDWriteFontFace> 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<IDWriteFontFamily> fontFamily;
[[maybe_unused]] auto hr = fontCollection.GetFontFamily (fontIndex, fontFamily.resetAndGetPointerAddress());
ComSmartPtr<IDWriteFont> 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<ID2D1SolidColorBrush> 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<IDWriteTextLayout>& 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<IDWriteFontFamily> dwFontFamily;
auto hr = fontCollection.GetFontFamily (fontIndex, dwFontFamily.resetAndGetPointerAddress());
ComSmartPtr<IDWriteFont> 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<IDWriteTextFormat> 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<IDWriteInlineObject> 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<IDWriteTextLayout> 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<DWRITE_LINE_METRICS> 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<int> (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<float>& area,
ID2D1RenderTarget& renderTarget,
IDWriteFactory& directWriteFactory,
IDWriteFontCollection& fontCollection)
{
ComSmartPtr<IDWriteTextLayout> dwTextLayout;
if (setupLayout (text, area.getWidth(), area.getHeight(), renderTarget,
directWriteFactory, fontCollection, dwTextLayout))
{
ComSmartPtr<ID2D1SolidColorBrush> 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<WindowsDirectWriteTypeface*> (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<Direct2DFactories> 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

View file

@ -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

View file

@ -633,9 +633,4 @@ void Typeface::scanFolderForFonts (const File&)
jassertfalse; // not currently available
}
bool TextLayout::createNativeLayout (const AttributedString&)
{
return false;
}
} // namespace juce

View file

@ -104,11 +104,6 @@ StringArray Font::findAllTypefaceStyles (const String& family)
return FTTypefaceList::getInstance()->findAllTypefaceStyles (family);
}
bool TextLayout::createNativeLayout (const AttributedString&)
{
return false;
}
//==============================================================================
struct DefaultFontInfo
{

View file

@ -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<CTFontRef> getFontWithPointSize (CTFontRef font, float pointSize)
{
return CFUniquePtr<CTFontRef> (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<CGSize> 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<CGGlyph> 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<CGPoint> 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<CTFontRef> getOrCreateFont (const Font& f)
{
if (auto ctf = getCTFontFromTypeface (f))
{
CFRetain (ctf);
return CFUniquePtr<CTFontRef> (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<CTFontRef> key;
Font value;
};
std::vector<Pair> pairs;
};
struct AttributedStringAndFontMap
{
CFUniquePtr<CFAttributedStringRef> string;
FontMap fontMap;
};
static AttributedStringAndFontMap createCFAttributedString (const AttributedString& text)
{
FontMap fontMap;
const detail::ColorSpacePtr rgbColourSpace { CGColorSpaceCreateWithName (kCGColorSpaceSRGB) };
auto attribString = CFAttributedStringCreateMutable (kCFAllocatorDefault, 0);
CFUniquePtr<CFStringRef> 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<CFNumberRef> 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<CFNumberRef> 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> ctParagraphStyleRef (CTParagraphStyleCreate (settings, (size_t) numElementsInArray (settings)));
CFAttributedStringSetAttribute (attribString, CFRangeMake (0, CFAttributedStringGetLength (attribString)),
kCTParagraphStyleAttributeName, ctParagraphStyleRef.get());
return { CFUniquePtr<CFAttributedStringRef> (attribString), std::move (fontMap) };
}
struct FramesetterAndFontMap
{
CFUniquePtr<CTFramesetterRef> framesetter;
FontMap fontMap;
};
static FramesetterAndFontMap createCTFramesetter (const AttributedString& text)
{
auto attribStringAndMap = createCFAttributedString (text);
return { CFUniquePtr<CTFramesetterRef> (CTFramesetterCreateWithAttributedString (attribStringAndMap.string.get())),
std::move (attribStringAndMap.fontMap) };
}
static CFUniquePtr<CTFrameRef> createCTFrame (CTFramesetterRef framesetter, CGRect bounds)
{
auto path = CGPathCreateMutable();
CGPathAddRect (path, nullptr, bounds);
CFUniquePtr<CTFrameRef> frame (CTFramesetterCreateFrame (framesetter, CFRangeMake (0, 0), path, nullptr));
CGPathRelease (path);
return frame;
}
struct FrameAndFontMap
{
CFUniquePtr<CTFrameRef> 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<int> lineStringRange ((int) cfrlineStringRange.location, (int) lineStringEnd);
LineInfo lineInfo (frameAndMap.frame.get(), line, i);
auto glyphLine = std::make_unique<TextLayout::Line> (lineStringRange,
Point<float> ((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> ((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<CFStringRef> cfsFontName (CTFontCopyPostScriptName (ctRunFont));
CFUniquePtr<CTFontRef> ctFontRef (CTFontCreateWithName (cfsFontName.get(), referenceFontSize, nullptr));
auto fontHeightToPointsFactor = getHeightToPointsFactor (ctFontRef.get());
CFUniquePtr<CFStringRef> cfsFontFamily ((CFStringRef) CTFontCopyAttribute (ctRunFont, kCTFontFamilyNameAttribute));
CFUniquePtr<CFStringRef> 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<CoreTextTypeface*> (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