gfx/2d/ScaledFontFreeType.cpp
author Jamie Nicol <jnicol@mozilla.com>
Wed, 31 Jul 2019 15:43:52 +0000
changeset 485607 bac526dab75d49d99d8802cab1c6f88e10d5198e
parent 479048 7d79dcab14bd5e5607f50fe2b489f30abf69d4b4
child 493301 e74a67e2afe33086ca785e6a4698be15efacd657
permissions -rw-r--r--
Bug 1562316 - Fix emoji rendering on android webrender. r=lsalzman ScaledFontFreeType::GetWRFontInstanceOptions() was neglecting to set the FontInstanceFlags::EMBEDDED_BITMAPS flag. This was causing us to always take the non-bitmap path when rasterizing the glyph, which fails on android because emoji fonts are bitmap only. Setting this flag causes glyphs to be rendered correctly on android webrender. Differential Revision: https://phabricator.services.mozilla.com/D40062

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "ScaledFontFreeType.h"
#include "UnscaledFontFreeType.h"
#include "NativeFontResourceFreeType.h"
#include "Logging.h"
#include "StackArray.h"
#include "mozilla/webrender/WebRenderTypes.h"

#ifdef USE_SKIA
#  include "skia/include/ports/SkTypeface_cairo.h"
#endif

#include FT_MULTIPLE_MASTERS_H

