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