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