/* -*- 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 "ScaledFontDWrite.h" #include "UnscaledFontDWrite.h" #include "PathD2D.h" #include "gfxFont.h" #include "Logging.h" #include "mozilla/webrender/WebRenderTypes.h" using namespace std; #ifdef USE_SKIA #include "PathSkia.h" #include "skia/include/core/SkPaint.h" #include "skia/include/core/SkPath.h" #include "skia/include/ports/SkTypeface_win.h" #endif #include #ifdef USE_CAIRO_SCALED_FONT #include "cairo-win32.h" #endif #include "HelpersWinFonts.h" namespace mozilla { namespace gfx { #define GASP_TAG 0x70736167 #define GASP_DOGRAY 0x2 static inline unsigned short readShort(const char *aBuf) { return (*aBuf << 8) | *(aBuf + 1); } static bool DoGrayscale(IDWriteFontFace *aDWFace, Float ppem) { void *tableContext; char *tableData; UINT32 tableSize; BOOL exists; aDWFace->TryGetFontTable(GASP_TAG, (const void**)&tableData, &tableSize, &tableContext, &exists); if (exists) { if (tableSize < 4) { aDWFace->ReleaseFontTable(tableContext); return true; } struct gaspRange { unsigned short maxPPEM; // Stored big-endian unsigned short behavior; // Stored big-endian }; unsigned short numRanges = readShort(tableData + 2); if (tableSize < (UINT)4 + numRanges * 4) { aDWFace->ReleaseFontTable(tableContext); return true; } gaspRange *ranges = (gaspRange *)(tableData + 4); for (int i = 0; i < numRanges; i++) { if (readShort((char*)&ranges[i].maxPPEM) > ppem) { if (!(readShort((char*)&ranges[i].behavior) & GASP_DOGRAY)) { aDWFace->ReleaseFontTable(tableContext); return false; } break; } } aDWFace->ReleaseFontTable(tableContext); } return true; } static inline DWRITE_FONT_STRETCH DWriteFontStretchFromStretch(int16_t aStretch) { switch (aStretch) { case NS_FONT_STRETCH_ULTRA_CONDENSED: return DWRITE_FONT_STRETCH_ULTRA_CONDENSED; case NS_FONT_STRETCH_EXTRA_CONDENSED: return DWRITE_FONT_STRETCH_EXTRA_CONDENSED; case NS_FONT_STRETCH_CONDENSED: return DWRITE_FONT_STRETCH_CONDENSED; case NS_FONT_STRETCH_SEMI_CONDENSED: return DWRITE_FONT_STRETCH_SEMI_CONDENSED; case NS_FONT_STRETCH_NORMAL: return DWRITE_FONT_STRETCH_NORMAL; case NS_FONT_STRETCH_SEMI_EXPANDED: return DWRITE_FONT_STRETCH_SEMI_EXPANDED; case NS_FONT_STRETCH_EXPANDED: return DWRITE_FONT_STRETCH_EXPANDED; case NS_FONT_STRETCH_EXTRA_EXPANDED: return DWRITE_FONT_STRETCH_EXTRA_EXPANDED; case NS_FONT_STRETCH_ULTRA_EXPANDED: return DWRITE_FONT_STRETCH_ULTRA_EXPANDED; default: return DWRITE_FONT_STRETCH_UNDEFINED; } } ScaledFontDWrite::ScaledFontDWrite(IDWriteFontFace *aFontFace, const RefPtr& aUnscaledFont, Float aSize, bool aUseEmbeddedBitmap, bool aForceGDIMode, IDWriteRenderingParams* aParams, Float aGamma, Float aContrast, const gfxFontStyle* aStyle) : ScaledFontBase(aUnscaledFont, aSize) , mFontFace(aFontFace) , mUseEmbeddedBitmap(aUseEmbeddedBitmap) , mForceGDIMode(aForceGDIMode) , mParams(aParams) , mGamma(aGamma) , mContrast(aContrast) { if (aStyle) { mStyle = SkFontStyle(aStyle->weight, DWriteFontStretchFromStretch(aStyle->stretch), aStyle->style == NS_FONT_STYLE_NORMAL ? SkFontStyle::kUpright_Slant : SkFontStyle::kItalic_Slant); } } already_AddRefed ScaledFontDWrite::GetPathForGlyphs(const GlyphBuffer &aBuffer, const DrawTarget *aTarget) { if (aTarget->GetBackendType() != BackendType::DIRECT2D && aTarget->GetBackendType() != BackendType::DIRECT2D1_1) { return ScaledFontBase::GetPathForGlyphs(aBuffer, aTarget); } RefPtr pathBuilder = aTarget->CreatePathBuilder(); PathBuilderD2D *pathBuilderD2D = static_cast(pathBuilder.get()); CopyGlyphsToSink(aBuffer, pathBuilderD2D->GetSink()); return pathBuilder->Finish(); } #ifdef USE_SKIA SkTypeface* ScaledFontDWrite::GetSkTypeface() { if (!mTypeface) { RefPtr factory = Factory::GetDWriteFactory(); if (!factory) { return nullptr; } Float gamma = mGamma; // Skia doesn't support a gamma value outside of 0-4, so default to 2.2 if (gamma < 0.0f || gamma > 4.0f) { gamma = 2.2f; } Float contrast = mContrast; // Skia doesn't support a contrast value outside of 0-1, so default to 1.0 if (contrast < 0.0f || contrast > 1.0f) { contrast = 1.0f; } mTypeface = SkCreateTypefaceFromDWriteFont(factory, mFontFace, mStyle, mForceGDIMode, gamma, contrast); } return mTypeface; } #endif void ScaledFontDWrite::CopyGlyphsToBuilder(const GlyphBuffer &aBuffer, PathBuilder *aBuilder, const Matrix *aTransformHint) { BackendType backendType = aBuilder->GetBackendType(); if (backendType != BackendType::DIRECT2D && backendType != BackendType::DIRECT2D1_1) { ScaledFontBase::CopyGlyphsToBuilder(aBuffer, aBuilder, aTransformHint); return; } PathBuilderD2D *pathBuilderD2D = static_cast(aBuilder); if (pathBuilderD2D->IsFigureActive()) { gfxCriticalNote << "Attempting to copy glyphs to PathBuilderD2D with active figure."; } CopyGlyphsToSink(aBuffer, pathBuilderD2D->GetSink()); } void ScaledFontDWrite::GetGlyphDesignMetrics(const uint16_t* aGlyphs, uint32_t aNumGlyphs, GlyphMetrics* aGlyphMetrics) { DWRITE_FONT_METRICS fontMetrics; mFontFace->GetMetrics(&fontMetrics); vector metrics(aNumGlyphs); mFontFace->GetDesignGlyphMetrics(aGlyphs, aNumGlyphs, &metrics.front()); Float scaleFactor = mSize / fontMetrics.designUnitsPerEm; for (uint32_t i = 0; i < aNumGlyphs; i++) { aGlyphMetrics[i].mXBearing = metrics[i].leftSideBearing * scaleFactor; aGlyphMetrics[i].mXAdvance = metrics[i].advanceWidth * scaleFactor; aGlyphMetrics[i].mYBearing = (metrics[i].topSideBearing - metrics[i].verticalOriginY) * scaleFactor; aGlyphMetrics[i].mYAdvance = metrics[i].advanceHeight * scaleFactor; aGlyphMetrics[i].mWidth = (metrics[i].advanceWidth - metrics[i].leftSideBearing - metrics[i].rightSideBearing) * scaleFactor; aGlyphMetrics[i].mHeight = (metrics[i].advanceHeight - metrics[i].topSideBearing - metrics[i].bottomSideBearing) * scaleFactor; } } void ScaledFontDWrite::CopyGlyphsToSink(const GlyphBuffer &aBuffer, ID2D1GeometrySink *aSink) { std::vector indices; std::vector advances; std::vector offsets; indices.resize(aBuffer.mNumGlyphs); advances.resize(aBuffer.mNumGlyphs); offsets.resize(aBuffer.mNumGlyphs); memset(&advances.front(), 0, sizeof(FLOAT) * aBuffer.mNumGlyphs); for (unsigned int i = 0; i < aBuffer.mNumGlyphs; i++) { indices[i] = aBuffer.mGlyphs[i].mIndex; offsets[i].advanceOffset = aBuffer.mGlyphs[i].mPosition.x; offsets[i].ascenderOffset = -aBuffer.mGlyphs[i].mPosition.y; } HRESULT hr = mFontFace->GetGlyphRunOutline(mSize, &indices.front(), &advances.front(), &offsets.front(), aBuffer.mNumGlyphs, FALSE, FALSE, aSink); if (FAILED(hr)) { gfxCriticalNote << "Failed to copy glyphs to geometry sink. Code: " << hexa(hr); } } bool UnscaledFontDWrite::GetFontFileData(FontFileDataOutput aDataCallback, void *aBaton) { UINT32 fileCount = 0; mFontFace->GetFiles(&fileCount, nullptr); if (fileCount > 1) { MOZ_ASSERT(false); return false; } if (!aDataCallback) { return true; } RefPtr file; mFontFace->GetFiles(&fileCount, getter_AddRefs(file)); const void *referenceKey; UINT32 refKeySize; // XXX - This can currently crash for webfonts, as when we get the reference // key out of the file, that can be an invalid reference key for the loader // we use it with. The fix to this is not obvious but it will probably // have to happen inside thebes. file->GetReferenceKey(&referenceKey, &refKeySize); RefPtr loader; file->GetLoader(getter_AddRefs(loader)); RefPtr stream; loader->CreateStreamFromKey(referenceKey, refKeySize, getter_AddRefs(stream)); UINT64 fileSize64; stream->GetFileSize(&fileSize64); if (fileSize64 > UINT32_MAX) { MOZ_ASSERT(false); return false; } uint32_t fileSize = static_cast(fileSize64); const void *fragmentStart; void *context; stream->ReadFileFragment(&fragmentStart, 0, fileSize, &context); aDataCallback((uint8_t*)fragmentStart, fileSize, mFontFace->GetIndex(), aBaton); stream->ReleaseFileFragment(context); return true; } static bool GetDWriteName(RefPtr aNames, std::vector& aOutName) { BOOL exists = false; UINT32 index = 0; HRESULT hr = aNames->FindLocaleName(L"en-us", &index, &exists); if (FAILED(hr)) { return false; } if (!exists) { // No english found, use whatever is first in the list. index = 0; } UINT32 length; hr = aNames->GetStringLength(index, &length); if (FAILED(hr)) { return false; } aOutName.resize(length + 1); hr = aNames->GetString(index, aOutName.data(), length + 1); return SUCCEEDED(hr); } static bool GetDWriteFamilyName(const RefPtr& aFamily, std::vector& aOutName) { RefPtr names; HRESULT hr = aFamily->GetFamilyNames(getter_AddRefs(names)); if (FAILED(hr)) { return false; } return GetDWriteName(names, aOutName); } bool UnscaledFontDWrite::GetWRFontDescriptor(WRFontDescriptorOutput aCb, void* aBaton) { if (!mFont) { return false; } RefPtr family; HRESULT hr = mFont->GetFontFamily(getter_AddRefs(family)); if (FAILED(hr)) { return false; } DWRITE_FONT_WEIGHT weight = mFont->GetWeight(); DWRITE_FONT_STRETCH stretch = mFont->GetStretch(); DWRITE_FONT_STYLE style = mFont->GetStyle(); RefPtr match; hr = family->GetFirstMatchingFont(weight, stretch, style, getter_AddRefs(match)); if (FAILED(hr) || match->GetWeight() != weight || match->GetStretch() != stretch || match->GetStyle() != style) { return false; } std::vector familyName; if (!GetDWriteFamilyName(family, familyName)) { return false; } // The style information that identifies the font can be encoded easily in // less than 32 bits. Since the index is needed for font descriptors, only // the family name and style information, pass along the style in the index // data to avoid requiring a more complicated structure packing for it in // the data payload. uint32_t index = weight | (stretch << 16) | (style << 24); aCb(reinterpret_cast(familyName.data()), (familyName.size() - 1) * sizeof(WCHAR), index, aBaton); return true; } bool ScaledFontDWrite::GetFontInstanceData(FontInstanceDataOutput aCb, void* aBaton) { InstanceData instance(this); aCb(reinterpret_cast(&instance), sizeof(instance), nullptr, 0, aBaton); return true; } bool ScaledFontDWrite::GetWRFontInstanceOptions(Maybe* aOutOptions, Maybe* aOutPlatformOptions, std::vector* aOutVariations) { wr::FontInstanceOptions options; options.render_mode = wr::ToFontRenderMode(GetDefaultAAMode()); options.subpx_dir = wr::SubpixelDirection::Horizontal; options.flags = 0; if (mFontFace->GetSimulations() & DWRITE_FONT_SIMULATIONS_BOLD) { options.flags |= wr::FontInstanceFlags::SYNTHETIC_BOLD; } if (UseEmbeddedBitmaps()) { options.flags |= wr::FontInstanceFlags::EMBEDDED_BITMAPS; } if (ForceGDIMode()) { options.flags |= wr::FontInstanceFlags::FORCE_GDI; } options.bg_color = wr::ToColorU(Color()); *aOutOptions = Some(options); return true; } already_AddRefed UnscaledFontDWrite::CreateScaledFont(Float aGlyphSize, const uint8_t* aInstanceData, uint32_t aInstanceDataLength, const FontVariation* aVariations, uint32_t aNumVariations) { if (aInstanceDataLength < sizeof(ScaledFontDWrite::InstanceData)) { gfxWarning() << "DWrite scaled font instance data is truncated."; return nullptr; } const ScaledFontDWrite::InstanceData *instanceData = reinterpret_cast(aInstanceData); RefPtr scaledFont = new ScaledFontDWrite(mFontFace, this, aGlyphSize, instanceData->mUseEmbeddedBitmap, instanceData->mForceGDIMode, nullptr, instanceData->mGamma, instanceData->mContrast); if (mNeedsCairo && !scaledFont->PopulateCairoScaledFont()) { gfxWarning() << "Unable to create cairo scaled font DWrite font."; return nullptr; } return scaledFont.forget(); } AntialiasMode ScaledFontDWrite::GetDefaultAAMode() { AntialiasMode defaultMode = GetSystemDefaultAAMode(); if (defaultMode == AntialiasMode::GRAY) { if (!DoGrayscale(mFontFace, mSize)) { defaultMode = AntialiasMode::NONE; } } return defaultMode; } #ifdef USE_CAIRO_SCALED_FONT cairo_font_face_t* ScaledFontDWrite::GetCairoFontFace() { if (!mFontFace) { return nullptr; } return cairo_dwrite_font_face_create_for_dwrite_fontface(nullptr, mFontFace); } #endif } }