1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    By using JUCE, you agree to the terms of both the JUCE 6 End-User License
11    Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
12 
13    End User License Agreement: www.juce.com/juce-6-licence
14    Privacy Policy: www.juce.com/juce-privacy-policy
15 
16    Or: You may also use this code under the terms of the GPL v3 (see
17    www.gnu.org/licenses).
18 
19    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21    DISCLAIMED.
22 
23   ==============================================================================
24 */
25 
26 namespace juce
27 {
28 
29 namespace
30 {
getLength(const Array<AttributedString::Attribute> & atts)31     int getLength (const Array<AttributedString::Attribute>& atts) noexcept
32     {
33         return atts.size() != 0 ? atts.getReference (atts.size() - 1).range.getEnd() : 0;
34     }
35 
splitAttributeRanges(Array<AttributedString::Attribute> & atts,int position)36     void splitAttributeRanges (Array<AttributedString::Attribute>& atts, int position)
37     {
38         for (int i = atts.size(); --i >= 0;)
39         {
40             const auto& att = atts.getUnchecked (i);
41             auto offset = position - att.range.getStart();
42 
43             if (offset >= 0)
44             {
45                 if (offset > 0 && position < att.range.getEnd())
46                 {
47                     atts.insert (i + 1, AttributedString::Attribute (att));
48                     atts.getReference (i).range.setEnd (position);
49                     atts.getReference (i + 1).range.setStart (position);
50                 }
51 
52                 break;
53             }
54         }
55     }
56 
splitAttributeRanges(Array<AttributedString::Attribute> & atts,Range<int> newRange)57     Range<int> splitAttributeRanges (Array<AttributedString::Attribute>& atts, Range<int> newRange)
58     {
59         newRange = newRange.getIntersectionWith ({ 0, getLength (atts) });
60 
61         if (! newRange.isEmpty())
62         {
63             splitAttributeRanges (atts, newRange.getStart());
64             splitAttributeRanges (atts, newRange.getEnd());
65         }
66 
67         return newRange;
68     }
69 
mergeAdjacentRanges(Array<AttributedString::Attribute> & atts)70     void mergeAdjacentRanges (Array<AttributedString::Attribute>& atts)
71     {
72         for (int i = atts.size() - 1; --i >= 0;)
73         {
74             auto& a1 = atts.getReference (i);
75             auto& a2 = atts.getReference (i + 1);
76 
77             if (a1.colour == a2.colour && a1.font == a2.font)
78             {
79                 a1.range.setEnd (a2.range.getEnd());
80                 atts.remove (i + 1);
81 
82                 if (i < atts.size() - 1)
83                     ++i;
84             }
85         }
86     }
87 
appendRange(Array<AttributedString::Attribute> & atts,int length,const Font * f,const Colour * c)88     void appendRange (Array<AttributedString::Attribute>& atts,
89                       int length, const Font* f, const Colour* c)
90     {
91         if (atts.size() == 0)
92         {
93             atts.add ({ Range<int> (0, length), f != nullptr ? *f : Font(), c != nullptr ? *c : Colour (0xff000000) });
94         }
95         else
96         {
97             auto start = getLength (atts);
98             atts.add ({ Range<int> (start, start + length),
99                         f != nullptr ? *f : atts.getReference (atts.size() - 1).font,
100                         c != nullptr ? *c : atts.getReference (atts.size() - 1).colour });
101 
102             mergeAdjacentRanges (atts);
103         }
104     }
105 
applyFontAndColour(Array<AttributedString::Attribute> & atts,Range<int> range,const Font * f,const Colour * c)106     void applyFontAndColour (Array<AttributedString::Attribute>& atts,
107                              Range<int> range, const Font* f, const Colour* c)
108     {
109         range = splitAttributeRanges (atts, range);
110 
111         for (auto& att : atts)
112         {
113             if (range.getStart() < att.range.getEnd())
114             {
115                 if (range.getEnd() <= att.range.getStart())
116                     break;
117 
118                 if (c != nullptr) att.colour = *c;
119                 if (f != nullptr) att.font = *f;
120             }
121         }
122 
123         mergeAdjacentRanges (atts);
124     }
125 
truncate(Array<AttributedString::Attribute> & atts,int newLength)126     void truncate (Array<AttributedString::Attribute>& atts, int newLength)
127     {
128         splitAttributeRanges (atts, newLength);
129 
130         for (int i = atts.size(); --i >= 0;)
131             if (atts.getReference (i).range.getStart() >= newLength)
132                 atts.remove (i);
133     }
134 }
135 
136 //==============================================================================
Attribute(Range<int> r,const Font & f,Colour c)137 AttributedString::Attribute::Attribute (Range<int> r, const Font& f, Colour c) noexcept
138     : range (r), font (f), colour (c)
139 {
140 }
141 
142 //==============================================================================
setText(const String & newText)143 void AttributedString::setText (const String& newText)
144 {
145     auto newLength = newText.length();
146     auto oldLength = getLength (attributes);
147 
148     if (newLength > oldLength)
149         appendRange (attributes, newLength - oldLength, nullptr, nullptr);
150     else if (newLength < oldLength)
151         truncate (attributes, newLength);
152 
153     text = newText;
154 }
155 
append(const String & textToAppend)156 void AttributedString::append (const String& textToAppend)
157 {
158     text += textToAppend;
159     appendRange (attributes, textToAppend.length(), nullptr, nullptr);
160 }
161 
append(const String & textToAppend,const Font & font)162 void AttributedString::append (const String& textToAppend, const Font& font)
163 {
164     text += textToAppend;
165     appendRange (attributes, textToAppend.length(), &font, nullptr);
166 }
167 
append(const String & textToAppend,Colour colour)168 void AttributedString::append (const String& textToAppend, Colour colour)
169 {
170     text += textToAppend;
171     appendRange (attributes, textToAppend.length(), nullptr, &colour);
172 }
173 
append(const String & textToAppend,const Font & font,Colour colour)174 void AttributedString::append (const String& textToAppend, const Font& font, Colour colour)
175 {
176     text += textToAppend;
177     appendRange (attributes, textToAppend.length(), &font, &colour);
178 }
179 
append(const AttributedString & other)180 void AttributedString::append (const AttributedString& other)
181 {
182     auto originalLength = getLength (attributes);
183     auto originalNumAtts = attributes.size();
184     text += other.text;
185     attributes.addArray (other.attributes);
186 
187     for (auto i = originalNumAtts; i < attributes.size(); ++i)
188         attributes.getReference (i).range += originalLength;
189 
190     mergeAdjacentRanges (attributes);
191 }
192 
clear()193 void AttributedString::clear()
194 {
195     text.clear();
196     attributes.clear();
197 }
198 
setJustification(Justification newJustification)199 void AttributedString::setJustification (Justification newJustification) noexcept
200 {
201     justification = newJustification;
202 }
203 
setWordWrap(WordWrap newWordWrap)204 void AttributedString::setWordWrap (WordWrap newWordWrap) noexcept
205 {
206     wordWrap = newWordWrap;
207 }
208 
setReadingDirection(ReadingDirection newReadingDirection)209 void AttributedString::setReadingDirection (ReadingDirection newReadingDirection) noexcept
210 {
211     readingDirection = newReadingDirection;
212 }
213 
setLineSpacing(const float newLineSpacing)214 void AttributedString::setLineSpacing (const float newLineSpacing) noexcept
215 {
216     lineSpacing = newLineSpacing;
217 }
218 
setColour(Range<int> range,Colour colour)219 void AttributedString::setColour (Range<int> range, Colour colour)
220 {
221     applyFontAndColour (attributes, range, nullptr, &colour);
222 }
223 
setFont(Range<int> range,const Font & font)224 void AttributedString::setFont (Range<int> range, const Font& font)
225 {
226     applyFontAndColour (attributes, range, &font, nullptr);
227 }
228 
setColour(Colour colour)229 void AttributedString::setColour (Colour colour)
230 {
231     setColour ({ 0, getLength (attributes) }, colour);
232 }
233 
setFont(const Font & font)234 void AttributedString::setFont (const Font& font)
235 {
236     setFont ({ 0, getLength (attributes) }, font);
237 }
238 
draw(Graphics & g,const Rectangle<float> & area) const239 void AttributedString::draw (Graphics& g, const Rectangle<float>& area) const
240 {
241     if (text.isNotEmpty() && g.clipRegionIntersects (area.getSmallestIntegerContainer()))
242     {
243         jassert (text.length() == getLength (attributes));
244 
245         if (! g.getInternalContext().drawTextLayout (*this, area))
246         {
247             TextLayout layout;
248             layout.createLayout (*this, area.getWidth());
249             layout.draw (g, area);
250         }
251     }
252 }
253 
254 } // namespace juce
255