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 struct FontStyleHelpers
30 {
getStyleNamejuce::FontStyleHelpers31 static const char* getStyleName (const bool bold,
32 const bool italic) noexcept
33 {
34 if (bold && italic) return "Bold Italic";
35 if (bold) return "Bold";
36 if (italic) return "Italic";
37 return "Regular";
38 }
39
getStyleNamejuce::FontStyleHelpers40 static const char* getStyleName (const int styleFlags) noexcept
41 {
42 return getStyleName ((styleFlags & Font::bold) != 0,
43 (styleFlags & Font::italic) != 0);
44 }
45
isBoldjuce::FontStyleHelpers46 static bool isBold (const String& style) noexcept
47 {
48 return style.containsWholeWordIgnoreCase ("Bold");
49 }
50
isItalicjuce::FontStyleHelpers51 static bool isItalic (const String& style) noexcept
52 {
53 return style.containsWholeWordIgnoreCase ("Italic")
54 || style.containsWholeWordIgnoreCase ("Oblique");
55 }
56
isPlaceholderFamilyNamejuce::FontStyleHelpers57 static bool isPlaceholderFamilyName (const String& family)
58 {
59 return family == Font::getDefaultSansSerifFontName()
60 || family == Font::getDefaultSerifFontName()
61 || family == Font::getDefaultMonospacedFontName();
62 }
63
64 struct ConcreteFamilyNames
65 {
ConcreteFamilyNamesjuce::FontStyleHelpers::ConcreteFamilyNames66 ConcreteFamilyNames()
67 : sans (findName (Font::getDefaultSansSerifFontName())),
68 serif (findName (Font::getDefaultSerifFontName())),
69 mono (findName (Font::getDefaultMonospacedFontName()))
70 {
71 }
72
lookUpjuce::FontStyleHelpers::ConcreteFamilyNames73 String lookUp (const String& placeholder)
74 {
75 if (placeholder == Font::getDefaultSansSerifFontName()) return sans;
76 if (placeholder == Font::getDefaultSerifFontName()) return serif;
77 if (placeholder == Font::getDefaultMonospacedFontName()) return mono;
78
79 return findName (placeholder);
80 }
81
82 private:
findNamejuce::FontStyleHelpers::ConcreteFamilyNames83 static String findName (const String& placeholder)
84 {
85 const Font f (placeholder, Font::getDefaultStyle(), 15.0f);
86 return Font::getDefaultTypefaceForFont (f)->getName();
87 }
88
89 String sans, serif, mono;
90 };
91
getConcreteFamilyNameFromPlaceholderjuce::FontStyleHelpers92 static String getConcreteFamilyNameFromPlaceholder (const String& placeholder)
93 {
94 static ConcreteFamilyNames names;
95 return names.lookUp (placeholder);
96 }
97
getConcreteFamilyNamejuce::FontStyleHelpers98 static String getConcreteFamilyName (const Font& font)
99 {
100 const String& family = font.getTypefaceName();
101
102 return isPlaceholderFamilyName (family) ? getConcreteFamilyNameFromPlaceholder (family)
103 : family;
104 }
105 };
106
107 //==============================================================================
Typeface(const String & faceName,const String & styleName)108 Typeface::Typeface (const String& faceName, const String& styleName) noexcept
109 : name (faceName), style (styleName)
110 {
111 }
112
~Typeface()113 Typeface::~Typeface()
114 {
115 }
116
getFallbackTypeface()117 Typeface::Ptr Typeface::getFallbackTypeface()
118 {
119 const Font fallbackFont (Font::getFallbackFontName(), Font::getFallbackFontStyle(), 10.0f);
120 return Typeface::Ptr (fallbackFont.getTypeface());
121 }
122
getEdgeTableForGlyph(int glyphNumber,const AffineTransform & transform,float fontHeight)123 EdgeTable* Typeface::getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform, float fontHeight)
124 {
125 Path path;
126
127 if (getOutlineForGlyph (glyphNumber, path) && ! path.isEmpty())
128 {
129 applyVerticalHintingTransform (fontHeight, path);
130
131 return new EdgeTable (path.getBoundsTransformed (transform).getSmallestIntegerContainer().expanded (1, 0),
132 path, transform);
133 }
134
135 return nullptr;
136 }
137
138 //==============================================================================
139 struct Typeface::HintingParams
140 {
HintingParamsjuce::Typeface::HintingParams141 HintingParams (Typeface& t)
142 {
143 Font font (t);
144 font = font.withHeight ((float) standardHeight);
145
146 top = getAverageY (font, "BDEFPRTZOQ", true);
147 middle = getAverageY (font, "acegmnopqrsuvwxy", true);
148 bottom = getAverageY (font, "BDELZOC", false);
149 }
150
applyVerticalHintingTransformjuce::Typeface::HintingParams151 void applyVerticalHintingTransform (float fontSize, Path& path)
152 {
153 if (cachedSize != fontSize)
154 {
155 cachedSize = fontSize;
156 cachedScale = Scaling (top, middle, bottom, fontSize);
157 }
158
159 if (bottom < top + 3.0f / fontSize)
160 return;
161
162 Path result;
163
164 for (Path::Iterator i (path); i.next();)
165 {
166 switch (i.elementType)
167 {
168 case Path::Iterator::startNewSubPath: result.startNewSubPath (i.x1, cachedScale.apply (i.y1)); break;
169 case Path::Iterator::lineTo: result.lineTo (i.x1, cachedScale.apply (i.y1)); break;
170 case Path::Iterator::quadraticTo: result.quadraticTo (i.x1, cachedScale.apply (i.y1),
171 i.x2, cachedScale.apply (i.y2)); break;
172 case Path::Iterator::cubicTo: result.cubicTo (i.x1, cachedScale.apply (i.y1),
173 i.x2, cachedScale.apply (i.y2),
174 i.x3, cachedScale.apply (i.y3)); break;
175 case Path::Iterator::closePath: result.closeSubPath(); break;
176 default: jassertfalse; break;
177 }
178 }
179
180 result.swapWithPath (path);
181 }
182
183 private:
184 struct Scaling
185 {
Scalingjuce::Typeface::HintingParams::Scaling186 Scaling() noexcept : middle(), upperScale(), upperOffset(), lowerScale(), lowerOffset() {}
187
Scalingjuce::Typeface::HintingParams::Scaling188 Scaling (float t, float m, float b, float fontSize) noexcept : middle (m)
189 {
190 const float newT = std::floor (fontSize * t + 0.5f) / fontSize;
191 const float newB = std::floor (fontSize * b + 0.5f) / fontSize;
192 const float newM = std::floor (fontSize * m + 0.3f) / fontSize; // this is slightly biased so that lower-case letters
193 // are more likely to become taller than shorter.
194 upperScale = jlimit (0.9f, 1.1f, (newM - newT) / (m - t));
195 lowerScale = jlimit (0.9f, 1.1f, (newB - newM) / (b - m));
196
197 upperOffset = newM - m * upperScale;
198 lowerOffset = newB - b * lowerScale;
199 }
200
applyjuce::Typeface::HintingParams::Scaling201 float apply (float y) const noexcept
202 {
203 return y < middle ? (y * upperScale + upperOffset)
204 : (y * lowerScale + lowerOffset);
205 }
206
207 float middle, upperScale, upperOffset, lowerScale, lowerOffset;
208 };
209
210 float cachedSize = 0;
211 Scaling cachedScale;
212
getAverageYjuce::Typeface::HintingParams213 static float getAverageY (const Font& font, const char* chars, bool getTop)
214 {
215 GlyphArrangement ga;
216 ga.addLineOfText (font, chars, 0, 0);
217
218 Array<float> yValues;
219
220 for (auto& glyph : ga)
221 {
222 Path p;
223 glyph.createPath (p);
224 auto bounds = p.getBounds();
225
226 if (! p.isEmpty())
227 yValues.add (getTop ? bounds.getY() : bounds.getBottom());
228 }
229
230 std::sort (yValues.begin(), yValues.end());
231
232 auto median = yValues[yValues.size() / 2];
233 float total = 0;
234 int num = 0;
235
236 for (auto y : yValues)
237 {
238 if (std::abs (median - y) < 0.05f * (float) standardHeight)
239 {
240 total += y;
241 ++num;
242 }
243 }
244
245 return num < 4 ? 0.0f : total / ((float) num * (float) standardHeight);
246 }
247
248 enum { standardHeight = 100 };
249 float top = 0, middle = 0, bottom = 0;
250 };
251
applyVerticalHintingTransform(float fontSize,Path & path)252 void Typeface::applyVerticalHintingTransform (float fontSize, Path& path)
253 {
254 if (fontSize > 3.0f && fontSize < 25.0f)
255 {
256 ScopedLock sl (hintingLock);
257
258 if (hintingParams == nullptr)
259 hintingParams.reset (new HintingParams (*this));
260
261 return hintingParams->applyVerticalHintingTransform (fontSize, path);
262 }
263 }
264
265 } // namespace juce
266