/* -*- 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 "gfxDWriteCommon.h" #include "UnscaledFontDWrite.h" #include "PathD2D.h" #include "gfxFont.h" #include "Logging.h" #include "mozilla/FontPropertyTypes.h" #include "mozilla/webrender/WebRenderTypes.h" #include "HelpersD2D.h" #include "StackArray.h" #include "dwrite_3.h" // Currently, we build with WINVER=0x601 (Win7), which means newer // declarations in dwrite_3.h will not be visible. Also, we don't // yet have the Fall Creators Update SDK available on build machines, // so even with updated WINVER, some of the interfaces we need would // not be present. // To work around this, until the build environment is updated, // we #include an extra header that contains copies of the relevant // classes/interfaces we need. #if !defined(__MINGW32__) && WINVER < 0x0A00 # include "dw-extra.h" #endif #include "PathSkia.h" #include "skia/include/core/SkPaint.h" #include "skia/include/core/SkPath.h" #include "skia/include/ports/SkTypeface_win.h" #include #include "cairo-win32.h" #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; } ScaledFontDWrite::ScaledFontDWrite(IDWriteFontFace* aFontFace, const RefPtr& aUnscaledFont, Float aSize, bool aUseEmbeddedBitmap, bool aUseMultistrikeBold, bool aGDIForced, const gfxFontStyle* aStyle) : ScaledFontBase(aUnscaledFont, aSize), mFontFace(aFontFace), mUseEmbeddedBitmap(aUseEmbeddedBitmap), mUseMultistrikeBold(aUseMultistrikeBold), mGDIForced(aGDIForced) { if (aStyle) { mStyle = SkFontStyle(aStyle->weight.ToIntRounded(), DWriteFontStretchFromStretch(aStyle->stretch), // FIXME(jwatt): also use kOblique_Slant aStyle->style == FontSlantStyle::NORMAL ? SkFontStyle::kUpright_Slant : SkFontStyle::kItalic_Slant); } } already_AddRefed ScaledFontDWrite::GetPathForGlyphs( const GlyphBuffer& aBuffer, const DrawTarget* aTarget) { RefPtr pathBuilder = aTarget->CreatePathBuilder(); if (pathBuilder->GetBackendType() != BackendType::DIRECT2D && pathBuilder->GetBackendType() != BackendType::DIRECT2D1_1) { return ScaledFontBase::GetPathForGlyphs(aBuffer, aTarget); } PathBuilderD2D* pathBuilderD2D = static_cast(pathBuilder.get()); CopyGlyphsToSink(aBuffer, pathBuilderD2D->GetSink()); return pathBuilder->Finish(); } SkTypeface* ScaledFontDWrite::CreateSkTypeface() { RefPtr factory = Factory::GetDWriteFactory(); if (!factory) { return nullptr; } auto& settings = DWriteSettings(); Float gamma = settings.Gamma(); // 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 = settings.EnhancedContrast(); // 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; } Float clearTypeLevel = settings.ClearTypeLevel(); if (clearTypeLevel < 0.0f || clearTypeLevel > 1.0f) { clearTypeLevel = 1.0f; } return SkCreateTypefaceFromDWriteFont(factory, mFontFace, mStyle, (int)settings.RenderingMode(), gamma, contrast, clearTypeLevel); } void ScaledFontDWrite::SetupSkFontDrawOptions(SkFont& aFont) { if (ForceGDIMode()) { aFont.setEmbeddedBitmaps(true); aFont.setSubpixel(false); } else { aFont.setEmbeddedBitmaps(UseEmbeddedBitmaps()); aFont.setSubpixel(true); } } bool ScaledFontDWrite::MayUseBitmaps() { return ForceGDIMode() || UseEmbeddedBitmaps(); } 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::CopyGlyphsToSink(const GlyphBuffer& aBuffer, ID2D1SimplifiedGeometrySink* 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; HRESULT hr = mFontFace->GetFiles(&fileCount, nullptr); if (FAILED(hr) || fileCount > 1) { MOZ_ASSERT(false); return false; } if (!aDataCallback) { return true; } RefPtr file; hr = mFontFace->GetFiles(&fileCount, getter_AddRefs(file)); if (FAILED(hr)) { return false; } 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. hr = file->GetReferenceKey(&referenceKey, &refKeySize); if (FAILED(hr)) { return false; } RefPtr loader; hr = file->GetLoader(getter_AddRefs(loader)); if (FAILED(hr)) { return false; } RefPtr stream; hr = loader->CreateStreamFromKey(referenceKey, refKeySize, getter_AddRefs(stream)); if (FAILED(hr)) { return false; } UINT64 fileSize64; hr = stream->GetFileSize(&fileSize64); if (FAILED(hr) || fileSize64 > UINT32_MAX) { MOZ_ASSERT(false); return false; } // Try to catch any device memory exceptions that may occur while attempting // to read the file fragment. void* context = nullptr; hr = E_FAIL; MOZ_SEH_TRY { uint32_t fileSize = static_cast(fileSize64); const void* fragmentStart = nullptr; hr = stream->ReadFileFragment(&fragmentStart, 0, fileSize, &context); if (SUCCEEDED(hr)) { aDataCallback((uint8_t*)fragmentStart, fileSize, mFontFace->GetIndex(), aBaton); } } MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { gfxCriticalNote << "Exception occurred reading DWrite font file data"; } if (FAILED(hr)) { return false; } stream->ReleaseFileFragment(context); return true; } static bool GetFontFileName(RefPtr aFontFace, std::vector& aFileName) { UINT32 numFiles; HRESULT hr = aFontFace->GetFiles(&numFiles, nullptr); if (FAILED(hr)) { gfxDebug() << "Failed getting file count for WR font"; return false; } else if (numFiles != 1) { gfxDebug() << "Invalid file count " << numFiles << " for WR font"; return false; } RefPtr file; hr = aFontFace->GetFiles(&numFiles, getter_AddRefs(file)); if (FAILED(hr)) { gfxDebug() << "Failed getting file for WR font"; return false; } const void* key; UINT32 keySize; hr = file->GetReferenceKey(&key, &keySize); if (FAILED(hr)) { gfxDebug() << "Failed getting file ref key for WR font"; return false; } RefPtr loader; hr = file->GetLoader(getter_AddRefs(loader)); if (FAILED(hr)) { gfxDebug() << "Failed getting file loader for WR font"; return false; } RefPtr localLoader; loader->QueryInterface(__uuidof(IDWriteLocalFontFileLoader), (void**)getter_AddRefs(localLoader)); if (!localLoader) { gfxDebug() << "Failed querying loader interface for WR font"; return false; } UINT32 pathLen; hr = localLoader->GetFilePathLengthFromKey(key, keySize, &pathLen); if (FAILED(hr)) { gfxDebug() << "Failed getting path length for WR font"; return false; } aFileName.resize(pathLen + 1); hr = localLoader->GetFilePathFromKey(key, keySize, aFileName.data(), pathLen + 1); if (FAILED(hr) || aFileName.back() != 0) { gfxDebug() << "Failed getting path for WR font"; return false; } DWORD attribs = GetFileAttributesW(aFileName.data()); if (attribs == INVALID_FILE_ATTRIBUTES) { gfxDebug() << "Invalid file \"" << aFileName.data() << "\" for WR font"; return false; } // We leave the null terminator at the end of the returned file name. return true; } bool UnscaledFontDWrite::GetFontDescriptor(FontDescriptorOutput aCb, void* aBaton) { if (!mFont) { return false; } std::vector fileName; if (!GetFontFileName(mFontFace, fileName)) { return false; } uint32_t index = mFontFace->GetIndex(); aCb(reinterpret_cast(fileName.data()), fileName.size() * sizeof(WCHAR), index, aBaton); return true; } ScaledFontDWrite::InstanceData::InstanceData( const wr::FontInstanceOptions* aOptions, const wr::FontInstancePlatformOptions* aPlatformOptions) { if (aOptions) { if (aOptions->flags & wr::FontInstanceFlags::EMBEDDED_BITMAPS) { mUseEmbeddedBitmap = true; } if (aOptions->flags & wr::FontInstanceFlags::SYNTHETIC_BOLD) { mUseBoldSimulation = true; } if (aOptions->flags & wr::FontInstanceFlags::MULTISTRIKE_BOLD) { mUseMultistrikeBold = true; } if (aOptions->flags & wr::FontInstanceFlags::FORCE_GDI) { mGDIForced = true; } } } // Helper for ScaledFontDWrite::GetFontInstanceData: if the font has variation // axes, get their current values into the aOutput vector. static void GetVariationsFromFontFace(IDWriteFontFace* aFace, std::vector* aOutput) { RefPtr ff5; aFace->QueryInterface(__uuidof(IDWriteFontFace5), (void**)getter_AddRefs(ff5)); if (!ff5 || !ff5->HasVariations()) { return; } uint32_t count = ff5->GetFontAxisValueCount(); if (!count) { return; } RefPtr res; if (FAILED(ff5->GetFontResource(getter_AddRefs(res)))) { return; } std::vector values(count); if (FAILED(ff5->GetFontAxisValues(values.data(), count))) { return; } aOutput->reserve(count); for (uint32_t i = 0; i < count; i++) { DWRITE_FONT_AXIS_ATTRIBUTES attr = res->GetFontAxisAttributes(i); if (attr & DWRITE_FONT_AXIS_ATTRIBUTES_VARIABLE) { float v = values[i].value; uint32_t t = TRUETYPE_TAG( uint8_t(values[i].axisTag), uint8_t(values[i].axisTag >> 8), uint8_t(values[i].axisTag >> 16), uint8_t(values[i].axisTag >> 24)); aOutput->push_back(FontVariation{uint32_t(t), float(v)}); } } } bool ScaledFontDWrite::GetFontInstanceData(FontInstanceDataOutput aCb, void* aBaton) { InstanceData instance(this); // If the font has variations, get the list of axis values. std::vector variations; GetVariationsFromFontFace(mFontFace, &variations); aCb(reinterpret_cast(&instance), sizeof(instance), variations.data(), variations.size(), aBaton); return true; } bool ScaledFontDWrite::GetWRFontInstanceOptions( Maybe* aOutOptions, Maybe* aOutPlatformOptions, std::vector* aOutVariations) { wr::FontInstanceOptions options; options.render_mode = wr::ToFontRenderMode(GetDefaultAAMode()); options.flags = wr::FontInstanceFlags{0}; if (HasBoldSimulation()) { options.flags |= wr::FontInstanceFlags::SYNTHETIC_BOLD; } if (UseMultistrikeBold()) { options.flags |= wr::FontInstanceFlags::MULTISTRIKE_BOLD; } if (UseEmbeddedBitmaps()) { options.flags |= wr::FontInstanceFlags::EMBEDDED_BITMAPS; } if (ForceGDIMode()) { options.flags |= wr::FontInstanceFlags::FORCE_GDI; } else { options.flags |= wr::FontInstanceFlags::SUBPIXEL_POSITION; } auto& settings = DWriteSettings(); switch (settings.RenderingMode()) { case DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL_SYMMETRIC: options.flags |= wr::FontInstanceFlags::FORCE_SYMMETRIC; break; case DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL: options.flags |= wr::FontInstanceFlags::NO_SYMMETRIC; break; default: break; } if (Factory::GetBGRSubpixelOrder()) { options.flags |= wr::FontInstanceFlags::SUBPIXEL_BGR; } options.bg_color = wr::ToColorU(DeviceColor()); options.synthetic_italics = wr::DegreesToSyntheticItalics(GetSyntheticObliqueAngle()); wr::FontInstancePlatformOptions platformOptions; platformOptions.gamma = uint16_t(std::round(settings.Gamma() * 100.0f)); platformOptions.contrast = uint8_t(std::round(std::min(settings.EnhancedContrast(), 1.0f) * 100.0f)); platformOptions.cleartype_level = uint8_t(std::round(std::min(settings.ClearTypeLevel(), 1.0f) * 100.0f)); *aOutOptions = Some(options); *aOutPlatformOptions = Some(platformOptions); GetVariationsFromFontFace(mFontFace, aOutVariations); return true; } DWriteSettings& ScaledFontDWrite::DWriteSettings() const { return DWriteSettings::Get(mGDIForced); } // Helper for UnscaledFontDWrite::CreateScaledFont: create a clone of the // given IDWriteFontFace, with specified variation-axis values applied. // Returns nullptr in case of failure. static already_AddRefed CreateFaceWithVariations( IDWriteFontFace* aFace, DWRITE_FONT_SIMULATIONS aSimulations, const FontVariation* aVariations = nullptr, uint32_t aNumVariations = 0) { auto makeDWriteAxisTag = [](uint32_t aTag) { return DWRITE_MAKE_FONT_AXIS_TAG((aTag >> 24) & 0xff, (aTag >> 16) & 0xff, (aTag >> 8) & 0xff, aTag & 0xff); }; MOZ_SEH_TRY { RefPtr ff5; aFace->QueryInterface(__uuidof(IDWriteFontFace5), (void**)getter_AddRefs(ff5)); if (!ff5) { return nullptr; } RefPtr res; if (FAILED(ff5->GetFontResource(getter_AddRefs(res)))) { return nullptr; } std::vector fontAxisValues; if (aNumVariations) { fontAxisValues.reserve(aNumVariations); for (uint32_t i = 0; i < aNumVariations; i++) { DWRITE_FONT_AXIS_VALUE axisValue = { makeDWriteAxisTag(aVariations[i].mTag), aVariations[i].mValue}; fontAxisValues.push_back(axisValue); } } else { uint32_t count = ff5->GetFontAxisValueCount(); if (count) { fontAxisValues.resize(count); if (FAILED(ff5->GetFontAxisValues(fontAxisValues.data(), count))) { fontAxisValues.clear(); } } } RefPtr newFace; if (FAILED(res->CreateFontFace(aSimulations, fontAxisValues.data(), fontAxisValues.size(), getter_AddRefs(newFace)))) { return nullptr; } return newFace.forget(); } MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { gfxCriticalNote << "Exception occurred initializing variation face"; return nullptr; } } bool UnscaledFontDWrite::InitBold() { if (mFontFaceBold) { return true; } DWRITE_FONT_SIMULATIONS sims = mFontFace->GetSimulations(); if (sims & DWRITE_FONT_SIMULATIONS_BOLD) { mFontFaceBold = mFontFace; return true; } sims |= DWRITE_FONT_SIMULATIONS_BOLD; RefPtr ff5 = CreateFaceWithVariations(mFontFace, sims); if (ff5) { mFontFaceBold = ff5; } else { MOZ_SEH_TRY { UINT32 numFiles = 0; if (FAILED(mFontFace->GetFiles(&numFiles, nullptr))) { return false; } StackArray files(numFiles); if (FAILED(mFontFace->GetFiles(&numFiles, files.data()))) { return false; } HRESULT hr = Factory::GetDWriteFactory()->CreateFontFace( mFontFace->GetType(), numFiles, files.data(), mFontFace->GetIndex(), sims, getter_AddRefs(mFontFaceBold)); for (UINT32 i = 0; i < numFiles; ++i) { files[i]->Release(); } if (FAILED(hr) || !mFontFaceBold) { return false; } } MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { gfxCriticalNote << "Exception occurred initializing bold face"; return false; } } 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); IDWriteFontFace* face = mFontFace; if (instanceData.mUseBoldSimulation) { if (!InitBold()) { gfxWarning() << "Failed creating bold IDWriteFontFace."; return nullptr; } face = mFontFaceBold; } DWRITE_FONT_SIMULATIONS sims = face->GetSimulations(); // If variations are required, we create a separate IDWriteFontFace5 with // the requested settings applied. RefPtr ff5; if (aNumVariations) { ff5 = CreateFaceWithVariations(mFontFace, sims, aVariations, aNumVariations); if (ff5) { face = ff5; } else { gfxWarning() << "Failed to create IDWriteFontFace5 with variations."; } } return MakeAndAddRef( face, this, aGlyphSize, instanceData.mUseEmbeddedBitmap, instanceData.mUseMultistrikeBold, instanceData.mGDIForced, nullptr); } already_AddRefed UnscaledFontDWrite::CreateScaledFontFromWRFont( Float aGlyphSize, const wr::FontInstanceOptions* aOptions, const wr::FontInstancePlatformOptions* aPlatformOptions, const FontVariation* aVariations, uint32_t aNumVariations) { ScaledFontDWrite::InstanceData instanceData(aOptions, aPlatformOptions); return CreateScaledFont(aGlyphSize, reinterpret_cast(&instanceData), sizeof(instanceData), aVariations, aNumVariations); } AntialiasMode ScaledFontDWrite::GetDefaultAAMode() { AntialiasMode defaultMode = GetSystemDefaultAAMode(); switch (defaultMode) { case AntialiasMode::SUBPIXEL: case AntialiasMode::DEFAULT: if (DWriteSettings().ClearTypeLevel() == 0.0f) { defaultMode = AntialiasMode::GRAY; } break; case AntialiasMode::GRAY: if (!DoGrayscale(mFontFace, mSize)) { defaultMode = AntialiasMode::NONE; } break; case AntialiasMode::NONE: break; } return defaultMode; } cairo_font_face_t* ScaledFontDWrite::CreateCairoFontFace( cairo_font_options_t* aFontOptions) { if (!mFontFace) { return nullptr; } return cairo_dwrite_font_face_create_for_dwrite_fontface(nullptr, mFontFace); } void ScaledFontDWrite::PrepareCairoScaledFont(cairo_scaled_font_t* aFont) { if (mGDIForced) { cairo_dwrite_scaled_font_set_force_GDI_classic(aFont, true); } } already_AddRefed UnscaledFontDWrite::CreateFromFontDescriptor( const uint8_t* aData, uint32_t aDataLength, uint32_t aIndex) { // Note that despite the type of aData here, it actually points to a 16-bit // Windows font file path (hence the cast to WCHAR* below). if (aDataLength == 0) { gfxWarning() << "DWrite font descriptor is truncated."; return nullptr; } RefPtr factory = Factory::GetDWriteFactory(); if (!factory) { return nullptr; } MOZ_SEH_TRY { RefPtr fontFile; HRESULT hr = factory->CreateFontFileReference((const WCHAR*)aData, nullptr, getter_AddRefs(fontFile)); if (FAILED(hr)) { return nullptr; } BOOL isSupported; DWRITE_FONT_FILE_TYPE fileType; DWRITE_FONT_FACE_TYPE faceType; UINT32 numFaces; hr = fontFile->Analyze(&isSupported, &fileType, &faceType, &numFaces); if (FAILED(hr) || !isSupported || aIndex >= numFaces) { return nullptr; } IDWriteFontFile* fontFiles[1] = {fontFile.get()}; RefPtr fontFace; hr = factory->CreateFontFace(faceType, 1, fontFiles, aIndex, DWRITE_FONT_SIMULATIONS_NONE, getter_AddRefs(fontFace)); if (FAILED(hr)) { return nullptr; } RefPtr unscaledFont = new UnscaledFontDWrite(fontFace, nullptr); return unscaledFont.forget(); } MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) { gfxCriticalNote << "Exception occurred creating unscaledFont for " << NS_ConvertUTF16toUTF8((const char16_t*)aData).get(); return nullptr; } } } // namespace gfx } // namespace mozilla