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