namespace mozilla {
namespace gfx {

// On Linux and Android our "platform" font is a cairo_scaled_font_t and we use
// an SkFontHost implementation that allows Skia to render using this.
// This is mainly because FT_Face is not good for sharing between libraries,
// which is a requirement when we consider runtime switchable backends and so on
ScaledFontFreeType::ScaledFontFreeType(
    cairo_scaled_font_t* aScaledFont, FT_Face aFace,
    const RefPtr<UnscaledFont>& aUnscaledFont, Float aSize)
    : ScaledFontBase(aUnscaledFont, aSize), mFace(aFace) {
  SetCairoScaledFont(aScaledFont);
}

#ifdef USE_SKIA
SkTypeface* ScaledFontFreeType::CreateSkTypeface() {
  return SkCreateTypefaceFromCairoFTFont(mScaledFont, mFace);
}
#endif

bool ScaledFontFreeType::GetFontInstanceData(FontInstanceDataOutput aCb,
                                             void* aBaton) {
  std::vector<FontVariation> variations;
  if (HasVariationSettings()) {
    UnscaledFontFreeType::GetVariationSettingsFromFace(&variations, mFace);
  }

  aCb(nullptr, 0, variations.data(), variations.size(), aBaton);
  return true;
}

bool ScaledFontFreeType::GetWRFontInstanceOptions(
    Maybe<wr::FontInstanceOptions>* aOutOptions,
    Maybe<wr::FontInstancePlatformOptions>* aOutPlatformOptions,
    std::vector<FontVariation>* aOutVariations) {
  wr::FontInstanceOptions options;
  options.render_mode = wr::FontRenderMode::Alpha;
  // FIXME: Cairo-FT metrics are not compatible with subpixel positioning.
  // options.flags = wr::FontInstanceFlags_SUBPIXEL_POSITION;
  options.flags = wr::FontInstanceFlags{0};
  options.flags |= wr::FontInstanceFlags_EMBEDDED_BITMAPS;
  options.bg_color = wr::ToColorU(Color());
  options.synthetic_italics =
      wr::DegreesToSyntheticItalics(GetSyntheticObliqueAngle());

  wr::FontInstancePlatformOptions platformOptions;
  platformOptions.lcd_filter = wr::FontLCDFilter::None;
  platformOptions.hinting = wr::FontHinting::None;

  *aOutOptions = Some(options);
  *aOutPlatformOptions = Some(platformOptions);

  if (HasVariationSettings()) {
    UnscaledFontFreeType::GetVariationSettingsFromFace(aOutVariations, mFace);
  }

  return true;
}

FT_Face UnscaledFontFreeType::InitFace() {
  if (mFace) {
    return mFace;
  }
  if (mFile.empty()) {
    return nullptr;
  }
  FT_Face face = Factory::NewFTFace(nullptr, mFile.c_str(), mIndex);
  if (!face) {
    gfxWarning() << "Failed initializing FreeType face from filename";
    return nullptr;
  }
  mOwnsFace = true;
  mFace = face;
  return mFace;
}

static cairo_user_data_key_t sNativeFontResourceKey;

static void ReleaseNativeFontResource(void* aData) {
  static_cast<NativeFontResource*>(aData)->Release();
}

static cairo_user_data_key_t sFaceKey;

static void ReleaseFace(void* aData) {
  Factory::ReleaseFTFace(static_cast<FT_Face>(aData));
}

already_AddRefed<ScaledFont> UnscaledFontFreeType::CreateScaledFont(
    Float aGlyphSize, const uint8_t* aInstanceData,
    uint32_t aInstanceDataLength, const FontVariation* aVariations,
    uint32_t aNumVariations) {
  FT_Face face = InitFace();
  if (!face) {
    gfxWarning() << "Attempted to deserialize FreeType scaled font without "
                    "FreeType face";
    return nullptr;
  }

  NativeFontResourceFreeType* nfr =
      static_cast<NativeFontResourceFreeType*>(mNativeFontResource.get());
  FT_Face varFace = nullptr;
  if (nfr && aNumVariations > 0) {
    varFace = nfr->CloneFace();
    if (varFace) {
      face = varFace;
    } else {
      gfxWarning() << "Failed cloning face for variations";
    }
  }

  StackArray<FT_Fixed, 32> coords(aNumVariations);
  for (uint32_t i = 0; i < aNumVariations; i++) {
    coords[i] = std::round(aVariations[i].mValue * 65536.0);
  }

  int flags = FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING;
  if (face->face_flags & FT_FACE_FLAG_TRICKY) {
    flags &= ~FT_LOAD_NO_AUTOHINT;
  }
  cairo_font_face_t* font = cairo_ft_font_face_create_for_ft_face(
      face, flags, coords.data(), aNumVariations);
  if (cairo_font_face_status(font) != CAIRO_STATUS_SUCCESS) {
    gfxWarning() << "Failed creating Cairo font face for FreeType face";
    if (varFace) {
      Factory::ReleaseFTFace(varFace);
    }
    return nullptr;
  }

  if (nfr) {
    // Bug 1362117 - Cairo may keep the font face alive after the owning
    // NativeFontResource was freed. To prevent this, we must bind the
    // NativeFontResource to the font face so that it stays alive at least as
    // long as the font face.
    nfr->AddRef();
    cairo_status_t err = CAIRO_STATUS_SUCCESS;
    bool cleanupFace = false;
    if (varFace) {
      err =
          cairo_font_face_set_user_data(font, &sFaceKey, varFace, ReleaseFace);
    }

    if (err != CAIRO_STATUS_SUCCESS) {
      cleanupFace = true;
    } else {
      err = cairo_font_face_set_user_data(font, &sNativeFontResourceKey, nfr,
                                          ReleaseNativeFontResource);
    }
    if (err != CAIRO_STATUS_SUCCESS) {
      gfxWarning() << "Failed binding NativeFontResource to Cairo font face";
      if (varFace && cleanupFace) {
        Factory::ReleaseFTFace(varFace);
      }
      nfr->Release();
      cairo_font_face_destroy(font);
      return nullptr;
    }
  }

  cairo_matrix_t sizeMatrix;
  cairo_matrix_init(&sizeMatrix, aGlyphSize, 0, 0, aGlyphSize, 0, 0);

  cairo_matrix_t identityMatrix;
  cairo_matrix_init_identity(&identityMatrix);

  cairo_font_options_t* fontOptions = cairo_font_options_create();
  cairo_font_options_set_hint_metrics(fontOptions, CAIRO_HINT_METRICS_OFF);

  cairo_scaled_font_t* cairoScaledFont =
      cairo_scaled_font_create(font, &sizeMatrix, &identityMatrix, fontOptions);

  cairo_font_options_destroy(fontOptions);

  cairo_font_face_destroy(font);

  if (cairo_scaled_font_status(cairoScaledFont) != CAIRO_STATUS_SUCCESS) {
    gfxWarning() << "Failed creating Cairo scaled font for font face";
    return nullptr;
  }

  RefPtr<ScaledFontFreeType> scaledFont =
      new ScaledFontFreeType(cairoScaledFont, face, this, aGlyphSize);

  cairo_scaled_font_destroy(cairoScaledFont);

  // Only apply variations if we have an explicitly cloned face. Otherwise,
  // if the pattern holds the pathname, Cairo will handle setting of variations.
  if (varFace) {
    ApplyVariationsToFace(aVariations, aNumVariations, varFace);
  }

  return scaledFont.forget();
}

bool ScaledFontFreeType::HasVariationSettings() {
  // Check if the FT face has been cloned.
  return mFace && mFace->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS &&
         mFace !=
             static_cast<UnscaledFontFreeType*>(mUnscaledFont.get())->GetFace();
}

}  // namespace gfx
}  // namespace mozilla