gfx/thebes/gfxMacFont.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     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/. */
     6 #include "gfxMacFont.h"
     8 #include "mozilla/MemoryReporting.h"
    10 #include "gfxCoreTextShaper.h"
    11 #include "gfxHarfBuzzShaper.h"
    12 #include <algorithm>
    13 #include "gfxGraphiteShaper.h"
    14 #include "gfxPlatformMac.h"
    15 #include "gfxContext.h"
    16 #include "gfxFontUtils.h"
    17 #include "gfxMacPlatformFontList.h"
    18 #include "gfxFontConstants.h"
    20 #include "cairo-quartz.h"
    22 using namespace mozilla;
    23 using namespace mozilla::gfx;
    25 gfxMacFont::gfxMacFont(MacOSFontEntry *aFontEntry, const gfxFontStyle *aFontStyle,
    26                        bool aNeedsBold)
    27     : gfxFont(aFontEntry, aFontStyle),
    28       mCGFont(nullptr),
    29       mFontFace(nullptr)
    30 {
    31     mApplySyntheticBold = aNeedsBold;
    33     mCGFont = aFontEntry->GetFontRef();
    34     if (!mCGFont) {
    35         mIsValid = false;
    36         return;
    37     }
    39     // InitMetrics will handle the sizeAdjust factor and set mAdjustedSize
    40     InitMetrics();
    41     if (!mIsValid) {
    42         return;
    43     }
    45     mFontFace = cairo_quartz_font_face_create_for_cgfont(mCGFont);
    47     cairo_status_t cairoerr = cairo_font_face_status(mFontFace);
    48     if (cairoerr != CAIRO_STATUS_SUCCESS) {
    49         mIsValid = false;
    50 #ifdef DEBUG
    51         char warnBuf[1024];
    52         sprintf(warnBuf, "Failed to create Cairo font face: %s status: %d",
    53                 NS_ConvertUTF16toUTF8(GetName()).get(), cairoerr);
    54         NS_WARNING(warnBuf);
    55 #endif
    56         return;
    57     }
    59     cairo_matrix_t sizeMatrix, ctm;
    60     cairo_matrix_init_identity(&ctm);
    61     cairo_matrix_init_scale(&sizeMatrix, mAdjustedSize, mAdjustedSize);
    63     // synthetic oblique by skewing via the font matrix
    64     bool needsOblique =
    65         (mFontEntry != nullptr) &&
    66         (!mFontEntry->IsItalic() &&
    67          (mStyle.style & (NS_FONT_STYLE_ITALIC | NS_FONT_STYLE_OBLIQUE)));
    69     if (needsOblique) {
    70         double skewfactor = (needsOblique ? Fix2X(kATSItalicQDSkew) : 0);
    72         cairo_matrix_t style;
    73         cairo_matrix_init(&style,
    74                           1,                //xx
    75                           0,                //yx
    76                           -1 * skewfactor,   //xy
    77                           1,                //yy
    78                           0,                //x0
    79                           0);               //y0
    80         cairo_matrix_multiply(&sizeMatrix, &sizeMatrix, &style);
    81     }
    83     cairo_font_options_t *fontOptions = cairo_font_options_create();
    85     // turn off font anti-aliasing based on user pref setting
    86     if (mAdjustedSize <=
    87         (gfxFloat)gfxPlatformMac::GetPlatform()->GetAntiAliasingThreshold()) {
    88         cairo_font_options_set_antialias(fontOptions, CAIRO_ANTIALIAS_NONE);
    89         mAntialiasOption = kAntialiasNone;
    90     } else if (mStyle.useGrayscaleAntialiasing) {
    91         cairo_font_options_set_antialias(fontOptions, CAIRO_ANTIALIAS_GRAY);
    92         mAntialiasOption = kAntialiasGrayscale;
    93     }
    95     mScaledFont = cairo_scaled_font_create(mFontFace, &sizeMatrix, &ctm,
    96                                            fontOptions);
    97     cairo_font_options_destroy(fontOptions);
    99     cairoerr = cairo_scaled_font_status(mScaledFont);
   100     if (cairoerr != CAIRO_STATUS_SUCCESS) {
   101         mIsValid = false;
   102 #ifdef DEBUG
   103         char warnBuf[1024];
   104         sprintf(warnBuf, "Failed to create scaled font: %s status: %d",
   105                 NS_ConvertUTF16toUTF8(GetName()).get(), cairoerr);
   106         NS_WARNING(warnBuf);
   107 #endif
   108     }
   110     if (FontCanSupportGraphite()) {
   111         mGraphiteShaper = new gfxGraphiteShaper(this);
   112     }
   113     if (FontCanSupportHarfBuzz()) {
   114         mHarfBuzzShaper = new gfxHarfBuzzShaper(this);
   115     }
   116 }
   118 gfxMacFont::~gfxMacFont()
   119 {
   120     if (mScaledFont) {
   121         cairo_scaled_font_destroy(mScaledFont);
   122     }
   123     if (mFontFace) {
   124         cairo_font_face_destroy(mFontFace);
   125     }
   126 }
   128 bool
   129 gfxMacFont::ShapeText(gfxContext      *aContext,
   130                       const char16_t *aText,
   131                       uint32_t         aOffset,
   132                       uint32_t         aLength,
   133                       int32_t          aScript,
   134                       gfxShapedText   *aShapedText,
   135                       bool             aPreferPlatformShaping)
   136 {
   137     if (!mIsValid) {
   138         NS_WARNING("invalid font! expect incorrect text rendering");
   139         return false;
   140     }
   142     bool requiresAAT =
   143         static_cast<MacOSFontEntry*>(GetFontEntry())->RequiresAATLayout();
   144     return gfxFont::ShapeText(aContext, aText, aOffset, aLength,
   145                               aScript, aShapedText, requiresAAT);
   146 }
   148 void
   149 gfxMacFont::CreatePlatformShaper()
   150 {
   151     mPlatformShaper = new gfxCoreTextShaper(this);
   152 }
   154 bool
   155 gfxMacFont::SetupCairoFont(gfxContext *aContext)
   156 {
   157     if (cairo_scaled_font_status(mScaledFont) != CAIRO_STATUS_SUCCESS) {
   158         // Don't cairo_set_scaled_font as that would propagate the error to
   159         // the cairo_t, precluding any further drawing.
   160         return false;
   161     }
   162     cairo_set_scaled_font(aContext->GetCairo(), mScaledFont);
   163     return true;
   164 }
   166 gfxFont::RunMetrics
   167 gfxMacFont::Measure(gfxTextRun *aTextRun,
   168                     uint32_t aStart, uint32_t aEnd,
   169                     BoundingBoxType aBoundingBoxType,
   170                     gfxContext *aRefContext,
   171                     Spacing *aSpacing)
   172 {
   173     gfxFont::RunMetrics metrics =
   174         gfxFont::Measure(aTextRun, aStart, aEnd,
   175                          aBoundingBoxType, aRefContext, aSpacing);
   177     // if aBoundingBoxType is not TIGHT_HINTED_OUTLINE_EXTENTS then we need to add
   178     // a pixel column each side of the bounding box in case of antialiasing "bleed"
   179     if (aBoundingBoxType != TIGHT_HINTED_OUTLINE_EXTENTS &&
   180         metrics.mBoundingBox.width > 0) {
   181         metrics.mBoundingBox.x -= aTextRun->GetAppUnitsPerDevUnit();
   182         metrics.mBoundingBox.width += aTextRun->GetAppUnitsPerDevUnit() * 2;
   183     }
   185     return metrics;
   186 }
   188 void
   189 gfxMacFont::InitMetrics()
   190 {
   191     mIsValid = false;
   192     ::memset(&mMetrics, 0, sizeof(mMetrics));
   194     uint32_t upem = 0;
   196     // try to get unitsPerEm from sfnt head table, to avoid calling CGFont
   197     // if possible (bug 574368) and because CGFontGetUnitsPerEm does not
   198     // return the true value for OpenType/CFF fonts (it normalizes to 1000,
   199     // which then leads to metrics errors when we read the 'hmtx' table to
   200     // get glyph advances for HarfBuzz, see bug 580863)
   201     CFDataRef headData =
   202         ::CGFontCopyTableForTag(mCGFont, TRUETYPE_TAG('h','e','a','d'));
   203     if (headData) {
   204         if (size_t(::CFDataGetLength(headData)) >= sizeof(HeadTable)) {
   205             const HeadTable *head =
   206                 reinterpret_cast<const HeadTable*>(::CFDataGetBytePtr(headData));
   207             upem = head->unitsPerEm;
   208         }
   209         ::CFRelease(headData);
   210     }
   211     if (!upem) {
   212         upem = ::CGFontGetUnitsPerEm(mCGFont);
   213     }
   215     if (upem < 16 || upem > 16384) {
   216         // See http://www.microsoft.com/typography/otspec/head.htm
   217 #ifdef DEBUG
   218         char warnBuf[1024];
   219         sprintf(warnBuf, "Bad font metrics for: %s (invalid unitsPerEm value)",
   220                 NS_ConvertUTF16toUTF8(mFontEntry->Name()).get());
   221         NS_WARNING(warnBuf);
   222 #endif
   223         return;
   224     }
   226     mAdjustedSize = std::max(mStyle.size, 1.0);
   227     mFUnitsConvFactor = mAdjustedSize / upem;
   229     // For CFF fonts, when scaling values read from CGFont* APIs, we need to
   230     // use CG's idea of unitsPerEm, which may differ from the "true" value in
   231     // the head table of the font (see bug 580863)
   232     gfxFloat cgConvFactor;
   233     if (static_cast<MacOSFontEntry*>(mFontEntry.get())->IsCFF()) {
   234         cgConvFactor = mAdjustedSize / ::CGFontGetUnitsPerEm(mCGFont);
   235     } else {
   236         cgConvFactor = mFUnitsConvFactor;
   237     }
   239     // Try to read 'sfnt' metrics; for local, non-sfnt fonts ONLY, fall back to
   240     // platform APIs. The InitMetrics...() functions will set mIsValid on success.
   241     if (!InitMetricsFromSfntTables(mMetrics) &&
   242         (!mFontEntry->IsUserFont() || mFontEntry->IsLocalUserFont())) {
   243         InitMetricsFromPlatform();
   244     }
   245     if (!mIsValid) {
   246         return;
   247     }
   249     if (mMetrics.xHeight == 0.0) {
   250         mMetrics.xHeight = ::CGFontGetXHeight(mCGFont) * cgConvFactor;
   251     }
   253     if (mStyle.sizeAdjust != 0.0 && mStyle.size > 0.0 &&
   254         mMetrics.xHeight > 0.0) {
   255         // apply font-size-adjust, and recalculate metrics
   256         gfxFloat aspect = mMetrics.xHeight / mStyle.size;
   257         mAdjustedSize = mStyle.GetAdjustedSize(aspect);
   258         mFUnitsConvFactor = mAdjustedSize / upem;
   259         if (static_cast<MacOSFontEntry*>(mFontEntry.get())->IsCFF()) {
   260             cgConvFactor = mAdjustedSize / ::CGFontGetUnitsPerEm(mCGFont);
   261         } else {
   262             cgConvFactor = mFUnitsConvFactor;
   263         }
   264         mMetrics.xHeight = 0.0;
   265         if (!InitMetricsFromSfntTables(mMetrics) &&
   266             (!mFontEntry->IsUserFont() || mFontEntry->IsLocalUserFont())) {
   267             InitMetricsFromPlatform();
   268         }
   269         if (!mIsValid) {
   270             // this shouldn't happen, as we succeeded earlier before applying
   271             // the size-adjust factor! But check anyway, for paranoia's sake.
   272             return;
   273         }
   274         if (mMetrics.xHeight == 0.0) {
   275             mMetrics.xHeight = ::CGFontGetXHeight(mCGFont) * cgConvFactor;
   276         }
   277     }
   279     // Once we reach here, we've got basic metrics and set mIsValid = TRUE;
   280     // there should be no further points of actual failure in InitMetrics().
   281     // (If one is introduced, be sure to reset mIsValid to FALSE!)
   283     mMetrics.emHeight = mAdjustedSize;
   285     // Measure/calculate additional metrics, independent of whether we used
   286     // the tables directly or ATS metrics APIs
   288     CFDataRef cmap =
   289         ::CGFontCopyTableForTag(mCGFont, TRUETYPE_TAG('c','m','a','p'));
   291     uint32_t glyphID;
   292     if (mMetrics.aveCharWidth <= 0) {
   293         mMetrics.aveCharWidth = GetCharWidth(cmap, 'x', &glyphID,
   294                                              cgConvFactor);
   295         if (glyphID == 0) {
   296             // we didn't find 'x', so use maxAdvance rather than zero
   297             mMetrics.aveCharWidth = mMetrics.maxAdvance;
   298         }
   299     }
   300     if (IsSyntheticBold()) {
   301         mMetrics.aveCharWidth += GetSyntheticBoldOffset();
   302         mMetrics.maxAdvance += GetSyntheticBoldOffset();
   303     }
   305     mMetrics.spaceWidth = GetCharWidth(cmap, ' ', &glyphID, cgConvFactor);
   306     if (glyphID == 0) {
   307         // no space glyph?!
   308         mMetrics.spaceWidth = mMetrics.aveCharWidth;
   309     }
   310     mSpaceGlyph = glyphID;
   312     mMetrics.zeroOrAveCharWidth = GetCharWidth(cmap, '0', &glyphID,
   313                                                cgConvFactor);
   314     if (glyphID == 0) {
   315         mMetrics.zeroOrAveCharWidth = mMetrics.aveCharWidth;
   316     }
   318     if (cmap) {
   319         ::CFRelease(cmap);
   320     }
   322     CalculateDerivedMetrics(mMetrics);
   324     SanitizeMetrics(&mMetrics, mFontEntry->mIsBadUnderlineFont);
   326 #if 0
   327     fprintf (stderr, "Font: %p (%s) size: %f\n", this,
   328              NS_ConvertUTF16toUTF8(GetName()).get(), mStyle.size);
   329 //    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);
   330     fprintf (stderr, "    emHeight: %f emAscent: %f emDescent: %f\n", mMetrics.emHeight, mMetrics.emAscent, mMetrics.emDescent);
   331     fprintf (stderr, "    maxAscent: %f maxDescent: %f maxAdvance: %f\n", mMetrics.maxAscent, mMetrics.maxDescent, mMetrics.maxAdvance);
   332     fprintf (stderr, "    internalLeading: %f externalLeading: %f\n", mMetrics.internalLeading, mMetrics.externalLeading);
   333     fprintf (stderr, "    spaceWidth: %f aveCharWidth: %f xHeight: %f\n", mMetrics.spaceWidth, mMetrics.aveCharWidth, mMetrics.xHeight);
   334     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);
   335 #endif
   336 }
   338 gfxFloat
   339 gfxMacFont::GetCharWidth(CFDataRef aCmap, char16_t aUniChar,
   340                          uint32_t *aGlyphID, gfxFloat aConvFactor)
   341 {
   342     CGGlyph glyph = 0;
   344     if (aCmap) {
   345         glyph = gfxFontUtils::MapCharToGlyph(::CFDataGetBytePtr(aCmap),
   346                                              ::CFDataGetLength(aCmap),
   347                                              aUniChar);
   348     }
   350     if (aGlyphID) {
   351         *aGlyphID = glyph;
   352     }
   354     if (glyph) {
   355         int advance;
   356         if (::CGFontGetGlyphAdvances(mCGFont, &glyph, 1, &advance)) {
   357             return advance * aConvFactor;
   358         }
   359     }
   361     return 0;
   362 }
   364 // Try to initialize font metrics via platform APIs (CG/CT),
   365 // and set mIsValid = TRUE on success.
   366 // We ONLY call this for local (platform) fonts that are not sfnt format;
   367 // for sfnts, including ALL downloadable fonts, we prefer to use
   368 // InitMetricsFromSfntTables and avoid platform APIs.
   369 void
   370 gfxMacFont::InitMetricsFromPlatform()
   371 {
   372     CTFontRef ctFont = ::CTFontCreateWithGraphicsFont(mCGFont,
   373                                                       mAdjustedSize,
   374                                                       nullptr, nullptr);
   375     if (!ctFont) {
   376         return;
   377     }
   379     mMetrics.underlineOffset = ::CTFontGetUnderlinePosition(ctFont);
   380     mMetrics.underlineSize = ::CTFontGetUnderlineThickness(ctFont);
   382     mMetrics.externalLeading = ::CTFontGetLeading(ctFont);
   384     mMetrics.maxAscent = ::CTFontGetAscent(ctFont);
   385     mMetrics.maxDescent = ::CTFontGetDescent(ctFont);
   387     // this is not strictly correct, but neither CTFont nor CGFont seems to
   388     // provide maxAdvance, unless we were to iterate over all the glyphs
   389     // (which isn't worth the cost here)
   390     CGRect r = ::CTFontGetBoundingBox(ctFont);
   391     mMetrics.maxAdvance = r.size.width;
   393     // aveCharWidth is also not provided, so leave it at zero
   394     // (fallback code in gfxMacFont::InitMetrics will then try measuring 'x');
   395     // this could lead to less-than-"perfect" text field sizing when width is
   396     // specified as a number of characters, and the font in use is a non-sfnt
   397     // legacy font, but that's a sufficiently obscure edge case that we can
   398     // ignore the potential discrepancy.
   399     mMetrics.aveCharWidth = 0;
   401     mMetrics.xHeight = ::CTFontGetXHeight(ctFont);
   403     ::CFRelease(ctFont);
   405     mIsValid = true;
   406 }
   408 TemporaryRef<ScaledFont>
   409 gfxMacFont::GetScaledFont(DrawTarget *aTarget)
   410 {
   411   if (!mAzureScaledFont) {
   412     NativeFont nativeFont;
   413     nativeFont.mType = NativeFontType::MAC_FONT_FACE;
   414     nativeFont.mFont = GetCGFontRef();
   415     mAzureScaledFont = mozilla::gfx::Factory::CreateScaledFontWithCairo(nativeFont, GetAdjustedSize(), mScaledFont);
   416   }
   418   return mAzureScaledFont;
   419 }
   421 void
   422 gfxMacFont::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
   423                                    FontCacheSizes* aSizes) const
   424 {
   425     gfxFont::AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
   426     // mCGFont is shared with the font entry, so not counted here;
   427     // and we don't have APIs to measure the cairo mFontFace object
   428 }
   430 void
   431 gfxMacFont::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
   432                                    FontCacheSizes* aSizes) const
   433 {
   434     aSizes->mFontInstances += aMallocSizeOf(this);
   435     AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
   436 }

mercurial