gfx/thebes/gfxMacFont.cpp

changeset 0
6474c204b198
     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 +}

mercurial