Browse Source

Fonts: Adjust attribute ranges correctly when rendering AttributedStrings

CFAttributedString ranges must be given in terms of 16-bit word offsets,
rather than codepoints.
v6.1.6
reuk 3 years ago
parent
commit
e27fb35996
No known key found for this signature in database GPG Key ID: 9ADCD339CFC98A11
4 changed files with 73 additions and 16 deletions
  1. +28
    -0
      modules/juce_graphics/fonts/juce_AttributedString.cpp
  2. +5
    -0
      modules/juce_graphics/fonts/juce_AttributedString.h
  3. +11
    -7
      modules/juce_graphics/native/juce_mac_Fonts.mm
  4. +29
    -9
      modules/juce_graphics/native/juce_win32_DirectWriteTypeLayout.cpp

+ 28
- 0
modules/juce_graphics/fonts/juce_AttributedString.cpp View File

@@ -54,6 +54,24 @@ namespace
} }
} }
inline bool areInvariantsMaintained (const String& text, const Array<AttributedString::Attribute>& atts)
{
if (atts.isEmpty())
return true;
if (atts.getFirst().range.getStart() != 0)
return false;
if (atts.getLast().range.getEnd() != text.length())
return false;
for (auto it = std::next (atts.begin()); it != atts.end(); ++it)
if (it->range.getStart() != std::prev (it)->range.getEnd())
return false;
return true;
}
Range<int> splitAttributeRanges (Array<AttributedString::Attribute>& atts, Range<int> newRange) Range<int> splitAttributeRanges (Array<AttributedString::Attribute>& atts, Range<int> newRange)
{ {
newRange = newRange.getIntersectionWith ({ 0, getLength (atts) }); newRange = newRange.getIntersectionWith ({ 0, getLength (atts) });
@@ -151,30 +169,35 @@ void AttributedString::setText (const String& newText)
truncate (attributes, newLength); truncate (attributes, newLength);
text = newText; text = newText;
jassert (areInvariantsMaintained (text, attributes));
} }
void AttributedString::append (const String& textToAppend) void AttributedString::append (const String& textToAppend)
{ {
text += textToAppend; text += textToAppend;
appendRange (attributes, textToAppend.length(), nullptr, nullptr); appendRange (attributes, textToAppend.length(), nullptr, nullptr);
jassert (areInvariantsMaintained (text, attributes));
} }
void AttributedString::append (const String& textToAppend, const Font& font) void AttributedString::append (const String& textToAppend, const Font& font)
{ {
text += textToAppend; text += textToAppend;
appendRange (attributes, textToAppend.length(), &font, nullptr); appendRange (attributes, textToAppend.length(), &font, nullptr);
jassert (areInvariantsMaintained (text, attributes));
} }
void AttributedString::append (const String& textToAppend, Colour colour) void AttributedString::append (const String& textToAppend, Colour colour)
{ {
text += textToAppend; text += textToAppend;
appendRange (attributes, textToAppend.length(), nullptr, &colour); appendRange (attributes, textToAppend.length(), nullptr, &colour);
jassert (areInvariantsMaintained (text, attributes));
} }
void AttributedString::append (const String& textToAppend, const Font& font, Colour colour) void AttributedString::append (const String& textToAppend, const Font& font, Colour colour)
{ {
text += textToAppend; text += textToAppend;
appendRange (attributes, textToAppend.length(), &font, &colour); appendRange (attributes, textToAppend.length(), &font, &colour);
jassert (areInvariantsMaintained (text, attributes));
} }
void AttributedString::append (const AttributedString& other) void AttributedString::append (const AttributedString& other)
@@ -188,6 +211,7 @@ void AttributedString::append (const AttributedString& other)
attributes.getReference (i).range += originalLength; attributes.getReference (i).range += originalLength;
mergeAdjacentRanges (attributes); mergeAdjacentRanges (attributes);
jassert (areInvariantsMaintained (text, attributes));
} }
void AttributedString::clear() void AttributedString::clear()
@@ -219,21 +243,25 @@ void AttributedString::setLineSpacing (const float newLineSpacing) noexcept
void AttributedString::setColour (Range<int> range, Colour colour) void AttributedString::setColour (Range<int> range, Colour colour)
{ {
applyFontAndColour (attributes, range, nullptr, &colour); applyFontAndColour (attributes, range, nullptr, &colour);
jassert (areInvariantsMaintained (text, attributes));
} }
void AttributedString::setFont (Range<int> range, const Font& font) void AttributedString::setFont (Range<int> range, const Font& font)
{ {
applyFontAndColour (attributes, range, &font, nullptr); applyFontAndColour (attributes, range, &font, nullptr);
jassert (areInvariantsMaintained (text, attributes));
} }
void AttributedString::setColour (Colour colour) void AttributedString::setColour (Colour colour)
{ {
setColour ({ 0, getLength (attributes) }, colour); setColour ({ 0, getLength (attributes) }, colour);
jassert (areInvariantsMaintained (text, attributes));
} }
void AttributedString::setFont (const Font& font) void AttributedString::setFont (const Font& font)
{ {
setFont ({ 0, getLength (attributes) }, font); setFont ({ 0, getLength (attributes) }, font);
jassert (areInvariantsMaintained (text, attributes));
} }
void AttributedString::draw (Graphics& g, const Rectangle<float>& area) const void AttributedString::draw (Graphics& g, const Rectangle<float>& area) const


