/* -*- 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 "ScaledFontFontconfig.h" #include "UnscaledFontFreeType.h" #include "Logging.h" #include "mozilla/StaticPrefs_gfx.h" #include "mozilla/webrender/WebRenderTypes.h" #include "skia/include/ports/SkTypeface_cairo.h" #include "HelpersSkia.h" #include #include FT_LCD_FILTER_H #include FT_MULTIPLE_MASTERS_H namespace mozilla::gfx { ScaledFontFontconfig::ScaledFontFontconfig( RefPtr&& aFace, FcPattern* aPattern, const RefPtr& aUnscaledFont, Float aSize) : ScaledFontBase(aUnscaledFont, aSize), mFace(std::move(aFace)), mInstanceData(aPattern) {} ScaledFontFontconfig::ScaledFontFontconfig( RefPtr&& aFace, const InstanceData& aInstanceData, const RefPtr& aUnscaledFont, Float aSize) : ScaledFontBase(aUnscaledFont, aSize), mFace(std::move(aFace)), mInstanceData(aInstanceData) {} bool ScaledFontFontconfig::UseSubpixelPosition() const { return !MOZ_UNLIKELY( StaticPrefs:: gfx_text_subpixel_position_force_disabled_AtStartup()) && mInstanceData.mAntialias != AntialiasMode::NONE && FT_IS_SCALABLE(mFace->GetFace()) && (mInstanceData.mHinting == FontHinting::NONE || mInstanceData.mHinting == FontHinting::LIGHT || MOZ_UNLIKELY( StaticPrefs:: gfx_text_subpixel_position_force_enabled_AtStartup())); } SkTypeface* ScaledFontFontconfig::CreateSkTypeface() { SkPixelGeometry geo = mInstanceData.mFlags & InstanceData::SUBPIXEL_BGR ? (mInstanceData.mFlags & InstanceData::LCD_VERTICAL ? kBGR_V_SkPixelGeometry : kBGR_H_SkPixelGeometry) : (mInstanceData.mFlags & InstanceData::LCD_VERTICAL ? kRGB_V_SkPixelGeometry : kRGB_H_SkPixelGeometry); return SkCreateTypefaceFromCairoFTFont(mFace->GetFace(), mFace.get(), geo, mInstanceData.mLcdFilter); } void ScaledFontFontconfig::SetupSkFontDrawOptions(SkFont& aFont) { aFont.setSubpixel(UseSubpixelPosition()); if (mInstanceData.mFlags & InstanceData::AUTOHINT) { aFont.setForceAutoHinting(true); } if (mInstanceData.mFlags & InstanceData::EMBEDDED_BITMAP) { aFont.setEmbeddedBitmaps(true); } if (mInstanceData.mFlags & InstanceData::EMBOLDEN) { aFont.setEmbolden(true); } aFont.setHinting(GfxHintingToSkiaHinting(mInstanceData.mHinting)); } bool ScaledFontFontconfig::MayUseBitmaps() { return mInstanceData.mFlags & InstanceData::EMBEDDED_BITMAP && !FT_IS_SCALABLE(mFace->GetFace()); } cairo_font_face_t* ScaledFontFontconfig::CreateCairoFontFace( cairo_font_options_t* aFontOptions) { int loadFlags; unsigned int synthFlags; mInstanceData.SetupFontOptions(aFontOptions, &loadFlags, &synthFlags); return cairo_ft_font_face_create_for_ft_face(mFace->GetFace(), loadFlags, synthFlags, mFace.get()); } AntialiasMode ScaledFontFontconfig::GetDefaultAAMode() { return mInstanceData.mAntialias; } bool FcPatternAllowsBitmaps(FcPattern* aPattern, bool aAntialias, bool aHinting) { if (!aAntialias) { // Always allow bitmaps when antialiasing is disabled return true; } FcBool bitmap; if (FcPatternGetBool(aPattern, FC_EMBEDDED_BITMAP, 0, &bitmap) != FcResultMatch || !bitmap) { // If bitmaps were explicitly disabled, then disallow them return false; } if (aHinting) { // If hinting is used and bitmaps were enabled, then allow them return true; } // When hinting is disabled, then avoid loading bitmaps from outline // fonts. However, emoji fonts may have no outlines while containing // bitmaps intended to be scaled, so still allow those. FcBool outline; if (FcPatternGetBool(aPattern, FC_OUTLINE, 0, &outline) == FcResultMatch && outline) { return false; } FcBool scalable; if (FcPatternGetBool(aPattern, FC_SCALABLE, 0, &scalable) != FcResultMatch || !scalable) { return false; } return true; } ScaledFontFontconfig::InstanceData::InstanceData(FcPattern* aPattern) : mFlags(0), mAntialias(AntialiasMode::NONE), mHinting(FontHinting::NONE), mLcdFilter(FT_LCD_FILTER_LEGACY) { // Record relevant Fontconfig properties into instance data. FcBool autohint; if (FcPatternGetBool(aPattern, FC_AUTOHINT, 0, &autohint) == FcResultMatch && autohint) { mFlags |= AUTOHINT; } FcBool embolden; if (FcPatternGetBool(aPattern, FC_EMBOLDEN, 0, &embolden) == FcResultMatch && embolden) { mFlags |= EMBOLDEN; } // For printer fonts, Cairo hint metrics and hinting will be disabled. // For other fonts, allow hint metrics and hinting. FcBool printing; if (FcPatternGetBool(aPattern, "gfx.printing", 0, &printing) != FcResultMatch || !printing) { mFlags |= HINT_METRICS; FcBool hinting; if (FcPatternGetBool(aPattern, FC_HINTING, 0, &hinting) != FcResultMatch || hinting) { int hintstyle; if (FcPatternGetInteger(aPattern, FC_HINT_STYLE, 0, &hintstyle) != FcResultMatch) { hintstyle = FC_HINT_FULL; } switch (hintstyle) { case FC_HINT_SLIGHT: mHinting = FontHinting::LIGHT; break; case FC_HINT_MEDIUM: mHinting = FontHinting::NORMAL; break; case FC_HINT_FULL: mHinting = FontHinting::FULL; break; case FC_HINT_NONE: default: break; } } } FcBool antialias; if (FcPatternGetBool(aPattern, FC_ANTIALIAS, 0, &antialias) == FcResultMatch && !antialias) { // If AA is explicitly disabled, leave bitmaps enabled. mFlags |= EMBEDDED_BITMAP; } else { mAntialias = AntialiasMode::GRAY; // Otherwise, if AA is enabled, disable embedded bitmaps unless explicitly // enabled. if (FcPatternAllowsBitmaps(aPattern, true, mHinting != FontHinting::NONE)) { mFlags |= EMBEDDED_BITMAP; } // Only record subpixel order and lcd filtering if antialiasing is enabled. int rgba; if (mFlags & HINT_METRICS && FcPatternGetInteger(aPattern, FC_RGBA, 0, &rgba) == FcResultMatch) { switch (rgba) { case FC_RGBA_RGB: case FC_RGBA_BGR: case FC_RGBA_VRGB: case FC_RGBA_VBGR: mAntialias = AntialiasMode::SUBPIXEL; if (rgba == FC_RGBA_VRGB || rgba == FC_RGBA_VBGR) { mFlags |= LCD_VERTICAL; } if (rgba == FC_RGBA_BGR || rgba == FC_RGBA_VBGR) { mFlags |= SUBPIXEL_BGR; } break; case FC_RGBA_NONE: case FC_RGBA_UNKNOWN: default: break; } } int filter; if (mAntialias == AntialiasMode::SUBPIXEL && FcPatternGetInteger(aPattern, FC_LCD_FILTER, 0, &filter) == FcResultMatch) { switch (filter) { case FC_LCD_NONE: mLcdFilter = FT_LCD_FILTER_NONE; break; case FC_LCD_DEFAULT: mLcdFilter = FT_LCD_FILTER_DEFAULT; break; case FC_LCD_LIGHT: mLcdFilter = FT_LCD_FILTER_LIGHT; break; case FC_LCD_LEGACY: default: break; } } } } ScaledFontFontconfig::InstanceData::InstanceData( const wr::FontInstanceOptions* aOptions, const wr::FontInstancePlatformOptions* aPlatformOptions) : mFlags(HINT_METRICS), mAntialias(AntialiasMode::NONE), mHinting(FontHinting::FULL), mLcdFilter(FT_LCD_FILTER_LEGACY) { if (aOptions) { if (aOptions->flags & wr::FontInstanceFlags::FORCE_AUTOHINT) { mFlags |= AUTOHINT; } if (aOptions->flags & wr::FontInstanceFlags::EMBEDDED_BITMAPS) { mFlags |= EMBEDDED_BITMAP; } if (aOptions->flags & wr::FontInstanceFlags::SYNTHETIC_BOLD) { mFlags |= EMBOLDEN; } if (aOptions->render_mode == wr::FontRenderMode::Subpixel) { mAntialias = AntialiasMode::SUBPIXEL; if (aOptions->flags & wr::FontInstanceFlags::SUBPIXEL_BGR) { mFlags |= SUBPIXEL_BGR; } if (aOptions->flags & wr::FontInstanceFlags::LCD_VERTICAL) { mFlags |= LCD_VERTICAL; } } else if (aOptions->render_mode != wr::FontRenderMode::Mono) { mAntialias = AntialiasMode::GRAY; } } if (aPlatformOptions) { switch (aPlatformOptions->hinting) { case wr::FontHinting::None: mHinting = FontHinting::NONE; break; case wr::FontHinting::Light: mHinting = FontHinting::LIGHT; break; case wr::FontHinting::Normal: mHinting = FontHinting::NORMAL; break; default: break; } switch (aPlatformOptions->lcd_filter) { case wr::FontLCDFilter::None: mLcdFilter = FT_LCD_FILTER_NONE; break; case wr::FontLCDFilter::Default: mLcdFilter = FT_LCD_FILTER_DEFAULT; break; case wr::FontLCDFilter::Light: mLcdFilter = FT_LCD_FILTER_LIGHT; break; default: break; } } } void ScaledFontFontconfig::InstanceData::SetupFontOptions( cairo_font_options_t* aFontOptions, int* aOutLoadFlags, unsigned int* aOutSynthFlags) const { // For regular (non-printer) fonts, enable hint metrics as well as hinting // and (possibly subpixel) antialiasing. cairo_font_options_set_hint_metrics( aFontOptions, mFlags & HINT_METRICS ? CAIRO_HINT_METRICS_ON : CAIRO_HINT_METRICS_OFF); cairo_hint_style_t hinting; switch (mHinting) { case FontHinting::NONE: hinting = CAIRO_HINT_STYLE_NONE; break; case FontHinting::LIGHT: hinting = CAIRO_HINT_STYLE_SLIGHT; break; case FontHinting::NORMAL: hinting = CAIRO_HINT_STYLE_MEDIUM; break; case FontHinting::FULL: hinting = CAIRO_HINT_STYLE_FULL; break; } cairo_font_options_set_hint_style(aFontOptions, hinting); switch (mAntialias) { case AntialiasMode::NONE: cairo_font_options_set_antialias(aFontOptions, CAIRO_ANTIALIAS_NONE); break; case AntialiasMode::GRAY: default: cairo_font_options_set_antialias(aFontOptions, CAIRO_ANTIALIAS_GRAY); break; case AntialiasMode::SUBPIXEL: { cairo_font_options_set_antialias(aFontOptions, CAIRO_ANTIALIAS_SUBPIXEL); cairo_font_options_set_subpixel_order( aFontOptions, mFlags & SUBPIXEL_BGR ? (mFlags & LCD_VERTICAL ? CAIRO_SUBPIXEL_ORDER_VBGR : CAIRO_SUBPIXEL_ORDER_BGR) : (mFlags & LCD_VERTICAL ? CAIRO_SUBPIXEL_ORDER_VRGB : CAIRO_SUBPIXEL_ORDER_RGB)); cairo_lcd_filter_t lcdFilter = CAIRO_LCD_FILTER_DEFAULT; switch (mLcdFilter) { case FT_LCD_FILTER_NONE: lcdFilter = CAIRO_LCD_FILTER_NONE; break; case FT_LCD_FILTER_DEFAULT: lcdFilter = CAIRO_LCD_FILTER_FIR5; break; case FT_LCD_FILTER_LIGHT: lcdFilter = CAIRO_LCD_FILTER_FIR3; break; case FT_LCD_FILTER_LEGACY: lcdFilter = CAIRO_LCD_FILTER_INTRA_PIXEL; break; } cairo_font_options_set_lcd_filter(aFontOptions, lcdFilter); break; } } // Try to build a sane initial set of Cairo font options based on the // Fontconfig pattern. int loadFlags = FT_LOAD_DEFAULT; unsigned int synthFlags = 0; if (!(mFlags & EMBEDDED_BITMAP)) { loadFlags |= FT_LOAD_NO_BITMAP; } if (mFlags & AUTOHINT) { loadFlags |= FT_LOAD_FORCE_AUTOHINT; } if (mFlags & EMBOLDEN) { synthFlags |= CAIRO_FT_SYNTHESIZE_BOLD; } *aOutLoadFlags = loadFlags; *aOutSynthFlags = synthFlags; } bool ScaledFontFontconfig::GetFontInstanceData(FontInstanceDataOutput aCb, void* aBaton) { std::vector variations; if (HasVariationSettings()) { UnscaledFontFreeType::GetVariationSettingsFromFace(&variations, mFace->GetFace()); } aCb(reinterpret_cast(&mInstanceData), sizeof(mInstanceData), variations.data(), variations.size(), aBaton); return true; } bool ScaledFontFontconfig::GetWRFontInstanceOptions( Maybe* aOutOptions, Maybe* aOutPlatformOptions, std::vector* aOutVariations) { wr::FontInstanceOptions options; options.render_mode = wr::FontRenderMode::Alpha; options.flags = wr::FontInstanceFlags{0}; if (UseSubpixelPosition()) { options.flags |= wr::FontInstanceFlags::SUBPIXEL_POSITION; } options.bg_color = wr::ToColorU(DeviceColor()); options.synthetic_italics = wr::DegreesToSyntheticItalics(GetSyntheticObliqueAngle()); wr::FontInstancePlatformOptions platformOptions; platformOptions.lcd_filter = wr::FontLCDFilter::Legacy; platformOptions.hinting = wr::FontHinting::Normal; if (mInstanceData.mFlags & InstanceData::AUTOHINT) { options.flags |= wr::FontInstanceFlags::FORCE_AUTOHINT; } if (mInstanceData.mFlags & InstanceData::EMBOLDEN) { options.flags |= wr::FontInstanceFlags::SYNTHETIC_BOLD; } if (mInstanceData.mFlags & InstanceData::EMBEDDED_BITMAP) { options.flags |= wr::FontInstanceFlags::EMBEDDED_BITMAPS; } if (mInstanceData.mAntialias != AntialiasMode::NONE) { if (mInstanceData.mAntialias == AntialiasMode::SUBPIXEL) { options.render_mode = wr::FontRenderMode::Subpixel; platformOptions.hinting = wr::FontHinting::LCD; if (mInstanceData.mFlags & InstanceData::LCD_VERTICAL) { options.flags |= wr::FontInstanceFlags::LCD_VERTICAL; } if (mInstanceData.mFlags & InstanceData::SUBPIXEL_BGR) { options.flags |= wr::FontInstanceFlags::SUBPIXEL_BGR; } } switch (mInstanceData.mLcdFilter) { case FT_LCD_FILTER_NONE: platformOptions.lcd_filter = wr::FontLCDFilter::None; break; case FT_LCD_FILTER_DEFAULT: platformOptions.lcd_filter = wr::FontLCDFilter::Default; break; case FT_LCD_FILTER_LIGHT: platformOptions.lcd_filter = wr::FontLCDFilter::Light; break; case FT_LCD_FILTER_LEGACY: default: break; } switch (mInstanceData.mHinting) { case FontHinting::NONE: platformOptions.hinting = wr::FontHinting::None; break; case FontHinting::LIGHT: platformOptions.hinting = wr::FontHinting::Light; break; case FontHinting::NORMAL: platformOptions.hinting = wr::FontHinting::Normal; break; case FontHinting::FULL: break; } } else { options.render_mode = wr::FontRenderMode::Mono; switch (mInstanceData.mHinting) { case FontHinting::NONE: platformOptions.hinting = wr::FontHinting::None; break; default: platformOptions.hinting = wr::FontHinting::Mono; break; } } *aOutOptions = Some(options); *aOutPlatformOptions = Some(platformOptions); if (HasVariationSettings()) { UnscaledFontFreeType::GetVariationSettingsFromFace(aOutVariations, mFace->GetFace()); } return true; } already_AddRefed UnscaledFontFontconfig::CreateScaledFont( Float aSize, const uint8_t* aInstanceData, uint32_t aInstanceDataLength, const FontVariation* aVariations, uint32_t aNumVariations) { if (aInstanceDataLength < sizeof(ScaledFontFontconfig::InstanceData)) { gfxWarning() << "Fontconfig scaled font instance data is truncated."; return nullptr; } const ScaledFontFontconfig::InstanceData& instanceData = *reinterpret_cast( aInstanceData); RefPtr face(InitFace()); if (!face) { gfxWarning() << "Attempted to deserialize Fontconfig scaled font without " "FreeType face"; return nullptr; } if (aNumVariations > 0 && face->GetData()) { if (RefPtr varFace = face->GetData()->CloneFace()) { face = varFace; } } // Only apply variations if we have an explicitly cloned face. if (aNumVariations > 0 && face != GetFace()) { ApplyVariationsToFace(aVariations, aNumVariations, face->GetFace()); } RefPtr scaledFont = new ScaledFontFontconfig(std::move(face), instanceData, this, aSize); return scaledFont.forget(); } already_AddRefed UnscaledFontFontconfig::CreateScaledFontFromWRFont( Float aGlyphSize, const wr::FontInstanceOptions* aOptions, const wr::FontInstancePlatformOptions* aPlatformOptions, const FontVariation* aVariations, uint32_t aNumVariations) { ScaledFontFontconfig::InstanceData instanceData(aOptions, aPlatformOptions); return CreateScaledFont(aGlyphSize, reinterpret_cast(&instanceData), sizeof(instanceData), aVariations, aNumVariations); } bool ScaledFontFontconfig::HasVariationSettings() { // Check if the FT face has been cloned. return mFace && mFace->GetFace()->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS && mFace != static_cast(mUnscaledFont.get()) ->GetFace(); } already_AddRefed UnscaledFontFontconfig::CreateFromFontDescriptor( const uint8_t* aData, uint32_t aDataLength, uint32_t aIndex) { if (aDataLength == 0) { gfxWarning() << "Fontconfig font descriptor is truncated."; return nullptr; } const char* path = reinterpret_cast(aData); RefPtr unscaledFont = new UnscaledFontFontconfig(std::string(path, aDataLength), aIndex); return unscaledFont.forget(); } } // namespace mozilla::gfx