357 lines
11 KiB
C++
357 lines
11 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
This file is part of the JUCE library.
|
|
Copyright (c) 2017 - ROLI Ltd.
|
|
|
|
JUCE is an open source library subject to commercial or open-source
|
|
licensing.
|
|
|
|
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
|
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
|
27th April 2017).
|
|
|
|
End User License Agreement: www.juce.com/juce-5-licence
|
|
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
|
|
|
Or: You may also use this code under the terms of the GPL v3 (see
|
|
www.gnu.org/licenses).
|
|
|
|
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
|
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
|
DISCLAIMED.
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
namespace juce
|
|
{
|
|
|
|
namespace
|
|
{
|
|
int getLength (const Array<AttributedString::Attribute>& atts) noexcept
|
|
{
|
|
return atts.size() != 0 ? atts.getReference (atts.size() - 1).range.getEnd() : 0;
|
|
}
|
|
|
|
void splitAttributeRanges (Array<AttributedString::Attribute>& atts, int position)
|
|
{
|
|
for (int i = atts.size(); --i >= 0;)
|
|
{
|
|
const AttributedString::Attribute& att = atts.getReference (i);
|
|
const int offset = position - att.range.getStart();
|
|
|
|
if (offset >= 0)
|
|
{
|
|
if (offset > 0 && position < att.range.getEnd())
|
|
{
|
|
atts.insert (i + 1, att);
|
|
atts.getReference (i).range.setEnd (position);
|
|
atts.getReference (i + 1).range.setStart (position);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
Range<int> splitAttributeRanges (Array<AttributedString::Attribute>& atts, Range<int> newRange)
|
|
{
|
|
newRange = newRange.getIntersectionWith (Range<int> (0, getLength (atts)));
|
|
|
|
if (! newRange.isEmpty())
|
|
{
|
|
splitAttributeRanges (atts, newRange.getStart());
|
|
splitAttributeRanges (atts, newRange.getEnd());
|
|
}
|
|
|
|
return newRange;
|
|
}
|
|
|
|
void mergeAdjacentRanges (Array<AttributedString::Attribute>& atts)
|
|
{
|
|
for (int i = atts.size() - 1; --i >= 0;)
|
|
{
|
|
AttributedString::Attribute& a1 = atts.getReference (i);
|
|
AttributedString::Attribute& a2 = atts.getReference (i + 1);
|
|
|
|
if (a1.colour == a2.colour && a1.font == a2.font)
|
|
{
|
|
a1.range.setEnd (a2.range.getEnd());
|
|
atts.remove (i + 1);
|
|
|
|
if (i < atts.size() - 1)
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
|
|
void appendRange (Array<AttributedString::Attribute>& atts,
|
|
int length, const Font* f, const Colour* c)
|
|
{
|
|
if (atts.size() == 0)
|
|
{
|
|
atts.add (AttributedString::Attribute (Range<int> (0, length),
|
|
f != nullptr ? *f : Font(),
|
|
c != nullptr ? *c : Colour (0xff000000)));
|
|
}
|
|
else
|
|
{
|
|
const int start = getLength (atts);
|
|
atts.add (AttributedString::Attribute (Range<int> (start, start + length),
|
|
f != nullptr ? *f : atts.getReference (atts.size() - 1).font,
|
|
c != nullptr ? *c : atts.getReference (atts.size() - 1).colour));
|
|
mergeAdjacentRanges (atts);
|
|
}
|
|
}
|
|
|
|
void applyFontAndColour (Array<AttributedString::Attribute>& atts,
|
|
Range<int> range, const Font* f, const Colour* c)
|
|
{
|
|
range = splitAttributeRanges (atts, range);
|
|
|
|
for (int i = 0; i < atts.size(); ++i)
|
|
{
|
|
AttributedString::Attribute& att = atts.getReference (i);
|
|
|
|
if (range.getStart() < att.range.getEnd())
|
|
{
|
|
if (range.getEnd() <= att.range.getStart())
|
|
break;
|
|
|
|
if (c != nullptr) att.colour = *c;
|
|
if (f != nullptr) att.font = *f;
|
|
}
|
|
}
|
|
|
|
mergeAdjacentRanges (atts);
|
|
}
|
|
|
|
void truncate (Array<AttributedString::Attribute>& atts, int newLength)
|
|
{
|
|
splitAttributeRanges (atts, newLength);
|
|
|
|
for (int i = atts.size(); --i >= 0;)
|
|
if (atts.getReference (i).range.getStart() >= newLength)
|
|
atts.remove (i);
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
AttributedString::Attribute::Attribute() noexcept : colour (0xff000000) {}
|
|
AttributedString::Attribute::~Attribute() noexcept {}
|
|
|
|
AttributedString::Attribute::Attribute (Attribute&& other) noexcept
|
|
: range (other.range),
|
|
font (static_cast<Font&&> (other.font)),
|
|
colour (other.colour)
|
|
{
|
|
}
|
|
|
|
AttributedString::Attribute& AttributedString::Attribute::operator= (Attribute&& other) noexcept
|
|
{
|
|
range = other.range;
|
|
font = static_cast<Font&&> (other.font);
|
|
colour = other.colour;
|
|
return *this;
|
|
}
|
|
|
|
AttributedString::Attribute::Attribute (const Attribute& other) noexcept
|
|
: range (other.range),
|
|
font (other.font),
|
|
colour (other.colour)
|
|
{
|
|
}
|
|
|
|
AttributedString::Attribute& AttributedString::Attribute::operator= (const Attribute& other) noexcept
|
|
{
|
|
range = other.range;
|
|
font = other.font;
|
|
colour = other.colour;
|
|
return *this;
|
|
}
|
|
|
|
AttributedString::Attribute::Attribute (Range<int> r, const Font& f, Colour c) noexcept
|
|
: range (r), font (f), colour (c)
|
|
{
|
|
}
|
|
|
|
//==============================================================================
|
|
AttributedString::AttributedString()
|
|
: lineSpacing (0.0f),
|
|
justification (Justification::left),
|
|
wordWrap (AttributedString::byWord),
|
|
readingDirection (AttributedString::natural)
|
|
{
|
|
}
|
|
|
|
AttributedString::AttributedString (const String& newString)
|
|
: lineSpacing (0.0f),
|
|
justification (Justification::left),
|
|
wordWrap (AttributedString::byWord),
|
|
readingDirection (AttributedString::natural)
|
|
{
|
|
setText (newString);
|
|
}
|
|
|
|
AttributedString::AttributedString (const AttributedString& other)
|
|
: text (other.text),
|
|
lineSpacing (other.lineSpacing),
|
|
justification (other.justification),
|
|
wordWrap (other.wordWrap),
|
|
readingDirection (other.readingDirection),
|
|
attributes (other.attributes)
|
|
{
|
|
}
|
|
|
|
AttributedString& AttributedString::operator= (const AttributedString& other)
|
|
{
|
|
if (this != &other)
|
|
{
|
|
text = other.text;
|
|
lineSpacing = other.lineSpacing;
|
|
justification = other.justification;
|
|
wordWrap = other.wordWrap;
|
|
readingDirection = other.readingDirection;
|
|
attributes = other.attributes;
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
AttributedString::AttributedString (AttributedString&& other) noexcept
|
|
: text (static_cast<String&&> (other.text)),
|
|
lineSpacing (other.lineSpacing),
|
|
justification (other.justification),
|
|
wordWrap (other.wordWrap),
|
|
readingDirection (other.readingDirection),
|
|
attributes (static_cast<Array<Attribute>&&> (other.attributes))
|
|
{
|
|
}
|
|
|
|
AttributedString& AttributedString::operator= (AttributedString&& other) noexcept
|
|
{
|
|
text = static_cast<String&&> (other.text);
|
|
lineSpacing = other.lineSpacing;
|
|
justification = other.justification;
|
|
wordWrap = other.wordWrap;
|
|
readingDirection = other.readingDirection;
|
|
attributes = static_cast<Array<Attribute>&&> (other.attributes);
|
|
return *this;
|
|
}
|
|
|
|
AttributedString::~AttributedString() noexcept {}
|
|
|
|
void AttributedString::setText (const String& newText)
|
|
{
|
|
const int newLength = newText.length();
|
|
const int oldLength = getLength (attributes);
|
|
|
|
if (newLength > oldLength)
|
|
appendRange (attributes, newLength - oldLength, nullptr, nullptr);
|
|
else if (newLength < oldLength)
|
|
truncate (attributes, newLength);
|
|
|
|
text = newText;
|
|
}
|
|
|
|
void AttributedString::append (const String& textToAppend)
|
|
{
|
|
text += textToAppend;
|
|
appendRange (attributes, textToAppend.length(), nullptr, nullptr);
|
|
}
|
|
|
|
void AttributedString::append (const String& textToAppend, const Font& font)
|
|
{
|
|
text += textToAppend;
|
|
appendRange (attributes, textToAppend.length(), &font, nullptr);
|
|
}
|
|
|
|
void AttributedString::append (const String& textToAppend, Colour colour)
|
|
{
|
|
text += textToAppend;
|
|
appendRange (attributes, textToAppend.length(), nullptr, &colour);
|
|
}
|
|
|
|
void AttributedString::append (const String& textToAppend, const Font& font, Colour colour)
|
|
{
|
|
text += textToAppend;
|
|
appendRange (attributes, textToAppend.length(), &font, &colour);
|
|
}
|
|
|
|
void AttributedString::append (const AttributedString& other)
|
|
{
|
|
const int originalLength = getLength (attributes);
|
|
const int originalNumAtts = attributes.size();
|
|
text += other.text;
|
|
attributes.addArray (other.attributes);
|
|
|
|
for (int i = originalNumAtts; i < attributes.size(); ++i)
|
|
attributes.getReference (i).range += originalLength;
|
|
|
|
mergeAdjacentRanges (attributes);
|
|
}
|
|
|
|
void AttributedString::clear()
|
|
{
|
|
text.clear();
|
|
attributes.clear();
|
|
}
|
|
|
|
void AttributedString::setJustification (Justification newJustification) noexcept
|
|
{
|
|
justification = newJustification;
|
|
}
|
|
|
|
void AttributedString::setWordWrap (WordWrap newWordWrap) noexcept
|
|
{
|
|
wordWrap = newWordWrap;
|
|
}
|
|
|
|
void AttributedString::setReadingDirection (ReadingDirection newReadingDirection) noexcept
|
|
{
|
|
readingDirection = newReadingDirection;
|
|
}
|
|
|
|
void AttributedString::setLineSpacing (const float newLineSpacing) noexcept
|
|
{
|
|
lineSpacing = newLineSpacing;
|
|
}
|
|
|
|
void AttributedString::setColour (Range<int> range, Colour colour)
|
|
{
|
|
applyFontAndColour (attributes, range, nullptr, &colour);
|
|
}
|
|
|
|
void AttributedString::setFont (Range<int> range, const Font& font)
|
|
{
|
|
applyFontAndColour (attributes, range, &font, nullptr);
|
|
}
|
|
|
|
void AttributedString::setColour (Colour colour)
|
|
{
|
|
setColour (Range<int> (0, getLength (attributes)), colour);
|
|
}
|
|
|
|
void AttributedString::setFont (const Font& font)
|
|
{
|
|
setFont (Range<int> (0, getLength (attributes)), font);
|
|
}
|
|
|
|
void AttributedString::draw (Graphics& g, const Rectangle<float>& area) const
|
|
{
|
|
if (text.isNotEmpty() && g.clipRegionIntersects (area.getSmallestIntegerContainer()))
|
|
{
|
|
jassert (text.length() == getLength (attributes));
|
|
|
|
if (! g.getInternalContext().drawTextLayout (*this, area))
|
|
{
|
|
TextLayout layout;
|
|
layout.createLayout (*this, area.getWidth());
|
|
layout.draw (g, area);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace juce
|