CFAttributedString ranges must be given in terms of 16-bit word offsets, rather than codepoints.v6.1.6
@@ -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) | |||
{ | |||
newRange = newRange.getIntersectionWith ({ 0, getLength (atts) }); | |||
@@ -151,30 +169,35 @@ void AttributedString::setText (const String& newText) | |||
truncate (attributes, newLength); | |||
text = newText; | |||
jassert (areInvariantsMaintained (text, attributes)); | |||
} | |||
void AttributedString::append (const String& textToAppend) | |||
{ | |||
text += textToAppend; | |||
appendRange (attributes, textToAppend.length(), nullptr, nullptr); | |||
jassert (areInvariantsMaintained (text, attributes)); | |||
} | |||
void AttributedString::append (const String& textToAppend, const Font& font) | |||
{ | |||
text += textToAppend; | |||
appendRange (attributes, textToAppend.length(), &font, nullptr); | |||
jassert (areInvariantsMaintained (text, attributes)); | |||
} | |||
void AttributedString::append (const String& textToAppend, Colour colour) | |||
{ | |||
text += textToAppend; | |||
appendRange (attributes, textToAppend.length(), nullptr, &colour); | |||
jassert (areInvariantsMaintained (text, attributes)); | |||
} | |||
void AttributedString::append (const String& textToAppend, const Font& font, Colour colour) | |||
{ | |||
text += textToAppend; | |||
appendRange (attributes, textToAppend.length(), &font, &colour); | |||
jassert (areInvariantsMaintained (text, attributes)); | |||
} | |||
void AttributedString::append (const AttributedString& other) | |||
@@ -188,6 +211,7 @@ void AttributedString::append (const AttributedString& other) | |||
attributes.getReference (i).range += originalLength; | |||
mergeAdjacentRanges (attributes); | |||
jassert (areInvariantsMaintained (text, attributes)); | |||
} | |||
void AttributedString::clear() | |||
@@ -219,21 +243,25 @@ void AttributedString::setLineSpacing (const float newLineSpacing) noexcept | |||
void AttributedString::setColour (Range<int> range, Colour colour) | |||
{ | |||
applyFontAndColour (attributes, range, nullptr, &colour); | |||
jassert (areInvariantsMaintained (text, attributes)); | |||
} | |||
void AttributedString::setFont (Range<int> range, const Font& font) | |||
{ | |||
applyFontAndColour (attributes, range, &font, nullptr); | |||
jassert (areInvariantsMaintained (text, attributes)); | |||
} | |||
void AttributedString::setColour (Colour colour) | |||
{ | |||
setColour ({ 0, getLength (attributes) }, colour); | |||
jassert (areInvariantsMaintained (text, attributes)); | |||
} | |||
void AttributedString::setFont (const Font& font) | |||
{ | |||
setFont ({ 0, getLength (attributes) }, font); | |||
jassert (areInvariantsMaintained (text, attributes)); | |||
} | |||
void AttributedString::draw (Graphics& g, const Rectangle<float>& area) const | |||
@@ -34,6 +34,11 @@ namespace juce | |||
An attributed string lets you create a string with varied fonts, colours, word-wrapping, | |||
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 | |||
@tags{Graphics} | |||
@@ -258,20 +258,24 @@ namespace CoreTextTypeLayout | |||
auto attribString = CFAttributedStringCreateMutable (kCFAllocatorDefault, 0); | |||
CFUniquePtr<CFStringRef> cfText (text.getText().toCFString()); | |||
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; | |||
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)) | |||
{ | |||
@@ -271,12 +271,22 @@ namespace DirectWriteTypeLayout | |||
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; | |||
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); | |||
@@ -367,18 +377,28 @@ namespace DirectWriteTypeLayout | |||
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) | |||
return false; | |||
auto numAttributes = text.getNumAttributes(); | |||
const auto numAttributes = text.getNumAttributes(); | |||
auto rangePointer = beginPtr; | |||
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; | |||
} | |||