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 class CustomTypeface::GlyphInfo
30 {
31 public:
GlyphInfo(juce_wchar c,const Path & p,float w)32     GlyphInfo (juce_wchar c, const Path& p, float w) noexcept
33         : character (c), path (p), width (w)
34     {
35     }
36 
37     struct KerningPair
38     {
39         juce_wchar character2;
40         float kerningAmount;
41     };
42 
addKerningPair(juce_wchar subsequentCharacter,float extraKerningAmount)43     void addKerningPair (juce_wchar subsequentCharacter, float extraKerningAmount) noexcept
44     {
45         kerningPairs.add ({ subsequentCharacter, extraKerningAmount });
46     }
47 
getHorizontalSpacing(juce_wchar subsequentCharacter) const48     float getHorizontalSpacing (juce_wchar subsequentCharacter) const noexcept
49     {
50         if (subsequentCharacter != 0)
51             for (auto& kp : kerningPairs)
52                 if (kp.character2 == subsequentCharacter)
53                     return width + kp.kerningAmount;
54 
55         return width;
56     }
57 
58     const juce_wchar character;
59     const Path path;
60     float width;
61     Array<KerningPair> kerningPairs;
62 
63 private:
64     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GlyphInfo)
65 };
66 
67 //==============================================================================
68 namespace CustomTypefaceHelpers
69 {
readChar(InputStream & in)70     static juce_wchar readChar (InputStream& in)
71     {
72         auto n = (uint32) (uint16) in.readShort();
73 
74         if (n >= 0xd800 && n <= 0xdfff)
75         {
76             auto nextWord = (uint32) (uint16) in.readShort();
77             jassert (nextWord >= 0xdc00); // illegal unicode character!
78 
79             n = 0x10000 + (((n - 0xd800) << 10) | (nextWord - 0xdc00));
80         }
81 
82         return (juce_wchar) n;
83     }
84 
writeChar(OutputStream & out,juce_wchar charToWrite)85     static void writeChar (OutputStream& out, juce_wchar charToWrite)
86     {
87         if (charToWrite >= 0x10000)
88         {
89             charToWrite -= 0x10000;
90             out.writeShort ((short) (uint16) (0xd800 + (charToWrite >> 10)));
91             out.writeShort ((short) (uint16) (0xdc00 + (charToWrite & 0x3ff)));
92         }
93         else
94         {
95             out.writeShort ((short) (uint16) charToWrite);
96         }
97     }
98 }
99 
100 //==============================================================================
CustomTypeface()101 CustomTypeface::CustomTypeface()
102     : Typeface (String(), String())
103 {
104     clear();
105 }
106 
CustomTypeface(InputStream & serialisedTypefaceStream)107 CustomTypeface::CustomTypeface (InputStream& serialisedTypefaceStream)
108     : Typeface (String(), String())
109 {
110     clear();
111 
112     GZIPDecompressorInputStream gzin (serialisedTypefaceStream);
113     BufferedInputStream in (gzin, 32768);
114 
115     name = in.readString();
116 
117     const bool isBold   = in.readBool();
118     const bool isItalic = in.readBool();
119     style = FontStyleHelpers::getStyleName (isBold, isItalic);
120 
121     ascent = in.readFloat();
122     defaultCharacter = CustomTypefaceHelpers::readChar (in);
123 
124     auto numChars = in.readInt();
125 
126     for (int i = 0; i < numChars; ++i)
127     {
128         auto c = CustomTypefaceHelpers::readChar (in);
129         auto width = in.readFloat();
130 
131         Path p;
132         p.loadPathFromStream (in);
133         addGlyph (c, p, width);
134     }
135 
136     auto numKerningPairs = in.readInt();
137 
138     for (int i = 0; i < numKerningPairs; ++i)
139     {
140         auto char1 = CustomTypefaceHelpers::readChar (in);
141         auto char2 = CustomTypefaceHelpers::readChar (in);
142 
143         addKerningPair (char1, char2, in.readFloat());
144     }
145 }
146 
~CustomTypeface()147 CustomTypeface::~CustomTypeface()
148 {
149 }
150 
151 //==============================================================================
clear()152 void CustomTypeface::clear()
153 {
154     defaultCharacter = 0;
155     ascent = 1.0f;
156     style = "Regular";
157     zeromem (lookupTable, sizeof (lookupTable));
158     glyphs.clear();
159 }
160 
setCharacteristics(const String & newName,float newAscent,bool isBold,bool isItalic,juce_wchar newDefaultCharacter)161 void CustomTypeface::setCharacteristics (const String& newName, float newAscent, bool isBold,
162                                          bool isItalic, juce_wchar newDefaultCharacter) noexcept
163 {
164     name = newName;
165     defaultCharacter = newDefaultCharacter;
166     ascent = newAscent;
167     style = FontStyleHelpers::getStyleName (isBold, isItalic);
168 }
169 
setCharacteristics(const String & newName,const String & newStyle,float newAscent,juce_wchar newDefaultCharacter)170 void CustomTypeface::setCharacteristics (const String& newName, const String& newStyle,
171                                          float newAscent, juce_wchar newDefaultCharacter) noexcept
172 {
173     name = newName;
174     style = newStyle;
175     defaultCharacter = newDefaultCharacter;
176     ascent = newAscent;
177 }
178 
addGlyph(juce_wchar character,const Path & path,float width)179 void CustomTypeface::addGlyph (juce_wchar character, const Path& path, float width) noexcept
180 {
181     // Check that you're not trying to add the same character twice..
182     jassert (findGlyph (character, false) == nullptr);
183 
184     if (isPositiveAndBelow ((int) character, numElementsInArray (lookupTable)))
185         lookupTable [character] = (short) glyphs.size();
186 
187     glyphs.add (new GlyphInfo (character, path, width));
188 }
189 
addKerningPair(juce_wchar char1,juce_wchar char2,float extraAmount)190 void CustomTypeface::addKerningPair (juce_wchar char1, juce_wchar char2, float extraAmount) noexcept
191 {
192     if (extraAmount != 0.0f)
193     {
194         if (auto* g = findGlyph (char1, true))
195             g->addKerningPair (char2, extraAmount);
196         else
197             jassertfalse; // can only add kerning pairs for characters that exist!
198     }
199 }
200 
findGlyph(juce_wchar character,bool loadIfNeeded)201 CustomTypeface::GlyphInfo* CustomTypeface::findGlyph (juce_wchar character, bool loadIfNeeded) noexcept
202 {
203     if (isPositiveAndBelow ((int) character, numElementsInArray (lookupTable)) && lookupTable [character] > 0)
204         return glyphs [(int) lookupTable [(int) character]];
205 
206     for (auto* g : glyphs)
207         if (g->character == character)
208             return g;
209 
210     if (loadIfNeeded && loadGlyphIfPossible (character))
211         return findGlyph (character, false);
212 
213     return nullptr;
214 }
215 
loadGlyphIfPossible(juce_wchar)216 bool CustomTypeface::loadGlyphIfPossible (juce_wchar)
217 {
218     return false;
219 }
220 
addGlyphsFromOtherTypeface(Typeface & typefaceToCopy,juce_wchar characterStartIndex,int numCharacters)221 void CustomTypeface::addGlyphsFromOtherTypeface (Typeface& typefaceToCopy, juce_wchar characterStartIndex, int numCharacters) noexcept
222 {
223     setCharacteristics (name, style, typefaceToCopy.getAscent(), defaultCharacter);
224 
225     for (int i = 0; i < numCharacters; ++i)
226     {
227         auto c = (juce_wchar) (characterStartIndex + static_cast<juce_wchar> (i));
228 
229         Array<int> glyphIndexes;
230         Array<float> offsets;
231         typefaceToCopy.getGlyphPositions (String::charToString (c), glyphIndexes, offsets);
232 
233         const int glyphIndex = glyphIndexes.getFirst();
234 
235         if (glyphIndex >= 0 && glyphIndexes.size() > 0)
236         {
237             auto glyphWidth = offsets[1];
238 
239             Path p;
240             typefaceToCopy.getOutlineForGlyph (glyphIndex, p);
241 
242             addGlyph (c, p, glyphWidth);
243 
244             for (int j = glyphs.size() - 1; --j >= 0;)
245             {
246                 auto char2 = glyphs.getUnchecked (j)->character;
247                 glyphIndexes.clearQuick();
248                 offsets.clearQuick();
249                 typefaceToCopy.getGlyphPositions (String::charToString (c) + String::charToString (char2), glyphIndexes, offsets);
250 
251                 if (offsets.size() > 1)
252                     addKerningPair (c, char2, offsets[1] - glyphWidth);
253             }
254         }
255     }
256 }
257 
writeToStream(OutputStream & outputStream)258 bool CustomTypeface::writeToStream (OutputStream& outputStream)
259 {
260     GZIPCompressorOutputStream out (outputStream);
261 
262     out.writeString (name);
263     out.writeBool (FontStyleHelpers::isBold (style));
264     out.writeBool (FontStyleHelpers::isItalic (style));
265     out.writeFloat (ascent);
266     CustomTypefaceHelpers::writeChar (out, defaultCharacter);
267     out.writeInt (glyphs.size());
268 
269     int numKerningPairs = 0;
270 
271     for (auto* g : glyphs)
272     {
273         CustomTypefaceHelpers::writeChar (out, g->character);
274         out.writeFloat (g->width);
275         g->path.writePathToStream (out);
276 
277         numKerningPairs += g->kerningPairs.size();
278     }
279 
280     out.writeInt (numKerningPairs);
281 
282     for (auto* g : glyphs)
283     {
284         for (auto& p : g->kerningPairs)
285         {
286             CustomTypefaceHelpers::writeChar (out, g->character);
287             CustomTypefaceHelpers::writeChar (out, p.character2);
288             out.writeFloat (p.kerningAmount);
289         }
290     }
291 
292     return true;
293 }
294 
295 //==============================================================================
getAscent() const296 float CustomTypeface::getAscent() const                 { return ascent; }
getDescent() const297 float CustomTypeface::getDescent() const                { return 1.0f - ascent; }
getHeightToPointsFactor() const298 float CustomTypeface::getHeightToPointsFactor() const   { return ascent; }
299 
getStringWidth(const String & text)300 float CustomTypeface::getStringWidth (const String& text)
301 {
302     float x = 0;
303 
304     for (auto t = text.getCharPointer(); ! t.isEmpty();)
305     {
306         auto c = t.getAndAdvance();
307 
308         if (auto* glyph = findGlyph (c, true))
309         {
310             x += glyph->getHorizontalSpacing (*t);
311         }
312         else
313         {
314             if (auto fallbackTypeface = Typeface::getFallbackTypeface())
315                 if (fallbackTypeface.get() != this)
316                     x += fallbackTypeface->getStringWidth (String::charToString (c));
317         }
318     }
319 
320     return x;
321 }
322 
getGlyphPositions(const String & text,Array<int> & resultGlyphs,Array<float> & xOffsets)323 void CustomTypeface::getGlyphPositions (const String& text, Array<int>& resultGlyphs, Array<float>& xOffsets)
324 {
325     xOffsets.add (0);
326     float x = 0;
327 
328     for (auto t = text.getCharPointer(); ! t.isEmpty();)
329     {
330         float width = 0.0f;
331         int glyphChar = 0;
332 
333         auto c = t.getAndAdvance();
334 
335         if (auto* glyph = findGlyph (c, true))
336         {
337             width = glyph->getHorizontalSpacing (*t);
338             glyphChar = (int) glyph->character;
339         }
340         else
341         {
342             auto fallbackTypeface = getFallbackTypeface();
343 
344             if (fallbackTypeface != nullptr && fallbackTypeface.get() != this)
345             {
346                 Array<int> subGlyphs;
347                 Array<float> subOffsets;
348                 fallbackTypeface->getGlyphPositions (String::charToString (c), subGlyphs, subOffsets);
349 
350                 if (subGlyphs.size() > 0)
351                 {
352                     glyphChar = subGlyphs.getFirst();
353                     width = subOffsets[1];
354                 }
355             }
356         }
357 
358         x += width;
359         resultGlyphs.add (glyphChar);
360         xOffsets.add (x);
361     }
362 }
363 
getOutlineForGlyph(int glyphNumber,Path & path)364 bool CustomTypeface::getOutlineForGlyph (int glyphNumber, Path& path)
365 {
366     if (auto* glyph = findGlyph ((juce_wchar) glyphNumber, true))
367     {
368         path = glyph->path;
369         return true;
370     }
371 
372     if (auto fallbackTypeface = getFallbackTypeface())
373         if (fallbackTypeface.get() != this)
374             return fallbackTypeface->getOutlineForGlyph (glyphNumber, path);
375 
376     return false;
377 }
378 
getEdgeTableForGlyph(int glyphNumber,const AffineTransform & transform,float fontHeight)379 EdgeTable* CustomTypeface::getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform, float fontHeight)
380 {
381     if (auto* glyph = findGlyph ((juce_wchar) glyphNumber, true))
382     {
383         if (! glyph->path.isEmpty())
384             return new EdgeTable (glyph->path.getBoundsTransformed (transform)
385                                              .getSmallestIntegerContainer().expanded (1, 0),
386                                   glyph->path, transform);
387     }
388     else
389     {
390         if (auto fallbackTypeface = getFallbackTypeface())
391             if (fallbackTypeface.get() != this)
392                 return fallbackTypeface->getEdgeTableForGlyph (glyphNumber, transform, fontHeight);
393     }
394 
395     return nullptr;
396 }
397 
398 } // namespace juce
399