gfx/thebes/gfxGDIFont.cpp

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:caad817ed501
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 "gfxGDIFont.h"
7
8 #include "mozilla/MemoryReporting.h"
9 #include "mozilla/WindowsVersion.h"
10
11 #include "gfxGDIShaper.h"
12 #include "gfxUniscribeShaper.h"
13 #include "gfxHarfBuzzShaper.h"
14 #include <algorithm>
15 #include "gfxGraphiteShaper.h"
16 #include "gfxWindowsPlatform.h"
17 #include "gfxContext.h"
18 #include "mozilla/Preferences.h"
19 #include "nsUnicodeProperties.h"
20 #include "gfxFontConstants.h"
21
22 #include "cairo-win32.h"
23
24 #define ROUND(x) floor((x) + 0.5)
25
26 using namespace mozilla;
27 using namespace mozilla::unicode;
28
29 static inline cairo_antialias_t
30 GetCairoAntialiasOption(gfxFont::AntialiasOption anAntialiasOption)
31 {
32 switch (anAntialiasOption) {
33 default:
34 case gfxFont::kAntialiasDefault:
35 return CAIRO_ANTIALIAS_DEFAULT;
36 case gfxFont::kAntialiasNone:
37 return CAIRO_ANTIALIAS_NONE;
38 case gfxFont::kAntialiasGrayscale:
39 return CAIRO_ANTIALIAS_GRAY;
40 case gfxFont::kAntialiasSubpixel:
41 return CAIRO_ANTIALIAS_SUBPIXEL;
42 }
43 }
44
45 gfxGDIFont::gfxGDIFont(GDIFontEntry *aFontEntry,
46 const gfxFontStyle *aFontStyle,
47 bool aNeedsBold,
48 AntialiasOption anAAOption)
49 : gfxFont(aFontEntry, aFontStyle, anAAOption),
50 mFont(nullptr),
51 mFontFace(nullptr),
52 mMetrics(nullptr),
53 mSpaceGlyph(0),
54 mNeedsBold(aNeedsBold)
55 {
56 if (FontCanSupportGraphite()) {
57 mGraphiteShaper = new gfxGraphiteShaper(this);
58 }
59 if (FontCanSupportHarfBuzz()) {
60 mHarfBuzzShaper = new gfxHarfBuzzShaper(this);
61 }
62 }
63
64 gfxGDIFont::~gfxGDIFont()
65 {
66 if (mScaledFont) {
67 cairo_scaled_font_destroy(mScaledFont);
68 }
69 if (mFontFace) {
70 cairo_font_face_destroy(mFontFace);
71 }
72 if (mFont) {
73 ::DeleteObject(mFont);
74 }
75 delete mMetrics;
76 }
77
78 void
79 gfxGDIFont::CreatePlatformShaper()
80 {
81 mPlatformShaper = new gfxGDIShaper(this);
82 }
83
84 gfxFont*
85 gfxGDIFont::CopyWithAntialiasOption(AntialiasOption anAAOption)
86 {
87 return new gfxGDIFont(static_cast<GDIFontEntry*>(mFontEntry.get()),
88 &mStyle, mNeedsBold, anAAOption);
89 }
90
91 static bool
92 UseUniscribe(gfxShapedText *aShapedText,
93 char16ptr_t aText,
94 uint32_t aLength)
95 {
96 uint32_t flags = aShapedText->Flags();
97 bool useGDI;
98
99 bool isXP = !IsVistaOrLater();
100
101 // bug 561304 - Uniscribe bug produces bad positioning at certain
102 // font sizes on XP, so default to GDI on XP using logic of 3.6
103
104 useGDI = isXP &&
105 (flags &
106 (gfxTextRunFactory::TEXT_OPTIMIZE_SPEED |
107 gfxTextRunFactory::TEXT_IS_RTL)
108 ) == gfxTextRunFactory::TEXT_OPTIMIZE_SPEED;
109
110 return !useGDI ||
111 ScriptIsComplex(aText, aLength, SIC_COMPLEX) == S_OK;
112 }
113
114 bool
115 gfxGDIFont::ShapeText(gfxContext *aContext,
116 const char16_t *aText,
117 uint32_t aOffset,
118 uint32_t aLength,
119 int32_t aScript,
120 gfxShapedText *aShapedText,
121 bool aPreferPlatformShaping)
122 {
123 if (!mMetrics) {
124 Initialize();
125 }
126 if (!mIsValid) {
127 NS_WARNING("invalid font! expect incorrect text rendering");
128 return false;
129 }
130
131 bool ok = false;
132
133 // Ensure the cairo font is set up, so there's no risk it'll fall back to
134 // creating a "toy" font internally (see bug 544617).
135 // We must check that this succeeded, otherwise we risk cairo creating the
136 // wrong kind of font internally as a fallback (bug 744480).
137 if (!SetupCairoFont(aContext)) {
138 return false;
139 }
140
141 if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
142 ok = mGraphiteShaper->ShapeText(aContext, aText,
143 aOffset, aLength,
144 aScript, aShapedText);
145 }
146
147 if (!ok && mHarfBuzzShaper) {
148 if (gfxPlatform::GetPlatform()->UseHarfBuzzForScript(aScript) ||
149 (!IsVistaOrLater() &&
150 ScriptShapingType(aScript) == SHAPING_INDIC &&
151 !Preferences::GetBool("gfx.font_rendering.winxp-indic-uniscribe",
152 false))) {
153 ok = mHarfBuzzShaper->ShapeText(aContext, aText, aOffset, aLength,
154 aScript, aShapedText);
155 }
156 }
157
158 if (!ok) {
159 GDIFontEntry *fe = static_cast<GDIFontEntry*>(GetFontEntry());
160 bool preferUniscribe =
161 (!fe->IsTrueType() || fe->IsSymbolFont()) && !fe->mForceGDI;
162
163 if (preferUniscribe || UseUniscribe(aShapedText, aText, aLength)) {
164 // first try Uniscribe
165 if (!mUniscribeShaper) {
166 mUniscribeShaper = new gfxUniscribeShaper(this);
167 }
168
169 ok = mUniscribeShaper->ShapeText(aContext, aText, aOffset, aLength,
170 aScript, aShapedText);
171 if (!ok) {
172 // fallback to GDI shaping
173 if (!mPlatformShaper) {
174 CreatePlatformShaper();
175 }
176
177 ok = mPlatformShaper->ShapeText(aContext, aText, aOffset,
178 aLength, aScript, aShapedText);
179 }
180 } else {
181 // first use GDI
182 if (!mPlatformShaper) {
183 CreatePlatformShaper();
184 }
185
186 ok = mPlatformShaper->ShapeText(aContext, aText, aOffset, aLength,
187 aScript, aShapedText);
188 if (!ok) {
189 // try Uniscribe if GDI failed
190 if (!mUniscribeShaper) {
191 mUniscribeShaper = new gfxUniscribeShaper(this);
192 }
193
194 // use Uniscribe shaping
195 ok = mUniscribeShaper->ShapeText(aContext, aText,
196 aOffset, aLength,
197 aScript, aShapedText);
198 }
199 }
200
201 #if DEBUG
202 if (!ok) {
203 NS_ConvertUTF16toUTF8 name(GetName());
204 char msg[256];
205
206 sprintf(msg,
207 "text shaping with both uniscribe and GDI failed for"
208 " font: %s",
209 name.get());
210 NS_WARNING(msg);
211 }
212 #endif
213 }
214
215 PostShapingFixup(aContext, aText, aOffset, aLength, aShapedText);
216
217 return ok;
218 }
219
220 const gfxFont::Metrics&
221 gfxGDIFont::GetMetrics()
222 {
223 if (!mMetrics) {
224 Initialize();
225 }
226 return *mMetrics;
227 }
228
229 uint32_t
230 gfxGDIFont::GetSpaceGlyph()
231 {
232 if (!mMetrics) {
233 Initialize();
234 }
235 return mSpaceGlyph;
236 }
237
238 bool
239 gfxGDIFont::SetupCairoFont(gfxContext *aContext)
240 {
241 if (!mMetrics) {
242 Initialize();
243 }
244 if (!mScaledFont ||
245 cairo_scaled_font_status(mScaledFont) != CAIRO_STATUS_SUCCESS) {
246 // Don't cairo_set_scaled_font as that would propagate the error to
247 // the cairo_t, precluding any further drawing.
248 return false;
249 }
250 cairo_set_scaled_font(aContext->GetCairo(), mScaledFont);
251 return true;
252 }
253
254 gfxFont::RunMetrics
255 gfxGDIFont::Measure(gfxTextRun *aTextRun,
256 uint32_t aStart, uint32_t aEnd,
257 BoundingBoxType aBoundingBoxType,
258 gfxContext *aRefContext,
259 Spacing *aSpacing)
260 {
261 gfxFont::RunMetrics metrics =
262 gfxFont::Measure(aTextRun, aStart, aEnd,
263 aBoundingBoxType, aRefContext, aSpacing);
264
265 // if aBoundingBoxType is LOOSE_INK_EXTENTS
266 // and the underlying cairo font may be antialiased,
267 // we can't trust Windows to have considered all the pixels
268 // so we need to add "padding" to the bounds.
269 // (see bugs 475968, 439831, compare also bug 445087)
270 if (aBoundingBoxType == LOOSE_INK_EXTENTS &&
271 mAntialiasOption != kAntialiasNone &&
272 metrics.mBoundingBox.width > 0) {
273 metrics.mBoundingBox.x -= aTextRun->GetAppUnitsPerDevUnit();
274 metrics.mBoundingBox.width += aTextRun->GetAppUnitsPerDevUnit() * 3;
275 }
276
277 return metrics;
278 }
279
280 #define OBLIQUE_SKEW_FACTOR 0.3
281
282 void
283 gfxGDIFont::Initialize()
284 {
285 NS_ASSERTION(!mMetrics, "re-creating metrics? this will leak");
286
287 LOGFONTW logFont;
288
289 // Figure out if we want to do synthetic oblique styling.
290 GDIFontEntry* fe = static_cast<GDIFontEntry*>(GetFontEntry());
291 bool wantFakeItalic =
292 (mStyle.style & (NS_FONT_STYLE_ITALIC | NS_FONT_STYLE_OBLIQUE)) &&
293 !fe->IsItalic();
294
295 // If the font's family has an actual italic face (but font matching
296 // didn't choose it), we have to use a cairo transform instead of asking
297 // GDI to italicize, because that would use a different face and result
298 // in a possible glyph ID mismatch between shaping and rendering.
299 //
300 // We use the mFamilyHasItalicFace flag in the entry in case of user fonts,
301 // where the *CSS* family may not know about italic faces that are present
302 // in the *GDI* family, and which GDI would use if we asked it to perform
303 // the "italicization".
304 bool useCairoFakeItalic = wantFakeItalic && fe->mFamilyHasItalicFace;
305
306 if (mAdjustedSize == 0.0) {
307 mAdjustedSize = mStyle.size;
308 if (mStyle.sizeAdjust != 0.0 && mAdjustedSize > 0.0) {
309 // to implement font-size-adjust, we first create the "unadjusted" font
310 FillLogFont(logFont, mAdjustedSize,
311 wantFakeItalic && !useCairoFakeItalic);
312 mFont = ::CreateFontIndirectW(&logFont);
313
314 // initialize its metrics so we can calculate size adjustment
315 Initialize();
316
317 // calculate the properly adjusted size, and then proceed
318 // to recreate mFont and recalculate metrics
319 gfxFloat aspect = mMetrics->xHeight / mMetrics->emHeight;
320 mAdjustedSize = mStyle.GetAdjustedSize(aspect);
321
322 // delete the temporary font and metrics
323 ::DeleteObject(mFont);
324 mFont = nullptr;
325 delete mMetrics;
326 mMetrics = nullptr;
327 }
328 }
329
330 // (bug 724231) for local user fonts, we don't use GDI's synthetic bold,
331 // as it could lead to a different, incompatible face being used
332 // but instead do our own multi-striking
333 if (mNeedsBold && GetFontEntry()->IsLocalUserFont()) {
334 mApplySyntheticBold = true;
335 }
336
337 // this may end up being zero
338 mAdjustedSize = ROUND(mAdjustedSize);
339 FillLogFont(logFont, mAdjustedSize, wantFakeItalic && !useCairoFakeItalic);
340 mFont = ::CreateFontIndirectW(&logFont);
341
342 mMetrics = new gfxFont::Metrics;
343 ::memset(mMetrics, 0, sizeof(*mMetrics));
344
345 AutoDC dc;
346 SetGraphicsMode(dc.GetDC(), GM_ADVANCED);
347 AutoSelectFont selectFont(dc.GetDC(), mFont);
348
349 // Get font metrics if size > 0
350 if (mAdjustedSize > 0.0) {
351
352 OUTLINETEXTMETRIC oMetrics;
353 TEXTMETRIC& metrics = oMetrics.otmTextMetrics;
354
355 if (0 < GetOutlineTextMetrics(dc.GetDC(), sizeof(oMetrics), &oMetrics)) {
356 mMetrics->superscriptOffset = (double)oMetrics.otmptSuperscriptOffset.y;
357 // Some fonts have wrong sign on their subscript offset, bug 410917.
358 mMetrics->subscriptOffset = fabs((double)oMetrics.otmptSubscriptOffset.y);
359 mMetrics->strikeoutSize = (double)oMetrics.otmsStrikeoutSize;
360 mMetrics->strikeoutOffset = (double)oMetrics.otmsStrikeoutPosition;
361 mMetrics->underlineSize = (double)oMetrics.otmsUnderscoreSize;
362 mMetrics->underlineOffset = (double)oMetrics.otmsUnderscorePosition;
363
364 const MAT2 kIdentityMatrix = { {0, 1}, {0, 0}, {0, 0}, {0, 1} };
365 GLYPHMETRICS gm;
366 DWORD len = GetGlyphOutlineW(dc.GetDC(), char16_t('x'), GGO_METRICS, &gm, 0, nullptr, &kIdentityMatrix);
367 if (len == GDI_ERROR || gm.gmptGlyphOrigin.y <= 0) {
368 // 56% of ascent, best guess for true type
369 mMetrics->xHeight =
370 ROUND((double)metrics.tmAscent * DEFAULT_XHEIGHT_FACTOR);
371 } else {
372 mMetrics->xHeight = gm.gmptGlyphOrigin.y;
373 }
374 mMetrics->emHeight = metrics.tmHeight - metrics.tmInternalLeading;
375 gfxFloat typEmHeight = (double)oMetrics.otmAscent - (double)oMetrics.otmDescent;
376 mMetrics->emAscent = ROUND(mMetrics->emHeight * (double)oMetrics.otmAscent / typEmHeight);
377 mMetrics->emDescent = mMetrics->emHeight - mMetrics->emAscent;
378 if (oMetrics.otmEMSquare > 0) {
379 mFUnitsConvFactor = float(mAdjustedSize / oMetrics.otmEMSquare);
380 }
381 } else {
382 // Make a best-effort guess at extended metrics
383 // this is based on general typographic guidelines
384
385 // GetTextMetrics can fail if the font file has been removed
386 // or corrupted recently.
387 BOOL result = GetTextMetrics(dc.GetDC(), &metrics);
388 if (!result) {
389 NS_WARNING("Missing or corrupt font data, fasten your seatbelt");
390 mIsValid = false;
391 memset(mMetrics, 0, sizeof(*mMetrics));
392 return;
393 }
394
395 mMetrics->xHeight =
396 ROUND((float)metrics.tmAscent * DEFAULT_XHEIGHT_FACTOR);
397 mMetrics->superscriptOffset = mMetrics->xHeight;
398 mMetrics->subscriptOffset = mMetrics->xHeight;
399 mMetrics->strikeoutSize = 1;
400 mMetrics->strikeoutOffset = ROUND(mMetrics->xHeight * 0.5f); // 50% of xHeight
401 mMetrics->underlineSize = 1;
402 mMetrics->underlineOffset = -ROUND((float)metrics.tmDescent * 0.30f); // 30% of descent
403 mMetrics->emHeight = metrics.tmHeight - metrics.tmInternalLeading;
404 mMetrics->emAscent = metrics.tmAscent - metrics.tmInternalLeading;
405 mMetrics->emDescent = metrics.tmDescent;
406 }
407
408 mMetrics->internalLeading = metrics.tmInternalLeading;
409 mMetrics->externalLeading = metrics.tmExternalLeading;
410 mMetrics->maxHeight = metrics.tmHeight;
411 mMetrics->maxAscent = metrics.tmAscent;
412 mMetrics->maxDescent = metrics.tmDescent;
413 mMetrics->maxAdvance = metrics.tmMaxCharWidth;
414 mMetrics->aveCharWidth = std::max<gfxFloat>(1, metrics.tmAveCharWidth);
415 // The font is monospace when TMPF_FIXED_PITCH is *not* set!
416 // See http://msdn2.microsoft.com/en-us/library/ms534202(VS.85).aspx
417 if (!(metrics.tmPitchAndFamily & TMPF_FIXED_PITCH)) {
418 mMetrics->maxAdvance = mMetrics->aveCharWidth;
419 }
420
421 // Cache the width of a single space.
422 SIZE size;
423 GetTextExtentPoint32W(dc.GetDC(), L" ", 1, &size);
424 mMetrics->spaceWidth = ROUND(size.cx);
425
426 // Cache the width of digit zero.
427 // XXX MSDN (http://msdn.microsoft.com/en-us/library/ms534223.aspx)
428 // does not say what the failure modes for GetTextExtentPoint32 are -
429 // is it safe to assume it will fail iff the font has no '0'?
430 if (GetTextExtentPoint32W(dc.GetDC(), L"0", 1, &size)) {
431 mMetrics->zeroOrAveCharWidth = ROUND(size.cx);
432 } else {
433 mMetrics->zeroOrAveCharWidth = mMetrics->aveCharWidth;
434 }
435
436 WORD glyph;
437 DWORD ret = GetGlyphIndicesW(dc.GetDC(), L" ", 1, &glyph,
438 GGI_MARK_NONEXISTING_GLYPHS);
439 if (ret != GDI_ERROR && glyph != 0xFFFF) {
440 mSpaceGlyph = glyph;
441 }
442
443 SanitizeMetrics(mMetrics, GetFontEntry()->mIsBadUnderlineFont);
444 }
445
446 if (IsSyntheticBold()) {
447 mMetrics->aveCharWidth += GetSyntheticBoldOffset();
448 mMetrics->maxAdvance += GetSyntheticBoldOffset();
449 }
450
451 mFontFace = cairo_win32_font_face_create_for_logfontw_hfont(&logFont,
452 mFont);
453
454 cairo_matrix_t sizeMatrix, ctm;
455 cairo_matrix_init_identity(&ctm);
456 cairo_matrix_init_scale(&sizeMatrix, mAdjustedSize, mAdjustedSize);
457
458 if (useCairoFakeItalic) {
459 // Skew the matrix to do fake italic if it wasn't already applied
460 // via the LOGFONT
461 double skewfactor = OBLIQUE_SKEW_FACTOR;
462 cairo_matrix_t style;
463 cairo_matrix_init(&style,
464 1, //xx
465 0, //yx
466 -1 * skewfactor, //xy
467 1, //yy
468 0, //x0
469 0); //y0
470 cairo_matrix_multiply(&sizeMatrix, &sizeMatrix, &style);
471 }
472
473 cairo_font_options_t *fontOptions = cairo_font_options_create();
474 if (mAntialiasOption != kAntialiasDefault) {
475 cairo_font_options_set_antialias(fontOptions,
476 GetCairoAntialiasOption(mAntialiasOption));
477 }
478 mScaledFont = cairo_scaled_font_create(mFontFace, &sizeMatrix,
479 &ctm, fontOptions);
480 cairo_font_options_destroy(fontOptions);
481
482 if (!mScaledFont ||
483 cairo_scaled_font_status(mScaledFont) != CAIRO_STATUS_SUCCESS) {
484 #ifdef DEBUG
485 char warnBuf[1024];
486 sprintf(warnBuf, "Failed to create scaled font: %s status: %d",
487 NS_ConvertUTF16toUTF8(mFontEntry->Name()).get(),
488 mScaledFont ? cairo_scaled_font_status(mScaledFont) : 0);
489 NS_WARNING(warnBuf);
490 #endif
491 mIsValid = false;
492 } else {
493 mIsValid = true;
494 }
495
496 #if 0
497 printf("Font: %p (%s) size: %f adjusted size: %f valid: %s\n", this,
498 NS_ConvertUTF16toUTF8(GetName()).get(), mStyle.size, mAdjustedSize, (mIsValid ? "yes" : "no"));
499 printf(" emHeight: %f emAscent: %f emDescent: %f\n", mMetrics->emHeight, mMetrics->emAscent, mMetrics->emDescent);
500 printf(" maxAscent: %f maxDescent: %f maxAdvance: %f\n", mMetrics->maxAscent, mMetrics->maxDescent, mMetrics->maxAdvance);
501 printf(" internalLeading: %f externalLeading: %f\n", mMetrics->internalLeading, mMetrics->externalLeading);
502 printf(" spaceWidth: %f aveCharWidth: %f xHeight: %f\n", mMetrics->spaceWidth, mMetrics->aveCharWidth, mMetrics->xHeight);
503 printf(" uOff: %f uSize: %f stOff: %f stSize: %f supOff: %f subOff: %f\n",
504 mMetrics->underlineOffset, mMetrics->underlineSize, mMetrics->strikeoutOffset, mMetrics->strikeoutSize,
505 mMetrics->superscriptOffset, mMetrics->subscriptOffset);
506 #endif
507 }
508
509 void
510 gfxGDIFont::FillLogFont(LOGFONTW& aLogFont, gfxFloat aSize,
511 bool aUseGDIFakeItalic)
512 {
513 GDIFontEntry *fe = static_cast<GDIFontEntry*>(GetFontEntry());
514
515 uint16_t weight;
516 if (fe->IsUserFont()) {
517 if (fe->IsLocalUserFont()) {
518 // for local user fonts, don't change the original weight
519 // in the entry's logfont, because that could alter the
520 // choice of actual face used (bug 724231)
521 weight = 0;
522 } else {
523 // avoid GDI synthetic bold which occurs when weight
524 // specified is >= font data weight + 200
525 weight = mNeedsBold ? 700 : 200;
526 }
527 } else {
528 weight = mNeedsBold ? 700 : fe->Weight();
529 }
530
531 fe->FillLogFont(&aLogFont, weight, aSize,
532 (mAntialiasOption == kAntialiasSubpixel) ? true : false);
533
534 // If GDI synthetic italic is wanted, force the lfItalic field to true
535 if (aUseGDIFakeItalic) {
536 aLogFont.lfItalic = 1;
537 }
538 }
539
540 int32_t
541 gfxGDIFont::GetGlyphWidth(gfxContext *aCtx, uint16_t aGID)
542 {
543 if (!mGlyphWidths) {
544 mGlyphWidths = new nsDataHashtable<nsUint32HashKey,int32_t>(200);
545 }
546
547 int32_t width;
548 if (mGlyphWidths->Get(aGID, &width)) {
549 return width;
550 }
551
552 DCFromContext dc(aCtx);
553 AutoSelectFont fs(dc, GetHFONT());
554
555 int devWidth;
556 if (GetCharWidthI(dc, aGID, 1, nullptr, &devWidth)) {
557 // ensure width is positive, 16.16 fixed-point value
558 width = (devWidth & 0x7fff) << 16;
559 mGlyphWidths->Put(aGID, width);
560 return width;
561 }
562
563 return -1;
564 }
565
566 void
567 gfxGDIFont::AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
568 FontCacheSizes* aSizes) const
569 {
570 gfxFont::AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
571 aSizes->mFontInstances += aMallocSizeOf(mMetrics);
572 if (mGlyphWidths) {
573 aSizes->mFontInstances +=
574 mGlyphWidths->SizeOfExcludingThis(nullptr, aMallocSizeOf);
575 }
576 }
577
578 void
579 gfxGDIFont::AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
580 FontCacheSizes* aSizes) const
581 {
582 aSizes->mFontInstances += aMallocSizeOf(this);
583 AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
584 }

mercurial