gfx/thebes/gfxMacFont.cpp

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:6766674f19d5
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 }

mercurial