From a1b18eba354dc773c214fc3b7ee92c3473ec4a5e Mon Sep 17 00:00:00 2001 From: Mike Kaganski Date: Tue, 20 Aug 2024 04:02:47 +0500 Subject: [PATCH] Implement Custom Font Collections on pre-Windows 10 systems Commit 68818db0ec0e9c308c8a0772d46af551f439b32c (build a IDWriteFontCollection1 of our FR_PRIVATE fonts, 2022-01-11) used dwrite_3.h, which has API available only starting from Windows 10. For pre-Windows 10 versions, there is a different way to implement this, as explained at https://learn.microsoft.com/en-us/windows/win32/directwrite/custom-font-collections This change implements that more complex way as a fallback, until we bump the baseline. Allows to not fall back to gdi in Skia, like with the original commit, just on older Windows versions. Change-Id: Ieca13e4a04bc72ce877ab9b512c7821d5466cb70 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/172090 Tested-by: Jenkins Reviewed-by: Mike Kaganski --- vcl/inc/skia/win/gdiimpl.hxx | 3 +- vcl/skia/win/gdiimpl.cxx | 254 +++++++++++++++++++++++++++++------ 2 files changed, 213 insertions(+), 44 deletions(-) diff --git a/vcl/inc/skia/win/gdiimpl.hxx b/vcl/inc/skia/win/gdiimpl.hxx index c5b12d08811a..7e399c59effb 100644 --- a/vcl/inc/skia/win/gdiimpl.hxx +++ b/vcl/inc/skia/win/gdiimpl.hxx @@ -65,8 +65,7 @@ protected: virtual void createWindowSurfaceInternal(bool forceRaster = false) override; static sk_sp createDirectWriteTypeface(const WinFontInstance* pWinFont); static void initFontInfo(); - inline static sal::systools::COMReference dwriteFontSetBuilder; - inline static sal::systools::COMReference dwritePrivateCollection; + inline static sal::systools::COMReference dwritePrivateCollection; inline static sk_sp dwriteFontMgr; inline static bool dwriteDone = false; static SkFont::Edging fontEdging; diff --git a/vcl/skia/win/gdiimpl.cxx b/vcl/skia/win/gdiimpl.cxx index 1d48fb9bbe71..4a8e6266fe63 100644 --- a/vcl/skia/win/gdiimpl.cxx +++ b/vcl/skia/win/gdiimpl.cxx @@ -32,6 +32,217 @@ #include +#include + +namespace +{ +sal::systools::COMReference +getDWritePrivateFontCollection_w10(IDWriteFontFile* fontFile) +{ + static sal::systools::COMReference dwriteFactory3 = [] { + IDWriteFactory* dwriteFactory = WinSalGraphics::getDWriteFactory(); + sal::systools::COMReference factory3; + dwriteFactory->QueryInterface(&factory3); + return factory3; + }(); + if (!dwriteFactory3) + return {}; + + static sal::systools::COMReference dwriteFontSetBuilder = [] { + sal::systools::COMReference builder; + dwriteFactory3->CreateFontSetBuilder(&dwriteFontSetBuilder); + return builder; + }(); + if (!dwriteFontSetBuilder) + return {}; + + BOOL isSupported; + DWRITE_FONT_FILE_TYPE fileType; + UINT32 numberOfFonts; + sal::systools::ThrowIfFailed( + fontFile->Analyze(&isSupported, &fileType, nullptr, &numberOfFonts), SAL_WHERE); + if (!isSupported) + return {}; + + // For each font within the font file, get a font face reference and add to the builder. + for (UINT32 fontIndex = 0; fontIndex < numberOfFonts; ++fontIndex) + { + sal::systools::COMReference fontFaceReference; + if (FAILED(dwriteFactory3->CreateFontFaceReference( + fontFile, fontIndex, DWRITE_FONT_SIMULATIONS_NONE, &fontFaceReference))) + continue; + + // Leave it to DirectWrite to read properties directly out of the font files + dwriteFontSetBuilder->AddFontFaceReference(fontFaceReference); + } + + sal::systools::COMReference fontSet; + sal::systools::ThrowIfFailed(dwriteFontSetBuilder->CreateFontSet(&fontSet), SAL_WHERE); + + sal::systools::COMReference fc1; + sal::systools::ThrowIfFailed(dwriteFactory3->CreateFontCollectionFromFontSet(fontSet, &fc1), + SAL_WHERE); + return { fc1.get() }; +} + +// The following is only needed until we bump baseline to Windows 10 + +template requires std::is_base_of_v class IUnknown_Impl : public I +{ +public: + virtual ~IUnknown_Impl() {} + + // IUnknown + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void** ppvObject) override + { + if (iid == IID_IUnknown || iid == __uuidof(I)) + { + *ppvObject = this; + AddRef(); + return S_OK; + } + + *ppvObject = nullptr; + return E_NOINTERFACE; + } + ULONG STDMETHODCALLTYPE AddRef() override { return ++m_nRef; } + ULONG STDMETHODCALLTYPE Release() override + { + ULONG n = --m_nRef; + if (n == 0) + delete this; + return n; + }; + +private: + std::atomic m_nRef = 0; +}; + +// A simple loader class, which only stores the font files (to fulfill the requirement that +// "each key is ... valid until the loader is unregistered using the factory"), and creates +// instances of enumerator +class FontCollectionLoader_w7 : public IUnknown_Impl +{ +public: + // IDWriteFontCollectionLoader + HRESULT STDMETHODCALLTYPE CreateEnumeratorFromKey( + IDWriteFactory* factory, void const* collectionKey, UINT32 collectionKeySize, + /* OUT */ IDWriteFontFileEnumerator** fontFileEnumerator) override; + +private: + std::vector> m_fontFiles; +}; + +// A singleton class, that (1) caches IDWriteFactory, to avoid destruction order problems, +// (2) holds the FontCollectionLoader_w7 singleton, and (3) calls IDWriteFactory's +// (Un)RegisterFontCollectionLoader, because these can't be called from destructor of +// FontCollectionLoader_w7, because RegisterFontCollectionLoader calls AddRef. +struct FontCollectionLoader_w7_singleton_t +{ + sal::systools::COMReference factory; + sal::systools::COMReference loader; + FontCollectionLoader_w7_singleton_t() + : factory(WinSalGraphics::getDWriteFactory()) + , loader(new FontCollectionLoader_w7) + { + factory->RegisterFontCollectionLoader(loader); + } + ~FontCollectionLoader_w7_singleton_t() { factory->UnregisterFontCollectionLoader(loader); } +}; + +// A simple enumerator class, which only operates on a single font file. +class FontFileEnumerator_w7 : public IUnknown_Impl +{ +public: + FontFileEnumerator_w7(IDWriteFontFile* collectionKey) + : m_fontFile(collectionKey) + { + assert(collectionKey); + AddRef(); + } + + // IDWriteFontFileEnumerator + HRESULT STDMETHODCALLTYPE MoveNext(BOOL* hasCurrentFile) override; + HRESULT STDMETHODCALLTYPE GetCurrentFontFile(IDWriteFontFile** fontFile) override; + +private: + sal::systools::COMReference m_fontFile; + size_t m_nextIndex = 0; +}; + +HRESULT STDMETHODCALLTYPE FontCollectionLoader_w7::CreateEnumeratorFromKey( + IDWriteFactory* /*factory*/, void const* collectionKey, UINT32 collectionKeySize, + /* OUT */ IDWriteFontFileEnumerator** fontFileEnumerator) +{ + if (!fontFileEnumerator) + return E_INVALIDARG; + *fontFileEnumerator = nullptr; + if (!collectionKey || collectionKeySize != sizeof(IDWriteFontFile*)) + return E_INVALIDARG; + + auto pFontFile = *static_cast(collectionKey); + auto it = std::find_if(m_fontFiles.begin(), m_fontFiles.end(), + [pFontFile](const auto& el) { return el.get() == pFontFile; }); + if (it == m_fontFiles.end()) + m_fontFiles.emplace_back(pFontFile); // cals AddRef + + *fontFileEnumerator = new (std::nothrow) FontFileEnumerator_w7(pFontFile); + return *fontFileEnumerator ? S_OK : E_OUTOFMEMORY; +} + +HRESULT STDMETHODCALLTYPE FontFileEnumerator_w7::MoveNext(BOOL* hasCurrentFile) +{ + if (!hasCurrentFile) + return E_INVALIDARG; + *hasCurrentFile = m_nextIndex == 0 ? TRUE : FALSE; + ++m_nextIndex; + return S_OK; +} + +HRESULT STDMETHODCALLTYPE FontFileEnumerator_w7::GetCurrentFontFile(IDWriteFontFile** fontFile) +{ + if (!fontFile) + return E_INVALIDARG; + if (m_nextIndex == 1) + { + *fontFile = m_fontFile; + m_fontFile->AddRef(); + return S_OK; + } + *fontFile = nullptr; + return E_FAIL; +} + +sal::systools::COMReference +getDWritePrivateFontCollection_w7(IDWriteFontFile* fontFile) +{ + static FontCollectionLoader_w7_singleton_t singleton; + sal::systools::COMReference collection; + sal::systools::ThrowIfFailed(singleton.factory->CreateCustomFontCollection( + singleton.loader, &fontFile, sizeof(fontFile), &collection), + SAL_WHERE); + return collection; +} + +// End of pre-Windows 10 compatibility code + +sal::systools::COMReference +getDWritePrivateFontCollection(IDWriteFontFace* fontFace) +{ + UINT32 numberOfFiles; + sal::systools::ThrowIfFailed(fontFace->GetFiles(&numberOfFiles, nullptr), SAL_WHERE); + if (numberOfFiles != 1) + return {}; + + sal::systools::COMReference fontFile; + sal::systools::ThrowIfFailed(fontFace->GetFiles(&numberOfFiles, &fontFile), SAL_WHERE); + + if (auto collection = getDWritePrivateFontCollection_w10(fontFile)) + return collection; + return getDWritePrivateFontCollection_w7(fontFile); +} +} + using namespace SkiaHelper; WinSkiaSalGraphicsImpl::WinSkiaSalGraphicsImpl(WinSalGraphics& rGraphics, @@ -155,47 +366,7 @@ WinSkiaSalGraphicsImpl::createDirectWriteTypeface(const WinFontInstance* pWinFon // collection. For such cases attempt to update a collection of // private fonts with this newly used font. - sal::systools::COMReference dwriteFactory3; - ThrowIfFailed(dwriteFactory->QueryInterface(&dwriteFactory3), SAL_WHERE); - - if (!dwriteFontSetBuilder) - ThrowIfFailed(dwriteFactory3->CreateFontSetBuilder(&dwriteFontSetBuilder), - SAL_WHERE); - - UINT32 numberOfFiles; - ThrowIfFailed(fontFace->GetFiles(&numberOfFiles, nullptr), SAL_WHERE); - if (numberOfFiles != 1) - return nullptr; - - sal::systools::COMReference fontFile; - ThrowIfFailed(fontFace->GetFiles(&numberOfFiles, &fontFile), SAL_WHERE); - - BOOL isSupported; - DWRITE_FONT_FILE_TYPE fileType; - UINT32 numberOfFonts; - ThrowIfFailed(fontFile->Analyze(&isSupported, &fileType, nullptr, &numberOfFonts), - SAL_WHERE); - if (!isSupported) - return nullptr; - - // For each font within the font file, get a font face reference and add to the builder. - for (UINT32 fontIndex = 0; fontIndex < numberOfFonts; ++fontIndex) - { - sal::systools::COMReference fontFaceReference; - if (FAILED(dwriteFactory3->CreateFontFaceReference(fontFile.get(), fontIndex, - DWRITE_FONT_SIMULATIONS_NONE, - &fontFaceReference))) - continue; - - // Leave it to DirectWrite to read properties directly out of the font files - dwriteFontSetBuilder->AddFontFaceReference(fontFaceReference.get()); - } - - sal::systools::COMReference fontSet; - ThrowIfFailed(dwriteFontSetBuilder->CreateFontSet(&fontSet), SAL_WHERE); - ThrowIfFailed(dwriteFactory3->CreateFontCollectionFromFontSet(fontSet.get(), - &dwritePrivateCollection), - SAL_WHERE); + dwritePrivateCollection = getDWritePrivateFontCollection(fontFace); ThrowIfFailed(dwritePrivateCollection->GetFontFromFontFace(fontFace, &font), SAL_WHERE); } } @@ -323,7 +494,6 @@ void WinSkiaSalGraphicsImpl::initFontInfo() void WinSkiaSalGraphicsImpl::ClearDevFontCache() { dwriteFontMgr.reset(); - dwriteFontSetBuilder.clear(); dwritePrivateCollection.clear(); dwriteDone = false; initFontInfo(); // get font info again, just in case