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