|
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 "gfxMacFont.h" |
|
7 |
|
8 #include "mozilla/MemoryReporting.h" |
|
9 |
|
10 #include "gfxCoreTextShaper.h" |
|
11 #include "gfxHarfBuzzShaper.h" |
|
12 #include <algorithm> |
|
13 #include "gfxGraphiteShaper.h" |
|
14 #include "gfxPlatformMac.h" |
|
15 #include "gfxContext.h" |
|
16 #include "gfxFontUtils.h" |
|
17 #include "gfxMacPlatformFontList.h" |
|
18 #include "gfxFontConstants.h" |
|
19 |
|
20 #include "cairo-quartz.h" |
|
21 |
|
22 using namespace mozilla; |
|
23 using namespace mozilla::gfx; |
|
24 |
|
25 gfxMacFont::gfxMacFont(MacOSFontEntry *aFontEntry, const gfxFontStyle *aFontStyle, |
|
26 bool aNeedsBold) |
|
27 : gfxFont(aFontEntry, aFontStyle), |
|
28 mCGFont(nullptr), |
|
29 mFontFace(nullptr) |
|
30 { |
|
31 mApplySyntheticBold = aNeedsBold; |
|
32 |
|
33 mCGFont = aFontEntry->GetFontRef(); |
|
34 if (!mCGFont) { |
|
35 mIsValid = false; |
|
36 return; |
|
37 } |
|
38 |
|
39 // InitMetrics will handle the sizeAdjust factor and set mAdjustedSize |
|
40 InitMetrics(); |
|
41 if (!mIsValid) { |
|
42 return; |
|
43 } |
|
44 |
|
45 mFontFace = cairo_quartz_font_face_create_for_cgfont(mCGFont); |
|
46 |
|
47 cairo_status_t cairoerr = cairo_font_face_status(mFontFace); |
|
48 if (cairoerr != CAIRO_STATUS_SUCCESS) { |
|
49 mIsValid = false; |
|
50 #ifdef DEBUG |
|
51 char warnBuf[1024]; |
|
52 sprintf(warnBuf, "Failed to create Cairo font face: %s status: %d", |
|
53 NS_ConvertUTF16toUTF8(GetName()).get(), cairoerr); |
|
54 NS_WARNING(warnBuf); |
|
55 #endif |
|
56 return; |
|
57 } |
|
58 |
|
59 cairo_matrix_t sizeMatrix, ctm; |
|
60 cairo_matrix_init_identity(&ctm); |
|
61 cairo_matrix_init_scale(&sizeMatrix, mAdjustedSize, mAdjustedSize); |
|
62 |
|
63 // synthetic oblique by skewing via the font matrix |
|
64 bool needsOblique = |
|
65 (mFontEntry != nullptr) && |
|
66 (!mFontEntry->IsItalic() && |
|
67 (mStyle.style & (NS_FONT_STYLE_ITALIC | NS_FONT_STYLE_OBLIQUE))); |
|
68 |
|
69 if (needsOblique) { |
|
70 double skewfactor = (needsOblique ? Fix2X(kATSItalicQDSkew) : 0); |
|
71 |
|
72 cairo_matrix_t style; |
|
73 cairo_matrix_init(&style, |
|
74 1, //xx |
|
75 0, //yx |
|
76 -1 * skewfactor, //xy |
|
77 1, //yy |
|
78 0, //x0 |
|
79 0); //y0 |
|
80 cairo_matrix_multiply(&sizeMatrix, &sizeMatrix, &style); |
|
81 } |
|
82 |
|
83 cairo_font_options_t *fontOptions = cairo_font_options_create(); |
|
84 |
|
85 // turn off font anti-aliasing based on user pref setting |
|
86 if (mAdjustedSize <= |
|
87 (gfxFloat)gfxPlatformMac::GetPlatform()->GetAntiAliasingThreshold()) { |
|
88 cairo_font_options_set_antialias(fontOptions, CAIRO_ANTIALIAS_NONE); |
|
89 mAntialiasOption = kAntialiasNone; |
|
90 } else if (mStyle.useGrayscaleAntialiasing) { |
|
91 cairo_font_options_set_antialias(fontOptions, CAIRO_ANTIALIAS_GRAY); |
|
92 mAntialiasOption = kAntialiasGrayscale; |
|
93 } |
|
94 |
|
95 mScaledFont = cairo_scaled_font_create(mFontFace, &sizeMatrix, &ctm, |
|
96 fontOptions); |
|
97 cairo_font_options_destroy(fontOptions); |
|
98 |
|
99 cairoerr = cairo_scaled_font_status(mScaledFont); |
|
100 if (cairoerr != CAIRO_STATUS_SUCCESS) { |
|
101 mIsValid = false; |
|
102 #ifdef DEBUG |
|
103 char warnBuf[1024]; |
|
104 sprintf(warnBuf, "Failed to create scaled font: %s status: %d", |
|
105 NS_ConvertUTF16toUTF8(GetName()).get(), cairoerr); |
|
106 NS_WARNING(warnBuf); |
|
107 #endif |
|
108 } |
|
109 |
|
110 if (FontCanSupportGraphite()) { |
|
111 mGraphiteShaper = new gfxGraphiteShaper(this); |
|
112 } |
|
113 if (FontCanSupportHarfBuzz()) { |
|
114 mHarfBuzzShaper = new gfxHarfBuzzShaper(this); |
|
115 } |
|
116 } |
|
117 |
|
118 gfxMacFont::~gfxMacFont() |
|
119 { |
|
120 if (mScaledFont) { |
|
121 cairo_scaled_font_destroy(mScaledFont); |
|
122 } |
|
123 if (mFontFace) { |
|
124 cairo_font_face_destroy(mFontFace); |
|
125 } |
|
126 } |
|
127 |
|
128 bool |
|
129 gfxMacFont::ShapeText(gfxContext *aContext, |
|
130 const char16_t *aText, |
|
131 uint32_t aOffset, |
|
132 uint32_t aLength, |
|
133 int32_t aScript, |
|
134 gfxShapedText *aShapedText, |
|
135 bool aPreferPlatformShaping) |
|
136 { |
|
137 if (!mIsValid) { |
|
138 NS_WARNING("invalid font! expect incorrect text rendering"); |
|
139 return false; |
|
140 } |
|
141 |
|
142 bool requiresAAT = |
|
143 static_cast<MacOSFontEntry*>(GetFontEntry())->RequiresAATLayout(); |
|
144 return gfxFont::ShapeText(aContext, aText, aOffset, aLength, |
|
145 aScript, aShapedText, requiresAAT); |
|
146 } |
|
147 |
|
148 void |
|
149 gfxMacFont::CreatePlatformShaper() |
|
150 { |
|
151 mPlatformShaper = new gfxCoreTextShaper(this); |
|
152 } |
|
153 |
|
154 bool |
|
155 gfxMacFont::SetupCairoFont(gfxContext *aContext) |
|
156 { |
|
157 if (cairo_scaled_font_status(mScaledFont) != CAIRO_STATUS_SUCCESS) { |
|
158 // Don't cairo_set_scaled_font as that would propagate the error to |
|
159 // the cairo_t, precluding any further drawing. |
|
160 return false; |
|
161 } |
|
162 cairo_set_scaled_font(aContext->GetCairo(), mScaledFont); |
|
163 return true; |
|
164 } |
|
165 |
|
166 gfxFont::RunMetrics |
|
167 gfxMacFont::Measure(gfxTextRun *aTextRun, |
|
168 uint32_t aStart, uint32_t aEnd, |
|
169 BoundingBoxType aBoundingBoxType, |
|
170 gfxContext *aRefContext, |
|
171 Spacing *aSpacing) |
|
172 { |
|
173 gfxFont::RunMetrics metrics = |
|
174 gfxFont::Measure(aTextRun, aStart, aEnd, |
|
175 aBoundingBoxType, aRefContext, aSpacing); |
|
176 |
|
177 // if aBoundingBoxType is not TIGHT_HINTED_OUTLINE_EXTENTS then we need to add |
|
178 // a pixel column each side of the bounding box in case of antialiasing "bleed" |
|
179 if (aBoundingBoxType != TIGHT_HINTED_OUTLINE_EXTENTS && |
|
180 metrics.mBoundingBox.width > 0) { |
|
181 metrics.mBoundingBox.x -= aTextRun->GetAppUnitsPerDevUnit(); |
|
182 metrics.mBoundingBox.width += aTextRun->GetAppUnitsPerDevUnit() * 2; |
|
183 } |
|
184 |
|
185 return metrics; |
|
186 } |
|
187 |
|
188 void |
|
189 gfxMacFont::InitMetrics() |
|
190 { |
|
191 mIsValid = false; |
|
192 ::memset(&mMetrics, 0, sizeof(mMetrics)); |
|
193 |
|
194 uint32_t upem = 0; |
|
195 |
|
196 // try to get unitsPerEm from sfnt head table, to avoid calling CGFont |
|
197 // if possible (bug 574368) and because CGFontGetUnitsPerEm does not |
|
198 // return the true value for OpenType/CFF fonts (it normalizes to 1000, |
|
199 // which then leads to metrics errors when we read the 'hmtx' table to |
|
200 // get glyph advances for HarfBuzz, see bug 580863) |
|
201 CFDataRef headData = |
|
202 ::CGFontCopyTableForTag(mCGFont, TRUETYPE_TAG('h','e','a','d')); |
|
203 if (headData) { |
|
204 if (size_t(::CFDataGetLength(headData)) >= sizeof(HeadTable)) { |
|
205 const HeadTable *head = |
|
206 reinterpret_cast<const HeadTable*>(::CFDataGetBytePtr(headData)); |
|
207 upem = head->unitsPerEm; |
|
208 } |
|
209 ::CFRelease(headData); |
|
210 } |
|
211 if (!upem) { |
|
212 upem = ::CGFontGetUnitsPerEm(mCGFont); |
|
213 } |
|
214 |
|
215 if (upem < 16 || upem > 16384) { |
|
216 // See http://www.microsoft.com/typography/otspec/head.htm |
|
217 #ifdef DEBUG |
|
218 char warnBuf[1024]; |
|
219 sprintf(warnBuf, "Bad font metrics for: %s (invalid unitsPerEm value)", |
|
220 NS_ConvertUTF16toUTF8(mFontEntry->Name()).get()); |
|
221 NS_WARNING(warnBuf); |
|
222 #endif |
|
223 return; |
|
224 } |
|
225 |
|
226 mAdjustedSize = std::max(mStyle.size, 1.0); |
|
227 mFUnitsConvFactor = mAdjustedSize / upem; |
|
228 |
|
229 // For CFF fonts, when scaling values read from CGFont* APIs, we need to |
|
230 // use CG's idea of unitsPerEm, which may differ from the "true" value in |
|
231 // the head table of the font (see bug 580863) |
|
232 gfxFloat cgConvFactor; |
|
233 if (static_cast<MacOSFontEntry*>(mFontEntry.get())->IsCFF()) { |
|
234 cgConvFactor = mAdjustedSize / ::CGFontGetUnitsPerEm(mCGFont); |
|
235 } else { |
|
236 cgConvFactor = mFUnitsConvFactor; |
|
237 } |
|
238 |
|
239 // Try to read 'sfnt' metrics; for local, non-sfnt fonts ONLY, fall back to |
|
240 // platform APIs. The InitMetrics...() functions will set mIsValid on success. |
|
241 if (!InitMetricsFromSfntTables(mMetrics) && |
|
242 (!mFontEntry->IsUserFont() || mFontEntry->IsLocalUserFont())) { |
|
243 InitMetricsFromPlatform(); |
|
244 } |
|
245 if (!mIsValid) { |
|
246 return; |
|
247 } |
|
248 |
|
249 if (mMetrics.xHeight == 0.0) { |
|
250 mMetrics.xHeight = ::CGFontGetXHeight(mCGFont) * cgConvFactor; |
|
251 } |
|
252 |
|
253 if (mStyle.sizeAdjust != 0.0 && mStyle.size > 0.0 && |
|
254 mMetrics.xHeight > 0.0) { |
|
255 // apply font-size-adjust, and recalculate metrics |
|
256 gfxFloat aspect = mMetrics.xHeight / mStyle.size; |
|
257 mAdjustedSize = mStyle.GetAdjustedSize(aspect); |
|
258 mFUnitsConvFactor = mAdjustedSize / upem; |
|
259 if (static_cast<MacOSFontEntry*>(mFontEntry.get())->IsCFF()) { |
|
260 cgConvFactor = mAdjustedSize / ::CGFontGetUnitsPerEm(mCGFont); |
|
261 } else { |
|
262 cgConvFactor = mFUnitsConvFactor; |
|
263 } |
|
264 mMetrics.xHeight = 0.0; |
|
265 if (!InitMetricsFromSfntTables(mMetrics) && |
|
266 (!mFontEntry->IsUserFont() || mFontEntry->IsLocalUserFont())) { |
|
267 InitMetricsFromPlatform(); |
|
268 } |
|
269 if (!mIsValid) { |
|
270 // this shouldn't happen, as we succeeded earlier before applying |
|
271 // the size-adjust factor! But check anyway, for paranoia's sake. |
|
272 return; |
|
273 } |
|
274 if (mMetrics.xHeight == 0.0) { |
|
275 mMetrics.xHeight = ::CGFontGetXHeight(mCGFont) * cgConvFactor; |
|
276 } |
|
277 } |
|
278 |
|
279 // Once we reach here, we've got basic metrics and set mIsValid = TRUE; |
|
280 // there should be no further points of actual failure in InitMetrics(). |
|
281 // (If one is introduced, be sure to reset mIsValid to FALSE!) |
|
282 |
|
283 mMetrics.emHeight = mAdjustedSize; |
|
284 |
|
285 // Measure/calculate additional metrics, independent of whether we used |
|
286 // the tables directly or ATS metrics APIs |
|
287 |
|
288 CFDataRef cmap = |
|
289 ::CGFontCopyTableForTag(mCGFont, TRUETYPE_TAG('c','m','a','p')); |
|
290 |
|
291 uint32_t glyphID; |
|
292 if (mMetrics.aveCharWidth <= 0) { |
|
293 mMetrics.aveCharWidth = GetCharWidth(cmap, 'x', &glyphID, |
|
294 cgConvFactor); |
|
295 if (glyphID == 0) { |
|
296 // we didn't find 'x', so use maxAdvance rather than zero |
|
297 mMetrics.aveCharWidth = mMetrics.maxAdvance; |
|
298 } |
|
299 } |
|
300 if (IsSyntheticBold()) { |
|
301 mMetrics.aveCharWidth += GetSyntheticBoldOffset(); |
|
302 mMetrics.maxAdvance += GetSyntheticBoldOffset(); |
|
303 } |
|
304 |
|
305 mMetrics.spaceWidth = GetCharWidth(cmap, ' ', &glyphID, cgConvFactor); |
|
306 if (glyphID == 0) { |
|
307 // no space glyph?! |
|
308 mMetrics.spaceWidth = mMetrics.aveCharWidth; |
|
309 } |
|
310 mSpaceGlyph = glyphID; |
|
311 |
|
312 mMetrics.zeroOrAveCharWidth = GetCharWidth(cmap, '0', &glyphID, |
|
313 cgConvFactor); |
|
314 if (glyphID == 0) { |
|
315 mMetrics.zeroOrAveCharWidth = mMetrics.aveCharWidth; |
|
316 } |
|
317 |
|
318 if (cmap) { |
|
319 ::CFRelease(cmap); |
|
320 } |
|
321 |
|
322 CalculateDerivedMetrics(mMetrics); |
|
323 |
|
324 SanitizeMetrics(&mMetrics, mFontEntry->mIsBadUnderlineFont); |
|
325 |
|
326 #if 0 |
|
327 fprintf (stderr, "Font: %p (%s) size: %f\n", this, |
|
328 NS_ConvertUTF16toUTF8(GetName()).get(), mStyle.size); |
|
329 // fprintf (stderr, " fbounds.origin.x %f y %f size.width %f height %f\n", fbounds.origin.x, fbounds.origin.y, fbounds.size.width, fbounds.size.height); |
|
330 fprintf (stderr, " emHeight: %f emAscent: %f emDescent: %f\n", mMetrics.emHeight, mMetrics.emAscent, mMetrics.emDescent); |
|
331 fprintf (stderr, " maxAscent: %f maxDescent: %f maxAdvance: %f\n", mMetrics.maxAscent, mMetrics.maxDescent, mMetrics.maxAdvance); |
|
332 fprintf (stderr, " internalLeading: %f externalLeading: %f\n", mMetrics.internalLeading, mMetrics.externalLeading); |
|
333 fprintf (stderr, " spaceWidth: %f aveCharWidth: %f xHeight: %f\n", mMetrics.spaceWidth, mMetrics.aveCharWidth, mMetrics.xHeight); |
|
334 fprintf (stderr, " uOff: %f uSize: %f stOff: %f stSize: %f supOff: %f subOff: %f\n", mMetrics.underlineOffset, mMetrics.underlineSize, mMetrics.strikeoutOffset, mMetrics.strikeoutSize, mMetrics.superscriptOffset, mMetrics.subscriptOffset); |
|
335 #endif |
|
336 } |
|
337 |
|
338 gfxFloat |
|
339 gfxMacFont::GetCharWidth(CFDataRef aCmap, char16_t aUniChar, |
|
340 uint32_t *aGlyphID, gfxFloat aConvFactor) |
|
341 { |
|
342 CGGlyph glyph = 0; |
|
343 |
|
344 if (aCmap) { |
|
345 glyph = gfxFontUtils::MapCharToGlyph(::CFDataGetBytePtr(aCmap), |
|
346 ::CFDataGetLength(aCmap), |
|
347 aUniChar); |
|
348 } |
|
349 |
|
350 if (aGlyphID) { |
|
351 *aGlyphID = glyph; |
|
352 } |
|
353 |
|
354 if (glyph) { |
|
355 int advance; |
|
356 if (::CGFontGetGlyphAdvances(mCGFont, &glyph, 1, &advance)) { |
|
357 return advance * aConvFactor; |
|
358 } |
|
359 } |
|
360 |
|
361 return 0; |
|
362 } |
|
363 |
|
364 // Try to initialize font metrics via platform APIs (CG/CT), |
|
365 // and set mIsValid = TRUE on success. |
|
366 // We ONLY call this for local (platform) fonts that are not sfnt format; |
|
367 // for sfnts, including ALL downloadable fonts, we prefer to use |
|
368 // InitMetricsFromSfntTables and avoid platform APIs. |
|
369 void |
|
370 gfxMacFont::InitMetricsFromPlatform() |
|
371 { |
|
372 CTFontRef ctFont = ::CTFontCreateWithGraphicsFont(mCGFont, |
|
373 mAdjustedSize, |
|
374 nullptr, nullptr); |
|
375 if (!ctFont) { |
|
376 return; |
|
377 } |
|
378 |
|
379 mMetrics.underlineOffset = ::CTFontGetUnderlinePosition(ctFont); |
|
380 mMetrics.underlineSize = ::CTFontGetUnderlineThickness(ctFont); |
|
381 |
|
382 mMetrics.externalLeading = ::CTFontGetLeading(ctFont); |
|
383 |
|
384 mMetrics.maxAscent = ::CTFontGetAscent(ctFont); |
|
385 mMetrics.maxDescent = ::CTFontGetDescent(ctFont); |
|
386 |
|
387 // this is not strictly correct, but neither CTFont nor CGFont seems to |
|
388 // provide maxAdvance, unless we were to iterate over all the glyphs |
|
389 // (which isn't worth the cost here) |
|
390 CGRect r = ::CTFontGetBoundingBox(ctFont); |
|
391 mMetrics.maxAdvance = r.size.width; |
|
392 |
|
393 // aveCharWidth is also not provided, so leave it at zero |
|
394 // (fallback code in gfxMacFont::InitMetrics will then try measuring 'x'); |
|
395 // this could lead to less-than-"perfect" text field sizing when width is |
|
396 // specified as a number of characters, and the font in use is a non-sfnt |
|
397 // legacy font, but that's a sufficiently obscure edge case that we can |
|
398 // ignore the potential discrepancy. |
|
399 mMetrics.aveCharWidth = 0; |
|
400 |
|
401 mMetrics.xHeight = ::CTFontGetXHeight(ctFont); |
|
402 |
|
403 ::CFRelease(ctFont); |
|
404 |
|
405 mIsValid = true; |
|
406 } |
|
407 |
|
408 TemporaryRef<ScaledFont> |
|
409 gfxMacFont::GetScaledFont(DrawTarget *aTarget) |
|
410 { |
|
411 if (!mAzureScaledFont) { |
|
412 NativeFont nativeFont; |
|
413 nativeFont.mType = NativeFontType::MAC_FONT_FACE; |
|
414 nativeFont.mFont = GetCGFontRef(); |
|
415 mAzureScaledFont = mozilla::gfx::Factory::CreateScaledFontWithCairo(nativeFont, GetAdjustedSize(), mScaledFont); |
|
416 } |
|
417 |
|
418 return mAzureScaledFont; |
|
419 } |
|
420 |
|
421 void |
|
422 gfxMacFont::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, |
|
423 FontCacheSizes* aSizes) const |
|
424 { |
|
425 gfxFont::AddSizeOfExcludingThis(aMallocSizeOf, aSizes); |
|
426 // mCGFont is shared with the font entry, so not counted here; |
|
427 // and we don't have APIs to measure the cairo mFontFace object |
|
428 } |
|
429 |
|
430 void |
|
431 gfxMacFont::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, |
|
432 FontCacheSizes* aSizes) const |
|
433 { |
|
434 aSizes->mFontInstances += aMallocSizeOf(this); |
|
435 AddSizeOfExcludingThis(aMallocSizeOf, aSizes); |
|
436 } |