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