michael@0: /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "gfxMacFont.h" michael@0: michael@0: #include "mozilla/MemoryReporting.h" michael@0: michael@0: #include "gfxCoreTextShaper.h" michael@0: #include "gfxHarfBuzzShaper.h" michael@0: #include michael@0: #include "gfxGraphiteShaper.h" michael@0: #include "gfxPlatformMac.h" michael@0: #include "gfxContext.h" michael@0: #include "gfxFontUtils.h" michael@0: #include "gfxMacPlatformFontList.h" michael@0: #include "gfxFontConstants.h" michael@0: michael@0: #include "cairo-quartz.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::gfx; michael@0: michael@0: gfxMacFont::gfxMacFont(MacOSFontEntry *aFontEntry, const gfxFontStyle *aFontStyle, michael@0: bool aNeedsBold) michael@0: : gfxFont(aFontEntry, aFontStyle), michael@0: mCGFont(nullptr), michael@0: mFontFace(nullptr) michael@0: { michael@0: mApplySyntheticBold = aNeedsBold; michael@0: michael@0: mCGFont = aFontEntry->GetFontRef(); michael@0: if (!mCGFont) { michael@0: mIsValid = false; michael@0: return; michael@0: } michael@0: michael@0: // InitMetrics will handle the sizeAdjust factor and set mAdjustedSize michael@0: InitMetrics(); michael@0: if (!mIsValid) { michael@0: return; michael@0: } michael@0: michael@0: mFontFace = cairo_quartz_font_face_create_for_cgfont(mCGFont); michael@0: michael@0: cairo_status_t cairoerr = cairo_font_face_status(mFontFace); michael@0: if (cairoerr != CAIRO_STATUS_SUCCESS) { michael@0: mIsValid = false; michael@0: #ifdef DEBUG michael@0: char warnBuf[1024]; michael@0: sprintf(warnBuf, "Failed to create Cairo font face: %s status: %d", michael@0: NS_ConvertUTF16toUTF8(GetName()).get(), cairoerr); michael@0: NS_WARNING(warnBuf); michael@0: #endif michael@0: return; michael@0: } michael@0: michael@0: cairo_matrix_t sizeMatrix, ctm; michael@0: cairo_matrix_init_identity(&ctm); michael@0: cairo_matrix_init_scale(&sizeMatrix, mAdjustedSize, mAdjustedSize); michael@0: michael@0: // synthetic oblique by skewing via the font matrix michael@0: bool needsOblique = michael@0: (mFontEntry != nullptr) && michael@0: (!mFontEntry->IsItalic() && michael@0: (mStyle.style & (NS_FONT_STYLE_ITALIC | NS_FONT_STYLE_OBLIQUE))); michael@0: michael@0: if (needsOblique) { michael@0: double skewfactor = (needsOblique ? Fix2X(kATSItalicQDSkew) : 0); michael@0: michael@0: cairo_matrix_t style; michael@0: cairo_matrix_init(&style, michael@0: 1, //xx michael@0: 0, //yx michael@0: -1 * skewfactor, //xy michael@0: 1, //yy michael@0: 0, //x0 michael@0: 0); //y0 michael@0: cairo_matrix_multiply(&sizeMatrix, &sizeMatrix, &style); michael@0: } michael@0: michael@0: cairo_font_options_t *fontOptions = cairo_font_options_create(); michael@0: michael@0: // turn off font anti-aliasing based on user pref setting michael@0: if (mAdjustedSize <= michael@0: (gfxFloat)gfxPlatformMac::GetPlatform()->GetAntiAliasingThreshold()) { michael@0: cairo_font_options_set_antialias(fontOptions, CAIRO_ANTIALIAS_NONE); michael@0: mAntialiasOption = kAntialiasNone; michael@0: } else if (mStyle.useGrayscaleAntialiasing) { michael@0: cairo_font_options_set_antialias(fontOptions, CAIRO_ANTIALIAS_GRAY); michael@0: mAntialiasOption = kAntialiasGrayscale; michael@0: } michael@0: michael@0: mScaledFont = cairo_scaled_font_create(mFontFace, &sizeMatrix, &ctm, michael@0: fontOptions); michael@0: cairo_font_options_destroy(fontOptions); michael@0: michael@0: cairoerr = cairo_scaled_font_status(mScaledFont); michael@0: if (cairoerr != CAIRO_STATUS_SUCCESS) { michael@0: mIsValid = false; michael@0: #ifdef DEBUG michael@0: char warnBuf[1024]; michael@0: sprintf(warnBuf, "Failed to create scaled font: %s status: %d", michael@0: NS_ConvertUTF16toUTF8(GetName()).get(), cairoerr); michael@0: NS_WARNING(warnBuf); michael@0: #endif michael@0: } michael@0: michael@0: if (FontCanSupportGraphite()) { michael@0: mGraphiteShaper = new gfxGraphiteShaper(this); michael@0: } michael@0: if (FontCanSupportHarfBuzz()) { michael@0: mHarfBuzzShaper = new gfxHarfBuzzShaper(this); michael@0: } michael@0: } michael@0: michael@0: gfxMacFont::~gfxMacFont() michael@0: { michael@0: if (mScaledFont) { michael@0: cairo_scaled_font_destroy(mScaledFont); michael@0: } michael@0: if (mFontFace) { michael@0: cairo_font_face_destroy(mFontFace); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: gfxMacFont::ShapeText(gfxContext *aContext, michael@0: const char16_t *aText, michael@0: uint32_t aOffset, michael@0: uint32_t aLength, michael@0: int32_t aScript, michael@0: gfxShapedText *aShapedText, michael@0: bool aPreferPlatformShaping) michael@0: { michael@0: if (!mIsValid) { michael@0: NS_WARNING("invalid font! expect incorrect text rendering"); michael@0: return false; michael@0: } michael@0: michael@0: bool requiresAAT = michael@0: static_cast(GetFontEntry())->RequiresAATLayout(); michael@0: return gfxFont::ShapeText(aContext, aText, aOffset, aLength, michael@0: aScript, aShapedText, requiresAAT); michael@0: } michael@0: michael@0: void michael@0: gfxMacFont::CreatePlatformShaper() michael@0: { michael@0: mPlatformShaper = new gfxCoreTextShaper(this); michael@0: } michael@0: michael@0: bool michael@0: gfxMacFont::SetupCairoFont(gfxContext *aContext) michael@0: { michael@0: if (cairo_scaled_font_status(mScaledFont) != CAIRO_STATUS_SUCCESS) { michael@0: // Don't cairo_set_scaled_font as that would propagate the error to michael@0: // the cairo_t, precluding any further drawing. michael@0: return false; michael@0: } michael@0: cairo_set_scaled_font(aContext->GetCairo(), mScaledFont); michael@0: return true; michael@0: } michael@0: michael@0: gfxFont::RunMetrics michael@0: gfxMacFont::Measure(gfxTextRun *aTextRun, michael@0: uint32_t aStart, uint32_t aEnd, michael@0: BoundingBoxType aBoundingBoxType, michael@0: gfxContext *aRefContext, michael@0: Spacing *aSpacing) michael@0: { michael@0: gfxFont::RunMetrics metrics = michael@0: gfxFont::Measure(aTextRun, aStart, aEnd, michael@0: aBoundingBoxType, aRefContext, aSpacing); michael@0: michael@0: // if aBoundingBoxType is not TIGHT_HINTED_OUTLINE_EXTENTS then we need to add michael@0: // a pixel column each side of the bounding box in case of antialiasing "bleed" michael@0: if (aBoundingBoxType != TIGHT_HINTED_OUTLINE_EXTENTS && michael@0: metrics.mBoundingBox.width > 0) { michael@0: metrics.mBoundingBox.x -= aTextRun->GetAppUnitsPerDevUnit(); michael@0: metrics.mBoundingBox.width += aTextRun->GetAppUnitsPerDevUnit() * 2; michael@0: } michael@0: michael@0: return metrics; michael@0: } michael@0: michael@0: void michael@0: gfxMacFont::InitMetrics() michael@0: { michael@0: mIsValid = false; michael@0: ::memset(&mMetrics, 0, sizeof(mMetrics)); michael@0: michael@0: uint32_t upem = 0; michael@0: michael@0: // try to get unitsPerEm from sfnt head table, to avoid calling CGFont michael@0: // if possible (bug 574368) and because CGFontGetUnitsPerEm does not michael@0: // return the true value for OpenType/CFF fonts (it normalizes to 1000, michael@0: // which then leads to metrics errors when we read the 'hmtx' table to michael@0: // get glyph advances for HarfBuzz, see bug 580863) michael@0: CFDataRef headData = michael@0: ::CGFontCopyTableForTag(mCGFont, TRUETYPE_TAG('h','e','a','d')); michael@0: if (headData) { michael@0: if (size_t(::CFDataGetLength(headData)) >= sizeof(HeadTable)) { michael@0: const HeadTable *head = michael@0: reinterpret_cast(::CFDataGetBytePtr(headData)); michael@0: upem = head->unitsPerEm; michael@0: } michael@0: ::CFRelease(headData); michael@0: } michael@0: if (!upem) { michael@0: upem = ::CGFontGetUnitsPerEm(mCGFont); michael@0: } michael@0: michael@0: if (upem < 16 || upem > 16384) { michael@0: // See http://www.microsoft.com/typography/otspec/head.htm michael@0: #ifdef DEBUG michael@0: char warnBuf[1024]; michael@0: sprintf(warnBuf, "Bad font metrics for: %s (invalid unitsPerEm value)", michael@0: NS_ConvertUTF16toUTF8(mFontEntry->Name()).get()); michael@0: NS_WARNING(warnBuf); michael@0: #endif michael@0: return; michael@0: } michael@0: michael@0: mAdjustedSize = std::max(mStyle.size, 1.0); michael@0: mFUnitsConvFactor = mAdjustedSize / upem; michael@0: michael@0: // For CFF fonts, when scaling values read from CGFont* APIs, we need to michael@0: // use CG's idea of unitsPerEm, which may differ from the "true" value in michael@0: // the head table of the font (see bug 580863) michael@0: gfxFloat cgConvFactor; michael@0: if (static_cast(mFontEntry.get())->IsCFF()) { michael@0: cgConvFactor = mAdjustedSize / ::CGFontGetUnitsPerEm(mCGFont); michael@0: } else { michael@0: cgConvFactor = mFUnitsConvFactor; michael@0: } michael@0: michael@0: // Try to read 'sfnt' metrics; for local, non-sfnt fonts ONLY, fall back to michael@0: // platform APIs. The InitMetrics...() functions will set mIsValid on success. michael@0: if (!InitMetricsFromSfntTables(mMetrics) && michael@0: (!mFontEntry->IsUserFont() || mFontEntry->IsLocalUserFont())) { michael@0: InitMetricsFromPlatform(); michael@0: } michael@0: if (!mIsValid) { michael@0: return; michael@0: } michael@0: michael@0: if (mMetrics.xHeight == 0.0) { michael@0: mMetrics.xHeight = ::CGFontGetXHeight(mCGFont) * cgConvFactor; michael@0: } michael@0: michael@0: if (mStyle.sizeAdjust != 0.0 && mStyle.size > 0.0 && michael@0: mMetrics.xHeight > 0.0) { michael@0: // apply font-size-adjust, and recalculate metrics michael@0: gfxFloat aspect = mMetrics.xHeight / mStyle.size; michael@0: mAdjustedSize = mStyle.GetAdjustedSize(aspect); michael@0: mFUnitsConvFactor = mAdjustedSize / upem; michael@0: if (static_cast(mFontEntry.get())->IsCFF()) { michael@0: cgConvFactor = mAdjustedSize / ::CGFontGetUnitsPerEm(mCGFont); michael@0: } else { michael@0: cgConvFactor = mFUnitsConvFactor; michael@0: } michael@0: mMetrics.xHeight = 0.0; michael@0: if (!InitMetricsFromSfntTables(mMetrics) && michael@0: (!mFontEntry->IsUserFont() || mFontEntry->IsLocalUserFont())) { michael@0: InitMetricsFromPlatform(); michael@0: } michael@0: if (!mIsValid) { michael@0: // this shouldn't happen, as we succeeded earlier before applying michael@0: // the size-adjust factor! But check anyway, for paranoia's sake. michael@0: return; michael@0: } michael@0: if (mMetrics.xHeight == 0.0) { michael@0: mMetrics.xHeight = ::CGFontGetXHeight(mCGFont) * cgConvFactor; michael@0: } michael@0: } michael@0: michael@0: // Once we reach here, we've got basic metrics and set mIsValid = TRUE; michael@0: // there should be no further points of actual failure in InitMetrics(). michael@0: // (If one is introduced, be sure to reset mIsValid to FALSE!) michael@0: michael@0: mMetrics.emHeight = mAdjustedSize; michael@0: michael@0: // Measure/calculate additional metrics, independent of whether we used michael@0: // the tables directly or ATS metrics APIs michael@0: michael@0: CFDataRef cmap = michael@0: ::CGFontCopyTableForTag(mCGFont, TRUETYPE_TAG('c','m','a','p')); michael@0: michael@0: uint32_t glyphID; michael@0: if (mMetrics.aveCharWidth <= 0) { michael@0: mMetrics.aveCharWidth = GetCharWidth(cmap, 'x', &glyphID, michael@0: cgConvFactor); michael@0: if (glyphID == 0) { michael@0: // we didn't find 'x', so use maxAdvance rather than zero michael@0: mMetrics.aveCharWidth = mMetrics.maxAdvance; michael@0: } michael@0: } michael@0: if (IsSyntheticBold()) { michael@0: mMetrics.aveCharWidth += GetSyntheticBoldOffset(); michael@0: mMetrics.maxAdvance += GetSyntheticBoldOffset(); michael@0: } michael@0: michael@0: mMetrics.spaceWidth = GetCharWidth(cmap, ' ', &glyphID, cgConvFactor); michael@0: if (glyphID == 0) { michael@0: // no space glyph?! michael@0: mMetrics.spaceWidth = mMetrics.aveCharWidth; michael@0: } michael@0: mSpaceGlyph = glyphID; michael@0: michael@0: mMetrics.zeroOrAveCharWidth = GetCharWidth(cmap, '0', &glyphID, michael@0: cgConvFactor); michael@0: if (glyphID == 0) { michael@0: mMetrics.zeroOrAveCharWidth = mMetrics.aveCharWidth; michael@0: } michael@0: michael@0: if (cmap) { michael@0: ::CFRelease(cmap); michael@0: } michael@0: michael@0: CalculateDerivedMetrics(mMetrics); michael@0: michael@0: SanitizeMetrics(&mMetrics, mFontEntry->mIsBadUnderlineFont); michael@0: michael@0: #if 0 michael@0: fprintf (stderr, "Font: %p (%s) size: %f\n", this, michael@0: NS_ConvertUTF16toUTF8(GetName()).get(), mStyle.size); michael@0: // 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); michael@0: fprintf (stderr, " emHeight: %f emAscent: %f emDescent: %f\n", mMetrics.emHeight, mMetrics.emAscent, mMetrics.emDescent); michael@0: fprintf (stderr, " maxAscent: %f maxDescent: %f maxAdvance: %f\n", mMetrics.maxAscent, mMetrics.maxDescent, mMetrics.maxAdvance); michael@0: fprintf (stderr, " internalLeading: %f externalLeading: %f\n", mMetrics.internalLeading, mMetrics.externalLeading); michael@0: fprintf (stderr, " spaceWidth: %f aveCharWidth: %f xHeight: %f\n", mMetrics.spaceWidth, mMetrics.aveCharWidth, mMetrics.xHeight); michael@0: 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); michael@0: #endif michael@0: } michael@0: michael@0: gfxFloat michael@0: gfxMacFont::GetCharWidth(CFDataRef aCmap, char16_t aUniChar, michael@0: uint32_t *aGlyphID, gfxFloat aConvFactor) michael@0: { michael@0: CGGlyph glyph = 0; michael@0: michael@0: if (aCmap) { michael@0: glyph = gfxFontUtils::MapCharToGlyph(::CFDataGetBytePtr(aCmap), michael@0: ::CFDataGetLength(aCmap), michael@0: aUniChar); michael@0: } michael@0: michael@0: if (aGlyphID) { michael@0: *aGlyphID = glyph; michael@0: } michael@0: michael@0: if (glyph) { michael@0: int advance; michael@0: if (::CGFontGetGlyphAdvances(mCGFont, &glyph, 1, &advance)) { michael@0: return advance * aConvFactor; michael@0: } michael@0: } michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: // Try to initialize font metrics via platform APIs (CG/CT), michael@0: // and set mIsValid = TRUE on success. michael@0: // We ONLY call this for local (platform) fonts that are not sfnt format; michael@0: // for sfnts, including ALL downloadable fonts, we prefer to use michael@0: // InitMetricsFromSfntTables and avoid platform APIs. michael@0: void michael@0: gfxMacFont::InitMetricsFromPlatform() michael@0: { michael@0: CTFontRef ctFont = ::CTFontCreateWithGraphicsFont(mCGFont, michael@0: mAdjustedSize, michael@0: nullptr, nullptr); michael@0: if (!ctFont) { michael@0: return; michael@0: } michael@0: michael@0: mMetrics.underlineOffset = ::CTFontGetUnderlinePosition(ctFont); michael@0: mMetrics.underlineSize = ::CTFontGetUnderlineThickness(ctFont); michael@0: michael@0: mMetrics.externalLeading = ::CTFontGetLeading(ctFont); michael@0: michael@0: mMetrics.maxAscent = ::CTFontGetAscent(ctFont); michael@0: mMetrics.maxDescent = ::CTFontGetDescent(ctFont); michael@0: michael@0: // this is not strictly correct, but neither CTFont nor CGFont seems to michael@0: // provide maxAdvance, unless we were to iterate over all the glyphs michael@0: // (which isn't worth the cost here) michael@0: CGRect r = ::CTFontGetBoundingBox(ctFont); michael@0: mMetrics.maxAdvance = r.size.width; michael@0: michael@0: // aveCharWidth is also not provided, so leave it at zero michael@0: // (fallback code in gfxMacFont::InitMetrics will then try measuring 'x'); michael@0: // this could lead to less-than-"perfect" text field sizing when width is michael@0: // specified as a number of characters, and the font in use is a non-sfnt michael@0: // legacy font, but that's a sufficiently obscure edge case that we can michael@0: // ignore the potential discrepancy. michael@0: mMetrics.aveCharWidth = 0; michael@0: michael@0: mMetrics.xHeight = ::CTFontGetXHeight(ctFont); michael@0: michael@0: ::CFRelease(ctFont); michael@0: michael@0: mIsValid = true; michael@0: } michael@0: michael@0: TemporaryRef michael@0: gfxMacFont::GetScaledFont(DrawTarget *aTarget) michael@0: { michael@0: if (!mAzureScaledFont) { michael@0: NativeFont nativeFont; michael@0: nativeFont.mType = NativeFontType::MAC_FONT_FACE; michael@0: nativeFont.mFont = GetCGFontRef(); michael@0: mAzureScaledFont = mozilla::gfx::Factory::CreateScaledFontWithCairo(nativeFont, GetAdjustedSize(), mScaledFont); michael@0: } michael@0: michael@0: return mAzureScaledFont; michael@0: } michael@0: michael@0: void michael@0: gfxMacFont::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, michael@0: FontCacheSizes* aSizes) const michael@0: { michael@0: gfxFont::AddSizeOfExcludingThis(aMallocSizeOf, aSizes); michael@0: // mCGFont is shared with the font entry, so not counted here; michael@0: // and we don't have APIs to measure the cairo mFontFace object michael@0: } michael@0: michael@0: void michael@0: gfxMacFont::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, michael@0: FontCacheSizes* aSizes) const michael@0: { michael@0: aSizes->mFontInstances += aMallocSizeOf(this); michael@0: AddSizeOfExcludingThis(aMallocSizeOf, aSizes); michael@0: }