diff --git a/CMakeLists.txt b/CMakeLists.txt index 21b771d..429f65d 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -130,7 +130,9 @@ if((NOT EMSCRIPTEN) AND (GRAPHICS_BACKEND STREQUAL "Vulkan")) endif() if(WIN32) - list(APPEND sources "windowsinstall/app.rc") + list(APPEND sources + "src/WindowsFontData/CustomFontSetManager.cpp" + "windowsinstall/app.rc") endif() # Get libraries @@ -289,6 +291,10 @@ endif() target_link_libraries(infinipaint zstd::libzstd_static) +if((NOT EMSCRIPTEN) AND WIN32) + target_link_libraries(infinipaint dwrite) +endif() + if(EMSCRIPTEN) add_subdirectory(deps/datachannel-wasm EXCLUDE_FROM_ALL) target_link_libraries(infinipaint datachannel-wasm) diff --git a/src/FontData.cpp b/src/FontData.cpp index 1464b75..444e94e 100644 --- a/src/FontData.cpp +++ b/src/FontData.cpp @@ -8,6 +8,10 @@ #include #elif _WIN32 #include + #include "WindowsFontData/CustomFontSetManager.h" + + DWriteCustomFontSets::CustomFontSetManager fontSetManagerWindows; + #else #include #include @@ -15,21 +19,29 @@ #endif #include +#include FontData::FontData() { + #ifdef __EMSCRIPTEN__ localFontMgr = SkFontMgr_New_Custom_Empty(); defaultFontMgr = SkFontMgr_New_Custom_Directory("data/fonts"); #elif _WIN32 localFontMgr = SkFontMgr_New_DirectWrite(); - defaultFontMgr = SkFontMgr_New_DirectWrite(); + std::vector fontPaths; + for(const std::filesystem::path& dirEntry : std::filesystem::recursive_directory_iterator(std::filesystem::path(L"data\\fonts"))) + fontPaths.emplace_back(dirEntry.wstring()); + fontSetManagerWindows.CreateFontSetUsingLocalFontFiles(fontPaths); + fontSetManagerWindows.CreateFontCollectionFromFontSet(); + + IDWriteFactory* fac = fontSetManagerWindows.IDWriteFactory5_IsAvailable() ? fontSetManagerWindows.m_dwriteFactory5.Get() : fontSetManagerWindows.m_dwriteFactory3.Get(); + defaultFontMgr = SkFontMgr_New_DirectWrite(fac, fontSetManagerWindows.m_customFontCollection.Get()); #else localFontMgr = SkFontMgr_New_FontConfig(nullptr, SkFontScanner_Make_FreeType()); defaultFontMgr = SkFontMgr_New_Custom_Directory("data/fonts"); #endif - map["Roboto"] = defaultFontMgr->makeFromFile("data/fonts/Roboto-variable.ttf"); collection = sk_make_sp(); diff --git a/src/FontData.hpp b/src/FontData.hpp index 4c1e2ef..5221979 100644 --- a/src/FontData.hpp +++ b/src/FontData.hpp @@ -6,6 +6,8 @@ #include #include +#include + using namespace Eigen; struct FontData { diff --git a/src/WindowsFontData/CustomFontSetManager.cpp b/src/WindowsFontData/CustomFontSetManager.cpp new file mode 100644 index 0000000..4bdb79f --- /dev/null +++ b/src/WindowsFontData/CustomFontSetManager.cpp @@ -0,0 +1,203 @@ +#pragma once +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +// Removed unnecessary functions. Is a sample from: +// https://github.com/microsoft/Windows-classic-samples/tree/main + +#include "CustomFontSetManager.h" + +using Microsoft::WRL::ComPtr; + +namespace DWriteCustomFontSets +{ + + //********************************************************************** + // + // Constructors, destructors + // + //********************************************************************** + + CustomFontSetManager::CustomFontSetManager() + { + HRESULT hr; + + // IDWriteFactory3 supports APIs available in any Windows 10 version (build 10240 or later). + DX::ThrowIfFailed( + DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory3), &m_dwriteFactory3) + ); + +#ifndef FORCE_TH1_IMPLEMENTATION + // IDWriteFactory5 supports APIs available in Windows 10 Creators Update (preview build 15021 or later). + hr = m_dwriteFactory3.As(&m_dwriteFactory5); + if (hr == E_NOINTERFACE) + { + // Let this go. Later, if we might use the interface, we'll branch gracefully. + } + else + { + DX::ThrowIfFailed(hr); + } +#endif // !FORCE_TH1_IMPLEMENTATION + + } // end CustomFontSetManager::CustomFontSetManager() + + + //********************************************************************** + // + // Method for checking API availability. + // + //********************************************************************** + + bool CustomFontSetManager::IDWriteFactory5_IsAvailable() + { + return m_dwriteFactory5 != nullptr; + } + + + void CustomFontSetManager::CreateFontCollectionFromFontSet() { + if(IDWriteFactory5_IsAvailable()) + m_dwriteFactory5->CreateFontCollectionFromFontSet(m_customFontSet.Get(), m_customFontCollection.GetAddressOf()); + else + m_dwriteFactory3->CreateFontCollectionFromFontSet(m_customFontSet.Get(), m_customFontCollection.GetAddressOf()); + } + + //********************************************************************** + // + // Methods for the creating a font set under the various scenarios. + // + //********************************************************************** + + void CustomFontSetManager::CreateFontSetUsingLocalFontFiles(const std::vector& selectedFilePathNames) + { + // Requires any version of Windows 10. + + // Creates a custom font set for font files at paths in local storage. If a file is + // an OpenType collection file, which contains multiple fonts, all of the fonts will + // be added to the collection. + // + // If running on Windows 10 Creators Update (preview build 15021 or later), the + // IDWriteFontSetBuilder1::AddFontFile method will be used. This method handles all of + // the fonts in an OpenType collection file in a single call, and it also supports + // OpenType variable fonts, which can be realized as many different font faces -- all + // named instances in the variable font will be added in a single call. This method is + // recommended when available. + // + // If running on earlier Windows 10 versions, the method used will be + // IDWriteFontSetBuilder::AddFontFaceReference. This does not support OpenType variable + // fonts, and also requires that a font file first be analyzed to determine whether it + // is an OpenType collection file, in which case each font must be handled in a separate + // call. + // + // If one of the input path names is not a font file, it will be ignored. + // + // When creating a custom font set with font files that are not assumed to be known by + // the app, DWrite will need to extract some basic font properties, such as names, + // directly from the font files. This will result in a little extra I/O overhead. + + // Check if IDWriteFontSetBuilder1 will be available (we're running on preview build 15021 or later) + if (m_dwriteFactory5 != nullptr) + { + // We'll need an IDWriteFontFile for each font file to be added to the font set. + // We won't assume every file is a font file in a supported format; if not, we'll + // ignore the file. + + // Get the font set builder -- IDWriteFontSetBuilder1. + ComPtr fontSetBuilder; + DX::ThrowIfFailed( + m_dwriteFactory5->CreateFontSetBuilder(&fontSetBuilder) + ); + + // Loop over the file paths. + for (auto& filePath : selectedFilePathNames) + { + ComPtr fontFile; + + DX::ThrowIfFailed( + m_dwriteFactory5->CreateFontFileReference(filePath.c_str(), /* filetime */ nullptr, &fontFile) + ); + + // Add to the font set builder. If the file is a collection, all of the fonts will + // get added. If the file is not a supported font file, the call will fail; we'll + // check for that and ignore. + HRESULT hr = fontSetBuilder->AddFontFile(fontFile.Get()); + if ((hr != DWRITE_E_FILEFORMAT) && (hr != DWRITE_E_FILENOTFOUND) && (hr != DWRITE_E_FILEACCESS)) + { + // Ignore file format or access errors. + DX::ThrowIfFailed(hr); + } + } // for loop + + // Now create the custom font set. + DX::ThrowIfFailed( + fontSetBuilder->CreateFontSet(&m_customFontSet) + ); + } + else + { + // We're limited to APIs and functionality available on earlier Windows 10 versions, prior + // to the Windows 10 Creators Update (preview build 15021). + // + // Also, we'll need an IDWriteFontFaceReference for each font to be added to the font set. + // If a file is an OpenType collection, it may contain multiple fonts. For that reason, + // we'll need to analyze the file to get the count of fonts, and then provide an index + // when creating each font face reference. + + // Get the font set builder - IDWriteFontSetBuilder. + ComPtr fontSetBuilder; + DX::ThrowIfFailed( + m_dwriteFactory3->CreateFontSetBuilder(&fontSetBuilder) + ); + + // Loop over the file paths. + for (auto& filePath : selectedFilePathNames) + { + ComPtr fontFile; + DX::ThrowIfFailed( + m_dwriteFactory3->CreateFontFileReference(filePath.c_str(), /* filetime */ nullptr, &fontFile) + ); + + // Confirm the file is a supported font file and get the collection face count. + BOOL isSupported; + DWRITE_FONT_FILE_TYPE fileType; + UINT32 numberOfFonts; + DX::ThrowIfFailed( + fontFile->Analyze(&isSupported, &fileType, /* face type */ nullptr, &numberOfFonts) + ); + if (!isSupported) + continue; + + // For each font within the font file, get a font face reference and add to the builder. + for (UINT32 fontIndex = 0; fontIndex < numberOfFonts; fontIndex++) + { + ComPtr fontFaceReference; + DX::ThrowIfFailed( + m_dwriteFactory3->CreateFontFaceReference(fontFile.Get(), fontIndex, DWRITE_FONT_SIMULATIONS_NONE, &fontFaceReference) + ); + + // If fonts were assumed known, we could set custom properties, and would do that here. + // But these are not assumed known, so we'll leave it to DirectWrite to read properties + // directly out of the font files. + + DX::ThrowIfFailed( + fontSetBuilder->AddFontFaceReference(fontFaceReference.Get()) + ); + } // for loop -- over fonts with font file + } // for loop -- over font files + + // Now create the custom font set + DX::ThrowIfFailed( + fontSetBuilder->CreateFontSet(&m_customFontSet) + ); + + } // end if (IDWriteFactory5_IsAvailable()) + } // end CustomFontSetManager::CreateFontSetUsingLocalFontFiles() +} // namespace DWriteCustomFontSets diff --git a/src/WindowsFontData/CustomFontSetManager.h b/src/WindowsFontData/CustomFontSetManager.h new file mode 100644 index 0000000..9985354 --- /dev/null +++ b/src/WindowsFontData/CustomFontSetManager.h @@ -0,0 +1,40 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +// Removed unnecessary functions. Is a sample from: +// https://github.com/microsoft/Windows-classic-samples/tree/main + +#pragma once + +#include "stdafx.h" +#include "DXHelper.h" + +namespace DWriteCustomFontSets +{ + class CustomFontSetManager + { + public: + CustomFontSetManager(); + + // Method for checking API availability + bool IDWriteFactory5_IsAvailable(); + + // Methods for creating font sets under the different scenarios. + void CreateFontSetUsingLocalFontFiles(const std::vector& selectedFilePathNames); + + void CreateFontCollectionFromFontSet(); + + Microsoft::WRL::ComPtr m_customFontSet; + Microsoft::WRL::ComPtr m_dwriteFactory3; // Available on all Windows 10 versions (build 10240 and later). + Microsoft::WRL::ComPtr m_dwriteFactory5; // Available on Windows 10 Creators Update (preview build 15021 or later). + Microsoft::WRL::ComPtr m_customFontCollection; + }; // class CustomFontSetManager +} // namespace DWriteCustomFontSets diff --git a/src/WindowsFontData/DXHelper.h b/src/WindowsFontData/DXHelper.h new file mode 100644 index 0000000..f695948 --- /dev/null +++ b/src/WindowsFontData/DXHelper.h @@ -0,0 +1,47 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + + +#pragma once + +#include "stdafx.h" + +// THROW_SYSTEM_ERROR is used to differentiate Win32 versus WinRT implementation for the error handling +// defined here. Define THROW_SYSTEM_ERROR in stdafx.h if this is being used in a Win32 project, but not +// if it's being used in a WinRT project. + + +namespace DX +{ +#ifdef THROW_SYSTEM_ERROR // Win32 + inline std::system_error CreateDXException(HRESULT hr) + { + return std::system_error(std::error_code(hr, std::system_category())); + } +#else // Throw WinRT exception. + inline Platform::Exception^ CreateDXException(HRESULT hr) + { + return Platform::Exception::CreateException(hr); + } +#endif // THROW_SYSTEM_ERROR + + + // Convert DirectX error codes to exceptions. + inline void ThrowIfFailed(HRESULT hr) + { + if (FAILED(hr)) + { + // Set a breakpoint on this line to catch Win32 API errors. + throw CreateDXException(hr); + } + } + +} // DX namespace diff --git a/src/WindowsFontData/stdafx.h b/src/WindowsFontData/stdafx.h new file mode 100644 index 0000000..3133fad --- /dev/null +++ b/src/WindowsFontData/stdafx.h @@ -0,0 +1,44 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + + +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently + +#pragma once + +// Define used to determine Win32 rather than WinRT implementation. This is used within DXHelper.h. +// Don't define in a WinRT project. +#ifndef THROW_SYSTEM_ERROR +#define THROW_SYSTEM_ERROR +#endif // !THROW_SYSTEM_ERROR + + +// Define used to force code paths that would be available on Windows 10 versions prior to the +// Creators Update (preview build 15021). Only define if you want to force those code paths. +//#ifndef FORCE_TH1_IMPLEMENTATION +//#define FORCE_TH1_IMPLEMENTATION +//#endif // !FORCE_TH1_IMPLEMENTATION + +#include +#include +#include +#include // std::map, used in CommandLineArgs +#include +#include +#include +#include // Microsoft::WRL, for ComPtr; could also use ATL CComPtr or raw pointers, but not std::shared_ptr + + +// The project linker settings have been modified to list dwrite.lib as an additional dependency. (Note: this needs to be +// done for all configurations and platforms.) Alternatively, the following pragma can be used to declare the dependency: +// #pragma comment(lib, "dwrite")