diff --git a/src/runtime/FontAtlasBuilder.cpp b/src/runtime/FontAtlasBuilder.cpp index f3e69ec..c963dfa 100644 --- a/src/runtime/FontAtlasBuilder.cpp +++ b/src/runtime/FontAtlasBuilder.cpp @@ -1,16 +1,9 @@ #include "FontAtlasBuilder.h" -#include "NativeHandles.h" -#include "RuntimeJson.h" - #include #include -#include #include #include -#include -#include -#include namespace RenderCadenceCompositor { @@ -27,45 +20,6 @@ std::string NumberText(double value) stream << value; return stream.str(); } - -std::string ReadTextFile(const std::filesystem::path& path) -{ - std::ifstream input(path, std::ios::binary); - if (!input) - return std::string(); - std::ostringstream buffer; - buffer << input.rdbuf(); - return buffer.str(); -} - -const JsonValue* FindObjectValue(const JsonValue& object, const std::string& key) -{ - return object.isObject() ? object.find(key) : nullptr; -} - -double NumberMember(const JsonValue& object, const std::string& key, double fallback = 0.0) -{ - const JsonValue* value = FindObjectValue(object, key); - return value && value->isNumber() ? value->asNumber(fallback) : fallback; -} - -struct ComThreadGuard -{ - ~ComThreadGuard() - { - if (initialized) - CoUninitialize(); - } - - bool Initialize() - { - const HRESULT result = CoInitializeEx(nullptr, COINIT_MULTITHREADED); - initialized = SUCCEEDED(result); - return SUCCEEDED(result) || result == RPC_E_CHANGED_MODE; - } - - bool initialized = false; -}; } FontAtlasBuilder::FontAtlasBuilder(FontAtlasBuildConfig config) : @@ -184,148 +138,6 @@ std::filesystem::path FontAtlasBuilder::PackageCacheDirectory(const ShaderPackag return mConfig.cacheRoot / SanitizePathToken(shaderPackage.id); } -bool FontAtlasBuilder::LoadAtlasJson(FontAtlasBuildOutput& output, std::string& error) const -{ - const std::string jsonText = ReadTextFile(output.jsonPath); - if (jsonText.empty()) - { - error = "Could not read font atlas json: " + output.jsonPath.string(); - return false; - } - - JsonValue root; - if (!ParseJson(jsonText, root, error)) - return false; - - const JsonValue* metrics = FindObjectValue(root, "metrics"); - if (metrics) - { - output.ascender = NumberMember(*metrics, "ascender", output.ascender); - output.descender = NumberMember(*metrics, "descender", output.descender); - output.lineHeight = NumberMember(*metrics, "lineHeight", output.lineHeight); - } - - const JsonValue* glyphs = FindObjectValue(root, "glyphs"); - if (!glyphs || !glyphs->isArray()) - { - error = "Font atlas json has no glyph array: " + output.jsonPath.string(); - return false; - } - - output.glyphsByCodepoint.clear(); - for (const JsonValue& glyphJson : glyphs->asArray()) - { - if (!glyphJson.isObject()) - continue; - - const unsigned codepoint = static_cast(NumberMember(glyphJson, "unicode", 0.0)); - FontAtlasBuildOutput::Glyph glyph; - glyph.advance = NumberMember(glyphJson, "advance", 0.0); - - const JsonValue* planeBounds = FindObjectValue(glyphJson, "planeBounds"); - const JsonValue* atlasBounds = FindObjectValue(glyphJson, "atlasBounds"); - if (planeBounds && atlasBounds) - { - glyph.planeLeft = NumberMember(*planeBounds, "left", 0.0); - glyph.planeTop = NumberMember(*planeBounds, "top", 0.0); - glyph.planeRight = NumberMember(*planeBounds, "right", 0.0); - glyph.planeBottom = NumberMember(*planeBounds, "bottom", 0.0); - glyph.atlasLeft = NumberMember(*atlasBounds, "left", 0.0); - glyph.atlasTop = NumberMember(*atlasBounds, "top", 0.0); - glyph.atlasRight = NumberMember(*atlasBounds, "right", 0.0); - glyph.atlasBottom = NumberMember(*atlasBounds, "bottom", 0.0); - glyph.hasBounds = true; - } - - output.glyphsByCodepoint[codepoint] = glyph; - } - - error.clear(); - return true; -} - -bool FontAtlasBuilder::LoadAtlasImage(FontAtlasBuildOutput& output, std::string& error) const -{ - ComThreadGuard comGuard; - if (!comGuard.Initialize()) - { - error = "Could not initialize COM for font atlas PNG loading."; - return false; - } - - Microsoft::WRL::ComPtr factory; - HRESULT result = CoCreateInstance( - CLSID_WICImagingFactory, - nullptr, - CLSCTX_INPROC_SERVER, - IID_PPV_ARGS(factory.GetAddressOf())); - if (FAILED(result)) - { - error = "Could not create WIC imaging factory for font atlas PNG loading."; - return false; - } - - Microsoft::WRL::ComPtr decoder; - result = factory->CreateDecoderFromFilename( - output.imagePath.wstring().c_str(), - nullptr, - GENERIC_READ, - WICDecodeMetadataCacheOnLoad, - decoder.GetAddressOf()); - if (FAILED(result)) - { - error = "Could not decode font atlas PNG: " + output.imagePath.string(); - return false; - } - - Microsoft::WRL::ComPtr frame; - result = decoder->GetFrame(0, frame.GetAddressOf()); - if (FAILED(result)) - { - error = "Could not read font atlas PNG frame: " + output.imagePath.string(); - return false; - } - - Microsoft::WRL::ComPtr converter; - result = factory->CreateFormatConverter(converter.GetAddressOf()); - if (FAILED(result)) - { - error = "Could not create WIC format converter for font atlas PNG."; - return false; - } - - result = converter->Initialize( - frame.Get(), - GUID_WICPixelFormat32bppRGBA, - WICBitmapDitherTypeNone, - nullptr, - 0.0, - WICBitmapPaletteTypeCustom); - if (FAILED(result)) - { - error = "Could not convert font atlas PNG to RGBA."; - return false; - } - - UINT width = 0; - UINT height = 0; - converter->GetSize(&width, &height); - output.width = static_cast(width); - output.height = static_cast(height); - output.rgbaPixels.assign(static_cast(output.width) * output.height * 4u, 0); - - const UINT stride = width * 4u; - result = converter->CopyPixels(nullptr, stride, static_cast(output.rgbaPixels.size()), output.rgbaPixels.data()); - if (FAILED(result)) - { - error = "Could not copy font atlas PNG pixels."; - return false; - } - - error.clear(); - return true; -} - std::string FontAtlasBuilder::SanitizePathToken(const std::string& value) { std::string sanitized; @@ -339,35 +151,4 @@ std::string FontAtlasBuilder::SanitizePathToken(const std::string& value) } return sanitized.empty() ? "font" : sanitized; } - -bool FontAtlasBuilder::RunProcess(const std::string& commandLine, const std::filesystem::path& workingDirectory, std::string& error) -{ - STARTUPINFOA startupInfo = {}; - PROCESS_INFORMATION processInfo = {}; - startupInfo.cb = sizeof(startupInfo); - - std::vector mutableCommandLine(commandLine.begin(), commandLine.end()); - mutableCommandLine.push_back('\0'); - - if (!CreateProcessA(nullptr, mutableCommandLine.data(), nullptr, nullptr, FALSE, CREATE_NO_WINDOW, nullptr, workingDirectory.string().c_str(), &startupInfo, &processInfo)) - { - error = "Failed to launch msdf-atlas-gen.exe."; - return false; - } - - UniqueHandle processHandle(processInfo.hProcess); - UniqueHandle threadHandle(processInfo.hThread); - WaitForSingleObject(processHandle.get(), INFINITE); - - DWORD exitCode = 0; - GetExitCodeProcess(processHandle.get(), &exitCode); - if (exitCode != 0) - { - error = "msdf-atlas-gen.exe returned a non-zero exit code."; - return false; - } - - error.clear(); - return true; -} } diff --git a/src/runtime/FontAtlasImageLoader.cpp b/src/runtime/FontAtlasImageLoader.cpp new file mode 100644 index 0000000..5bc5c9b --- /dev/null +++ b/src/runtime/FontAtlasImageLoader.cpp @@ -0,0 +1,111 @@ +#include "FontAtlasBuilder.h" + +#include +#include +#include + +namespace RenderCadenceCompositor +{ +namespace +{ +struct ComThreadGuard +{ + ~ComThreadGuard() + { + if (initialized) + CoUninitialize(); + } + + bool Initialize() + { + const HRESULT result = CoInitializeEx(nullptr, COINIT_MULTITHREADED); + initialized = SUCCEEDED(result); + return SUCCEEDED(result) || result == RPC_E_CHANGED_MODE; + } + + bool initialized = false; +}; +} + +bool FontAtlasBuilder::LoadAtlasImage(FontAtlasBuildOutput& output, std::string& error) const +{ + ComThreadGuard comGuard; + if (!comGuard.Initialize()) + { + error = "Could not initialize COM for font atlas PNG loading."; + return false; + } + + Microsoft::WRL::ComPtr factory; + HRESULT result = CoCreateInstance( + CLSID_WICImagingFactory, + nullptr, + CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(factory.GetAddressOf())); + if (FAILED(result)) + { + error = "Could not create WIC imaging factory for font atlas PNG loading."; + return false; + } + + Microsoft::WRL::ComPtr decoder; + result = factory->CreateDecoderFromFilename( + output.imagePath.wstring().c_str(), + nullptr, + GENERIC_READ, + WICDecodeMetadataCacheOnLoad, + decoder.GetAddressOf()); + if (FAILED(result)) + { + error = "Could not decode font atlas PNG: " + output.imagePath.string(); + return false; + } + + Microsoft::WRL::ComPtr frame; + result = decoder->GetFrame(0, frame.GetAddressOf()); + if (FAILED(result)) + { + error = "Could not read font atlas PNG frame: " + output.imagePath.string(); + return false; + } + + Microsoft::WRL::ComPtr converter; + result = factory->CreateFormatConverter(converter.GetAddressOf()); + if (FAILED(result)) + { + error = "Could not create WIC format converter for font atlas PNG."; + return false; + } + + result = converter->Initialize( + frame.Get(), + GUID_WICPixelFormat32bppRGBA, + WICBitmapDitherTypeNone, + nullptr, + 0.0, + WICBitmapPaletteTypeCustom); + if (FAILED(result)) + { + error = "Could not convert font atlas PNG to RGBA."; + return false; + } + + UINT width = 0; + UINT height = 0; + converter->GetSize(&width, &height); + output.width = static_cast(width); + output.height = static_cast(height); + output.rgbaPixels.assign(static_cast(output.width) * output.height * 4u, 0); + + const UINT stride = width * 4u; + result = converter->CopyPixels(nullptr, stride, static_cast(output.rgbaPixels.size()), output.rgbaPixels.data()); + if (FAILED(result)) + { + error = "Could not copy font atlas PNG pixels."; + return false; + } + + error.clear(); + return true; +} +} diff --git a/src/runtime/FontAtlasMetadata.cpp b/src/runtime/FontAtlasMetadata.cpp new file mode 100644 index 0000000..3e4b605 --- /dev/null +++ b/src/runtime/FontAtlasMetadata.cpp @@ -0,0 +1,93 @@ +#include "FontAtlasBuilder.h" + +#include "RuntimeJson.h" + +#include +#include + +namespace RenderCadenceCompositor +{ +namespace +{ +std::string ReadTextFile(const std::filesystem::path& path) +{ + std::ifstream input(path, std::ios::binary); + if (!input) + return std::string(); + std::ostringstream buffer; + buffer << input.rdbuf(); + return buffer.str(); +} + +const JsonValue* FindObjectValue(const JsonValue& object, const std::string& key) +{ + return object.isObject() ? object.find(key) : nullptr; +} + +double NumberMember(const JsonValue& object, const std::string& key, double fallback = 0.0) +{ + const JsonValue* value = FindObjectValue(object, key); + return value && value->isNumber() ? value->asNumber(fallback) : fallback; +} +} + +bool FontAtlasBuilder::LoadAtlasJson(FontAtlasBuildOutput& output, std::string& error) const +{ + const std::string jsonText = ReadTextFile(output.jsonPath); + if (jsonText.empty()) + { + error = "Could not read font atlas json: " + output.jsonPath.string(); + return false; + } + + JsonValue root; + if (!ParseJson(jsonText, root, error)) + return false; + + const JsonValue* metrics = FindObjectValue(root, "metrics"); + if (metrics) + { + output.ascender = NumberMember(*metrics, "ascender", output.ascender); + output.descender = NumberMember(*metrics, "descender", output.descender); + output.lineHeight = NumberMember(*metrics, "lineHeight", output.lineHeight); + } + + const JsonValue* glyphs = FindObjectValue(root, "glyphs"); + if (!glyphs || !glyphs->isArray()) + { + error = "Font atlas json has no glyph array: " + output.jsonPath.string(); + return false; + } + + output.glyphsByCodepoint.clear(); + for (const JsonValue& glyphJson : glyphs->asArray()) + { + if (!glyphJson.isObject()) + continue; + + const unsigned codepoint = static_cast(NumberMember(glyphJson, "unicode", 0.0)); + FontAtlasBuildOutput::Glyph glyph; + glyph.advance = NumberMember(glyphJson, "advance", 0.0); + + const JsonValue* planeBounds = FindObjectValue(glyphJson, "planeBounds"); + const JsonValue* atlasBounds = FindObjectValue(glyphJson, "atlasBounds"); + if (planeBounds && atlasBounds) + { + glyph.planeLeft = NumberMember(*planeBounds, "left", 0.0); + glyph.planeTop = NumberMember(*planeBounds, "top", 0.0); + glyph.planeRight = NumberMember(*planeBounds, "right", 0.0); + glyph.planeBottom = NumberMember(*planeBounds, "bottom", 0.0); + glyph.atlasLeft = NumberMember(*atlasBounds, "left", 0.0); + glyph.atlasTop = NumberMember(*atlasBounds, "top", 0.0); + glyph.atlasRight = NumberMember(*atlasBounds, "right", 0.0); + glyph.atlasBottom = NumberMember(*atlasBounds, "bottom", 0.0); + glyph.hasBounds = true; + } + + output.glyphsByCodepoint[codepoint] = glyph; + } + + error.clear(); + return true; +} +} diff --git a/src/runtime/FontAtlasProcess.cpp b/src/runtime/FontAtlasProcess.cpp new file mode 100644 index 0000000..4ac4d51 --- /dev/null +++ b/src/runtime/FontAtlasProcess.cpp @@ -0,0 +1,39 @@ +#include "FontAtlasBuilder.h" + +#include "NativeHandles.h" + +#include + +namespace RenderCadenceCompositor +{ +bool FontAtlasBuilder::RunProcess(const std::string& commandLine, const std::filesystem::path& workingDirectory, std::string& error) +{ + STARTUPINFOA startupInfo = {}; + PROCESS_INFORMATION processInfo = {}; + startupInfo.cb = sizeof(startupInfo); + + std::vector mutableCommandLine(commandLine.begin(), commandLine.end()); + mutableCommandLine.push_back('\0'); + + if (!CreateProcessA(nullptr, mutableCommandLine.data(), nullptr, nullptr, FALSE, CREATE_NO_WINDOW, nullptr, workingDirectory.string().c_str(), &startupInfo, &processInfo)) + { + error = "Failed to launch msdf-atlas-gen.exe."; + return false; + } + + UniqueHandle processHandle(processInfo.hProcess); + UniqueHandle threadHandle(processInfo.hThread); + WaitForSingleObject(processHandle.get(), INFINITE); + + DWORD exitCode = 0; + GetExitCodeProcess(processHandle.get(), &exitCode); + if (exitCode != 0) + { + error = "msdf-atlas-gen.exe returned a non-zero exit code."; + return false; + } + + error.clear(); + return true; +} +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ac99ece..2d898c3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -41,6 +41,9 @@ add_video_shader_test(RenderCadenceCompositorRuntimeShaderParamsTests add_video_shader_test(RenderCadenceCompositorRuntimeLayerModelTests "${SRC_DIR}/runtime/FontAtlasBuilder.cpp" + "${SRC_DIR}/runtime/FontAtlasImageLoader.cpp" + "${SRC_DIR}/runtime/FontAtlasMetadata.cpp" + "${SRC_DIR}/runtime/FontAtlasProcess.cpp" "${SRC_DIR}/runtime/RuntimeLayerModel.cpp" "${SRC_DIR}/runtime/RuntimeLayerReload.cpp" "${SRC_DIR}/runtime/RuntimeLayerSnapshot.cpp" @@ -65,6 +68,9 @@ add_video_shader_test(RuntimeStatePersistenceTests add_video_shader_test(FontAtlasBuilderTests "${SRC_DIR}/runtime/FontAtlasBuilder.cpp" + "${SRC_DIR}/runtime/FontAtlasImageLoader.cpp" + "${SRC_DIR}/runtime/FontAtlasMetadata.cpp" + "${SRC_DIR}/runtime/FontAtlasProcess.cpp" "${SRC_DIR}/runtime/RuntimeJson.cpp" "${SRC_DIR}/shader/ShaderManifestAssets.cpp" "${SRC_DIR}/shader/ShaderManifestParameters.cpp" @@ -75,6 +81,9 @@ add_video_shader_test(FontAtlasBuilderTests add_video_shader_test(RenderCadenceCompositorSupportedShaderCatalogTests "${SRC_DIR}/runtime/FontAtlasBuilder.cpp" + "${SRC_DIR}/runtime/FontAtlasImageLoader.cpp" + "${SRC_DIR}/runtime/FontAtlasMetadata.cpp" + "${SRC_DIR}/runtime/FontAtlasProcess.cpp" "${SRC_DIR}/runtime/RuntimeJson.cpp" "${SRC_DIR}/runtime/SupportedShaderCatalog.cpp" "${SRC_DIR}/shader/ShaderManifestAssets.cpp" @@ -89,6 +98,9 @@ add_video_shader_test(RenderCadenceCompositorRuntimeStateJsonTests "${SRC_DIR}/app/AppConfigProvider.cpp" "${SRC_DIR}/json/JsonWriter.cpp" "${SRC_DIR}/runtime/FontAtlasBuilder.cpp" + "${SRC_DIR}/runtime/FontAtlasImageLoader.cpp" + "${SRC_DIR}/runtime/FontAtlasMetadata.cpp" + "${SRC_DIR}/runtime/FontAtlasProcess.cpp" "${SRC_DIR}/runtime/RuntimeJson.cpp" "${SRC_DIR}/runtime/RuntimeLayerModel.cpp" "${SRC_DIR}/runtime/RuntimeLayerReload.cpp"