From 169d6c412964f8c4cc4a2aaff20420255b7ddd9c Mon Sep 17 00:00:00 2001 From: ohyzha Date: Fri, 17 Jan 2025 13:11:04 +0200 Subject: [PATCH] atlas factory Windows implementation --- openVulkanoCpp/Host/Linux/SystemInfo.cpp | 6 ++ openVulkanoCpp/Host/MacOS/SystemInfo.mm | 7 +- openVulkanoCpp/Host/SystemInfo.hpp | 1 + openVulkanoCpp/Host/Windows/SystemInfo.cpp | 57 +++++++++++++ openVulkanoCpp/Host/iOS/SystemInfo.mm | 7 +- openVulkanoCpp/Scene/Text/FontAtlas.hpp | 2 + .../Scene/Text/FontAtlasFactory.cpp | 79 +++++++++++++++++++ .../Scene/Text/FontAtlasFactory.hpp | 69 ++++++++++++++++ tests/Host/Windows/SystemFontsSearchTests.cpp | 34 ++++++++ 9 files changed, 260 insertions(+), 2 deletions(-) create mode 100644 openVulkanoCpp/Scene/Text/FontAtlasFactory.cpp create mode 100644 openVulkanoCpp/Scene/Text/FontAtlasFactory.hpp create mode 100644 tests/Host/Windows/SystemFontsSearchTests.cpp diff --git a/openVulkanoCpp/Host/Linux/SystemInfo.cpp b/openVulkanoCpp/Host/Linux/SystemInfo.cpp index 19fe0cf..896a9c8 100644 --- a/openVulkanoCpp/Host/Linux/SystemInfo.cpp +++ b/openVulkanoCpp/Host/Linux/SystemInfo.cpp @@ -394,4 +394,10 @@ namespace OpenVulkano { return InterfaceOrientation::Landscape; // TODO? } + + std::string SystemInfo::GetSystemFontPath(const std::string& fontName) + { + return ""; + } + } \ No newline at end of file diff --git a/openVulkanoCpp/Host/MacOS/SystemInfo.mm b/openVulkanoCpp/Host/MacOS/SystemInfo.mm index ca295f6..db95584 100644 --- a/openVulkanoCpp/Host/MacOS/SystemInfo.mm +++ b/openVulkanoCpp/Host/MacOS/SystemInfo.mm @@ -111,7 +111,7 @@ namespace OpenVulkano { NSOperatingSystemVersion sysVersion = [NSProcessInfo processInfo].operatingSystemVersion; osVersion = { static_cast(sysVersion.majorVersion), static_cast(sysVersion.minorVersion), - static_cast(sysVersion.patchVersion), 0 }; + static_cast(sysVersion.patchVersion), 0 }; } return osVersion; } @@ -202,4 +202,9 @@ namespace OpenVulkano { return InterfaceOrientation::Landscape; //TODO? } + + std::string SystemInfo::GetSystemFontPath(const std::string& fontName) + { + return ""; + } } diff --git a/openVulkanoCpp/Host/SystemInfo.hpp b/openVulkanoCpp/Host/SystemInfo.hpp index 7ab324c..f3a0b3e 100644 --- a/openVulkanoCpp/Host/SystemInfo.hpp +++ b/openVulkanoCpp/Host/SystemInfo.hpp @@ -69,6 +69,7 @@ namespace OpenVulkano static DeviceOrientation GetDeviceOrientation(); static void EnableDeviceOrientationEvents(); static InterfaceOrientation GetInterfaceOrientation(); + static std::string GetSystemFontPath(const std::string& fontName); static Event<> OnLowPowerModeChanged; static Event<> OnBatteryStateChanged; diff --git a/openVulkanoCpp/Host/Windows/SystemInfo.cpp b/openVulkanoCpp/Host/Windows/SystemInfo.cpp index bea3c6b..3366009 100644 --- a/openVulkanoCpp/Host/Windows/SystemInfo.cpp +++ b/openVulkanoCpp/Host/Windows/SystemInfo.cpp @@ -19,6 +19,9 @@ #include #include #include +#include +#include +#include // NOTE(vb): Windows defines macros like GetUserName that are used to automatically select the appropriate function version (GetUserNameA for ANSI and GetUserNameW for Unicode) // based on whether the _UNICODE macro is defined, so we manually undefine these macros to avoid naming collisions. @@ -28,6 +31,8 @@ #pragma comment(lib, "PowrProf.lib") #pragma comment(lib, "wbemuuid.lib") +#define QFR_DESCRIPTION 1 + namespace OpenVulkano { namespace @@ -585,4 +590,56 @@ namespace OpenVulkano return InterfaceOrientation::Landscape; } } + + std::string SystemInfo::GetSystemFontPath(const std::string& fontName) + { + // font name -> filename + static std::map fontFileMapping; + if (fontFileMapping.empty()) + { + // thank you Microsoft for function that is not even documented, but exists and it's the only function that + // can return real font name from font filename (e.g. font filename is times.ttf that corresponds to Times New Roman) + int(WINAPI * GetFontResourceInfoW)(wchar_t*, unsigned long*, void*, unsigned long); + *(FARPROC*) &GetFontResourceInfoW = GetProcAddress(GetModuleHandleA("gdi32"), "GetFontResourceInfoW"); + + std::wstring winDir; + winDir.resize(MAX_PATH); + UINT len = GetWindowsDirectoryW(winDir.data(), MAX_PATH); + winDir.resize(len); + std::filesystem::path fontsDir = std::filesystem::path(winDir) / "Fonts"; + for (const auto& fontFilename : std::filesystem::directory_iterator(fontsDir)) + { + unsigned long size = 0; + std::wstring ws = fontFilename.path().wstring(); + if (!GetFontResourceInfoW(ws.data(), &size, NULL, QFR_DESCRIPTION)) + { + continue; + } + std::wstring fontName; + fontName.resize(size); + if (GetFontResourceInfoW(ws.data(), &size, fontName.data(), QFR_DESCRIPTION)) + { + // remove null-terminated characters since size is always bigger than needed + std::string fontNameCropped(fontName.begin(), fontName.end()); + size_t realSize = 0; + for (; realSize < fontNameCropped.size(); realSize++) + { + if (fontNameCropped[realSize] == '\0') + { + break; + } + } + fontNameCropped.resize(realSize); + fontFileMapping[std::move(fontNameCropped)] = fontFilename.path().string(); + } + } + } + + // maybe check everything in lower case ? so that we can use Arial/arial as input parameter + if (fontFileMapping.contains(fontName)) + { + return fontFileMapping.at(fontName); + } + return ""; + } } \ No newline at end of file diff --git a/openVulkanoCpp/Host/iOS/SystemInfo.mm b/openVulkanoCpp/Host/iOS/SystemInfo.mm index 3182ba2..17b7855 100644 --- a/openVulkanoCpp/Host/iOS/SystemInfo.mm +++ b/openVulkanoCpp/Host/iOS/SystemInfo.mm @@ -114,7 +114,7 @@ namespace OpenVulkano { NSOperatingSystemVersion osVersion = [NSProcessInfo processInfo].operatingSystemVersion; osv = { static_cast(osVersion.majorVersion), static_cast(osVersion.minorVersion), - static_cast(osVersion.patchVersion), 0 }; + static_cast(osVersion.patchVersion), 0 }; } return osv; } @@ -288,4 +288,9 @@ namespace OpenVulkano } return InterfaceOrientation::Landscape; } + + std::string SystemInfo::GetSystemFontPath(const std::string& fontName) + { + return ""; + } } diff --git a/openVulkanoCpp/Scene/Text/FontAtlas.hpp b/openVulkanoCpp/Scene/Text/FontAtlas.hpp index 9f04be8..5703512 100644 --- a/openVulkanoCpp/Scene/Text/FontAtlas.hpp +++ b/openVulkanoCpp/Scene/Text/FontAtlas.hpp @@ -45,6 +45,8 @@ namespace OpenVulkano::Scene void DeserializeMetadata(const std::span& data); public: + using Ptr = std::shared_ptr; + FontAtlas() = default; FontAtlas(const Math::Vector2ui textureResolution, const double lineHeight, const FontAtlasType atlasType, DataFormat dataFormat) diff --git a/openVulkanoCpp/Scene/Text/FontAtlasFactory.cpp b/openVulkanoCpp/Scene/Text/FontAtlasFactory.cpp new file mode 100644 index 0000000..fd0427d --- /dev/null +++ b/openVulkanoCpp/Scene/Text/FontAtlasFactory.cpp @@ -0,0 +1,79 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +#include "FontAtlasFactory.hpp" +#include "Scene/SdfFontAtlasGenerator.hpp" +#include "Scene/BitmapFontAtlasGenerator.hpp" +#include "Host/SystemInfo.hpp" + +namespace OpenVulkano::Scene +{ + FontAtlas::Ptr FontAtlasFactory::GetFontAtlasScalable(const std::string& fontIdentifier, + const std::set& charset, bool msdf) const + { + const std::string fileName = FindFont(fontIdentifier); + if (fileName.empty()) + { + return nullptr; + } + + const std::set& setRef = (charset.empty() ? FontAtlasGeneratorBase::LoadAllGlyphs(fontIdentifier) : charset); + FontIdentifier id(fontIdentifier, setRef, SubpixelLayout::UNKNOWN, 0, msdf); + if (m_atlasesCache.contains(id)) + { + return m_atlasesCache.at(id); + } + + if (msdf) + { + MsdfFontAtlasGenerator msdfGen; + msdfGen.GenerateAtlas(fileName, setRef); + m_atlasesCache[id] = msdfGen.GetAtlas(); + return m_atlasesCache.at(id); + } + SdfFontAtlasGenerator sdfGen; + sdfGen.GenerateAtlas(fileName, setRef); + m_atlasesCache[id] = sdfGen.GetAtlas(); + return m_atlasesCache.at(id); + } + + FontAtlas::Ptr FontAtlasFactory::GetFontAtlas(const std::string& fontIdentifier, float ptSize, + const std::set& charset, + std::optional subpixelLayout) const + { + const std::string fileName = FindFont(fontIdentifier); + if (fileName.empty()) + { + return nullptr; + } + + const std::set& setRef = (charset.empty() ? FontAtlasGeneratorBase::LoadAllGlyphs(fontIdentifier) : charset); + FontIdentifier id(fontIdentifier, setRef, subpixelLayout.value_or(SubpixelLayout::UNKNOWN), ptSize, false); + if (m_atlasesCache.contains(id)) + { + return m_atlasesCache.at(id); + } + + FontPixelSizeConfig cfg(ptSize); + BitmapFontAtlasGenerator bitmapGen(cfg, subpixelLayout); + bitmapGen.GenerateAtlas(fileName, setRef); + m_atlasesCache[id] = bitmapGen.GetAtlas(); + return m_atlasesCache.at(id); + } + + std::string FontAtlasFactory::FindFont(const std::string& fontIdentifier) const + { + if (!std::filesystem::exists(fontIdentifier)) + { + if (!m_allowSystemFonts) + { + return ""; + } + return SystemInfo::GetSystemFontPath(fontIdentifier); + } + return fontIdentifier; + } +} diff --git a/openVulkanoCpp/Scene/Text/FontAtlasFactory.hpp b/openVulkanoCpp/Scene/Text/FontAtlasFactory.hpp new file mode 100644 index 0000000..31ed708 --- /dev/null +++ b/openVulkanoCpp/Scene/Text/FontAtlasFactory.hpp @@ -0,0 +1,69 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +#pragma once + +#include "FontAtlas.hpp" +#include "Scene/SubpixelLayout.hpp" + +namespace OpenVulkano::Scene +{ + class FontAtlasFactory final + { + struct FontIdentifier + { + FontIdentifier(const std::string& font_, const std::set& charset, SubpixelLayout subpixelLayout_, + float ptSize_, bool msdf_) + : font(font_), subpixelLayout(subpixelLayout_), ptSize(ptSize_), msdf(msdf_) + { + std::for_each(charset.begin(), charset.end(), [&](uint32_t c) { charsetHash ^= c; }); + } + + std::string font; + size_t charsetHash = 0; + SubpixelLayout subpixelLayout = SubpixelLayout::UNKNOWN; + float ptSize = 0; + bool msdf = true; + + bool operator<(const FontIdentifier& other) const + { + if (font != other.font) + { + return font < other.font; + } + if (charsetHash != other.charsetHash) + { + return charsetHash < other.charsetHash; + } + if (subpixelLayout != other.subpixelLayout) + { + return subpixelLayout < other.subpixelLayout; + } + if (ptSize != other.ptSize) + { + return ptSize < other.ptSize; + } + return msdf < other.msdf; + } + + }; + public: + FontAtlasFactory(bool allowSystemFonts = true) : m_allowSystemFonts(allowSystemFonts) {} + [[nodiscard]] FontAtlas::Ptr GetFontAtlasScalable(const std::string& fontIdentifier, + const std::set& charset = {}, + bool msdf = true) const; + [[nodiscard]] FontAtlas::Ptr GetFontAtlas(const std::string& fontIdentifier, float ptSize, + const std::set& charset = {}, + std::optional subpixelLayout = std::nullopt) const; + + private: + std::string FindFont(const std::string& fontFile) const; + + private: + bool m_allowSystemFonts; + mutable std::map m_atlasesCache; + }; +} diff --git a/tests/Host/Windows/SystemFontsSearchTests.cpp b/tests/Host/Windows/SystemFontsSearchTests.cpp new file mode 100644 index 0000000..4fdf919 --- /dev/null +++ b/tests/Host/Windows/SystemFontsSearchTests.cpp @@ -0,0 +1,34 @@ +/* + * 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 https://mozilla.org/MPL/2.0/. + */ + +#include +#include "Base/Logger.hpp" +#include "Host/SystemInfo.hpp" +#include + +#include "Scene/Text/FontAtlasFactory.hpp" +#include "Host/ResourceLoader.hpp" + +using namespace OpenVulkano; + +TEST_CASE("Search system fonts") +{ + Logger::SetupLogger("", "tests.log"); + + // assume these fonts are present since they are default + std::filesystem::path path = SystemInfo::GetSystemFontPath("Arial"); + REQUIRE(path.filename() == "arial.ttf"); + + // assume these fonts are present since they are default + path = SystemInfo::GetSystemFontPath("Times New Roman"); + REQUIRE(path.filename() == "times.ttf"); + + path = SystemInfo::GetSystemFontPath("Arial Bold Italic"); + REQUIRE(path.filename() == "arialbi.ttf"); + + path = SystemInfo::GetSystemFontPath("NON-EXISTING Font"); + REQUIRE(path.empty()); +}