|
1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
|
2 * This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "gfxFT2FontBase.h" |
|
7 #include "gfxFT2Utils.h" |
|
8 #include "mozilla/Likely.h" |
|
9 #include FT_TRUETYPE_TAGS_H |
|
10 #include FT_TRUETYPE_TABLES_H |
|
11 #include <algorithm> |
|
12 |
|
13 #ifdef HAVE_FONTCONFIG_FCFREETYPE_H |
|
14 #include <fontconfig/fcfreetype.h> |
|
15 #endif |
|
16 |
|
17 #include "prlink.h" |
|
18 |
|
19 // aScale is intended for a 16.16 x/y_scale of an FT_Size_Metrics |
|
20 static inline FT_Long |
|
21 ScaleRoundDesignUnits(FT_Short aDesignMetric, FT_Fixed aScale) |
|
22 { |
|
23 FT_Long fixed26dot6 = FT_MulFix(aDesignMetric, aScale); |
|
24 return ROUND_26_6_TO_INT(fixed26dot6); |
|
25 } |
|
26 |
|
27 // Snap a line to pixels while keeping the center and size of the line as |
|
28 // close to the original position as possible. |
|
29 // |
|
30 // Pango does similar snapping for underline and strikethrough when fonts are |
|
31 // hinted, but nsCSSRendering::GetTextDecorationRectInternal always snaps the |
|
32 // top and size of lines. Optimizing the distance between the line and |
|
33 // baseline is probably good for the gap between text and underline, but |
|
34 // optimizing the center of the line is better for positioning strikethough. |
|
35 static void |
|
36 SnapLineToPixels(gfxFloat& aOffset, gfxFloat& aSize) |
|
37 { |
|
38 gfxFloat snappedSize = std::max(floor(aSize + 0.5), 1.0); |
|
39 // Correct offset for change in size |
|
40 gfxFloat offset = aOffset - 0.5 * (aSize - snappedSize); |
|
41 // Snap offset |
|
42 aOffset = floor(offset + 0.5); |
|
43 aSize = snappedSize; |
|
44 } |
|
45 |
|
46 void |
|
47 gfxFT2LockedFace::GetMetrics(gfxFont::Metrics* aMetrics, |
|
48 uint32_t* aSpaceGlyph) |
|
49 { |
|
50 NS_PRECONDITION(aMetrics != nullptr, "aMetrics must not be NULL"); |
|
51 NS_PRECONDITION(aSpaceGlyph != nullptr, "aSpaceGlyph must not be NULL"); |
|
52 |
|
53 if (MOZ_UNLIKELY(!mFace)) { |
|
54 // No face. This unfortunate situation might happen if the font |
|
55 // file is (re)moved at the wrong time. |
|
56 const gfxFloat emHeight = mGfxFont->GetStyle()->size; |
|
57 aMetrics->emHeight = emHeight; |
|
58 aMetrics->maxAscent = aMetrics->emAscent = 0.8 * emHeight; |
|
59 aMetrics->maxDescent = aMetrics->emDescent = 0.2 * emHeight; |
|
60 aMetrics->maxHeight = emHeight; |
|
61 aMetrics->internalLeading = 0.0; |
|
62 aMetrics->externalLeading = 0.2 * emHeight; |
|
63 const gfxFloat spaceWidth = 0.5 * emHeight; |
|
64 aMetrics->spaceWidth = spaceWidth; |
|
65 aMetrics->maxAdvance = spaceWidth; |
|
66 aMetrics->aveCharWidth = spaceWidth; |
|
67 aMetrics->zeroOrAveCharWidth = spaceWidth; |
|
68 const gfxFloat xHeight = 0.5 * emHeight; |
|
69 aMetrics->xHeight = xHeight; |
|
70 aMetrics->superscriptOffset = xHeight; |
|
71 aMetrics->subscriptOffset = xHeight; |
|
72 const gfxFloat underlineSize = emHeight / 14.0; |
|
73 aMetrics->underlineSize = underlineSize; |
|
74 aMetrics->underlineOffset = -underlineSize; |
|
75 aMetrics->strikeoutOffset = 0.25 * emHeight; |
|
76 aMetrics->strikeoutSize = underlineSize; |
|
77 |
|
78 *aSpaceGlyph = 0; |
|
79 return; |
|
80 } |
|
81 |
|
82 const FT_Size_Metrics& ftMetrics = mFace->size->metrics; |
|
83 |
|
84 gfxFloat emHeight; |
|
85 // Scale for vertical design metric conversion: pixels per design unit. |
|
86 // If this remains at 0.0, we can't use metrics from OS/2 etc. |
|
87 gfxFloat yScale = 0.0; |
|
88 if (FT_IS_SCALABLE(mFace)) { |
|
89 // Prefer FT_Size_Metrics::x_scale to x_ppem as x_ppem does not |
|
90 // have subpixel accuracy. |
|
91 // |
|
92 // FT_Size_Metrics::y_scale is in 16.16 fixed point format. Its |
|
93 // (fractional) value is a factor that converts vertical metrics from |
|
94 // design units to units of 1/64 pixels, so that the result may be |
|
95 // interpreted as pixels in 26.6 fixed point format. |
|
96 yScale = FLOAT_FROM_26_6(FLOAT_FROM_16_16(ftMetrics.y_scale)); |
|
97 emHeight = mFace->units_per_EM * yScale; |
|
98 } else { // Not scalable. |
|
99 emHeight = ftMetrics.y_ppem; |
|
100 // FT_Face doc says units_per_EM and a bunch of following fields |
|
101 // are "only relevant to scalable outlines". If it's an sfnt, |
|
102 // we can get units_per_EM from the 'head' table instead; otherwise, |
|
103 // we don't have a unitsPerEm value so we can't compute/use yScale. |
|
104 const TT_Header* head = |
|
105 static_cast<TT_Header*>(FT_Get_Sfnt_Table(mFace, ft_sfnt_head)); |
|
106 if (head) { |
|
107 gfxFloat emUnit = head->Units_Per_EM; |
|
108 yScale = emHeight / emUnit; |
|
109 } |
|
110 } |
|
111 |
|
112 TT_OS2 *os2 = |
|
113 static_cast<TT_OS2*>(FT_Get_Sfnt_Table(mFace, ft_sfnt_os2)); |
|
114 |
|
115 aMetrics->maxAscent = FLOAT_FROM_26_6(ftMetrics.ascender); |
|
116 aMetrics->maxDescent = -FLOAT_FROM_26_6(ftMetrics.descender); |
|
117 aMetrics->maxAdvance = FLOAT_FROM_26_6(ftMetrics.max_advance); |
|
118 |
|
119 gfxFloat lineHeight; |
|
120 if (os2 && os2->sTypoAscender && yScale > 0.0) { |
|
121 aMetrics->emAscent = os2->sTypoAscender * yScale; |
|
122 aMetrics->emDescent = -os2->sTypoDescender * yScale; |
|
123 FT_Short typoHeight = |
|
124 os2->sTypoAscender - os2->sTypoDescender + os2->sTypoLineGap; |
|
125 lineHeight = typoHeight * yScale; |
|
126 |
|
127 // If the OS/2 fsSelection USE_TYPO_METRICS bit is set, |
|
128 // or if this is an OpenType Math font, |
|
129 // set maxAscent/Descent from the sTypo* fields instead of hhea. |
|
130 const uint16_t kUseTypoMetricsMask = 1 << 7; |
|
131 FT_ULong length = 0; |
|
132 if ((os2->fsSelection & kUseTypoMetricsMask) || |
|
133 0 == FT_Load_Sfnt_Table(mFace, FT_MAKE_TAG('M','A','T','H'), |
|
134 0, nullptr, &length)) { |
|
135 aMetrics->maxAscent = NS_round(aMetrics->emAscent); |
|
136 aMetrics->maxDescent = NS_round(aMetrics->emDescent); |
|
137 } else { |
|
138 // maxAscent/maxDescent get used for frame heights, and some fonts |
|
139 // don't have the HHEA table ascent/descent set (bug 279032). |
|
140 // We use NS_round here to parallel the pixel-rounded values that |
|
141 // freetype gives us for ftMetrics.ascender/descender. |
|
142 aMetrics->maxAscent = |
|
143 std::max(aMetrics->maxAscent, NS_round(aMetrics->emAscent)); |
|
144 aMetrics->maxDescent = |
|
145 std::max(aMetrics->maxDescent, NS_round(aMetrics->emDescent)); |
|
146 } |
|
147 } else { |
|
148 aMetrics->emAscent = aMetrics->maxAscent; |
|
149 aMetrics->emDescent = aMetrics->maxDescent; |
|
150 lineHeight = FLOAT_FROM_26_6(ftMetrics.height); |
|
151 } |
|
152 |
|
153 cairo_text_extents_t extents; |
|
154 *aSpaceGlyph = GetCharExtents(' ', &extents); |
|
155 if (*aSpaceGlyph) { |
|
156 aMetrics->spaceWidth = extents.x_advance; |
|
157 } else { |
|
158 aMetrics->spaceWidth = aMetrics->maxAdvance; // guess |
|
159 } |
|
160 |
|
161 aMetrics->zeroOrAveCharWidth = 0.0; |
|
162 if (GetCharExtents('0', &extents)) { |
|
163 aMetrics->zeroOrAveCharWidth = extents.x_advance; |
|
164 } |
|
165 |
|
166 // Prefering a measured x over sxHeight because sxHeight doesn't consider |
|
167 // hinting, but maybe the x extents are not quite right in some fancy |
|
168 // script fonts. CSS 2.1 suggests possibly using the height of an "o", |
|
169 // which would have a more consistent glyph across fonts. |
|
170 if (GetCharExtents('x', &extents) && extents.y_bearing < 0.0) { |
|
171 aMetrics->xHeight = -extents.y_bearing; |
|
172 aMetrics->aveCharWidth = extents.x_advance; |
|
173 } else { |
|
174 if (os2 && os2->sxHeight && yScale > 0.0) { |
|
175 aMetrics->xHeight = os2->sxHeight * yScale; |
|
176 } else { |
|
177 // CSS 2.1, section 4.3.2 Lengths: "In the cases where it is |
|
178 // impossible or impractical to determine the x-height, a value of |
|
179 // 0.5em should be used." |
|
180 aMetrics->xHeight = 0.5 * emHeight; |
|
181 } |
|
182 aMetrics->aveCharWidth = 0.0; // updated below |
|
183 } |
|
184 // aveCharWidth is used for the width of text input elements so be |
|
185 // liberal rather than conservative in the estimate. |
|
186 if (os2 && os2->xAvgCharWidth) { |
|
187 // Round to pixels as this is compared with maxAdvance to guess |
|
188 // whether this is a fixed width font. |
|
189 gfxFloat avgCharWidth = |
|
190 ScaleRoundDesignUnits(os2->xAvgCharWidth, ftMetrics.x_scale); |
|
191 aMetrics->aveCharWidth = |
|
192 std::max(aMetrics->aveCharWidth, avgCharWidth); |
|
193 } |
|
194 aMetrics->aveCharWidth = |
|
195 std::max(aMetrics->aveCharWidth, aMetrics->zeroOrAveCharWidth); |
|
196 if (aMetrics->aveCharWidth == 0.0) { |
|
197 aMetrics->aveCharWidth = aMetrics->spaceWidth; |
|
198 } |
|
199 if (aMetrics->zeroOrAveCharWidth == 0.0) { |
|
200 aMetrics->zeroOrAveCharWidth = aMetrics->aveCharWidth; |
|
201 } |
|
202 // Apparently hinting can mean that max_advance is not always accurate. |
|
203 aMetrics->maxAdvance = |
|
204 std::max(aMetrics->maxAdvance, aMetrics->aveCharWidth); |
|
205 |
|
206 // gfxFont::Metrics::underlineOffset is the position of the top of the |
|
207 // underline. |
|
208 // |
|
209 // FT_FaceRec documentation describes underline_position as "the |
|
210 // center of the underlining stem". This was the original definition |
|
211 // of the PostScript metric, but in the PostScript table of OpenType |
|
212 // fonts the metric is "the top of the underline" |
|
213 // (http://www.microsoft.com/typography/otspec/post.htm), and FreeType |
|
214 // (up to version 2.3.7) doesn't make any adjustment. |
|
215 // |
|
216 // Therefore get the underline position directly from the table |
|
217 // ourselves when this table exists. Use FreeType's metrics for |
|
218 // other (including older PostScript) fonts. |
|
219 if (mFace->underline_position && mFace->underline_thickness && yScale > 0.0) { |
|
220 aMetrics->underlineSize = mFace->underline_thickness * yScale; |
|
221 TT_Postscript *post = static_cast<TT_Postscript*> |
|
222 (FT_Get_Sfnt_Table(mFace, ft_sfnt_post)); |
|
223 if (post && post->underlinePosition) { |
|
224 aMetrics->underlineOffset = post->underlinePosition * yScale; |
|
225 } else { |
|
226 aMetrics->underlineOffset = mFace->underline_position * yScale |
|
227 + 0.5 * aMetrics->underlineSize; |
|
228 } |
|
229 } else { // No underline info. |
|
230 // Imitate Pango. |
|
231 aMetrics->underlineSize = emHeight / 14.0; |
|
232 aMetrics->underlineOffset = -aMetrics->underlineSize; |
|
233 } |
|
234 |
|
235 if (os2 && os2->yStrikeoutSize && os2->yStrikeoutPosition && yScale > 0.0) { |
|
236 aMetrics->strikeoutSize = os2->yStrikeoutSize * yScale; |
|
237 aMetrics->strikeoutOffset = os2->yStrikeoutPosition * yScale; |
|
238 } else { // No strikeout info. |
|
239 aMetrics->strikeoutSize = aMetrics->underlineSize; |
|
240 // Use OpenType spec's suggested position for Roman font. |
|
241 aMetrics->strikeoutOffset = emHeight * 409.0 / 2048.0 |
|
242 + 0.5 * aMetrics->strikeoutSize; |
|
243 } |
|
244 SnapLineToPixels(aMetrics->strikeoutOffset, aMetrics->strikeoutSize); |
|
245 |
|
246 if (os2 && os2->ySuperscriptYOffset) { |
|
247 gfxFloat val = ScaleRoundDesignUnits(os2->ySuperscriptYOffset, |
|
248 ftMetrics.y_scale); |
|
249 aMetrics->superscriptOffset = std::max(1.0, val); |
|
250 } else { |
|
251 aMetrics->superscriptOffset = aMetrics->xHeight; |
|
252 } |
|
253 |
|
254 if (os2 && os2->ySubscriptYOffset) { |
|
255 gfxFloat val = ScaleRoundDesignUnits(os2->ySubscriptYOffset, |
|
256 ftMetrics.y_scale); |
|
257 // some fonts have the incorrect sign. |
|
258 val = fabs(val); |
|
259 aMetrics->subscriptOffset = std::max(1.0, val); |
|
260 } else { |
|
261 aMetrics->subscriptOffset = aMetrics->xHeight; |
|
262 } |
|
263 |
|
264 aMetrics->maxHeight = aMetrics->maxAscent + aMetrics->maxDescent; |
|
265 |
|
266 // Make the line height an integer number of pixels so that lines will be |
|
267 // equally spaced (rather than just being snapped to pixels, some up and |
|
268 // some down). Layout calculates line height from the emHeight + |
|
269 // internalLeading + externalLeading, but first each of these is rounded |
|
270 // to layout units. To ensure that the result is an integer number of |
|
271 // pixels, round each of the components to pixels. |
|
272 aMetrics->emHeight = floor(emHeight + 0.5); |
|
273 |
|
274 // maxHeight will normally be an integer, but round anyway in case |
|
275 // FreeType is configured differently. |
|
276 aMetrics->internalLeading = |
|
277 floor(aMetrics->maxHeight - aMetrics->emHeight + 0.5); |
|
278 |
|
279 // Text input boxes currently don't work well with lineHeight |
|
280 // significantly less than maxHeight (with Verdana, for example). |
|
281 lineHeight = floor(std::max(lineHeight, aMetrics->maxHeight) + 0.5); |
|
282 aMetrics->externalLeading = |
|
283 lineHeight - aMetrics->internalLeading - aMetrics->emHeight; |
|
284 |
|
285 // Ensure emAscent + emDescent == emHeight |
|
286 gfxFloat sum = aMetrics->emAscent + aMetrics->emDescent; |
|
287 aMetrics->emAscent = sum > 0.0 ? |
|
288 aMetrics->emAscent * aMetrics->emHeight / sum : 0.0; |
|
289 aMetrics->emDescent = aMetrics->emHeight - aMetrics->emAscent; |
|
290 } |
|
291 |
|
292 uint32_t |
|
293 gfxFT2LockedFace::GetGlyph(uint32_t aCharCode) |
|
294 { |
|
295 if (MOZ_UNLIKELY(!mFace)) |
|
296 return 0; |
|
297 |
|
298 #ifdef HAVE_FONTCONFIG_FCFREETYPE_H |
|
299 // FcFreeTypeCharIndex will search starting from the most recently |
|
300 // selected charmap. This can cause non-determistic behavior when more |
|
301 // than one charmap supports a character but with different glyphs, as |
|
302 // with older versions of MS Gothic, for example. Always prefer a Unicode |
|
303 // charmap, if there is one. (FcFreeTypeCharIndex usually does the |
|
304 // appropriate Unicode conversion, but some fonts have non-Roman glyphs |
|
305 // for FT_ENCODING_APPLE_ROMAN characters.) |
|
306 if (!mFace->charmap || mFace->charmap->encoding != FT_ENCODING_UNICODE) { |
|
307 FT_Select_Charmap(mFace, FT_ENCODING_UNICODE); |
|
308 } |
|
309 |
|
310 return FcFreeTypeCharIndex(mFace, aCharCode); |
|
311 #else |
|
312 return FT_Get_Char_Index(mFace, aCharCode); |
|
313 #endif |
|
314 } |
|
315 |
|
316 typedef FT_UInt (*GetCharVariantFunction)(FT_Face face, |
|
317 FT_ULong charcode, |
|
318 FT_ULong variantSelector); |
|
319 |
|
320 uint32_t |
|
321 gfxFT2LockedFace::GetUVSGlyph(uint32_t aCharCode, uint32_t aVariantSelector) |
|
322 { |
|
323 NS_PRECONDITION(aVariantSelector, "aVariantSelector should not be NULL"); |
|
324 |
|
325 if (MOZ_UNLIKELY(!mFace)) |
|
326 return 0; |
|
327 |
|
328 // This function is available from FreeType 2.3.6 (June 2008). |
|
329 static CharVariantFunction sGetCharVariantPtr = FindCharVariantFunction(); |
|
330 if (!sGetCharVariantPtr) |
|
331 return 0; |
|
332 |
|
333 #ifdef HAVE_FONTCONFIG_FCFREETYPE_H |
|
334 // FcFreeTypeCharIndex may have changed the selected charmap. |
|
335 // FT_Face_GetCharVariantIndex needs a unicode charmap. |
|
336 if (!mFace->charmap || mFace->charmap->encoding != FT_ENCODING_UNICODE) { |
|
337 FT_Select_Charmap(mFace, FT_ENCODING_UNICODE); |
|
338 } |
|
339 #endif |
|
340 |
|
341 return (*sGetCharVariantPtr)(mFace, aCharCode, aVariantSelector); |
|
342 } |
|
343 |
|
344 uint32_t |
|
345 gfxFT2LockedFace::GetCharExtents(char aChar, cairo_text_extents_t* aExtents) |
|
346 { |
|
347 NS_PRECONDITION(aExtents != nullptr, "aExtents must not be NULL"); |
|
348 |
|
349 if (!mFace) |
|
350 return 0; |
|
351 |
|
352 FT_UInt gid = mGfxFont->GetGlyph(aChar); |
|
353 if (gid) { |
|
354 mGfxFont->GetGlyphExtents(gid, aExtents); |
|
355 } |
|
356 |
|
357 return gid; |
|
358 } |
|
359 |
|
360 gfxFT2LockedFace::CharVariantFunction |
|
361 gfxFT2LockedFace::FindCharVariantFunction() |
|
362 { |
|
363 // This function is available from FreeType 2.3.6 (June 2008). |
|
364 PRLibrary *lib = nullptr; |
|
365 CharVariantFunction function = |
|
366 reinterpret_cast<CharVariantFunction> |
|
367 (PR_FindFunctionSymbolAndLibrary("FT_Face_GetCharVariantIndex", &lib)); |
|
368 if (!lib) { |
|
369 return nullptr; |
|
370 } |
|
371 |
|
372 FT_Int major; |
|
373 FT_Int minor; |
|
374 FT_Int patch; |
|
375 FT_Library_Version(mFace->glyph->library, &major, &minor, &patch); |
|
376 |
|
377 // Versions 2.4.0 to 2.4.3 crash if configured with |
|
378 // FT_CONFIG_OPTION_OLD_INTERNALS. Presence of the symbol FT_Alloc |
|
379 // indicates FT_CONFIG_OPTION_OLD_INTERNALS. |
|
380 if (major == 2 && minor == 4 && patch < 4 && |
|
381 PR_FindFunctionSymbol(lib, "FT_Alloc")) { |
|
382 function = nullptr; |
|
383 } |
|
384 |
|
385 // Decrement the reference count incremented in |
|
386 // PR_FindFunctionSymbolAndLibrary. |
|
387 PR_UnloadLibrary(lib); |
|
388 |
|
389 return function; |
|
390 } |