LMMS
Loading...
Searching...
No Matches
juce_TextLayout.cpp
Go to the documentation of this file.
1/*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - 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 7 End-User License
11 Agreement and JUCE Privacy Policy.
12
13 End User License Agreement: www.juce.com/juce-7-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
26namespace juce
27{
28
29static String substring (const String& text, Range<int> range)
30{
31 return text.substring (range.getStart(), range.getEnd());
32}
33
35 : glyphCode (glyph), anchor (anch), width (w)
36{
37}
38
39//==============================================================================
40TextLayout::Run::Run (Range<int> range, int numGlyphsToPreallocate)
41 : stringRange (range)
42{
43 glyphs.ensureStorageAllocated (numGlyphsToPreallocate);
44}
45
47{
48 Range<float> range;
49 bool isFirst = true;
50
51 for (auto& glyph : glyphs)
52 {
53 Range<float> r (glyph.anchor.x, glyph.anchor.x + glyph.width);
54
55 if (isFirst)
56 {
57 isFirst = false;
58 range = r;
59 }
60 else
61 {
62 range = range.getUnionWith (r);
63 }
64 }
65
66 return range;
67}
68
69//==============================================================================
70TextLayout::Line::Line (Range<int> range, Point<float> o, float asc, float desc,
71 float lead, int numRunsToPreallocate)
72 : stringRange (range), lineOrigin (o),
73 ascent (asc), descent (desc), leading (lead)
74{
75 runs.ensureStorageAllocated (numRunsToPreallocate);
76}
77
80 ascent (other.ascent), descent (other.descent), leading (other.leading)
81{
82 runs.addCopiesOf (other.runs);
83}
84
85TextLayout::Line& TextLayout::Line::operator= (const Line& other)
86{
87 auto copy = other;
88 swap (copy);
89 return *this;
90}
91
93{
94 Range<float> range;
95 bool isFirst = true;
96
97 for (auto* run : runs)
98 {
99 auto runRange = run->getRunBoundsX();
100
101 if (isFirst)
102 {
103 isFirst = false;
104 range = runRange;
105 }
106 else
107 {
108 range = range.getUnionWith (runRange);
109 }
110 }
111
112 return range + lineOrigin.x;
113}
114
120
122{
123 auto x = getLineBoundsX();
124 auto y = getLineBoundsY();
125
126 return { x.getStart(), y.getStart(), x.getLength(), y.getLength() };
127}
128
129void TextLayout::Line::swap (Line& other) noexcept
130{
131 std::swap (other.runs, runs);
132 std::swap (other.stringRange, stringRange);
133 std::swap (other.lineOrigin, lineOrigin);
134 std::swap (other.ascent, ascent);
135 std::swap (other.descent, descent);
136 std::swap (other.leading, leading);
137}
138
139//==============================================================================
141 : width (0), height (0), justification (Justification::topLeft)
142{
143}
144
146 : width (other.width), height (other.height),
148{
149 lines.addCopiesOf (other.lines);
150}
151
153 : lines (std::move (other.lines)),
154 width (other.width), height (other.height),
155 justification (other.justification)
156{
157}
158
159TextLayout& TextLayout::operator= (TextLayout&& other) noexcept
160{
161 lines = std::move (other.lines);
162 width = other.width;
163 height = other.height;
164 justification = other.justification;
165 return *this;
166}
167
168TextLayout& TextLayout::operator= (const TextLayout& other)
169{
170 width = other.width;
171 height = other.height;
173 lines.clear();
174 lines.addCopiesOf (other.lines);
175 return *this;
176}
177
181
182TextLayout::Line& TextLayout::getLine (int index) const noexcept
183{
184 return *lines.getUnchecked (index);
185}
186
187void TextLayout::ensureStorageAllocated (int numLinesNeeded)
188{
189 lines.ensureStorageAllocated (numLinesNeeded);
190}
191
192void TextLayout::addLine (std::unique_ptr<Line> line)
193{
194 lines.add (line.release());
195}
196
198{
199 auto origin = justification.appliedToRectangle (Rectangle<float> (width, getHeight()), area).getPosition();
200
201 auto& context = g.getInternalContext();
202 context.saveState();
203
204 auto clip = context.getClipBounds();
205 auto clipTop = (float) clip.getY() - origin.y;
206 auto clipBottom = (float) clip.getBottom() - origin.y;
207
208 for (auto& line : *this)
209 {
210 auto lineRangeY = line.getLineBoundsY();
211
212 if (lineRangeY.getEnd() < clipTop)
213 continue;
214
215 if (lineRangeY.getStart() > clipBottom)
216 break;
217
218 auto lineOrigin = origin + line.lineOrigin;
219
220 for (auto* run : line.runs)
221 {
222 context.setFont (run->font);
223 context.setFill (run->colour);
224
225 for (auto& glyph : run->glyphs)
226 context.drawGlyph (glyph.glyphCode, AffineTransform::translation (lineOrigin.x + glyph.anchor.x,
227 lineOrigin.y + glyph.anchor.y));
228
229 if (run->font.isUnderlined())
230 {
231 auto runExtent = run->getRunBoundsX();
232 auto lineThickness = run->font.getDescent() * 0.3f;
233
234 context.fillRect ({ runExtent.getStart() + lineOrigin.x, lineOrigin.y + lineThickness * 2.0f,
235 runExtent.getLength(), lineThickness });
236 }
237 }
238 }
239
240 context.restoreState();
241}
242
243void TextLayout::createLayout (const AttributedString& text, float maxWidth)
244{
245 createLayout (text, maxWidth, 1.0e7f);
246}
247
248void TextLayout::createLayout (const AttributedString& text, float maxWidth, float maxHeight)
249{
250 lines.clear();
251 width = maxWidth;
252 height = maxHeight;
253 justification = text.getJustification();
254
255 if (! createNativeLayout (text))
257
259}
260
265
266void TextLayout::createLayoutWithBalancedLineLengths (const AttributedString& text, float maxWidth, float maxHeight)
267{
268 auto minimumWidth = maxWidth / 2.0f;
269 auto bestWidth = maxWidth;
270 float bestLineProportion = 0.0f;
271
272 while (maxWidth > minimumWidth)
273 {
274 createLayout (text, maxWidth, maxHeight);
275
276 if (getNumLines() < 2)
277 return;
278
279 auto line1 = lines.getUnchecked (lines.size() - 1)->getLineBoundsX().getLength();
280 auto line2 = lines.getUnchecked (lines.size() - 2)->getLineBoundsX().getLength();
281 auto shortest = jmin (line1, line2);
282 auto longest = jmax (line1, line2);
283 auto prop = shortest > 0 ? longest / shortest : 1.0f;
284
285 if (prop > 0.9f && prop < 1.1f)
286 return;
287
288 if (prop > bestLineProportion)
289 {
290 bestLineProportion = prop;
291 bestWidth = maxWidth;
292 }
293
294 maxWidth -= 10.0f;
295 }
296
297 if (bestWidth != maxWidth)
298 createLayout (text, bestWidth, maxHeight);
299}
300
301//==============================================================================
303{
304 struct Token
305 {
306 Token (const String& t, const Font& f, Colour c, bool whitespace)
307 : text (t), font (f), colour (c),
308 area (font.getStringWidthFloat (t), f.getHeight()),
309 isWhitespace (whitespace),
310 isNewLine (t.containsChar ('\n') || t.containsChar ('\r'))
311 {}
312
314 const Font font;
317 int line;
320
321 Token& operator= (const Token&) = delete;
322 };
323
325 {
327
329 {
331
333 layoutRuns (layout.getWidth(), text.getLineSpacing(), text.getWordWrap());
334
335 int charPosition = 0;
336 int lineStartPosition = 0;
337 int runStartPosition = 0;
338
339 std::unique_ptr<TextLayout::Line> currentLine;
340 std::unique_ptr<TextLayout::Run> currentRun;
341
342 bool needToSetLineOrigin = true;
343
344 for (int i = 0; i < tokens.size(); ++i)
345 {
346 auto& t = *tokens.getUnchecked (i);
347
348 Array<int> newGlyphs;
349 Array<float> xOffsets;
350 t.font.getGlyphPositions (getTrimmedEndIfNotAllWhitespace (t.text), newGlyphs, xOffsets);
351
352 if (currentRun == nullptr) currentRun = std::make_unique<TextLayout::Run>();
353 if (currentLine == nullptr) currentLine = std::make_unique<TextLayout::Line>();
354
355 const auto numGlyphs = newGlyphs.size();
356 charPosition += numGlyphs;
357
358 if (numGlyphs > 0
359 && (! (t.isWhitespace || t.isNewLine) || needToSetLineOrigin))
360 {
361 currentRun->glyphs.ensureStorageAllocated (currentRun->glyphs.size() + newGlyphs.size());
362 auto tokenOrigin = t.area.getPosition().translated (0, t.font.getAscent());
363
364 if (needToSetLineOrigin)
365 {
366 needToSetLineOrigin = false;
367 currentLine->lineOrigin = tokenOrigin;
368 }
369
370 auto glyphOffset = tokenOrigin - currentLine->lineOrigin;
371
372 for (int j = 0; j < newGlyphs.size(); ++j)
373 {
374 auto x = xOffsets.getUnchecked (j);
375 currentRun->glyphs.add (TextLayout::Glyph (newGlyphs.getUnchecked (j),
376 glyphOffset.translated (x, 0),
377 xOffsets.getUnchecked (j + 1) - x));
378 }
379 }
380
381 if (auto* nextToken = tokens[i + 1])
382 {
383 if (t.font != nextToken->font || t.colour != nextToken->colour)
384 {
385 addRun (*currentLine, currentRun.release(), t, runStartPosition, charPosition);
386 runStartPosition = charPosition;
387 }
388
389 if (t.line != nextToken->line)
390 {
391 if (currentRun == nullptr)
392 currentRun = std::make_unique<TextLayout::Run>();
393
394 addRun (*currentLine, currentRun.release(), t, runStartPosition, charPosition);
395 currentLine->stringRange = { lineStartPosition, charPosition };
396
397 if (! needToSetLineOrigin)
398 layout.addLine (std::move (currentLine));
399
400 runStartPosition = charPosition;
401 lineStartPosition = charPosition;
402 needToSetLineOrigin = true;
403 }
404 }
405 else
406 {
407 addRun (*currentLine, currentRun.release(), t, runStartPosition, charPosition);
408 currentLine->stringRange = { lineStartPosition, charPosition };
409
410 if (! needToSetLineOrigin)
411 layout.addLine (std::move (currentLine));
412
413 needToSetLineOrigin = true;
414 }
415 }
416
417 if ((text.getJustification().getFlags() & (Justification::right | Justification::horizontallyCentred)) != 0)
418 {
419 auto totalW = layout.getWidth();
420 bool isCentred = (text.getJustification().getFlags() & Justification::horizontallyCentred) != 0;
421
422 for (auto& line : layout)
423 {
424 auto dx = totalW - line.getLineBoundsX().getLength();
425
426 if (isCentred)
427 dx /= 2.0f;
428
429 line.lineOrigin.x += dx;
430 }
431 }
432 }
433
434 private:
435 static void addRun (TextLayout::Line& glyphLine, TextLayout::Run* glyphRun,
436 const Token& t, int start, int end)
437 {
438 glyphRun->stringRange = { start, end };
439 glyphRun->font = t.font;
440 glyphRun->colour = t.colour;
441 glyphLine.ascent = jmax (glyphLine.ascent, t.font.getAscent());
442 glyphLine.descent = jmax (glyphLine.descent, t.font.getDescent());
443 glyphLine.runs.add (glyphRun);
444 }
445
446 static int getCharacterType (juce_wchar c) noexcept
447 {
448 if (c == '\r' || c == '\n')
449 return 0;
450
451 return CharacterFunctions::isWhitespace (c) ? 2 : 1;
452 }
453
454 void appendText (const String& stringText, const Font& font, Colour colour)
455 {
456 auto t = stringText.getCharPointer();
457 String currentString;
458 int lastCharType = 0;
459
460 for (;;)
461 {
462 auto c = t.getAndAdvance();
463
464 if (c == 0)
465 break;
466
467 auto charType = getCharacterType (c);
468
469 if (charType == 0 || charType != lastCharType)
470 {
471 if (currentString.isNotEmpty())
472 tokens.add (new Token (currentString, font, colour,
473 lastCharType == 2 || lastCharType == 0));
474
475 currentString = String::charToString (c);
476
477 if (c == '\r' && *t == '\n')
478 currentString += t.getAndAdvance();
479 }
480 else
481 {
482 currentString += c;
483 }
484
485 lastCharType = charType;
486 }
487
488 if (currentString.isNotEmpty())
489 tokens.add (new Token (currentString, font, colour, lastCharType == 2));
490 }
491
492 void layoutRuns (float maxWidth, float extraLineSpacing, AttributedString::WordWrap wordWrap)
493 {
494 float x = 0, y = 0, h = 0;
495 int i;
496
497 for (i = 0; i < tokens.size(); ++i)
498 {
499 auto& t = *tokens.getUnchecked (i);
500 t.area.setPosition (x, y);
501 t.line = totalLines;
502 x += t.area.getWidth();
503 h = jmax (h, t.area.getHeight() + extraLineSpacing);
504
505 auto* nextTok = tokens[i + 1];
506
507 if (nextTok == nullptr)
508 break;
509
510 bool tokenTooLarge = (x + nextTok->area.getWidth() > maxWidth);
511
512 if (t.isNewLine || ((! nextTok->isWhitespace) && (tokenTooLarge && wordWrap != AttributedString::none)))
513 {
514 setLastLineHeight (i + 1, h);
515 x = 0;
516 y += h;
517 h = 0;
518 ++totalLines;
519 }
520 }
521
522 setLastLineHeight (jmin (i + 1, tokens.size()), h);
523 ++totalLines;
524 }
525
526 void setLastLineHeight (int i, float height) noexcept
527 {
528 while (--i >= 0)
529 {
530 auto& tok = *tokens.getUnchecked (i);
531
532 if (tok.line == totalLines)
533 tok.lineHeight = height;
534 else
535 break;
536 }
537 }
538
540 {
541 auto numAttributes = text.getNumAttributes();
542 tokens.ensureStorageAllocated (jmax (64, numAttributes));
543
544 for (int i = 0; i < numAttributes; ++i)
545 {
546 auto& attr = text.getAttribute (i);
547
548 appendText (substring (text.getText(), attr.range),
549 attr.font, attr.colour);
550 }
551 }
552
554 {
555 auto trimmed = s.trimEnd();
556
557 if (trimmed.isEmpty() && s.isNotEmpty())
558 trimmed = s.replaceCharacters ("\r\n\t", " ");
559
560 return trimmed;
561 }
562
564 int totalLines = 0;
565
567 };
568}
569
570//==============================================================================
572{
574 l.createLayout (text, *this);
575}
576
578{
579 if (! lines.isEmpty())
580 {
581 auto bounds = lines.getFirst()->getLineBounds();
582
583 for (auto* line : lines)
584 bounds = bounds.getUnion (line->getLineBounds());
585
586 for (auto* line : lines)
587 line->lineOrigin.x -= bounds.getX();
588
589 width = bounds.getWidth();
590 height = bounds.getHeight();
591 }
592 else
593 {
594 width = 0;
595 height = 0;
596 }
597}
598
599} // namespace juce
#define copy(x)
Definition ADnoteParameters.cpp:1011
Type jmin(const Type a, const Type b)
Definition MathsFunctions.h:60
Type jmax(const Type a, const Type b)
Definition MathsFunctions.h:48
#define noexcept
Definition DistrhoDefines.h:72
static void run(LV2_Handle instance, uint32_t n_samples)
Definition bindings_test_plugin.c:112
static String charToString(water_uchar character)
Definition String.cpp:311
static AffineTransform translation(float deltaX, float deltaY) noexcept
Definition juce_AffineTransform.cpp:81
Definition juce_Array.h:56
ElementType getUnchecked(int index) const
Definition juce_Array.h:252
int size() const noexcept
Definition juce_Array.h:215
Definition juce_AttributedString.h:47
WordWrap
Definition juce_AttributedString.h:114
@ none
Definition juce_AttributedString.h:115
static bool isWhitespace(char character) noexcept
Definition juce_CharacterFunctions.cpp:59
Definition juce_Colour.h:38
Definition juce_Font.h:42
Definition juce_GraphicsContext.h:45
Definition juce_Justification.h:41
@ horizontallyCentred
Definition juce_Justification.h:115
@ right
Definition juce_Justification.h:111
Definition juce_OwnedArray.h:51
Definition juce_Point.h:42
Definition juce_Range.h:40
constexpr ValueType getStart() const noexcept
Definition juce_Range.h:80
constexpr ValueType getEnd() const noexcept
Definition juce_Range.h:86
JUCE_NODISCARD constexpr Range getUnionWith(Range other) const noexcept
Definition juce_Range.h:241
Definition juce_Rectangle.h:67
Definition juce_String.h:53
CharPointerType getCharPointer() const noexcept
Definition juce_String.h:1153
bool isNotEmpty() const noexcept
Definition juce_String.h:316
Definition juce_TextLayout.h:145
Point< float > anchor
Definition juce_TextLayout.h:155
int glyphCode
Definition juce_TextLayout.h:150
Glyph(int glyphCode, Point< float > anchor, float width) noexcept
Definition juce_TextLayout.cpp:34
Definition juce_TextLayout.h:186
float ascent
Definition juce_TextLayout.h:215
float descent
Definition juce_TextLayout.h:215
float leading
Definition juce_TextLayout.h:215
Range< float > getLineBoundsY() const noexcept
Definition juce_TextLayout.cpp:115
Range< int > stringRange
Definition juce_TextLayout.h:212
void swap(Line &other) noexcept
Definition juce_TextLayout.cpp:129
Rectangle< float > getLineBounds() const noexcept
Definition juce_TextLayout.cpp:121
Point< float > lineOrigin
Definition juce_TextLayout.h:214
Range< float > getLineBoundsX() const noexcept
Definition juce_TextLayout.cpp:92
OwnedArray< Run > runs
Definition juce_TextLayout.h:211
Definition juce_TextLayout.h:166
Range< int > stringRange
Definition juce_TextLayout.h:177
Range< float > getRunBoundsX() const noexcept
Definition juce_TextLayout.cpp:46
Array< Glyph > glyphs
Definition juce_TextLayout.h:176
Colour colour
Definition juce_TextLayout.h:175
Font font
Definition juce_TextLayout.h:174
Definition juce_TextLayout.h:41
~TextLayout()
Definition juce_TextLayout.cpp:178
float width
Definition juce_TextLayout.h:261
Justification justification
Definition juce_TextLayout.h:262
void recalculateSize()
Definition juce_TextLayout.cpp:577
void createLayoutWithBalancedLineLengths(const AttributedString &, float maxWidth)
Definition juce_TextLayout.cpp:261
int getNumLines() const noexcept
Definition juce_TextLayout.h:229
bool createNativeLayout(const AttributedString &)
Definition juce_linux_Fonts.cpp:108
float getWidth() const noexcept
Definition juce_TextLayout.h:223
void ensureStorageAllocated(int numLinesNeeded)
Definition juce_TextLayout.cpp:187
void createStandardLayout(const AttributedString &)
Definition juce_TextLayout.cpp:571
float height
Definition juce_TextLayout.h:261
OwnedArray< Line > lines
Definition juce_TextLayout.h:260
void addLine(std::unique_ptr< Line >)
Definition juce_TextLayout.cpp:192
void draw(Graphics &, Rectangle< float > area) const
Definition juce_TextLayout.cpp:197
float getHeight() const noexcept
Definition juce_TextLayout.h:226
void createLayout(const AttributedString &, float maxWidth)
Definition juce_TextLayout.cpp:243
TextLayout()
Definition juce_TextLayout.cpp:140
Line & getLine(int index) const noexcept
Definition juce_TextLayout.cpp:182
UINT_D64 w
Definition inflate.c:942
int * l
Definition inflate.c:1579
struct huft * t
Definition inflate.c:943
register unsigned j
Definition inflate.c:1576
int y
Definition inflate.c:1588
int g
Definition inflate.c:1573
register unsigned i
Definition inflate.c:1575
unsigned s
Definition inflate.c:1555
unsigned x[BMAX+1]
Definition inflate.c:1586
unsigned f
Definition inflate.c:1572
static int int height
Definition pugl.h:1594
static int width
Definition pugl.h:1593
virtual ASIOError start()=0
#define JUCE_DECLARE_NON_COPYABLE(className)
Definition juce_TextLayout.cpp:303
Definition carla_juce.cpp:31
constexpr Type jmin(Type a, Type b)
Definition juce_MathsFunctions.h:106
constexpr Type jmax(Type a, Type b)
Definition juce_MathsFunctions.h:94
RangedDirectoryIterator end(const RangedDirectoryIterator &)
Definition juce_RangedDirectoryIterator.h:184
wchar_t juce_wchar
Definition juce_CharacterFunctions.h:42
static String substring(const String &text, Range< int > range)
Definition juce_TextLayout.cpp:29
Definition juce_TextLayout.cpp:305
const Font font
Definition juce_TextLayout.cpp:314
int line
Definition juce_TextLayout.cpp:317
const String text
Definition juce_TextLayout.cpp:313
float lineHeight
Definition juce_TextLayout.cpp:318
Rectangle< float > area
Definition juce_TextLayout.cpp:316
const bool isNewLine
Definition juce_TextLayout.cpp:319
const Colour colour
Definition juce_TextLayout.cpp:315
Token(const String &t, const Font &f, Colour c, bool whitespace)
Definition juce_TextLayout.cpp:306
const bool isWhitespace
Definition juce_TextLayout.cpp:319
Definition juce_TextLayout.cpp:325
static String getTrimmedEndIfNotAllWhitespace(const String &s)
Definition juce_TextLayout.cpp:553
OwnedArray< Token > tokens
Definition juce_TextLayout.cpp:563
void layoutRuns(float maxWidth, float extraLineSpacing, AttributedString::WordWrap wordWrap)
Definition juce_TextLayout.cpp:492
void setLastLineHeight(int i, float height) noexcept
Definition juce_TextLayout.cpp:526
void addTextRuns(const AttributedString &text)
Definition juce_TextLayout.cpp:539
TokenList() noexcept
Definition juce_TextLayout.cpp:326
void createLayout(const AttributedString &text, TextLayout &layout)
Definition juce_TextLayout.cpp:328
static void addRun(TextLayout::Line &glyphLine, TextLayout::Run *glyphRun, const Token &t, int start, int end)
Definition juce_TextLayout.cpp:435
void appendText(const String &stringText, const Font &font, Colour colour)
Definition juce_TextLayout.cpp:454
static int getCharacterType(juce_wchar c) noexcept
Definition juce_TextLayout.cpp:446
int totalLines
Definition juce_TextLayout.cpp:564
const char * text
Definition swell-functions.h:167
return c
Definition crypt.c:175
int r
Definition crypt.c:458
uch h[RAND_HEAD_LEN]
Definition crypt.c:459
#define const
Definition zconf.h:137