Add remaining files
[juce-lv2.git] / juce / source / src / gui / graphics / fonts / juce_GlyphArrangement.cpp
blob077c8882d17f58dde06c8a9c92ed154460c38a0f
1 /*
2 ==============================================================================
4 This file is part of the JUCE library - "Jules' Utility Class Extensions"
5 Copyright 2004-11 by Raw Material Software Ltd.
7 ------------------------------------------------------------------------------
9 JUCE can be redistributed and/or modified under the terms of the GNU General
10 Public License (Version 2), as published by the Free Software Foundation.
11 A copy of the license is included in the JUCE distribution, or can be found
12 online at www.gnu.org/licenses.
14 JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 A PARTICULAR PURPOSE. See the GNU General Public License for more details.
18 ------------------------------------------------------------------------------
20 To release a closed-source product which uses JUCE, commercial licenses are
21 available: visit www.rawmaterialsoftware.com/juce for more information.
23 ==============================================================================
26 #include "../../../core/juce_StandardHeader.h"
28 BEGIN_JUCE_NAMESPACE
30 #include "juce_GlyphArrangement.h"
31 #include "../contexts/juce_LowLevelGraphicsSoftwareRenderer.h"
32 #include "../imaging/juce_Image.h"
33 #include "../../../utilities/juce_DeletedAtShutdown.h"
36 //==============================================================================
37 PositionedGlyph::PositionedGlyph (const Font& font_, const juce_wchar character_, const int glyph_,
38 const float x_, const float y_, const float w_, const bool whitespace_)
39 : font (font_), character (character_), glyph (glyph_),
40 x (x_), y (y_), w (w_), whitespace (whitespace_)
44 PositionedGlyph::PositionedGlyph (const PositionedGlyph& other)
45 : font (other.font), character (other.character), glyph (other.glyph),
46 x (other.x), y (other.y), w (other.w), whitespace (other.whitespace)
50 PositionedGlyph::~PositionedGlyph() {}
52 PositionedGlyph& PositionedGlyph::operator= (const PositionedGlyph& other)
54 font = other.font;
55 character = other.character;
56 glyph = other.glyph;
57 x = other.x;
58 y = other.y;
59 w = other.w;
60 whitespace = other.whitespace;
61 return *this;
64 void PositionedGlyph::draw (const Graphics& g) const
66 if (! isWhitespace())
68 LowLevelGraphicsContext* const context = g.getInternalContext();
69 context->setFont (font);
70 context->drawGlyph (glyph, AffineTransform::translation (x, y));
74 void PositionedGlyph::draw (const Graphics& g,
75 const AffineTransform& transform) const
77 if (! isWhitespace())
79 LowLevelGraphicsContext* const context = g.getInternalContext();
80 context->setFont (font);
81 context->drawGlyph (glyph, AffineTransform::translation (x, y)
82 .followedBy (transform));
86 void PositionedGlyph::createPath (Path& path) const
88 if (! isWhitespace())
90 Typeface* const t = font.getTypeface();
92 if (t != nullptr)
94 Path p;
95 t->getOutlineForGlyph (glyph, p);
97 path.addPath (p, AffineTransform::scale (font.getHeight() * font.getHorizontalScale(), font.getHeight())
98 .translated (x, y));
103 bool PositionedGlyph::hitTest (float px, float py) const
105 if (getBounds().contains (px, py) && ! isWhitespace())
107 Typeface* const t = font.getTypeface();
109 if (t != nullptr)
111 Path p;
112 t->getOutlineForGlyph (glyph, p);
114 AffineTransform::translation (-x, -y)
115 .scaled (1.0f / (font.getHeight() * font.getHorizontalScale()), 1.0f / font.getHeight())
116 .transformPoint (px, py);
118 return p.contains (px, py);
122 return false;
125 void PositionedGlyph::moveBy (const float deltaX,
126 const float deltaY)
128 x += deltaX;
129 y += deltaY;
133 //==============================================================================
134 GlyphArrangement::GlyphArrangement()
136 glyphs.ensureStorageAllocated (128);
139 GlyphArrangement::GlyphArrangement (const GlyphArrangement& other)
141 addGlyphArrangement (other);
144 GlyphArrangement& GlyphArrangement::operator= (const GlyphArrangement& other)
146 if (this != &other)
148 clear();
149 addGlyphArrangement (other);
152 return *this;
155 GlyphArrangement::~GlyphArrangement()
159 //==============================================================================
160 void GlyphArrangement::clear()
162 glyphs.clear();
165 PositionedGlyph& GlyphArrangement::getGlyph (const int index) const
167 jassert (isPositiveAndBelow (index, glyphs.size()));
169 return *glyphs [index];
172 //==============================================================================
173 void GlyphArrangement::addGlyphArrangement (const GlyphArrangement& other)
175 glyphs.ensureStorageAllocated (glyphs.size() + other.glyphs.size());
176 glyphs.addCopiesOf (other.glyphs);
179 void GlyphArrangement::addGlyph (const PositionedGlyph& glyph)
181 glyphs.add (new PositionedGlyph (glyph));
184 void GlyphArrangement::removeRangeOfGlyphs (int startIndex, const int num)
186 glyphs.removeRange (startIndex, num < 0 ? glyphs.size() : num);
189 //==============================================================================
190 void GlyphArrangement::addLineOfText (const Font& font,
191 const String& text,
192 const float xOffset,
193 const float yOffset)
195 addCurtailedLineOfText (font, text,
196 xOffset, yOffset,
197 1.0e10f, false);
200 void GlyphArrangement::addCurtailedLineOfText (const Font& font,
201 const String& text,
202 const float xOffset,
203 const float yOffset,
204 const float maxWidthPixels,
205 const bool useEllipsis)
207 if (text.isNotEmpty())
209 Array <int> newGlyphs;
210 Array <float> xOffsets;
211 font.getGlyphPositions (text, newGlyphs, xOffsets);
212 const int textLen = newGlyphs.size();
213 glyphs.ensureStorageAllocated (glyphs.size() + textLen);
215 String::CharPointerType t (text.getCharPointer());
217 for (int i = 0; i < textLen; ++i)
219 const float thisX = xOffsets.getUnchecked (i);
220 const float nextX = xOffsets.getUnchecked (i + 1);
222 if (nextX > maxWidthPixels + 1.0f)
224 // curtail the string if it's too wide..
225 if (useEllipsis && textLen > 3 && glyphs.size() >= 3)
226 insertEllipsis (font, xOffset + maxWidthPixels, 0, glyphs.size());
228 break;
230 else
232 const bool isWhitespace = t.isWhitespace();
234 glyphs.add (new PositionedGlyph (font, t.getAndAdvance(),
235 newGlyphs.getUnchecked(i),
236 xOffset + thisX, yOffset,
237 nextX - thisX, isWhitespace));
243 int GlyphArrangement::insertEllipsis (const Font& font, const float maxXPos,
244 const int startIndex, int endIndex)
246 int numDeleted = 0;
248 if (glyphs.size() > 0)
250 Array<int> dotGlyphs;
251 Array<float> dotXs;
252 font.getGlyphPositions ("..", dotGlyphs, dotXs);
254 const float dx = dotXs[1];
255 float xOffset = 0.0f, yOffset = 0.0f;
257 while (endIndex > startIndex)
259 const PositionedGlyph* pg = glyphs.getUnchecked (--endIndex);
260 xOffset = pg->x;
261 yOffset = pg->y;
263 glyphs.remove (endIndex);
264 ++numDeleted;
266 if (xOffset + dx * 3 <= maxXPos)
267 break;
270 for (int i = 3; --i >= 0;)
272 glyphs.insert (endIndex++, new PositionedGlyph (font, '.', dotGlyphs.getFirst(),
273 xOffset, yOffset, dx, false));
274 --numDeleted;
275 xOffset += dx;
277 if (xOffset > maxXPos)
278 break;
282 return numDeleted;
285 void GlyphArrangement::addJustifiedText (const Font& font,
286 const String& text,
287 float x, float y,
288 const float maxLineWidth,
289 const Justification& horizontalLayout)
291 int lineStartIndex = glyphs.size();
292 addLineOfText (font, text, x, y);
294 const float originalY = y;
296 while (lineStartIndex < glyphs.size())
298 int i = lineStartIndex;
300 if (glyphs.getUnchecked(i)->getCharacter() != '\n'
301 && glyphs.getUnchecked(i)->getCharacter() != '\r')
302 ++i;
304 const float lineMaxX = glyphs.getUnchecked (lineStartIndex)->getLeft() + maxLineWidth;
305 int lastWordBreakIndex = -1;
307 while (i < glyphs.size())
309 const PositionedGlyph* pg = glyphs.getUnchecked (i);
310 const juce_wchar c = pg->getCharacter();
312 if (c == '\r' || c == '\n')
314 ++i;
316 if (c == '\r' && i < glyphs.size()
317 && glyphs.getUnchecked(i)->getCharacter() == '\n')
318 ++i;
320 break;
322 else if (pg->isWhitespace())
324 lastWordBreakIndex = i + 1;
326 else if (pg->getRight() - 0.0001f >= lineMaxX)
328 if (lastWordBreakIndex >= 0)
329 i = lastWordBreakIndex;
331 break;
334 ++i;
337 const float currentLineStartX = glyphs.getUnchecked (lineStartIndex)->getLeft();
338 float currentLineEndX = currentLineStartX;
340 for (int j = i; --j >= lineStartIndex;)
342 if (! glyphs.getUnchecked (j)->isWhitespace())
344 currentLineEndX = glyphs.getUnchecked (j)->getRight();
345 break;
349 float deltaX = 0.0f;
351 if (horizontalLayout.testFlags (Justification::horizontallyJustified))
352 spreadOutLine (lineStartIndex, i - lineStartIndex, maxLineWidth);
353 else if (horizontalLayout.testFlags (Justification::horizontallyCentred))
354 deltaX = (maxLineWidth - (currentLineEndX - currentLineStartX)) * 0.5f;
355 else if (horizontalLayout.testFlags (Justification::right))
356 deltaX = maxLineWidth - (currentLineEndX - currentLineStartX);
358 moveRangeOfGlyphs (lineStartIndex, i - lineStartIndex,
359 x + deltaX - currentLineStartX, y - originalY);
361 lineStartIndex = i;
363 y += font.getHeight();
367 void GlyphArrangement::addFittedText (const Font& f,
368 const String& text,
369 const float x, const float y,
370 const float width, const float height,
371 const Justification& layout,
372 int maximumLines,
373 const float minimumHorizontalScale)
375 // doesn't make much sense if this is outside a sensible range of 0.5 to 1.0
376 jassert (minimumHorizontalScale > 0 && minimumHorizontalScale <= 1.0f);
378 if (text.containsAnyOf ("\r\n"))
380 GlyphArrangement ga;
381 ga.addJustifiedText (f, text, x, y, width, layout);
383 const Rectangle<float> bb (ga.getBoundingBox (0, -1, false));
385 float dy = y - bb.getY();
387 if (layout.testFlags (Justification::verticallyCentred))
388 dy += (height - bb.getHeight()) * 0.5f;
389 else if (layout.testFlags (Justification::bottom))
390 dy += height - bb.getHeight();
392 ga.moveRangeOfGlyphs (0, -1, 0.0f, dy);
394 glyphs.ensureStorageAllocated (glyphs.size() + ga.glyphs.size());
396 for (int i = 0; i < ga.glyphs.size(); ++i)
397 glyphs.add (ga.glyphs.getUnchecked (i));
399 ga.glyphs.clear (false);
400 return;
403 int startIndex = glyphs.size();
404 addLineOfText (f, text.trim(), x, y);
406 if (glyphs.size() > startIndex)
408 float lineWidth = glyphs.getUnchecked (glyphs.size() - 1)->getRight()
409 - glyphs.getUnchecked (startIndex)->getLeft();
411 if (lineWidth <= 0)
412 return;
414 if (lineWidth * minimumHorizontalScale < width)
416 if (lineWidth > width)
417 stretchRangeOfGlyphs (startIndex, glyphs.size() - startIndex,
418 width / lineWidth);
420 justifyGlyphs (startIndex, glyphs.size() - startIndex,
421 x, y, width, height, layout);
423 else if (maximumLines <= 1)
425 fitLineIntoSpace (startIndex, glyphs.size() - startIndex,
426 x, y, width, height, f, layout, minimumHorizontalScale);
428 else
430 Font font (f);
431 String txt (text.trim());
432 const int length = txt.length();
433 const int originalStartIndex = startIndex;
434 int numLines = 1;
436 if (length <= 12 && ! txt.containsAnyOf (" -\t\r\n"))
437 maximumLines = 1;
439 maximumLines = jmin (maximumLines, length);
441 while (numLines < maximumLines)
443 ++numLines;
445 const float newFontHeight = height / (float) numLines;
447 if (newFontHeight < font.getHeight())
449 font.setHeight (jmax (8.0f, newFontHeight));
451 removeRangeOfGlyphs (startIndex, -1);
452 addLineOfText (font, txt, x, y);
454 lineWidth = glyphs.getUnchecked (glyphs.size() - 1)->getRight()
455 - glyphs.getUnchecked (startIndex)->getLeft();
458 if (numLines > lineWidth / width || newFontHeight < 8.0f)
459 break;
462 if (numLines < 1)
463 numLines = 1;
465 float lineY = y;
466 float widthPerLine = lineWidth / numLines;
467 int lastLineStartIndex = 0;
469 for (int line = 0; line < numLines; ++line)
471 int i = startIndex;
472 lastLineStartIndex = i;
473 float lineStartX = glyphs.getUnchecked (startIndex)->getLeft();
475 if (line == numLines - 1)
477 widthPerLine = width;
478 i = glyphs.size();
480 else
482 while (i < glyphs.size())
484 lineWidth = (glyphs.getUnchecked (i)->getRight() - lineStartX);
486 if (lineWidth > widthPerLine)
488 // got to a point where the line's too long, so skip forward to find a
489 // good place to break it..
490 const int searchStartIndex = i;
492 while (i < glyphs.size())
494 if ((glyphs.getUnchecked (i)->getRight() - lineStartX) * minimumHorizontalScale < width)
496 if (glyphs.getUnchecked (i)->isWhitespace()
497 || glyphs.getUnchecked (i)->getCharacter() == '-')
499 ++i;
500 break;
503 else
505 // can't find a suitable break, so try looking backwards..
506 i = searchStartIndex;
508 for (int back = 1; back < jmin (5, i - startIndex - 1); ++back)
510 if (glyphs.getUnchecked (i - back)->isWhitespace()
511 || glyphs.getUnchecked (i - back)->getCharacter() == '-')
513 i -= back - 1;
514 break;
518 break;
521 ++i;
524 break;
527 ++i;
530 int wsStart = i;
531 while (wsStart > 0 && glyphs.getUnchecked (wsStart - 1)->isWhitespace())
532 --wsStart;
534 int wsEnd = i;
536 while (wsEnd < glyphs.size() && glyphs.getUnchecked (wsEnd)->isWhitespace())
537 ++wsEnd;
539 removeRangeOfGlyphs (wsStart, wsEnd - wsStart);
540 i = jmax (wsStart, startIndex + 1);
543 i -= fitLineIntoSpace (startIndex, i - startIndex,
544 x, lineY, width, font.getHeight(), font,
545 layout.getOnlyHorizontalFlags() | Justification::verticallyCentred,
546 minimumHorizontalScale);
548 startIndex = i;
549 lineY += font.getHeight();
551 if (startIndex >= glyphs.size())
552 break;
555 justifyGlyphs (originalStartIndex, glyphs.size() - originalStartIndex,
556 x, y, width, height, layout.getFlags() & ~Justification::horizontallyJustified);
561 //==============================================================================
562 void GlyphArrangement::moveRangeOfGlyphs (int startIndex, int num,
563 const float dx, const float dy)
565 jassert (startIndex >= 0);
567 if (dx != 0.0f || dy != 0.0f)
569 if (num < 0 || startIndex + num > glyphs.size())
570 num = glyphs.size() - startIndex;
572 while (--num >= 0)
573 glyphs.getUnchecked (startIndex++)->moveBy (dx, dy);
577 int GlyphArrangement::fitLineIntoSpace (int start, int numGlyphs, float x, float y, float w, float h, const Font& font,
578 const Justification& justification, float minimumHorizontalScale)
580 int numDeleted = 0;
581 const float lineStartX = glyphs.getUnchecked (start)->getLeft();
582 float lineWidth = glyphs.getUnchecked (start + numGlyphs - 1)->getRight() - lineStartX;
584 if (lineWidth > w)
586 if (minimumHorizontalScale < 1.0f)
588 stretchRangeOfGlyphs (start, numGlyphs, jmax (minimumHorizontalScale, w / lineWidth));
589 lineWidth = glyphs.getUnchecked (start + numGlyphs - 1)->getRight() - lineStartX - 0.5f;
592 if (lineWidth > w)
594 numDeleted = insertEllipsis (font, lineStartX + w, start, start + numGlyphs);
595 numGlyphs -= numDeleted;
599 justifyGlyphs (start, numGlyphs, x, y, w, h, justification);
600 return numDeleted;
603 void GlyphArrangement::stretchRangeOfGlyphs (int startIndex, int num,
604 const float horizontalScaleFactor)
606 jassert (startIndex >= 0);
608 if (num < 0 || startIndex + num > glyphs.size())
609 num = glyphs.size() - startIndex;
611 if (num > 0)
613 const float xAnchor = glyphs.getUnchecked (startIndex)->getLeft();
615 while (--num >= 0)
617 PositionedGlyph* const pg = glyphs.getUnchecked (startIndex++);
619 pg->x = xAnchor + (pg->x - xAnchor) * horizontalScaleFactor;
620 pg->font.setHorizontalScale (pg->font.getHorizontalScale() * horizontalScaleFactor);
621 pg->w *= horizontalScaleFactor;
626 const Rectangle<float> GlyphArrangement::getBoundingBox (int startIndex, int num, const bool includeWhitespace) const
628 jassert (startIndex >= 0);
630 if (num < 0 || startIndex + num > glyphs.size())
631 num = glyphs.size() - startIndex;
633 Rectangle<float> result;
635 while (--num >= 0)
637 const PositionedGlyph* const pg = glyphs.getUnchecked (startIndex++);
639 if (includeWhitespace || ! pg->isWhitespace())
640 result = result.getUnion (pg->getBounds());
643 return result;
646 void GlyphArrangement::justifyGlyphs (const int startIndex, const int num,
647 const float x, const float y, const float width, const float height,
648 const Justification& justification)
650 jassert (num >= 0 && startIndex >= 0);
652 if (glyphs.size() > 0 && num > 0)
654 const Rectangle<float> bb (getBoundingBox (startIndex, num, ! justification.testFlags (Justification::horizontallyJustified
655 | Justification::horizontallyCentred)));
656 float deltaX = 0.0f;
658 if (justification.testFlags (Justification::horizontallyJustified))
659 deltaX = x - bb.getX();
660 else if (justification.testFlags (Justification::horizontallyCentred))
661 deltaX = x + (width - bb.getWidth()) * 0.5f - bb.getX();
662 else if (justification.testFlags (Justification::right))
663 deltaX = (x + width) - bb.getRight();
664 else
665 deltaX = x - bb.getX();
667 float deltaY = 0.0f;
669 if (justification.testFlags (Justification::top))
670 deltaY = y - bb.getY();
671 else if (justification.testFlags (Justification::bottom))
672 deltaY = (y + height) - bb.getBottom();
673 else
674 deltaY = y + (height - bb.getHeight()) * 0.5f - bb.getY();
676 moveRangeOfGlyphs (startIndex, num, deltaX, deltaY);
678 if (justification.testFlags (Justification::horizontallyJustified))
680 int lineStart = 0;
681 float baseY = glyphs.getUnchecked (startIndex)->getBaselineY();
683 int i;
684 for (i = 0; i < num; ++i)
686 const float glyphY = glyphs.getUnchecked (startIndex + i)->getBaselineY();
688 if (glyphY != baseY)
690 spreadOutLine (startIndex + lineStart, i - lineStart, width);
692 lineStart = i;
693 baseY = glyphY;
697 if (i > lineStart)
698 spreadOutLine (startIndex + lineStart, i - lineStart, width);
703 void GlyphArrangement::spreadOutLine (const int start, const int num, const float targetWidth)
705 if (start + num < glyphs.size()
706 && glyphs.getUnchecked (start + num - 1)->getCharacter() != '\r'
707 && glyphs.getUnchecked (start + num - 1)->getCharacter() != '\n')
709 int numSpaces = 0;
710 int spacesAtEnd = 0;
712 for (int i = 0; i < num; ++i)
714 if (glyphs.getUnchecked (start + i)->isWhitespace())
716 ++spacesAtEnd;
717 ++numSpaces;
719 else
721 spacesAtEnd = 0;
725 numSpaces -= spacesAtEnd;
727 if (numSpaces > 0)
729 const float startX = glyphs.getUnchecked (start)->getLeft();
730 const float endX = glyphs.getUnchecked (start + num - 1 - spacesAtEnd)->getRight();
732 const float extraPaddingBetweenWords
733 = (targetWidth - (endX - startX)) / (float) numSpaces;
735 float deltaX = 0.0f;
737 for (int i = 0; i < num; ++i)
739 glyphs.getUnchecked (start + i)->moveBy (deltaX, 0.0f);
741 if (glyphs.getUnchecked (start + i)->isWhitespace())
742 deltaX += extraPaddingBetweenWords;
748 //==============================================================================
749 void GlyphArrangement::draw (const Graphics& g) const
751 for (int i = 0; i < glyphs.size(); ++i)
753 const PositionedGlyph* const pg = glyphs.getUnchecked(i);
755 if (pg->font.isUnderlined())
757 const float lineThickness = (pg->font.getDescent()) * 0.3f;
759 float nextX = pg->x + pg->w;
761 if (i < glyphs.size() - 1 && glyphs.getUnchecked (i + 1)->y == pg->y)
762 nextX = glyphs.getUnchecked (i + 1)->x;
764 g.fillRect (pg->x, pg->y + lineThickness * 2.0f,
765 nextX - pg->x, lineThickness);
768 pg->draw (g);
772 void GlyphArrangement::draw (const Graphics& g, const AffineTransform& transform) const
774 for (int i = 0; i < glyphs.size(); ++i)
776 const PositionedGlyph* const pg = glyphs.getUnchecked(i);
778 if (pg->font.isUnderlined())
780 const float lineThickness = (pg->font.getDescent()) * 0.3f;
782 float nextX = pg->x + pg->w;
784 if (i < glyphs.size() - 1 && glyphs.getUnchecked (i + 1)->y == pg->y)
785 nextX = glyphs.getUnchecked (i + 1)->x;
787 Path p;
788 p.addLineSegment (Line<float> (pg->x, pg->y + lineThickness * 2.0f,
789 nextX, pg->y + lineThickness * 2.0f),
790 lineThickness);
792 g.fillPath (p, transform);
795 pg->draw (g, transform);
799 void GlyphArrangement::createPath (Path& path) const
801 for (int i = 0; i < glyphs.size(); ++i)
802 glyphs.getUnchecked (i)->createPath (path);
805 int GlyphArrangement::findGlyphIndexAt (float x, float y) const
807 for (int i = 0; i < glyphs.size(); ++i)
808 if (glyphs.getUnchecked (i)->hitTest (x, y))
809 return i;
811 return -1;
814 END_JUCE_NAMESPACE