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 //==============================================================================
30 /**
31     A glyph from a particular font, with a particular size, style,
32     typeface and position.
33 
34     You should rarely need to use this class directly - for most purposes, the
35     GlyphArrangement class will do what you need for text layout.
36 
37     @see GlyphArrangement, Font
38 
39     @tags{Graphics}
40 */
41 class JUCE_API  PositionedGlyph  final
42 {
43 public:
44     //==============================================================================
45     PositionedGlyph() noexcept;
46 
47     PositionedGlyph (const Font& font, juce_wchar character, int glyphNumber,
48                      float anchorX, float baselineY, float width, bool isWhitespace);
49 
50     PositionedGlyph (const PositionedGlyph&) = default;
51     PositionedGlyph& operator= (const PositionedGlyph&) = default;
52     PositionedGlyph (PositionedGlyph&&) noexcept = default;
53     PositionedGlyph& operator= (PositionedGlyph&&) noexcept = default;
54 
55     ~PositionedGlyph();
56 
57     /** Returns the character the glyph represents. */
getCharacter()58     juce_wchar getCharacter() const noexcept    { return character; }
59     /** Checks whether the glyph is actually empty. */
isWhitespace()60     bool isWhitespace() const noexcept          { return whitespace; }
61 
62     /** Returns the position of the glyph's left-hand edge. */
getLeft()63     float getLeft() const noexcept              { return x; }
64     /** Returns the position of the glyph's right-hand edge. */
getRight()65     float getRight() const noexcept             { return x + w; }
66     /** Returns the y position of the glyph's baseline. */
getBaselineY()67     float getBaselineY() const noexcept         { return y; }
68     /** Returns the y position of the top of the glyph. */
getTop()69     float getTop() const                        { return y - font.getAscent(); }
70     /** Returns the y position of the bottom of the glyph. */
getBottom()71     float getBottom() const                     { return y + font.getDescent(); }
72     /** Returns the bounds of the glyph. */
getBounds()73     Rectangle<float> getBounds() const          { return { x, getTop(), w, font.getHeight() }; }
74 
75     //==============================================================================
76     /** Shifts the glyph's position by a relative amount. */
77     void moveBy (float deltaX, float deltaY);
78 
79     //==============================================================================
80     /** Draws the glyph into a graphics context.
81         (Note that this may change the context's currently selected font).
82     */
83     void draw (Graphics& g) const;
84 
85     /** Draws the glyph into a graphics context, with an extra transform applied to it.
86         (Note that this may change the context's currently selected font).
87     */
88     void draw (Graphics& g, AffineTransform transform) const;
89 
90     /** Returns the path for this glyph.
91         @param path     the glyph's outline will be appended to this path
92     */
93     void createPath (Path& path) const;
94 
95     /** Checks to see if a point lies within this glyph. */
96     bool hitTest (float x, float y) const;
97 
98 private:
99     //==============================================================================
100     friend class GlyphArrangement;
101     Font font;
102     juce_wchar character;
103     int glyph;
104     float x, y, w;
105     bool whitespace;
106 
107     JUCE_LEAK_DETECTOR (PositionedGlyph)
108 };
109 
110 
111 //==============================================================================
112 /**
113     A set of glyphs, each with a position.
114 
115     You can create a GlyphArrangement, text to it and then draw it onto a
116     graphics context. It's used internally by the text methods in the
117     Graphics class, but can be used directly if more control is needed.
118 
119     @see Font, PositionedGlyph
120 
121     @tags{Graphics}
122 */
123 class JUCE_API  GlyphArrangement  final
124 {
125 public:
126     //==============================================================================
127     /** Creates an empty arrangement. */
128     GlyphArrangement();
129 
130     GlyphArrangement (const GlyphArrangement&) = default;
131     GlyphArrangement& operator= (const GlyphArrangement&) = default;
132     GlyphArrangement (GlyphArrangement&&) = default;
133     GlyphArrangement& operator= (GlyphArrangement&&) = default;
134 
135     /** Destructor. */
136     ~GlyphArrangement() = default;
137 
138     //==============================================================================
139     /** Returns the total number of glyphs in the arrangement. */
getNumGlyphs()140     int getNumGlyphs() const noexcept                           { return glyphs.size(); }
141 
142     /** Returns one of the glyphs from the arrangement.
143 
144         @param index    the glyph's index, from 0 to (getNumGlyphs() - 1). Be
145                         careful not to pass an out-of-range index here, as it
146                         doesn't do any bounds-checking.
147     */
148     PositionedGlyph& getGlyph (int index) noexcept;
149 
begin()150     const PositionedGlyph* begin() const                        { return glyphs.begin(); }
end()151     const PositionedGlyph* end() const                          { return glyphs.end(); }
152 
153     //==============================================================================
154     /** Clears all text from the arrangement and resets it. */
155     void clear();
156 
157     /** Appends a line of text to the arrangement.
158 
159         This will add the text as a single line, where x is the left-hand edge of the
160         first character, and y is the position for the text's baseline.
161 
162         If the text contains new-lines or carriage-returns, this will ignore them - use
163         addJustifiedText() to add multi-line arrangements.
164     */
165     void addLineOfText (const Font& font,
166                         const String& text,
167                         float x, float y);
168 
169     /** Adds a line of text, truncating it if it's wider than a specified size.
170 
171         This is the same as addLineOfText(), but if the line's width exceeds the value
172         specified in maxWidthPixels, it will be truncated using either ellipsis (i.e. dots: "..."),
173         if useEllipsis is true, or if this is false, it will just drop any subsequent characters.
174     */
175     void addCurtailedLineOfText (const Font& font,
176                                  const String& text,
177                                  float x, float y,
178                                  float maxWidthPixels,
179                                  bool useEllipsis);
180 
181     /** Adds some multi-line text, breaking lines at word-boundaries if they are too wide.
182 
183         This will add text to the arrangement, breaking it into new lines either where there
184         is a new-line or carriage-return character in the text, or where a line's width
185         exceeds the value set in maxLineWidth.
186 
187         Each line that is added will be laid out using the flags set in horizontalLayout, so
188         the lines can be left- or right-justified, or centred horizontally in the space
189         between x and (x + maxLineWidth).
190 
191         The y coordinate is the position of the baseline of the first line of text - subsequent
192         lines will be placed below it, separated by a distance of font.getHeight() + leading.
193     */
194     void addJustifiedText (const Font& font,
195                            const String& text,
196                            float x, float y,
197                            float maxLineWidth,
198                            Justification horizontalLayout,
199                            float leading = 0.0f);
200 
201     /** Tries to fit some text within a given space.
202 
203         This does its best to make the given text readable within the specified rectangle,
204         so it's useful for labelling things.
205 
206         If the text is too big, it'll be squashed horizontally or broken over multiple lines
207         if the maximumLinesToUse value allows this. If the text just won't fit into the space,
208         it'll cram as much as possible in there, and put some ellipsis at the end to show that
209         it's been truncated.
210 
211         A Justification parameter lets you specify how the text is laid out within the rectangle,
212         both horizontally and vertically.
213 
214         The minimumHorizontalScale parameter specifies how much the text can be squashed horizontally
215         to try to squeeze it into the space. If you don't want any horizontal scaling to occur, you
216         can set this value to 1.0f. Pass 0 if you want it to use the default value.
217 
218         @see Graphics::drawFittedText
219     */
220     void addFittedText (const Font& font,
221                         const String& text,
222                         float x, float y, float width, float height,
223                         Justification layout,
224                         int maximumLinesToUse,
225                         float minimumHorizontalScale = 0.0f);
226 
227     /** Appends another glyph arrangement to this one. */
228     void addGlyphArrangement (const GlyphArrangement&);
229 
230     /** Appends a custom glyph to the arrangement. */
231     void addGlyph (const PositionedGlyph&);
232 
233     //==============================================================================
234     /** Draws this glyph arrangement to a graphics context.
235 
236         This uses cached bitmaps so is much faster than the draw (Graphics&, AffineTransform)
237         method, which renders the glyphs as filled vectors.
238     */
239     void draw (const Graphics&) const;
240 
241     /** Draws this glyph arrangement to a graphics context.
242 
243         This renders the paths as filled vectors, so is far slower than the draw (Graphics&)
244         method for non-transformed arrangements.
245     */
246     void draw (const Graphics&, AffineTransform) const;
247 
248     /** Converts the set of glyphs into a path.
249         @param path     the glyphs' outlines will be appended to this path
250     */
251     void createPath (Path& path) const;
252 
253     /** Looks for a glyph that contains the given coordinate.
254         @returns the index of the glyph, or -1 if none were found.
255     */
256     int findGlyphIndexAt (float x, float y) const;
257 
258     //==============================================================================
259     /** Finds the smallest rectangle that will enclose a subset of the glyphs.
260 
261 
262         @param startIndex               the first glyph to test
263         @param numGlyphs                the number of glyphs to include; if this is < 0, all glyphs after
264                                         startIndex will be included
265         @param includeWhitespace        if true, the extent of any whitespace characters will also
266                                         be taken into account
267     */
268     Rectangle<float> getBoundingBox (int startIndex, int numGlyphs, bool includeWhitespace) const;
269 
270     /** Shifts a set of glyphs by a given amount.
271 
272         @param startIndex   the first glyph to transform
273         @param numGlyphs    the number of glyphs to move; if this is < 0, all glyphs after
274                             startIndex will be used
275         @param deltaX       the amount to add to their x-positions
276         @param deltaY       the amount to add to their y-positions
277     */
278     void moveRangeOfGlyphs (int startIndex, int numGlyphs,
279                             float deltaX, float deltaY);
280 
281     /** Removes a set of glyphs from the arrangement.
282 
283         @param startIndex   the first glyph to remove
284         @param numGlyphs    the number of glyphs to remove; if this is < 0, all glyphs after
285                             startIndex will be deleted
286     */
287     void removeRangeOfGlyphs (int startIndex, int numGlyphs);
288 
289     /** Expands or compresses a set of glyphs horizontally.
290 
291         @param startIndex               the first glyph to transform
292         @param numGlyphs                the number of glyphs to stretch; if this is < 0, all glyphs after
293                                         startIndex will be used
294         @param horizontalScaleFactor    how much to scale their horizontal width by
295     */
296     void stretchRangeOfGlyphs (int startIndex, int numGlyphs,
297                                float horizontalScaleFactor);
298 
299     /** Justifies a set of glyphs within a given space.
300 
301         This moves the glyphs as a block so that the whole thing is located within the
302         given rectangle with the specified layout.
303 
304         If the Justification::horizontallyJustified flag is specified, each line will
305         be stretched out to fill the specified width.
306     */
307     void justifyGlyphs (int startIndex, int numGlyphs,
308                         float x, float y, float width, float height,
309                         Justification justification);
310 
311 
312 private:
313     //==============================================================================
314     Array<PositionedGlyph> glyphs;
315 
316     int insertEllipsis (const Font&, float maxXPos, int startIndex, int endIndex);
317     int fitLineIntoSpace (int start, int numGlyphs, float x, float y, float w, float h, const Font&,
318                           Justification, float minimumHorizontalScale);
319     void spreadOutLine (int start, int numGlyphs, float targetWidth);
320     void splitLines (const String&, Font, int start, float x, float y, float w, float h, int maxLines,
321                      float lineWidth, Justification, float minimumHorizontalScale);
322     void addLinesWithLineBreaks (const String&, const Font&, float x, float y, float width, float height, Justification);
323     void drawGlyphUnderline (const Graphics&, const PositionedGlyph&, int, AffineTransform) const;
324 
325     JUCE_LEAK_DETECTOR (GlyphArrangement)
326 };
327 
328 } // namespace juce
329