1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/gfx/thebes/gfxMacFont.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,436 @@ 1.4 +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- 1.5 + * This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "gfxMacFont.h" 1.10 + 1.11 +#include "mozilla/MemoryReporting.h" 1.12 + 1.13 +#include "gfxCoreTextShaper.h" 1.14 +#include "gfxHarfBuzzShaper.h" 1.15 +#include <algorithm> 1.16 +#include "gfxGraphiteShaper.h" 1.17 +#include "gfxPlatformMac.h" 1.18 +#include "gfxContext.h" 1.19 +#include "gfxFontUtils.h" 1.20 +#include "gfxMacPlatformFontList.h" 1.21 +#include "gfxFontConstants.h" 1.22 + 1.23 +#include "cairo-quartz.h" 1.24 + 1.25 +using namespace mozilla; 1.26 +using namespace mozilla::gfx; 1.27 + 1.28 +gfxMacFont::gfxMacFont(MacOSFontEntry *aFontEntry, const gfxFontStyle *aFontStyle, 1.29 + bool aNeedsBold) 1.30 + : gfxFont(aFontEntry, aFontStyle), 1.31 + mCGFont(nullptr), 1.32 + mFontFace(nullptr) 1.33 +{ 1.34 + mApplySyntheticBold = aNeedsBold; 1.35 + 1.36 + mCGFont = aFontEntry->GetFontRef(); 1.37 + if (!mCGFont) { 1.38 + mIsValid = false; 1.39 + return; 1.40 + } 1.41 + 1.42 + // InitMetrics will handle the sizeAdjust factor and set mAdjustedSize 1.43 + InitMetrics(); 1.44 + if (!mIsValid) { 1.45 + return; 1.46 + } 1.47 + 1.48 + mFontFace = cairo_quartz_font_face_create_for_cgfont(mCGFont); 1.49 + 1.50 + cairo_status_t cairoerr = cairo_font_face_status(mFontFace); 1.51 + if (cairoerr != CAIRO_STATUS_SUCCESS) { 1.52 + mIsValid = false; 1.53 +#ifdef DEBUG 1.54 + char warnBuf[1024]; 1.55 + sprintf(warnBuf, "Failed to create Cairo font face: %s status: %d", 1.56 + NS_ConvertUTF16toUTF8(GetName()).get(), cairoerr); 1.57 + NS_WARNING(warnBuf); 1.58 +#endif 1.59 + return; 1.60 + } 1.61 + 1.62 + cairo_matrix_t sizeMatrix, ctm; 1.63 + cairo_matrix_init_identity(&ctm); 1.64 + cairo_matrix_init_scale(&sizeMatrix, mAdjustedSize, mAdjustedSize); 1.65 + 1.66 + // synthetic oblique by skewing via the font matrix 1.67 + bool needsOblique = 1.68 + (mFontEntry != nullptr) && 1.69 + (!mFontEntry->IsItalic() && 1.70 + (mStyle.style & (NS_FONT_STYLE_ITALIC | NS_FONT_STYLE_OBLIQUE))); 1.71 + 1.72 + if (needsOblique) { 1.73 + double skewfactor = (needsOblique ? Fix2X(kATSItalicQDSkew) : 0); 1.74 + 1.75 + cairo_matrix_t style; 1.76 + cairo_matrix_init(&style, 1.77 + 1, //xx 1.78 + 0, //yx 1.79 + -1 * skewfactor, //xy 1.80 + 1, //yy 1.81 + 0, //x0 1.82 + 0); //y0 1.83 + cairo_matrix_multiply(&sizeMatrix, &sizeMatrix, &style); 1.84 + } 1.85 + 1.86 + cairo_font_options_t *fontOptions = cairo_font_options_create(); 1.87 + 1.88 + // turn off font anti-aliasing based on user pref setting 1.89 + if (mAdjustedSize <= 1.90 + (gfxFloat)gfxPlatformMac::GetPlatform()->GetAntiAliasingThreshold()) { 1.91 + cairo_font_options_set_antialias(fontOptions, CAIRO_ANTIALIAS_NONE); 1.92 + mAntialiasOption = kAntialiasNone; 1.93 + } else if (mStyle.useGrayscaleAntialiasing) { 1.94 + cairo_font_options_set_antialias(fontOptions, CAIRO_ANTIALIAS_GRAY); 1.95 + mAntialiasOption = kAntialiasGrayscale; 1.96 + } 1.97 + 1.98 + mScaledFont = cairo_scaled_font_create(mFontFace, &sizeMatrix, &ctm, 1.99 + fontOptions); 1.100 + cairo_font_options_destroy(fontOptions); 1.101 + 1.102 + cairoerr = cairo_scaled_font_status(mScaledFont); 1.103 + if (cairoerr != CAIRO_STATUS_SUCCESS) { 1.104 + mIsValid = false; 1.105 +#ifdef DEBUG 1.106 + char warnBuf[1024]; 1.107 + sprintf(warnBuf, "Failed to create scaled font: %s status: %d", 1.108 + NS_ConvertUTF16toUTF8(GetName()).get(), cairoerr); 1.109 + NS_WARNING(warnBuf); 1.110 +#endif 1.111 + } 1.112 + 1.113 + if (FontCanSupportGraphite()) { 1.114 + mGraphiteShaper = new gfxGraphiteShaper(this); 1.115 + } 1.116 + if (FontCanSupportHarfBuzz()) { 1.117 + mHarfBuzzShaper = new gfxHarfBuzzShaper(this); 1.118 + } 1.119 +} 1.120 + 1.121 +gfxMacFont::~gfxMacFont() 1.122 +{ 1.123 + if (mScaledFont) { 1.124 + cairo_scaled_font_destroy(mScaledFont); 1.125 + } 1.126 + if (mFontFace) { 1.127 + cairo_font_face_destroy(mFontFace); 1.128 + } 1.129 +} 1.130 + 1.131 +bool 1.132 +gfxMacFont::ShapeText(gfxContext *aContext, 1.133 + const char16_t *aText, 1.134 + uint32_t aOffset, 1.135 + uint32_t aLength, 1.136 + int32_t aScript, 1.137 + gfxShapedText *aShapedText, 1.138 + bool aPreferPlatformShaping) 1.139 +{ 1.140 + if (!mIsValid) { 1.141 + NS_WARNING("invalid font! expect incorrect text rendering"); 1.142 + return false; 1.143 + } 1.144 + 1.145 + bool requiresAAT = 1.146 + static_cast<MacOSFontEntry*>(GetFontEntry())->RequiresAATLayout(); 1.147 + return gfxFont::ShapeText(aContext, aText, aOffset, aLength, 1.148 + aScript, aShapedText, requiresAAT); 1.149 +} 1.150 + 1.151 +void 1.152 +gfxMacFont::CreatePlatformShaper() 1.153 +{ 1.154 + mPlatformShaper = new gfxCoreTextShaper(this); 1.155 +} 1.156 + 1.157 +bool 1.158 +gfxMacFont::SetupCairoFont(gfxContext *aContext) 1.159 +{ 1.160 + if (cairo_scaled_font_status(mScaledFont) != CAIRO_STATUS_SUCCESS) { 1.161 + // Don't cairo_set_scaled_font as that would propagate the error to 1.162 + // the cairo_t, precluding any further drawing. 1.163 + return false; 1.164 + } 1.165 + cairo_set_scaled_font(aContext->GetCairo(), mScaledFont); 1.166 + return true; 1.167 +} 1.168 + 1.169 +gfxFont::RunMetrics 1.170 +gfxMacFont::Measure(gfxTextRun *aTextRun, 1.171 + uint32_t aStart, uint32_t aEnd, 1.172 + BoundingBoxType aBoundingBoxType, 1.173 + gfxContext *aRefContext, 1.174 + Spacing *aSpacing) 1.175 +{ 1.176 + gfxFont::RunMetrics metrics = 1.177 + gfxFont::Measure(aTextRun, aStart, aEnd, 1.178 + aBoundingBoxType, aRefContext, aSpacing); 1.179 + 1.180 + // if aBoundingBoxType is not TIGHT_HINTED_OUTLINE_EXTENTS then we need to add 1.181 + // a pixel column each side of the bounding box in case of antialiasing "bleed" 1.182 + if (aBoundingBoxType != TIGHT_HINTED_OUTLINE_EXTENTS && 1.183 + metrics.mBoundingBox.width > 0) { 1.184 + metrics.mBoundingBox.x -= aTextRun->GetAppUnitsPerDevUnit(); 1.185 + metrics.mBoundingBox.width += aTextRun->GetAppUnitsPerDevUnit() * 2; 1.186 + } 1.187 + 1.188 + return metrics; 1.189 +} 1.190 + 1.191 +void 1.192 +gfxMacFont::InitMetrics() 1.193 +{ 1.194 + mIsValid = false; 1.195 + ::memset(&mMetrics, 0, sizeof(mMetrics)); 1.196 + 1.197 + uint32_t upem = 0; 1.198 + 1.199 + // try to get unitsPerEm from sfnt head table, to avoid calling CGFont 1.200 + // if possible (bug 574368) and because CGFontGetUnitsPerEm does not 1.201 + // return the true value for OpenType/CFF fonts (it normalizes to 1000, 1.202 + // which then leads to metrics errors when we read the 'hmtx' table to 1.203 + // get glyph advances for HarfBuzz, see bug 580863) 1.204 + CFDataRef headData = 1.205 + ::CGFontCopyTableForTag(mCGFont, TRUETYPE_TAG('h','e','a','d')); 1.206 + if (headData) { 1.207 + if (size_t(::CFDataGetLength(headData)) >= sizeof(HeadTable)) { 1.208 + const HeadTable *head = 1.209 + reinterpret_cast<const HeadTable*>(::CFDataGetBytePtr(headData)); 1.210 + upem = head->unitsPerEm; 1.211 + } 1.212 + ::CFRelease(headData); 1.213 + } 1.214 + if (!upem) { 1.215 + upem = ::CGFontGetUnitsPerEm(mCGFont); 1.216 + } 1.217 + 1.218 + if (upem < 16 || upem > 16384) { 1.219 + // See http://www.microsoft.com/typography/otspec/head.htm 1.220 +#ifdef DEBUG 1.221 + char warnBuf[1024]; 1.222 + sprintf(warnBuf, "Bad font metrics for: %s (invalid unitsPerEm value)", 1.223 + NS_ConvertUTF16toUTF8(mFontEntry->Name()).get()); 1.224 + NS_WARNING(warnBuf); 1.225 +#endif 1.226 + return; 1.227 + } 1.228 + 1.229 + mAdjustedSize = std::max(mStyle.size, 1.0); 1.230 + mFUnitsConvFactor = mAdjustedSize / upem; 1.231 + 1.232 + // For CFF fonts, when scaling values read from CGFont* APIs, we need to 1.233 + // use CG's idea of unitsPerEm, which may differ from the "true" value in 1.234 + // the head table of the font (see bug 580863) 1.235 + gfxFloat cgConvFactor; 1.236 + if (static_cast<MacOSFontEntry*>(mFontEntry.get())->IsCFF()) { 1.237 + cgConvFactor = mAdjustedSize / ::CGFontGetUnitsPerEm(mCGFont); 1.238 + } else { 1.239 + cgConvFactor = mFUnitsConvFactor; 1.240 + } 1.241 + 1.242 + // Try to read 'sfnt' metrics; for local, non-sfnt fonts ONLY, fall back to 1.243 + // platform APIs. The InitMetrics...() functions will set mIsValid on success. 1.244 + if (!InitMetricsFromSfntTables(mMetrics) && 1.245 + (!mFontEntry->IsUserFont() || mFontEntry->IsLocalUserFont())) { 1.246 + InitMetricsFromPlatform(); 1.247 + } 1.248 + if (!mIsValid) { 1.249 + return; 1.250 + } 1.251 + 1.252 + if (mMetrics.xHeight == 0.0) { 1.253 + mMetrics.xHeight = ::CGFontGetXHeight(mCGFont) * cgConvFactor; 1.254 + } 1.255 + 1.256 + if (mStyle.sizeAdjust != 0.0 && mStyle.size > 0.0 && 1.257 + mMetrics.xHeight > 0.0) { 1.258 + // apply font-size-adjust, and recalculate metrics 1.259 + gfxFloat aspect = mMetrics.xHeight / mStyle.size; 1.260 + mAdjustedSize = mStyle.GetAdjustedSize(aspect); 1.261 + mFUnitsConvFactor = mAdjustedSize / upem; 1.262 + if (static_cast<MacOSFontEntry*>(mFontEntry.get())->IsCFF()) { 1.263 + cgConvFactor = mAdjustedSize / ::CGFontGetUnitsPerEm(mCGFont); 1.264 + } else { 1.265 + cgConvFactor = mFUnitsConvFactor; 1.266 + } 1.267 + mMetrics.xHeight = 0.0; 1.268 + if (!InitMetricsFromSfntTables(mMetrics) && 1.269 + (!mFontEntry->IsUserFont() || mFontEntry->IsLocalUserFont())) { 1.270 + InitMetricsFromPlatform(); 1.271 + } 1.272 + if (!mIsValid) { 1.273 + // this shouldn't happen, as we succeeded earlier before applying 1.274 + // the size-adjust factor! But check anyway, for paranoia's sake. 1.275 + return; 1.276 + } 1.277 + if (mMetrics.xHeight == 0.0) { 1.278 + mMetrics.xHeight = ::CGFontGetXHeight(mCGFont) * cgConvFactor; 1.279 + } 1.280 + } 1.281 + 1.282 + // Once we reach here, we've got basic metrics and set mIsValid = TRUE; 1.283 + // there should be no further points of actual failure in InitMetrics(). 1.284 + // (If one is introduced, be sure to reset mIsValid to FALSE!) 1.285 + 1.286 + mMetrics.emHeight = mAdjustedSize; 1.287 + 1.288 + // Measure/calculate additional metrics, independent of whether we used 1.289 + // the tables directly or ATS metrics APIs 1.290 + 1.291 + CFDataRef cmap = 1.292 + ::CGFontCopyTableForTag(mCGFont, TRUETYPE_TAG('c','m','a','p')); 1.293 + 1.294 + uint32_t glyphID; 1.295 + if (mMetrics.aveCharWidth <= 0) { 1.296 + mMetrics.aveCharWidth = GetCharWidth(cmap, 'x', &glyphID, 1.297 + cgConvFactor); 1.298 + if (glyphID == 0) { 1.299 + // we didn't find 'x', so use maxAdvance rather than zero 1.300 + mMetrics.aveCharWidth = mMetrics.maxAdvance; 1.301 + } 1.302 + } 1.303 + if (IsSyntheticBold()) { 1.304 + mMetrics.aveCharWidth += GetSyntheticBoldOffset(); 1.305 + mMetrics.maxAdvance += GetSyntheticBoldOffset(); 1.306 + } 1.307 + 1.308 + mMetrics.spaceWidth = GetCharWidth(cmap, ' ', &glyphID, cgConvFactor); 1.309 + if (glyphID == 0) { 1.310 + // no space glyph?! 1.311 + mMetrics.spaceWidth = mMetrics.aveCharWidth; 1.312 + } 1.313 + mSpaceGlyph = glyphID; 1.314 + 1.315 + mMetrics.zeroOrAveCharWidth = GetCharWidth(cmap, '0', &glyphID, 1.316 + cgConvFactor); 1.317 + if (glyphID == 0) { 1.318 + mMetrics.zeroOrAveCharWidth = mMetrics.aveCharWidth; 1.319 + } 1.320 + 1.321 + if (cmap) { 1.322 + ::CFRelease(cmap); 1.323 + } 1.324 + 1.325 + CalculateDerivedMetrics(mMetrics); 1.326 + 1.327 + SanitizeMetrics(&mMetrics, mFontEntry->mIsBadUnderlineFont); 1.328 + 1.329 +#if 0 1.330 + fprintf (stderr, "Font: %p (%s) size: %f\n", this, 1.331 + NS_ConvertUTF16toUTF8(GetName()).get(), mStyle.size); 1.332 +// 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); 1.333 + fprintf (stderr, " emHeight: %f emAscent: %f emDescent: %f\n", mMetrics.emHeight, mMetrics.emAscent, mMetrics.emDescent); 1.334 + fprintf (stderr, " maxAscent: %f maxDescent: %f maxAdvance: %f\n", mMetrics.maxAscent, mMetrics.maxDescent, mMetrics.maxAdvance); 1.335 + fprintf (stderr, " internalLeading: %f externalLeading: %f\n", mMetrics.internalLeading, mMetrics.externalLeading); 1.336 + fprintf (stderr, " spaceWidth: %f aveCharWidth: %f xHeight: %f\n", mMetrics.spaceWidth, mMetrics.aveCharWidth, mMetrics.xHeight); 1.337 + 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); 1.338 +#endif 1.339 +} 1.340 + 1.341 +gfxFloat 1.342 +gfxMacFont::GetCharWidth(CFDataRef aCmap, char16_t aUniChar, 1.343 + uint32_t *aGlyphID, gfxFloat aConvFactor) 1.344 +{ 1.345 + CGGlyph glyph = 0; 1.346 + 1.347 + if (aCmap) { 1.348 + glyph = gfxFontUtils::MapCharToGlyph(::CFDataGetBytePtr(aCmap), 1.349 + ::CFDataGetLength(aCmap), 1.350 + aUniChar); 1.351 + } 1.352 + 1.353 + if (aGlyphID) { 1.354 + *aGlyphID = glyph; 1.355 + } 1.356 + 1.357 + if (glyph) { 1.358 + int advance; 1.359 + if (::CGFontGetGlyphAdvances(mCGFont, &glyph, 1, &advance)) { 1.360 + return advance * aConvFactor; 1.361 + } 1.362 + } 1.363 + 1.364 + return 0; 1.365 +} 1.366 + 1.367 +// Try to initialize font metrics via platform APIs (CG/CT), 1.368 +// and set mIsValid = TRUE on success. 1.369 +// We ONLY call this for local (platform) fonts that are not sfnt format; 1.370 +// for sfnts, including ALL downloadable fonts, we prefer to use 1.371 +// InitMetricsFromSfntTables and avoid platform APIs. 1.372 +void 1.373 +gfxMacFont::InitMetricsFromPlatform() 1.374 +{ 1.375 + CTFontRef ctFont = ::CTFontCreateWithGraphicsFont(mCGFont, 1.376 + mAdjustedSize, 1.377 + nullptr, nullptr); 1.378 + if (!ctFont) { 1.379 + return; 1.380 + } 1.381 + 1.382 + mMetrics.underlineOffset = ::CTFontGetUnderlinePosition(ctFont); 1.383 + mMetrics.underlineSize = ::CTFontGetUnderlineThickness(ctFont); 1.384 + 1.385 + mMetrics.externalLeading = ::CTFontGetLeading(ctFont); 1.386 + 1.387 + mMetrics.maxAscent = ::CTFontGetAscent(ctFont); 1.388 + mMetrics.maxDescent = ::CTFontGetDescent(ctFont); 1.389 + 1.390 + // this is not strictly correct, but neither CTFont nor CGFont seems to 1.391 + // provide maxAdvance, unless we were to iterate over all the glyphs 1.392 + // (which isn't worth the cost here) 1.393 + CGRect r = ::CTFontGetBoundingBox(ctFont); 1.394 + mMetrics.maxAdvance = r.size.width; 1.395 + 1.396 + // aveCharWidth is also not provided, so leave it at zero 1.397 + // (fallback code in gfxMacFont::InitMetrics will then try measuring 'x'); 1.398 + // this could lead to less-than-"perfect" text field sizing when width is 1.399 + // specified as a number of characters, and the font in use is a non-sfnt 1.400 + // legacy font, but that's a sufficiently obscure edge case that we can 1.401 + // ignore the potential discrepancy. 1.402 + mMetrics.aveCharWidth = 0; 1.403 + 1.404 + mMetrics.xHeight = ::CTFontGetXHeight(ctFont); 1.405 + 1.406 + ::CFRelease(ctFont); 1.407 + 1.408 + mIsValid = true; 1.409 +} 1.410 + 1.411 +TemporaryRef<ScaledFont> 1.412 +gfxMacFont::GetScaledFont(DrawTarget *aTarget) 1.413 +{ 1.414 + if (!mAzureScaledFont) { 1.415 + NativeFont nativeFont; 1.416 + nativeFont.mType = NativeFontType::MAC_FONT_FACE; 1.417 + nativeFont.mFont = GetCGFontRef(); 1.418 + mAzureScaledFont = mozilla::gfx::Factory::CreateScaledFontWithCairo(nativeFont, GetAdjustedSize(), mScaledFont); 1.419 + } 1.420 + 1.421 + return mAzureScaledFont; 1.422 +} 1.423 + 1.424 +void 1.425 +gfxMacFont::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, 1.426 + FontCacheSizes* aSizes) const 1.427 +{ 1.428 + gfxFont::AddSizeOfExcludingThis(aMallocSizeOf, aSizes); 1.429 + // mCGFont is shared with the font entry, so not counted here; 1.430 + // and we don't have APIs to measure the cairo mFontFace object 1.431 +} 1.432 + 1.433 +void 1.434 +gfxMacFont::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, 1.435 + FontCacheSizes* aSizes) const 1.436 +{ 1.437 + aSizes->mFontInstances += aMallocSizeOf(this); 1.438 + AddSizeOfExcludingThis(aMallocSizeOf, aSizes); 1.439 +}