+ 5
- 0
modules/juce_graphics/fonts/juce_AttributedString.h View File

@@ -34,6 +34,11 @@ namespace juce
An attributed string lets you create a string with varied fonts, colours, word-wrapping, An attributed string lets you create a string with varied fonts, colours, word-wrapping,
layout, etc., and draw it using AttributedString::draw(). layout, etc., and draw it using AttributedString::draw().
Invariants:
- Every character in the string is a member of exactly one attribute.
- Attributes are sorted such that the range-end of attribute 'i' is equal to the
range-begin of attribute 'i + 1'.
@see TextLayout @see TextLayout
@tags{Graphics} @tags{Graphics}


+ 11
- 7
modules/juce_graphics/native/juce_mac_Fonts.mm View File

@@ -258,20 +258,24 @@ namespace CoreTextTypeLayout
auto attribString = CFAttributedStringCreateMutable (kCFAllocatorDefault, 0); auto attribString = CFAttributedStringCreateMutable (kCFAllocatorDefault, 0);
CFUniquePtr<CFStringRef> cfText (text.getText().toCFString()); CFUniquePtr<CFStringRef> cfText (text.getText().toCFString());
CFAttributedStringReplaceString (attribString, CFRangeMake (0, 0), cfText.get()); CFAttributedStringReplaceString (attribString, CFRangeMake (0, 0), cfText.get());
auto numCharacterAttributes = text.getNumAttributes();
auto attribStringLen = CFAttributedStringGetLength (attribString);
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; ++i)
for (int i = 0; i < numCharacterAttributes; currentPosition += text.getAttribute (i).range.getLength(), ++i)
{ {
auto& attr = text.getAttribute (i);
auto rangeStart = attr.range.getStart();
const auto& attr = text.getAttribute (i);
const auto wordBegin = currentPosition.getAddress() - beginPtr.getAddress();
if (rangeStart >= attribStringLen)
if (attribStringLen <= wordBegin)
continue; continue;
auto range = CFRangeMake (rangeStart, jmin (attr.range.getEnd(), (int) attribStringLen) - rangeStart);
const auto wordEnd = jmin (attribStringLen, (currentPosition + attr.range.getLength()).getAddress() - beginPtr.getAddress());
const auto range = CFRangeMake (wordBegin, wordEnd - wordBegin);
if (auto ctFontRef = getOrCreateFont (attr.font)) if (auto ctFontRef = getOrCreateFont (attr.font))
{ {


+ 29
- 9
modules/juce_graphics/native/juce_win32_DirectWriteTypeLayout.cpp View File

@@ -271,12 +271,22 @@ namespace DirectWriteTypeLayout
format.SetWordWrapping (wrapType); format.SetWordWrapping (wrapType);
} }
void addAttributedRange (const AttributedString::Attribute& attr, IDWriteTextLayout& textLayout,
const int textLen, ID2D1RenderTarget& renderTarget, IDWriteFontCollection& fontCollection)
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; DWRITE_TEXT_RANGE range;
range.startPosition = (UINT32) attr.range.getStart();
range.length = (UINT32) jmin (attr.range.getLength(), textLen - attr.range.getStart());
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); auto familyName = FontStyleHelpers::getConcreteFamilyName (attr.font);
@@ -367,18 +377,28 @@ namespace DirectWriteTypeLayout
hr = dwTextFormat->SetTrimming (&trimming, trimmingSign); hr = dwTextFormat->SetTrimming (&trimming, trimmingSign);
} }
auto textLen = text.getText().length();
const auto beginPtr = text.getText().toUTF16();
const auto textLen = (UINT32) (beginPtr.findTerminatingNull().getAddress() - beginPtr.getAddress());
hr = directWriteFactory.CreateTextLayout (text.getText().toWideCharPointer(), (UINT32) textLen, dwTextFormat,
maxWidth, maxHeight, textLayout.resetAndGetPointerAddress());
hr = directWriteFactory.CreateTextLayout (beginPtr.getAddress(),
textLen,
dwTextFormat,
maxWidth,
maxHeight,
textLayout.resetAndGetPointerAddress());
if (FAILED (hr) || textLayout == nullptr) if (FAILED (hr) || textLayout == nullptr)
return false; return false;
auto numAttributes = text.getNumAttributes();
const auto numAttributes = text.getNumAttributes();
auto rangePointer = beginPtr;
for (int i = 0; i < numAttributes; ++i) for (int i = 0; i < numAttributes; ++i)
addAttributedRange (text.getAttribute (i), *textLayout, textLen, renderTarget, fontCollection);
{
const auto attribute = text.getAttribute (i);
addAttributedRange (attribute, *textLayout, beginPtr, rangePointer, textLen, renderTarget, fontCollection);
rangePointer += attribute.range.getLength();
}
return true; return true;
} }


Loading…
Cancel
Save