Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* 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 "gfxDWriteFonts.h"
#include <algorithm>
#include "gfxDWriteFontList.h"
#include "gfxContext.h"
#include "gfxHarfBuzzShaper.h"
#include "gfxTextRun.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/DWriteSettings.h"
#include "mozilla/gfx/Logging.h"
#include "mozilla/gfx/gfxVars.h"
#include "mozilla/Preferences.h"
#include "harfbuzz/hb.h"
#include "mozilla/FontPropertyTypes.h"
using namespace mozilla;
using namespace mozilla::gfx;
// Code to determine whether Windows is set to use ClearType font smoothing;
// based on private functions in cairo-win32-font.c
#ifndef SPI_GETFONTSMOOTHINGTYPE
# define SPI_GETFONTSMOOTHINGTYPE 0x200a
#endif
#ifndef FE_FONTSMOOTHINGCLEARTYPE
# define FE_FONTSMOOTHINGCLEARTYPE 2
#endif
// Cleartype can be dynamically enabled/disabled, so we have to allow for
// dynamically updating it.
static BYTE GetSystemTextQuality() {
BOOL font_smoothing;
UINT smoothing_type;
if (!SystemParametersInfo(SPI_GETFONTSMOOTHING, 0, &font_smoothing, 0)) {
return DEFAULT_QUALITY;
}
if (font_smoothing) {
if (!SystemParametersInfo(SPI_GETFONTSMOOTHINGTYPE, 0, &smoothing_type,
0)) {
return DEFAULT_QUALITY;
}
if (smoothing_type == FE_FONTSMOOTHINGCLEARTYPE) {
return CLEARTYPE_QUALITY;
}
return ANTIALIASED_QUALITY;
}
return DEFAULT_QUALITY;
}
#ifndef SPI_GETFONTSMOOTHINGCONTRAST
# define SPI_GETFONTSMOOTHINGCONTRAST 0x200c
#endif
// "Retrieves a contrast value that is used in ClearType smoothing. Valid
// contrast values are from 1000 to 2200. The default value is 1400."
static FLOAT GetSystemGDIGamma() {
UINT value = 0;
if (!SystemParametersInfo(SPI_GETFONTSMOOTHINGCONTRAST, 0, &value, 0) ||
value < 1000 || value > 2200) {
value = 1400;
}
return value / 1000.0f;
}
////////////////////////////////////////////////////////////////////////////////
// gfxDWriteFont
gfxDWriteFont::gfxDWriteFont(const RefPtr<UnscaledFontDWrite>& aUnscaledFont,
gfxFontEntry* aFontEntry,
const gfxFontStyle* aFontStyle,
RefPtr<IDWriteFontFace> aFontFace,
AntialiasOption anAAOption)
: gfxFont(aUnscaledFont, aFontEntry, aFontStyle, anAAOption),
mFontFace(aFontFace ? aFontFace : aUnscaledFont->GetFontFace()),
mUseSubpixelPositions(false),
mAllowManualShowGlyphs(true),
mAzureScaledFontUsedClearType(false) {
// If the IDWriteFontFace1 interface is available, we can use that for
// faster glyph width retrieval.
mFontFace->QueryInterface(__uuidof(IDWriteFontFace1),
(void**)getter_AddRefs(mFontFace1));
// If a fake-bold effect is needed, determine whether we're using DWrite's
// "simulation" or applying our multi-strike "synthetic bold".
if (aFontStyle->NeedsSyntheticBold(aFontEntry)) {
switch (StaticPrefs::gfx_font_rendering_directwrite_bold_simulation()) {
case 0: // never use the DWrite simulation
mApplySyntheticBold = true;
break;
case 1: // use DWrite simulation for installed fonts but not webfonts
mApplySyntheticBold = aFontEntry->mIsDataUserFont;
break;
default: // always use DWrite bold simulation
// the flag is initialized to false in gfxFont
break;
}
}
ComputeMetrics(anAAOption);
}
gfxDWriteFont::~gfxDWriteFont() {
if (auto* scaledFont = mAzureScaledFontGDI.exchange(nullptr)) {
scaledFont->Release();
}
}
/* static */
bool gfxDWriteFont::InitDWriteSupport() {
if (!Factory::EnsureDWriteFactory()) {
return false;
}
if (XRE_IsParentProcess()) {
UpdateSystemTextVars();
} else {
// UpdateClearTypeVars doesn't update the vars in non parent processes, but
// it does set sForceGDIClassicEnabled so we still need to call it.
UpdateClearTypeVars();
}
DWriteSettings::Initialize();
return true;
}
/* static */
void gfxDWriteFont::UpdateSystemTextVars() {
MOZ_ASSERT(XRE_IsParentProcess());
BYTE newQuality = GetSystemTextQuality();
if (gfxVars::SystemTextQuality() != newQuality) {
gfxVars::SetSystemTextQuality(newQuality);
}
FLOAT newGDIGamma = GetSystemGDIGamma();
if (gfxVars::SystemGDIGamma() != newGDIGamma) {
gfxVars::SetSystemGDIGamma(newGDIGamma);
}
UpdateClearTypeVars();
}
void gfxDWriteFont::SystemTextQualityChanged() {
// If ClearType status has changed, update our value,
Factory::SetSystemTextQuality(gfxVars::SystemTextQuality());
// flush cached stuff that depended on the old setting, and force
// reflow everywhere to ensure we are using correct glyph metrics.
gfxPlatform::FlushFontAndWordCaches();
gfxPlatform::ForceGlobalReflow(gfxPlatform::NeedsReframe::No);
}
mozilla::Atomic<bool> gfxDWriteFont::sForceGDIClassicEnabled{true};
/* static */
void gfxDWriteFont::UpdateClearTypeVars() {
// We don't force GDI classic if the cleartype rendering mode pref is set to
// something valid.
int32_t renderingModePref =
Preferences::GetInt(GFX_CLEARTYPE_PARAMS_MODE, -1);
if (renderingModePref < 0 || renderingModePref > 5) {
renderingModePref = -1;
}
sForceGDIClassicEnabled = (renderingModePref == -1);
if (!XRE_IsParentProcess()) {
return;
}
if (!Factory::GetDWriteFactory()) {
return;
}
// First set sensible hard coded defaults.
float clearTypeLevel = 1.0f;
float enhancedContrast = 1.0f;
float gamma = 2.2f;
int pixelGeometry = DWRITE_PIXEL_GEOMETRY_RGB;
int renderingMode = DWRITE_RENDERING_MODE_DEFAULT;
// Override these from DWrite function if available.
RefPtr<IDWriteRenderingParams> defaultRenderingParams;
HRESULT hr = Factory::GetDWriteFactory()->CreateRenderingParams(
getter_AddRefs(defaultRenderingParams));
if (SUCCEEDED(hr) && defaultRenderingParams) {
clearTypeLevel = defaultRenderingParams->GetClearTypeLevel();
// For enhanced contrast, we only use the default if the user has set it
// in the registry (by using the ClearType Tuner).
// XXXbobowen it seems slightly odd that we do this and only for enhanced
// contrast, but this reproduces previous functionality from
// gfxWindowsPlatform::SetupClearTypeParams.
HKEY hKey;
LONG res = RegOpenKeyExW(DISPLAY1_REGISTRY_KEY, 0, KEY_READ, &hKey);
if (res == ERROR_SUCCESS) {
res = RegQueryValueExW(hKey, ENHANCED_CONTRAST_VALUE_NAME, nullptr,
nullptr, nullptr, nullptr);
if (res == ERROR_SUCCESS) {
enhancedContrast = defaultRenderingParams->GetEnhancedContrast();
}
RegCloseKey(hKey);
}
gamma = defaultRenderingParams->GetGamma();
pixelGeometry = defaultRenderingParams->GetPixelGeometry();
renderingMode = defaultRenderingParams->GetRenderingMode();
} else {
gfxWarning() << "Failed to create default rendering params";
}
// Finally override from prefs if valid values are set. If ClearType is
// turned off we just use the default params, this reproduces the previous
// functionality that was spread across gfxDWriteFont::GetScaledFont and
// gfxWindowsPlatform::SetupClearTypeParams, but it seems odd because the
// default params will still be the ClearType ones although we won't use the
// anti-alias for ClearType because of GetSystemDefaultAAMode.
if (gfxVars::SystemTextQuality() == CLEARTYPE_QUALITY) {
int32_t prefInt = Preferences::GetInt(GFX_CLEARTYPE_PARAMS_LEVEL, -1);
if (prefInt >= 0 && prefInt <= 100) {
clearTypeLevel = float(prefInt / 100.0);
}
prefInt = Preferences::GetInt(GFX_CLEARTYPE_PARAMS_CONTRAST, -1);
if (prefInt >= 0 && prefInt <= 1000) {
enhancedContrast = float(prefInt / 100.0);
}
prefInt = Preferences::GetInt(GFX_CLEARTYPE_PARAMS_GAMMA, -1);
if (prefInt >= 1000 && prefInt <= 2200) {
gamma = float(prefInt / 1000.0);
}
prefInt = Preferences::GetInt(GFX_CLEARTYPE_PARAMS_STRUCTURE, -1);
if (prefInt >= 0 && prefInt <= 2) {
pixelGeometry = prefInt;
}
// renderingModePref is retrieved and validated above.
if (renderingModePref != -1) {
renderingMode = renderingModePref;
}
}
if (gfxVars::SystemTextClearTypeLevel() != clearTypeLevel) {
gfxVars::SetSystemTextClearTypeLevel(clearTypeLevel);
}
if (gfxVars::SystemTextEnhancedContrast() != enhancedContrast) {
gfxVars::SetSystemTextEnhancedContrast(enhancedContrast);
}
if (gfxVars::SystemTextGamma() != gamma) {
gfxVars::SetSystemTextGamma(gamma);
}
if (gfxVars::SystemTextPixelGeometry() != pixelGeometry) {
gfxVars::SetSystemTextPixelGeometry(pixelGeometry);
}
if (gfxVars::SystemTextRenderingMode() != renderingMode) {
gfxVars::SetSystemTextRenderingMode(renderingMode);
}
// Set cairo dwrite params in the parent process where it might still be
// needed for printing. We use the validated pref int directly for rendering
// mode, because a negative (i.e. not set) rendering mode is also used for
// deciding on forcing GDI in cairo.
cairo_dwrite_set_cleartype_params(gamma, enhancedContrast, clearTypeLevel,
pixelGeometry, renderingModePref);
}
gfxFont* gfxDWriteFont::CopyWithAntialiasOption(
AntialiasOption anAAOption) const {
auto entry = static_cast<gfxDWriteFontEntry*>(mFontEntry.get());
RefPtr<UnscaledFontDWrite> unscaledFont =
static_cast<UnscaledFontDWrite*>(mUnscaledFont.get());
return new gfxDWriteFont(unscaledFont, entry, &mStyle, mFontFace, anAAOption);
}
bool gfxDWriteFont::GetFakeMetricsForArialBlack(
DWRITE_FONT_METRICS* aFontMetrics) {
gfxFontStyle style(mStyle);
style.weight = FontWeight::FromInt(700);
gfxFontEntry* fe = gfxPlatformFontList::PlatformFontList()->FindFontForFamily(
nullptr, "Arial"_ns, &style);
if (!fe || fe == mFontEntry) {
return false;
}
RefPtr<gfxFont> font = fe->FindOrMakeFont(&style);
gfxDWriteFont* dwFont = static_cast<gfxDWriteFont*>(font.get());
dwFont->mFontFace->GetMetrics(aFontMetrics);
return true;
}
void gfxDWriteFont::ComputeMetrics(AntialiasOption anAAOption) {
::memset(&mMetrics, 0, sizeof(mMetrics));
DWRITE_FONT_METRICS fontMetrics;
if (!(mFontEntry->Weight().Min() == FontWeight::FromInt(900) &&
mFontEntry->Weight().Max() == FontWeight::FromInt(900) &&
!mFontEntry->IsUserFont() &&
mFontEntry->Name().EqualsLiteral("Arial Black") &&
GetFakeMetricsForArialBlack(&fontMetrics))) {
mFontFace->GetMetrics(&fontMetrics);
}
if (GetAdjustedSize() > 0.0 && mStyle.sizeAdjust >= 0.0 &&
FontSizeAdjust::Tag(mStyle.sizeAdjustBasis) !=
FontSizeAdjust::Tag::None) {
// For accurate measurement during the font-size-adjust computations;
// these may be reset later according to the adjusted size.
mUseSubpixelPositions = true;
mFUnitsConvFactor = float(mAdjustedSize / fontMetrics.designUnitsPerEm);
gfxFloat aspect;
switch (FontSizeAdjust::Tag(mStyle.sizeAdjustBasis)) {
default:
MOZ_ASSERT_UNREACHABLE("unhandled sizeAdjustBasis?");
aspect = 0.0;
break;
case FontSizeAdjust::Tag::ExHeight:
aspect = (gfxFloat)fontMetrics.xHeight / fontMetrics.designUnitsPerEm;
break;
case FontSizeAdjust::Tag::CapHeight:
aspect = (gfxFloat)fontMetrics.capHeight / fontMetrics.designUnitsPerEm;
break;
case FontSizeAdjust::Tag::ChWidth: {
gfxFloat advance = GetCharAdvance('0');
aspect = advance > 0.0 ? advance / mAdjustedSize : 0.5;
break;
}
case FontSizeAdjust::Tag::IcWidth:
case FontSizeAdjust::Tag::IcHeight: {
bool vertical = FontSizeAdjust::Tag(mStyle.sizeAdjustBasis) ==
FontSizeAdjust::Tag::IcHeight;
gfxFloat advance = GetCharAdvance(kWaterIdeograph, vertical);
aspect = advance > 0.0 ? advance / mAdjustedSize : 1.0;
break;
}
}
if (aspect > 0.0) {
// If we created a shaper above (to measure glyphs), discard it so we
// get a new one for the adjusted scaling.
delete mHarfBuzzShaper.exchange(nullptr);
mAdjustedSize = mStyle.GetAdjustedSize(aspect);
}
}
// Update now that we've adjusted the size if necessary.
mFUnitsConvFactor = float(mAdjustedSize / fontMetrics.designUnitsPerEm);
// Note that GetMeasuringMode depends on mAdjustedSize
if ((anAAOption == gfxFont::kAntialiasDefault && UsingClearType() &&
GetMeasuringMode() == DWRITE_MEASURING_MODE_NATURAL) ||
anAAOption == gfxFont::kAntialiasSubpixel) {
mUseSubpixelPositions = true;
// note that this may be reset to FALSE if we determine that a bitmap
// strike is going to be used
} else {
mUseSubpixelPositions = false;
}
gfxDWriteFontEntry* fe = static_cast<gfxDWriteFontEntry*>(mFontEntry.get());
if (fe->IsCJKFont() && HasBitmapStrikeForSize(NS_lround(mAdjustedSize))) {
mAdjustedSize = NS_lround(mAdjustedSize);
mUseSubpixelPositions = false;
// if we have bitmaps, we need to tell Cairo NOT to use subpixel AA,
// to avoid the manual-subpixel codepath in cairo-d2d-surface.cpp
// which fails to render bitmap glyphs (see bug 626299).
// This option will be passed to the cairo_dwrite_scaled_font_t
// after creation.
mAllowManualShowGlyphs = false;
}
mMetrics.xHeight = fontMetrics.xHeight * mFUnitsConvFactor;
mMetrics.capHeight = fontMetrics.capHeight * mFUnitsConvFactor;
mMetrics.maxAscent = round(fontMetrics.ascent * mFUnitsConvFactor);
mMetrics.maxDescent = round(fontMetrics.descent * mFUnitsConvFactor);
mMetrics.maxHeight = mMetrics.maxAscent + mMetrics.maxDescent;
mMetrics.emHeight = mAdjustedSize;
mMetrics.emAscent =
mMetrics.emHeight * mMetrics.maxAscent / mMetrics.maxHeight;
mMetrics.emDescent = mMetrics.emHeight - mMetrics.emAscent;
mMetrics.maxAdvance = mAdjustedSize;
// try to get the true maxAdvance value from 'hhea'
gfxFontEntry::AutoTable hheaTable(GetFontEntry(),
TRUETYPE_TAG('h', 'h', 'e', 'a'));
if (hheaTable) {
uint32_t len;
const MetricsHeader* hhea = reinterpret_cast<const MetricsHeader*>(
hb_blob_get_data(hheaTable, &len));
if (len >= sizeof(MetricsHeader)) {
mMetrics.maxAdvance = uint16_t(hhea->advanceWidthMax) * mFUnitsConvFactor;
}
}
mMetrics.internalLeading =
std::max(mMetrics.maxHeight - mMetrics.emHeight, 0.0);
mMetrics.externalLeading = ceil(fontMetrics.lineGap * mFUnitsConvFactor);
UINT32 ucs = L' ';
UINT16 glyph;
if (SUCCEEDED(mFontFace->GetGlyphIndices(&ucs, 1, &glyph)) && glyph != 0) {
mSpaceGlyph = glyph;
mMetrics.spaceWidth = MeasureGlyphWidth(glyph);
} else {
mMetrics.spaceWidth = 0;
}
// try to get aveCharWidth from the OS/2 table, fall back to measuring 'x'
// if the table is not available or if using hinted/pixel-snapped widths
if (mUseSubpixelPositions) {
mMetrics.aveCharWidth = 0;
gfxFontEntry::AutoTable os2Table(GetFontEntry(),
TRUETYPE_TAG('O', 'S', '/', '2'));
if (os2Table) {
uint32_t len;
const OS2Table* os2 =
reinterpret_cast<const OS2Table*>(hb_blob_get_data(os2Table, &len));
if (len >= 4) {
// Not checking against sizeof(mozilla::OS2Table) here because older
// versions of the table have different sizes; we only need the first
// two 16-bit fields here.
mMetrics.aveCharWidth = int16_t(os2->xAvgCharWidth) * mFUnitsConvFactor;
}
}
}
if (mMetrics.aveCharWidth < 1) {
mMetrics.aveCharWidth = GetCharAdvance('x');
if (mMetrics.aveCharWidth < 1) {
// Let's just assume the X is square.
mMetrics.aveCharWidth = fontMetrics.xHeight * mFUnitsConvFactor;
}
}
mMetrics.zeroWidth = GetCharAdvance('0');
mMetrics.ideographicWidth = GetCharAdvance(kWaterIdeograph);
mMetrics.underlineOffset = fontMetrics.underlinePosition * mFUnitsConvFactor;
mMetrics.underlineSize = fontMetrics.underlineThickness * mFUnitsConvFactor;
mMetrics.strikeoutOffset =
fontMetrics.strikethroughPosition * mFUnitsConvFactor;
mMetrics.strikeoutSize =
fontMetrics.strikethroughThickness * mFUnitsConvFactor;
SanitizeMetrics(&mMetrics, GetFontEntry()->mIsBadUnderlineFont);
if (ApplySyntheticBold()) {
auto delta = GetSyntheticBoldOffset();
mMetrics.spaceWidth += delta;
mMetrics.aveCharWidth += delta;
mMetrics.maxAdvance += delta;
if (mMetrics.zeroWidth > 0) {
mMetrics.zeroWidth += delta;
}
if (mMetrics.ideographicWidth > 0) {
mMetrics.ideographicWidth += delta;
}
}
#if 0
printf("Font: %p (%s) size: %f\n", this,
NS_ConvertUTF16toUTF8(GetName()).get(), mStyle.size);
printf(" emHeight: %f emAscent: %f emDescent: %f\n", mMetrics.emHeight, mMetrics.emAscent, mMetrics.emDescent);
printf(" maxAscent: %f maxDescent: %f maxAdvance: %f\n", mMetrics.maxAscent, mMetrics.maxDescent, mMetrics.maxAdvance);
printf(" internalLeading: %f externalLeading: %f\n", mMetrics.internalLeading, mMetrics.externalLeading);
printf(" spaceWidth: %f aveCharWidth: %f zeroWidth: %f\n",
mMetrics.spaceWidth, mMetrics.aveCharWidth, mMetrics.zeroWidth);
printf(" xHeight: %f capHeight: %f\n", mMetrics.xHeight, mMetrics.capHeight);
printf(" uOff: %f uSize: %f stOff: %f stSize: %f\n",
mMetrics.underlineOffset, mMetrics.underlineSize, mMetrics.strikeoutOffset, mMetrics.strikeoutSize);
#endif
}
using namespace mozilla; // for AutoSwap_* types
struct EBLCHeader {
AutoSwap_PRUint32 version;
AutoSwap_PRUint32 numSizes;
};
struct SbitLineMetrics {
int8_t ascender;
int8_t descender;
uint8_t widthMax;
int8_t caretSlopeNumerator;
int8_t caretSlopeDenominator;
int8_t caretOffset;
int8_t minOriginSB;
int8_t minAdvanceSB;
int8_t maxBeforeBL;
int8_t minAfterBL;
int8_t pad1;
int8_t pad2;
};
struct BitmapSizeTable {
AutoSwap_PRUint32 indexSubTableArrayOffset;
AutoSwap_PRUint32 indexTablesSize;
AutoSwap_PRUint32 numberOfIndexSubTables;
AutoSwap_PRUint32 colorRef;
SbitLineMetrics hori;
SbitLineMetrics vert;
AutoSwap_PRUint16 startGlyphIndex;
AutoSwap_PRUint16 endGlyphIndex;
uint8_t ppemX;
uint8_t ppemY;
uint8_t bitDepth;
uint8_t flags;
};
typedef EBLCHeader EBSCHeader;
struct BitmapScaleTable {
SbitLineMetrics hori;
SbitLineMetrics vert;
uint8_t ppemX;
uint8_t ppemY;
uint8_t substitutePpemX;
uint8_t substitutePpemY;
};
bool gfxDWriteFont::HasBitmapStrikeForSize(uint32_t aSize) {
uint8_t* tableData;
uint32_t len;
void* tableContext;
BOOL exists;
HRESULT hr = mFontFace->TryGetFontTable(
DWRITE_MAKE_OPENTYPE_TAG('E', 'B', 'L', 'C'), (const void**)&tableData,
&len, &tableContext, &exists);
if (FAILED(hr)) {
return false;
}
bool hasStrike = false;
// not really a loop, but this lets us use 'break' to skip out of the block
// as soon as we know the answer, and skips it altogether if the table is
// not present
while (exists) {
if (len < sizeof(EBLCHeader)) {
break;
}
const EBLCHeader* hdr = reinterpret_cast<const EBLCHeader*>(tableData);
if (hdr->version != 0x00020000) {
break;
}
uint32_t numSizes = hdr->numSizes;
if (numSizes > 0xffff) { // sanity-check, prevent overflow below
break;
}
if (len < sizeof(EBLCHeader) + numSizes * sizeof(BitmapSizeTable)) {
break;
}
const BitmapSizeTable* sizeTable =
reinterpret_cast<const BitmapSizeTable*>(hdr + 1);
for (uint32_t i = 0; i < numSizes; ++i, ++sizeTable) {
if (sizeTable->ppemX == aSize && sizeTable->ppemY == aSize) {
// we ignore a strike that contains fewer than 4 glyphs,
// as that probably indicates a font such as Courier New
// that provides bitmaps ONLY for the "shading" characters
// U+2591..2593
hasStrike = (uint16_t(sizeTable->endGlyphIndex) >=
uint16_t(sizeTable->startGlyphIndex) + 3);
break;
}
}
// if we reach here, we didn't find a strike; unconditionally break
// out of the while-loop block
break;
}
mFontFace->ReleaseFontTable(tableContext);
if (hasStrike) {
return true;
}
// if we didn't find a real strike, check if the font calls for scaling
// another bitmap to this size
hr = mFontFace->TryGetFontTable(DWRITE_MAKE_OPENTYPE_TAG('E', 'B', 'S', 'C'),
(const void**)&tableData, &len, &tableContext,
&exists);
if (FAILED(hr)) {
return false;
}
while (exists) {
if (len < sizeof(EBSCHeader)) {
break;
}
const EBSCHeader* hdr = reinterpret_cast<const EBSCHeader*>(tableData);
if (hdr->version != 0x00020000) {
break;
}
uint32_t numSizes = hdr->numSizes;
if (numSizes > 0xffff) {
break;
}
if (len < sizeof(EBSCHeader) + numSizes * sizeof(BitmapScaleTable)) {
break;
}
const BitmapScaleTable* scaleTable =
reinterpret_cast<const BitmapScaleTable*>(hdr + 1);
for (uint32_t i = 0; i < numSizes; ++i, ++scaleTable) {
if (scaleTable->ppemX == aSize && scaleTable->ppemY == aSize) {
hasStrike = true;
break;
}
}
break;
}
mFontFace->ReleaseFontTable(tableContext);
return hasStrike;
}
bool gfxDWriteFont::IsValid() const { return mFontFace != nullptr; }
IDWriteFontFace* gfxDWriteFont::GetFontFace() { return mFontFace.get(); }
gfxFont::RunMetrics gfxDWriteFont::Measure(const gfxTextRun* aTextRun,
uint32_t aStart, uint32_t aEnd,
BoundingBoxType aBoundingBoxType,
DrawTarget* aRefDrawTarget,
Spacing* aSpacing,
gfx::ShapedTextFlags aOrientation) {
gfxFont::RunMetrics metrics =
gfxFont::Measure(aTextRun, aStart, aEnd, aBoundingBoxType, aRefDrawTarget,
aSpacing, aOrientation);
// if aBoundingBoxType is LOOSE_INK_EXTENTS
// and the underlying cairo font may be antialiased,
// we can't trust Windows to have considered all the pixels
// so we need to add "padding" to the bounds.
// (see bugs 475968, 439831, compare also bug 445087)
if (aBoundingBoxType == LOOSE_INK_EXTENTS &&
mAntialiasOption != kAntialiasNone &&
GetMeasuringMode() == DWRITE_MEASURING_MODE_GDI_CLASSIC &&
metrics.mBoundingBox.Width() > 0) {
metrics.mBoundingBox.MoveByX(-aTextRun->GetAppUnitsPerDevUnit());
metrics.mBoundingBox.SetWidth(metrics.mBoundingBox.Width() +
aTextRun->GetAppUnitsPerDevUnit() * 3);
}
return metrics;
}
bool gfxDWriteFont::ProvidesGlyphWidths() const {
return !mUseSubpixelPositions ||
(mFontFace->GetSimulations() & DWRITE_FONT_SIMULATIONS_BOLD) ||
((gfxDWriteFontEntry*)(GetFontEntry()))->HasVariations();
}
int32_t gfxDWriteFont::GetGlyphWidth(uint16_t aGID) {
if (!mGlyphWidths) {
mGlyphWidths = MakeUnique<nsTHashMap<nsUint32HashKey, int32_t>>(128);
}
return mGlyphWidths->LookupOrInsertWith(
aGID, [&] { return NS_lround(MeasureGlyphWidth(aGID) * 65536.0); });
}
bool gfxDWriteFont::GetForceGDIClassic() const {
return sForceGDIClassicEnabled &&
static_cast<gfxDWriteFontEntry*>(mFontEntry.get())
->GetForceGDIClassic() &&
GetAdjustedSize() <= gfxDWriteFontList::PlatformFontList()
->GetForceGDIClassicMaxFontSize();
}
DWRITE_MEASURING_MODE
gfxDWriteFont::GetMeasuringMode() const {
return DWriteSettings::Get(GetForceGDIClassic()).MeasuringMode();
}
gfxFloat gfxDWriteFont::MeasureGlyphWidth(uint16_t aGlyph) {
MOZ_SEH_TRY {
HRESULT hr;
if (mFontFace1) {
int32_t advance;
if (mUseSubpixelPositions) {
hr = mFontFace1->GetDesignGlyphAdvances(1, &aGlyph, &advance, FALSE);
if (SUCCEEDED(hr)) {
return advance * mFUnitsConvFactor;
}
} else {
hr = mFontFace1->GetGdiCompatibleGlyphAdvances(
FLOAT(mAdjustedSize), 1.0f, nullptr,
GetMeasuringMode() == DWRITE_MEASURING_MODE_GDI_NATURAL, FALSE, 1,
&aGlyph, &advance);
if (SUCCEEDED(hr)) {
return NS_lround(advance * mFUnitsConvFactor);
}
}
} else {
DWRITE_GLYPH_METRICS metrics;
if (mUseSubpixelPositions) {
hr = mFontFace->GetDesignGlyphMetrics(&aGlyph, 1, &metrics, FALSE);
if (SUCCEEDED(hr)) {
return metrics.advanceWidth * mFUnitsConvFactor;
}
} else {
hr = mFontFace->GetGdiCompatibleGlyphMetrics(
FLOAT(mAdjustedSize), 1.0f, nullptr,
GetMeasuringMode() == DWRITE_MEASURING_MODE_GDI_NATURAL, &aGlyph, 1,
&metrics, FALSE);
if (SUCCEEDED(hr)) {
return NS_lround(metrics.advanceWidth * mFUnitsConvFactor);
}
}
}
}
MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
// Exception (e.g. disk i/o error) occurred when DirectWrite tried to use
// the font resource; possibly a failing drive or similar hardware issue.
// Mark the font as invalid, and wipe the fontEntry's charmap so that font
// selection will skip it; we'll use a fallback font instead.
mIsValid = false;
GetFontEntry()->mCharacterMap = new gfxCharacterMap();
GetFontEntry()->mShmemCharacterMap = nullptr;
gfxCriticalError() << "Exception occurred measuring glyph width for "
<< GetFontEntry()->Name().get();
}
return 0.0;
}
bool gfxDWriteFont::GetGlyphBounds(uint16_t aGID, gfxRect* aBounds,
bool aTight) const {
DWRITE_GLYPH_METRICS m;
HRESULT hr = mFontFace->GetDesignGlyphMetrics(&aGID, 1, &m, FALSE);
if (FAILED(hr)) {
return false;
}
gfxRect bounds(m.leftSideBearing, m.topSideBearing - m.verticalOriginY,
m.advanceWidth - m.leftSideBearing - m.rightSideBearing,
m.advanceHeight - m.topSideBearing - m.bottomSideBearing);
bounds.Scale(mFUnitsConvFactor);
// GetDesignGlyphMetrics returns 'ideal' glyph metrics, we need to pad to
// account for antialiasing.
if (!aTight && !aBounds->IsEmpty()) {
bounds.Inflate(1.0, 0.0);
}
*aBounds = bounds;
return true;
}
void gfxDWriteFont::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
FontCacheSizes* aSizes) const {
gfxFont::AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
if (mGlyphWidths) {
aSizes->mFontInstances +=
mGlyphWidths->ShallowSizeOfIncludingThis(aMallocSizeOf);
}
}
void gfxDWriteFont::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
FontCacheSizes* aSizes) const {
aSizes->mFontInstances += aMallocSizeOf(this);
AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
}
already_AddRefed<ScaledFont> gfxDWriteFont::GetScaledFont(
const TextRunDrawParams& aRunParams) {
bool useClearType = UsingClearType();
if (mAzureScaledFontUsedClearType != useClearType) {
if (auto* oldScaledFont = mAzureScaledFont.exchange(nullptr)) {
oldScaledFont->Release();
}
if (auto* oldScaledFont = mAzureScaledFontGDI.exchange(nullptr)) {
oldScaledFont->Release();
}
}
bool forceGDI = aRunParams.allowGDI && GetForceGDIClassic();
ScaledFont* scaledFont = forceGDI ? mAzureScaledFontGDI : mAzureScaledFont;
if (scaledFont) {
return do_AddRef(scaledFont);
}
gfxDWriteFontEntry* fe = static_cast<gfxDWriteFontEntry*>(mFontEntry.get());
bool useEmbeddedBitmap =
(gfxVars::SystemTextRenderingMode() == DWRITE_RENDERING_MODE_DEFAULT ||
forceGDI) &&
fe->IsCJKFont() && HasBitmapStrikeForSize(NS_lround(mAdjustedSize));
const gfxFontStyle* fontStyle = GetStyle();
RefPtr<ScaledFont> newScaledFont = Factory::CreateScaledFontForDWriteFont(
mFontFace, fontStyle, GetUnscaledFont(), GetAdjustedSize(),
useEmbeddedBitmap, ApplySyntheticBold(), forceGDI);
if (!newScaledFont) {
return nullptr;
}
InitializeScaledFont(newScaledFont);
if (forceGDI) {
if (mAzureScaledFontGDI.compareExchange(nullptr, newScaledFont.get())) {
Unused << newScaledFont.forget();
mAzureScaledFontUsedClearType = useClearType;
}
scaledFont = mAzureScaledFontGDI;
} else {
if (mAzureScaledFont.compareExchange(nullptr, newScaledFont.get())) {
Unused << newScaledFont.forget();
mAzureScaledFontUsedClearType = useClearType;
}
scaledFont = mAzureScaledFont;
}
return do_AddRef(scaledFont);
}
bool gfxDWriteFont::ShouldRoundXOffset(cairo_t* aCairo) const {
// show_glyphs is implemented on the font and so is used for all Cairo
// surface types; however, it may pixel-snap depending on the dwrite
// rendering mode
return GetMeasuringMode() != DWRITE_MEASURING_MODE_NATURAL;
}