From d6e4e98c8b6d45946db4f05e17f7d2f627235500 Mon Sep 17 00:00:00 2001 From: Mr-Wiseguy Date: Fri, 19 Jan 2024 18:14:42 -0500 Subject: [PATCH] Added custom font interface that scales font glyphs down to avoid downsampling --- CMakeLists.txt | 9 + .../FontEngineInterfaceScaled.cpp | 100 +++ .../FontEngineInterfaceScaled.h | 75 +++ src/ui/FontEngineScaled/FontFace.cpp | 95 +++ src/ui/FontEngineScaled/FontFace.h | 74 +++ .../FontEngineScaled/FontFaceHandleScaled.cpp | 487 ++++++++++++++ .../FontEngineScaled/FontFaceHandleScaled.h | 157 +++++ src/ui/FontEngineScaled/FontFaceLayer.cpp | 275 ++++++++ src/ui/FontEngineScaled/FontFaceLayer.h | 138 ++++ src/ui/FontEngineScaled/FontFamily.cpp | 97 +++ src/ui/FontEngineScaled/FontFamily.h | 82 +++ src/ui/FontEngineScaled/FontProvider.cpp | 268 ++++++++ src/ui/FontEngineScaled/FontProvider.h | 102 +++ src/ui/FontEngineScaled/FontTypes.h | 58 ++ src/ui/FontEngineScaled/FreeTypeInterface.cpp | 602 ++++++++++++++++++ src/ui/FontEngineScaled/FreeTypeInterface.h | 73 +++ src/ui/ui_renderer.cpp | 6 + 17 files changed, 2698 insertions(+) create mode 100644 src/ui/FontEngineScaled/FontEngineInterfaceScaled.cpp create mode 100644 src/ui/FontEngineScaled/FontEngineInterfaceScaled.h create mode 100644 src/ui/FontEngineScaled/FontFace.cpp create mode 100644 src/ui/FontEngineScaled/FontFace.h create mode 100644 src/ui/FontEngineScaled/FontFaceHandleScaled.cpp create mode 100644 src/ui/FontEngineScaled/FontFaceHandleScaled.h create mode 100644 src/ui/FontEngineScaled/FontFaceLayer.cpp create mode 100644 src/ui/FontEngineScaled/FontFaceLayer.h create mode 100644 src/ui/FontEngineScaled/FontFamily.cpp create mode 100644 src/ui/FontEngineScaled/FontFamily.h create mode 100644 src/ui/FontEngineScaled/FontProvider.cpp create mode 100644 src/ui/FontEngineScaled/FontProvider.h create mode 100644 src/ui/FontEngineScaled/FontTypes.h create mode 100644 src/ui/FontEngineScaled/FreeTypeInterface.cpp create mode 100644 src/ui/FontEngineScaled/FreeTypeInterface.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1bb0222..bae5092 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -128,6 +128,14 @@ set (SOURCES ${CMAKE_SOURCE_DIR}/src/ui/ui_config.cpp ${CMAKE_SOURCE_DIR}/src/ui/ui_color_hack.cpp + ${CMAKE_SOURCE_DIR}/src/ui/FontEngineScaled/FontEngineInterfaceScaled.cpp + ${CMAKE_SOURCE_DIR}/src/ui/FontEngineScaled/FontFace.cpp + ${CMAKE_SOURCE_DIR}/src/ui/FontEngineScaled/FontFaceHandleScaled.cpp + ${CMAKE_SOURCE_DIR}/src/ui/FontEngineScaled/FontFaceLayer.cpp + ${CMAKE_SOURCE_DIR}/src/ui/FontEngineScaled/FontFamily.cpp + ${CMAKE_SOURCE_DIR}/src/ui/FontEngineScaled/FontProvider.cpp + ${CMAKE_SOURCE_DIR}/src/ui/FontEngineScaled/FreeTypeInterface.cpp + ${CMAKE_SOURCE_DIR}/rsp/aspMain.cpp ${CMAKE_SOURCE_DIR}/rsp/njpgdspMain.cpp @@ -146,6 +154,7 @@ target_include_directories(MMRecomp PRIVATE ${CMAKE_SOURCE_DIR}/lib/RT64-HLE/src ${CMAKE_SOURCE_DIR}/lib/RT64-HLE/src/rhi ${CMAKE_SOURCE_DIR}/lib/RT64-HLE/src/render + ${CMAKE_SOURCE_DIR}/lib/freetype-windows-binaries/include ${CMAKE_BINARY_DIR}/shaders ) diff --git a/src/ui/FontEngineScaled/FontEngineInterfaceScaled.cpp b/src/ui/FontEngineScaled/FontEngineInterfaceScaled.cpp new file mode 100644 index 0000000..c42a9b7 --- /dev/null +++ b/src/ui/FontEngineScaled/FontEngineInterfaceScaled.cpp @@ -0,0 +1,100 @@ +/* + * This source file is modified from a part of RmlUi, the HTML/CSS Interface Middleware + * + * For the latest information, see http://github.com/mikke89/RmlUi + * + * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd + * Copyright (c) 2019-2023 The RmlUi Team, and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include "FontEngineInterfaceScaled.h" +#include "FontFaceHandleScaled.h" +#include "FontProvider.h" + +namespace RecompRml { + +using namespace Rml; + +FontEngineInterfaceScaled::FontEngineInterfaceScaled() +{ + FontProvider::Initialise(); +} + +FontEngineInterfaceScaled::~FontEngineInterfaceScaled() +{ + FontProvider::Shutdown(); +} + +bool FontEngineInterfaceScaled::LoadFontFace(const String& file_name, bool fallback_face, Style::FontWeight weight) +{ + return FontProvider::LoadFontFace(file_name, fallback_face, weight); +} + +bool FontEngineInterfaceScaled::LoadFontFace(const byte* data, int data_size, const String& font_family, Style::FontStyle style, + Style::FontWeight weight, bool fallback_face) +{ + return FontProvider::LoadFontFace(data, data_size, font_family, style, weight, fallback_face); +} + +FontFaceHandle FontEngineInterfaceScaled::GetFontFaceHandle(const String& family, Style::FontStyle style, Style::FontWeight weight, int size) +{ + auto handle = FontProvider::GetFontFaceHandle(family, style, weight, size); + return reinterpret_cast(handle); +} + +FontEffectsHandle FontEngineInterfaceScaled::PrepareFontEffects(FontFaceHandle handle, const FontEffectList& font_effects) +{ + auto handle_scaled = reinterpret_cast(handle); + return (FontEffectsHandle)handle_scaled->GenerateLayerConfiguration(font_effects); +} + +const FontMetrics& FontEngineInterfaceScaled::GetFontMetrics(FontFaceHandle handle) +{ + auto handle_scaled = reinterpret_cast(handle); + return handle_scaled->GetFontMetrics(); +} + +int FontEngineInterfaceScaled::GetStringWidth(FontFaceHandle handle, const String& string, float letter_spacing, Character prior_character) +{ + auto handle_scaled = reinterpret_cast(handle); + return handle_scaled->GetStringWidth(string, letter_spacing, prior_character); +} + +int FontEngineInterfaceScaled::GenerateString(FontFaceHandle handle, FontEffectsHandle font_effects_handle, const String& string, + const Vector2f& position, const Colourb& colour, float opacity, float letter_spacing, GeometryList& geometry) +{ + auto handle_scaled = reinterpret_cast(handle); + return handle_scaled->GenerateString(geometry, string, position, colour, opacity, letter_spacing, (int)font_effects_handle); +} + +int FontEngineInterfaceScaled::GetVersion(FontFaceHandle handle) +{ + auto handle_scaled = reinterpret_cast(handle); + return handle_scaled->GetVersion(); +} + +void FontEngineInterfaceScaled::ReleaseFontResources() +{ + FontProvider::ReleaseFontResources(); +} + +} // namespace Rml diff --git a/src/ui/FontEngineScaled/FontEngineInterfaceScaled.h b/src/ui/FontEngineScaled/FontEngineInterfaceScaled.h new file mode 100644 index 0000000..0e94008 --- /dev/null +++ b/src/ui/FontEngineScaled/FontEngineInterfaceScaled.h @@ -0,0 +1,75 @@ +/* + * This source file is modified from a part, the HTML/CSS Interface Middleware + * + * For the latest information, see http://github.com/mikke89/RmlUi + * + * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef RMLUI_CORE_FONTENGINESCALED_FONTENGINEINTERFACESCALED_H +#define RMLUI_CORE_FONTENGINESCALED_FONTENGINEINTERFACESCALED_H + +#include "RmlUi/Core/FontEngineInterface.h" + +namespace RecompRml { + +using namespace Rml; + +class RMLUICORE_API FontEngineInterfaceScaled : public FontEngineInterface { +public: + FontEngineInterfaceScaled(); + virtual ~FontEngineInterfaceScaled(); + + /// Adds a new font face to the database. The face's family, style and weight will be determined from the face itself. + bool LoadFontFace(const String& file_name, bool fallback_face, Style::FontWeight weight) override; + + /// Adds a new font face to the database using the provided family, style and weight. + bool LoadFontFace(const byte* data, int data_size, const String& font_family, Style::FontStyle style, Style::FontWeight weight, + bool fallback_face) override; + + /// Returns a handle to a font face that can be used to position and render text. This will return the closest match + /// it can find, but in the event a font family is requested that does not exist, NULL will be returned instead of a + /// valid handle. + FontFaceHandle GetFontFaceHandle(const String& family, Style::FontStyle style, Style::FontWeight weight, int size) override; + + /// Prepares for font effects by configuring a new, or returning an existing, layer configuration. + FontEffectsHandle PrepareFontEffects(FontFaceHandle, const FontEffectList& font_effects) override; + + /// Returns the font metrics of the given font face. + const FontMetrics& GetFontMetrics(FontFaceHandle handle) override; + + /// Returns the width a string will take up if rendered with this handle. + int GetStringWidth(FontFaceHandle, const String& string, float letter_spacing, Character prior_character) override; + + /// Generates the geometry required to render a single line of text. + int GenerateString(FontFaceHandle, FontEffectsHandle, const String& string, const Vector2f& position, const Colourb& colour, float opacity, + float letter_spacing, GeometryList& geometry) override; + + /// Returns the current version of the font face. + int GetVersion(FontFaceHandle handle) override; + + /// Releases resources owned by sized font faces, including their textures and rendered glyphs. + void ReleaseFontResources() override; +}; + +} // namespace Rml +#endif diff --git a/src/ui/FontEngineScaled/FontFace.cpp b/src/ui/FontEngineScaled/FontFace.cpp new file mode 100644 index 0000000..0424888 --- /dev/null +++ b/src/ui/FontEngineScaled/FontFace.cpp @@ -0,0 +1,95 @@ +/* + * This source file is modified from a part of RmlUi the HTML/CSS Interface Middleware + * + * For the latest information, see http://github.com/mikke89/RmlUi + * + * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd + * Copyright (c) 2019-2023 The RmlUi Team, and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include "FontFace.h" +#include "RmlUi/Core/Log.h" +#include "FontFaceHandleScaled.h" +#include "FreeTypeInterface.h" + +namespace RecompRml { + +using namespace Rml; + +FontFace::FontFace(FontFaceHandleFreetype _face, Style::FontStyle _style, Style::FontWeight _weight) +{ + style = _style; + weight = _weight; + face = _face; +} + +FontFace::~FontFace() +{ + if (face) + FreeType::ReleaseFace(face); +} + +Style::FontStyle FontFace::GetStyle() const +{ + return style; +} + +Style::FontWeight FontFace::GetWeight() const +{ + return weight; +} + +FontFaceHandleScaled* FontFace::GetHandle(int size, bool load_default_glyphs) +{ + auto it = handles.find(size); + if (it != handles.end()) + return it->second.get(); + + // See if this face has been released. + if (!face) + { + Log::Message(Log::LT_WARNING, "Font face has been released, unable to generate new handle."); + return nullptr; + } + + // Construct and initialise the new handle. + auto handle = MakeUnique(); + if (!handle->Initialize(face, size, load_default_glyphs)) + { + handles[size] = nullptr; + return nullptr; + } + + FontFaceHandleScaled* result = handle.get(); + + // Save the new handle to the font face + handles[size] = std::move(handle); + + return result; +} + +void FontFace::ReleaseFontResources() +{ + HandleMap().swap(handles); +} + +} // namespace Rml diff --git a/src/ui/FontEngineScaled/FontFace.h b/src/ui/FontEngineScaled/FontFace.h new file mode 100644 index 0000000..e632203 --- /dev/null +++ b/src/ui/FontEngineScaled/FontFace.h @@ -0,0 +1,74 @@ +/* + * This source file is modified from a part of RmlUi, the HTML/CSS Interface Middleware + * + * For the latest information, see http://github.com/mikke89/RmlUi + * + * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd + * Copyright (c) 2019-2023 The RmlUi Team, and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef RMLUI_CORE_FONTENGINESCALED_FONTFACE_H +#define RMLUI_CORE_FONTENGINESCALED_FONTFACE_H + +#include "RmlUi/Core/StyleTypes.h" +#include "FontTypes.h" + +namespace RecompRml { + +using namespace Rml; + +class FontFaceHandleScaled; + +/** + @author Peter Curry + */ + +class FontFace { +public: + FontFace(FontFaceHandleFreetype face, Style::FontStyle style, Style::FontWeight weight); + ~FontFace(); + + Style::FontStyle GetStyle() const; + Style::FontWeight GetWeight() const; + + /// Returns a handle for positioning and rendering this face at the given size. + /// @param[in] size The size of the desired handle, in points. + /// @param[in] load_default_glyphs True to load the default set of glyph (ASCII range). + /// @return The font handle. + FontFaceHandleScaled* GetHandle(int size, bool load_default_glyphs); + + /// Releases resources owned by sized font faces, including their textures and rendered glyphs. + void ReleaseFontResources(); + +private: + Style::FontStyle style; + Style::FontWeight weight; + + // Key is font size + using HandleMap = UnorderedMap>; + HandleMap handles; + + FontFaceHandleFreetype face; +}; + +} // namespace Rml +#endif diff --git a/src/ui/FontEngineScaled/FontFaceHandleScaled.cpp b/src/ui/FontEngineScaled/FontFaceHandleScaled.cpp new file mode 100644 index 0000000..42f57c2 --- /dev/null +++ b/src/ui/FontEngineScaled/FontFaceHandleScaled.cpp @@ -0,0 +1,487 @@ +/* + * This source file is modified from a part of RmlUi, the HTML/CSS Interface Middleware + * + * For the latest information, see http://github.com/mikke89/RmlUi + * + * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd + * Copyright (c) 2019-2023 The RmlUi Team, and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include "FontFaceHandleScaled.h" +#include "RmlUi/Core.h" +#include "FontFaceLayer.h" +#include "FontProvider.h" +#include "FreeTypeInterface.h" +#include + +namespace RecompRml { + +using namespace Rml; + +static constexpr char32_t KerningCache_AsciiSubsetBegin = 32; +static constexpr char32_t KerningCache_AsciiSubsetLast = 126; + +FontFaceHandleScaled::FontFaceHandleScaled() +{ + base_layer = nullptr; + metrics = {}; + ft_face = 0; +} + +FontFaceHandleScaled::~FontFaceHandleScaled() +{ + glyphs.clear(); + layers.clear(); +} + +bool FontFaceHandleScaled::Initialize(FontFaceHandleFreetype face, int font_size, bool load_default_glyphs) +{ + ft_face = face; + + RMLUI_ASSERTMSG(layer_configurations.empty(), "Initialize must only be called once."); + + if (!FreeType::InitialiseFaceHandle(ft_face, font_size, glyphs, metrics, load_default_glyphs)) + return false; + + has_kerning = FreeType::HasKerning(ft_face); + FillKerningPairCache(); + + // Generate the default layer and layer configuration. + base_layer = GetOrCreateLayer(nullptr); + layer_configurations.push_back(LayerConfiguration{base_layer}); + + return true; +} + +const FontMetrics& FontFaceHandleScaled::GetFontMetrics() const +{ + return metrics; +} + +const FontGlyphMap& FontFaceHandleScaled::GetGlyphs() const +{ + return glyphs; +} + +int FontFaceHandleScaled::GetStringWidth(const String& string, float letter_spacing, Character prior_character) +{ + RMLUI_ZoneScoped; + + int width = 0; + for (auto it_string = StringIteratorU8(string); it_string; ++it_string) + { + Character character = *it_string; + + const FontGlyph* glyph = GetOrAppendGlyph(character); + if (!glyph) + continue; + + // Adjust the cursor for the kerning between this character and the previous one. + width += GetKerning(prior_character, character); + + // Adjust the cursor for this character's advance. + width += glyph->advance; + width += (int)letter_spacing; + + prior_character = character; + } + + return Math::Max(width, 0); +} + +int FontFaceHandleScaled::GenerateLayerConfiguration(const FontEffectList& font_effects) +{ + if (font_effects.empty()) + return 0; + + // Check each existing configuration for a match with this arrangement of effects. + int configuration_index = 1; + for (; configuration_index < (int)layer_configurations.size(); ++configuration_index) + { + const LayerConfiguration& configuration = layer_configurations[configuration_index]; + + // Check the size is correct. For a match, there should be one layer in the configuration + // plus an extra for the base layer. + if (configuration.size() != font_effects.size() + 1) + continue; + + // Check through each layer, checking it was created by the same effect as the one we're + // checking. + size_t effect_index = 0; + for (size_t i = 0; i < configuration.size(); ++i) + { + // Skip the base layer ... + if (configuration[i]->GetFontEffect() == nullptr) + continue; + + // If the ith layer's effect doesn't match the equivalent effect, then this + // configuration can't match. + if (configuration[i]->GetFontEffect() != font_effects[effect_index].get()) + break; + + // Check the next one ... + ++effect_index; + } + + if (effect_index == font_effects.size()) + return configuration_index; + } + + // No match, so we have to generate a new layer configuration. + layer_configurations.push_back(LayerConfiguration()); + LayerConfiguration& layer_configuration = layer_configurations.back(); + + bool added_base_layer = false; + + for (size_t i = 0; i < font_effects.size(); ++i) + { + if (!added_base_layer && font_effects[i]->GetLayer() == FontEffect::Layer::Front) + { + layer_configuration.push_back(base_layer); + added_base_layer = true; + } + + FontFaceLayer* new_layer = GetOrCreateLayer(font_effects[i]); + layer_configuration.push_back(new_layer); + } + + // Add the base layer now if we still haven't added it. + if (!added_base_layer) + layer_configuration.push_back(base_layer); + + return (int)(layer_configurations.size() - 1); +} + +bool FontFaceHandleScaled::GenerateLayerTexture(UniquePtr& texture_data, Vector2i& texture_dimensions, const FontEffect* font_effect, + int texture_id, int handle_version) const +{ + if (handle_version != version) + { + RMLUI_ERRORMSG("While generating font layer texture: Handle version mismatch in texture vs font-face."); + return false; + } + + auto it = std::find_if(layers.begin(), layers.end(), [font_effect](const EffectLayerPair& pair) { return pair.font_effect == font_effect; }); + + if (it == layers.end()) + { + RMLUI_ERRORMSG("While generating font layer texture: Layer id not found."); + return false; + } + + return it->layer->GenerateTexture(texture_data, texture_dimensions, texture_id, glyphs); +} + +int FontFaceHandleScaled::GenerateString(GeometryList& geometry, const String& string, const Vector2f position, const Colourb colour, + const float opacity, const float letter_spacing, const int layer_configuration_index) +{ + int geometry_index = 0; + int line_width = 0; + + RMLUI_ASSERT(layer_configuration_index >= 0); + RMLUI_ASSERT(layer_configuration_index < (int)layer_configurations.size()); + + UpdateLayersOnDirty(); + + // Fetch the requested configuration and generate the geometry for each one. + const LayerConfiguration& layer_configuration = layer_configurations[layer_configuration_index]; + + // Reserve for the common case of one texture per layer. + geometry.reserve(layer_configuration.size()); + + for (size_t i = 0; i < layer_configuration.size(); ++i) + { + FontFaceLayer* layer = layer_configuration[i]; + + Colourb layer_colour; + if (layer == base_layer) + { + layer_colour = colour; + } + else + { + layer_colour = layer->GetColour(); + if (opacity < 1.f) + layer_colour.alpha = byte(opacity * float(layer_colour.alpha)); + } + + const int num_textures = layer->GetNumTextures(); + + if (num_textures == 0) + continue; + + // Resize the geometry list if required. + if ((int)geometry.size() < geometry_index + num_textures) + geometry.resize(geometry_index + num_textures); + + RMLUI_ASSERT(geometry_index < (int)geometry.size()); + + // Bind the textures to the geometries. + for (int tex_index = 0; tex_index < num_textures; ++tex_index) + geometry[geometry_index + tex_index].SetTexture(layer->GetTexture(tex_index)); + + line_width = 0; + Character prior_character = Character::Null; + + geometry[geometry_index].GetIndices().reserve(string.size() * 6); + geometry[geometry_index].GetVertices().reserve(string.size() * 4); + + for (auto it_string = StringIteratorU8(string); it_string; ++it_string) + { + Character character = *it_string; + + const FontGlyph* glyph = GetOrAppendGlyph(character); + if (!glyph) + continue; + + // Adjust the cursor for the kerning between this character and the previous one. + line_width += GetKerning(prior_character, character); + + // Use white vertex colors on RGB glyphs. + const Colourb glyph_color = + (layer == base_layer && glyph->color_format == ColorFormat::RGBA8 ? Colourb(255, layer_colour.alpha) : layer_colour); + + layer->GenerateGeometry(&geometry[geometry_index], character, Vector2f(position.x + line_width, position.y), glyph_color); + + line_width += glyph->advance; + line_width += (int)letter_spacing; + prior_character = character; + } + + geometry_index += num_textures; + } + + // Cull any excess geometry from a previous generation. + geometry.resize(geometry_index); + + return Math::Max(line_width, 0); +} + +bool FontFaceHandleScaled::UpdateLayersOnDirty() +{ + bool result = false; + + // If we are dirty, regenerate all the layers and increment the version + if (is_layers_dirty && base_layer) + { + is_layers_dirty = false; + ++version; + + // Regenerate all the layers. + // Note: The layer regeneration needs to happen in the order in which the layers were created, + // otherwise we may end up cloning a layer which has not yet been regenerated. This means trouble! + for (auto& pair : layers) + { + GenerateLayer(pair.layer.get()); + } + + result = true; + } + + return result; +} + +int FontFaceHandleScaled::GetVersion() const +{ + return version; +} + +bool FontFaceHandleScaled::AppendGlyph(Character character) +{ + bool result = FreeType::AppendGlyph(ft_face, metrics.size, character, glyphs); + return result; +} + +void FontFaceHandleScaled::FillKerningPairCache() +{ + if (!has_kerning) + return; + + for (char32_t i = KerningCache_AsciiSubsetBegin; i <= KerningCache_AsciiSubsetLast; i++) + { + for (char32_t j = KerningCache_AsciiSubsetBegin; j <= KerningCache_AsciiSubsetLast; j++) + { + const bool first_iteration = (i == KerningCache_AsciiSubsetBegin && j == KerningCache_AsciiSubsetBegin); + + // Fetch the kerning from the font face. Submit zero font size on subsequent iterations for performance reasons. + const int kerning = FreeType::GetKerning(ft_face, first_iteration ? metrics.size : 0, Character(i), Character(j)); + if (kerning != 0) + { + kerning_pair_cache.emplace(AsciiPair((i << 8) | j), KerningIntType(kerning)); + } + } + } +} + +int FontFaceHandleScaled::GetKerning(Character lhs, Character rhs) const +{ + static_assert(' ' == 32, "Only ASCII/UTF8 character set supported."); + + // Check if we have no kerning, or if we are have a control character. + if (!has_kerning || char32_t(lhs) < ' ' || char32_t(rhs) < ' ') + return 0; + + // See if the kerning pair has been cached. + const bool lhs_in_cache = (char32_t(lhs) >= KerningCache_AsciiSubsetBegin && char32_t(lhs) <= KerningCache_AsciiSubsetLast); + const bool rhs_in_cache = (char32_t(rhs) >= KerningCache_AsciiSubsetBegin && char32_t(rhs) <= KerningCache_AsciiSubsetLast); + + if (lhs_in_cache && rhs_in_cache) + { + const auto it = kerning_pair_cache.find(AsciiPair((int(lhs) << 8) | int(rhs))); + + if (it != kerning_pair_cache.end()) + { + return it->second; + } + + return 0; + } + + // Fetch it from the font face instead. + const int result = FreeType::GetKerning(ft_face, metrics.size, lhs, rhs); + return result; +} + +const FontGlyph* FontFaceHandleScaled::GetOrAppendGlyph(Character& character, bool look_in_fallback_fonts) +{ + // Don't try to render control characters + if ((char32_t)character < (char32_t)' ') + return nullptr; + + auto it_glyph = glyphs.find(character); + if (it_glyph == glyphs.end()) + { + bool result = AppendGlyph(character); + + if (result) + { + it_glyph = glyphs.find(character); + if (it_glyph == glyphs.end()) + { + RMLUI_ERROR; + return nullptr; + } + + is_layers_dirty = true; + } + else if (look_in_fallback_fonts) + { + const int num_fallback_faces = FontProvider::CountFallbackFontFaces(); + for (int i = 0; i < num_fallback_faces; i++) + { + FontFaceHandleScaled* fallback_face = FontProvider::GetFallbackFontFace(i, metrics.size); + if (!fallback_face || fallback_face == this) + continue; + + const FontGlyph* glyph = fallback_face->GetOrAppendGlyph(character, false); + if (glyph) + { + // Insert the new glyph into our own set of glyphs + auto pair = glyphs.emplace(character, glyph->WeakCopy()); + it_glyph = pair.first; + if (pair.second) + is_layers_dirty = true; + break; + } + } + + // If we still have not found a glyph, use the replacement character. + if (it_glyph == glyphs.end()) + { + character = Character::Replacement; + it_glyph = glyphs.find(character); + if (it_glyph == glyphs.end()) + return nullptr; + } + } + else + { + return nullptr; + } + } + + const FontGlyph* glyph = &it_glyph->second; + return glyph; +} + +FontFaceLayer* FontFaceHandleScaled::GetOrCreateLayer(const SharedPtr& font_effect) +{ + // Search for the font effect layer first, it may have been instanced before as part of a different configuration. + const FontEffect* font_effect_ptr = font_effect.get(); + auto it = + std::find_if(layers.begin(), layers.end(), [font_effect_ptr](const EffectLayerPair& pair) { return pair.font_effect == font_effect_ptr; }); + + if (it != layers.end()) + return it->layer.get(); + + // No existing effect matches, generate a new layer for the effect. + layers.push_back(EffectLayerPair{font_effect_ptr, nullptr}); + auto& layer = layers.back().layer; + + layer = MakeUnique(font_effect); + GenerateLayer(layer.get()); + + return layer.get(); +} + +bool FontFaceHandleScaled::GenerateLayer(FontFaceLayer* layer) +{ + RMLUI_ASSERT(layer); + const FontEffect* font_effect = layer->GetFontEffect(); + bool result = false; + + if (!font_effect) + { + result = layer->Generate(this); + } + else + { + // Determine which, if any, layer the new layer should copy its geometry and textures from. + FontFaceLayer* clone = nullptr; + bool clone_glyph_origins = true; + String generation_key; + size_t fingerprint = font_effect->GetFingerprint(); + + if (!font_effect->HasUniqueTexture()) + { + clone = base_layer; + clone_glyph_origins = false; + } + else + { + auto cache_iterator = layer_cache.find(fingerprint); + if (cache_iterator != layer_cache.end() && cache_iterator->second != layer) + clone = cache_iterator->second; + } + + // Create a new layer. + result = layer->Generate(this, clone, clone_glyph_origins); + + // Cache the layer in the layer cache if it generated its own textures (ie, didn't clone). + if (!clone) + layer_cache[fingerprint] = layer; + } + + return result; +} + +} // namespace Rml diff --git a/src/ui/FontEngineScaled/FontFaceHandleScaled.h b/src/ui/FontEngineScaled/FontFaceHandleScaled.h new file mode 100644 index 0000000..609349f --- /dev/null +++ b/src/ui/FontEngineScaled/FontFaceHandleScaled.h @@ -0,0 +1,157 @@ +/* + * This source file is modified from a part of RmlUi, the HTML/CSS Interface Middleware + * + * For the latest information, see http://github.com/mikke89/RmlUi + * + * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd + * Copyright (c) 2019-2023 The RmlUi Team, and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef RMLUI_CORE_FONTENGINESCALED_FONTFACEHANDLE_H +#define RMLUI_CORE_FONTENGINESCALED_FONTFACEHANDLE_H + +#include "RmlUi/Core.h" +///FontEffect.h" +// #include "../../../Include/RmlUi/Core/FontGlyph.h" +// #include "../../../Include/RmlUi/Core/FontMetrics.h" +// #include "../../../Include/RmlUi/Core/Geometry.h" +// #include "../../../Include/RmlUi/Core/Texture.h" +// #include "../../../Include/RmlUi/Core/Traits.h" +#include "FontTypes.h" + +namespace RecompRml { + +using namespace Rml; + +class FontFaceLayer; + +/** + @author Peter Curry + */ + +class FontFaceHandleScaled final : public NonCopyMoveable { +public: + FontFaceHandleScaled(); + ~FontFaceHandleScaled(); + + bool Initialize(FontFaceHandleFreetype face, int font_size, bool load_default_glyphs); + + const FontMetrics& GetFontMetrics() const; + + const FontGlyphMap& GetGlyphs() const; + + /// Returns the width a string will take up if rendered with this handle. + /// @param[in] string The string to measure. + /// @param[in] prior_character The optionally-specified character that immediately precedes the string. This may have an impact on the string + /// width due to kerning. + /// @return The width, in pixels, this string will occupy if rendered with this handle. + int GetStringWidth(const String& string, float letter_spacing, Character prior_character = Character::Null); + + /// Generates, if required, the layer configuration for a given list of font effects. + /// @param[in] font_effects The list of font effects to generate the configuration for. + /// @return The index to use when generating geometry using this configuration. + int GenerateLayerConfiguration(const FontEffectList& font_effects); + /// Generates the texture data for a layer (for the texture database). + /// @param[out] texture_data The pointer to be set to the generated texture data. + /// @param[out] texture_dimensions The dimensions of the texture. + /// @param[in] font_effect The font effect used for the layer. + /// @param[in] texture_id The index of the texture within the layer to generate. + /// @param[in] handle_version The version of the handle data. Function returns false if out of date. + bool GenerateLayerTexture(UniquePtr& texture_data, Vector2i& texture_dimensions, const FontEffect* font_effect, int texture_id, + int handle_version) const; + + /// Generates the geometry required to render a single line of text. + /// @param[out] geometry An array of geometries to generate the geometry into. + /// @param[in] string The string to render. + /// @param[in] position The position of the baseline of the first character to render. + /// @param[in] colour The colour to render the text. + /// @param[in] opacity The opacity of the text, should be applied to font effects. + /// @param[in] layer_configuration Face configuration index to use for generating string. + /// @return The width, in pixels, of the string geometry. + int GenerateString(GeometryList& geometry, const String& string, Vector2f position, Colourb colour, float opacity, float letter_spacing, + int layer_configuration = 0); + + /// Version is changed whenever the layers are dirtied, requiring regeneration of string geometry. + int GetVersion() const; + +private: + // Build and append glyph to 'glyphs' + bool AppendGlyph(Character character); + + // Build a kerning cache for common characters. + void FillKerningPairCache(); + + // Return the kerning for a character pair. + int GetKerning(Character lhs, Character rhs) const; + + /// Retrieve a glyph from the given code point, building and appending a new glyph if not already built. + /// @param[in-out] character The character, can be changed e.g. to the replacement character if no glyph is found. + /// @param[in] look_in_fallback_fonts Look for the glyph in fallback fonts if not found locally, adding it to our glyphs. + /// @return The font glyph for the returned code point. + const FontGlyph* GetOrAppendGlyph(Character& character, bool look_in_fallback_fonts = true); + + // Regenerate layers if dirty, such as after adding new glyphs. + bool UpdateLayersOnDirty(); + + // Create a new layer from the given font effect if it does not already exist. + FontFaceLayer* GetOrCreateLayer(const SharedPtr& font_effect); + + // (Re-)generate a layer in this font face handle. + bool GenerateLayer(FontFaceLayer* layer); + + FontGlyphMap glyphs; + + struct EffectLayerPair { + const FontEffect* font_effect; + UniquePtr layer; + }; + using FontLayerMap = Vector; + using FontLayerCache = SmallUnorderedMap; + using LayerConfiguration = Vector; + using LayerConfigurationList = Vector; + + // The list of all font layers, index by the effect that instanced them. + FontFaceLayer* base_layer; + FontLayerMap layers; + // Each font layer that generated geometry or textures, indexed by the font-effect's fingerprint key. + FontLayerCache layer_cache; + + // Pre-cache kerning pairs for some ascii subset of all characters. + using AsciiPair = uint16_t; + using KerningIntType = int16_t; + using KerningPairs = UnorderedMap; + KerningPairs kerning_pair_cache; + + bool has_kerning = false; + bool is_layers_dirty = false; + int version = 0; + + // All configurations currently in use on this handle. New configurations will be generated as required. + LayerConfigurationList layer_configurations; + + FontMetrics metrics; + + FontFaceHandleFreetype ft_face; +}; + +} // namespace Rml +#endif diff --git a/src/ui/FontEngineScaled/FontFaceLayer.cpp b/src/ui/FontEngineScaled/FontFaceLayer.cpp new file mode 100644 index 0000000..51b4913 --- /dev/null +++ b/src/ui/FontEngineScaled/FontFaceLayer.cpp @@ -0,0 +1,275 @@ +/* + * This source file is modified from a part of RmlUi, the HTML/CSS Interface Middleware + * + * For the latest information, see http://github.com/mikke89/RmlUi + * + * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd + * Copyright (c) 2019-2023 The RmlUi Team, and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include "FontFaceLayer.h" +#include "RmlUi/Core/Log.h" +#include "RmlUi/Core/RenderInterface.h" +#include "FontFaceHandleScaled.h" +#include + +namespace RecompRml { + +using namespace Rml; + +FontFaceLayer::FontFaceLayer(const SharedPtr& _effect) : colour(255, 255, 255) +{ + effect = _effect; + if (effect) + colour = effect->GetColour(); +} + +FontFaceLayer::~FontFaceLayer() {} + +bool FontFaceLayer::Generate(const FontFaceHandleScaled* handle, const FontFaceLayer* clone, bool clone_glyph_origins) +{ + // Clear the old layout if it exists. + { + // @performance: We could be much smarter about this, e.g. such as adding new glyphs to the existing texture layout and textures. + // Right now we re-generate the whole thing, including textures. + texture_layout = TextureLayout{}; + character_boxes.clear(); + textures.clear(); + } + + const FontGlyphMap& glyphs = handle->GetGlyphs(); + + // Generate the new layout. + if (clone) + { + // Clone the geometry and textures from the clone layer. + character_boxes = clone->character_boxes; + + // Copy the cloned layer's textures. + for (size_t i = 0; i < clone->textures.size(); ++i) + textures.push_back(clone->textures[i]); + + // Request the effect (if we have one) and adjust the origins as appropriate. + if (effect && !clone_glyph_origins) + { + for (auto& pair : glyphs) + { + Character character = pair.first; + const FontGlyph& glyph = pair.second; + + auto it = character_boxes.find(character); + if (it == character_boxes.end()) + { + // This can happen if the layers have been dirtied in FontHandleScaled. We will + // probably be regenerated soon, just skip the character for now. + continue; + } + + TextureBox& box = it->second; + + Vector2i glyph_origin = Vector2i(box.origin); + Vector2i glyph_dimensions = Vector2i(box.dimensions); + + if (effect->GetGlyphMetrics(glyph_origin, glyph_dimensions, glyph)) + box.origin = Vector2f(glyph_origin); + else + box.texture_index = -1; + } + } + } + else + { + // Initialise the texture layout for the glyphs. + character_boxes.reserve(glyphs.size()); + for (auto& pair : glyphs) + { + Character character = pair.first; + const FontGlyph& glyph = pair.second; + + Vector2i glyph_origin(0, 0); + Vector2i glyph_dimensions = glyph.bitmap_dimensions; + + // Adjust glyph origin / dimensions for the font effect. + if (effect) + { + if (!effect->GetGlyphMetrics(glyph_origin, glyph_dimensions, glyph)) + continue; + } + + Vector2i scaled_origin = glyph_origin * global_font_scale; + Vector2i scaled_dimensions = glyph_dimensions * global_font_scale; + TextureBox box; + box.origin = Vector2f(float(scaled_origin.x + glyph.bearing.x), float(scaled_origin.y - glyph.bearing.y)); + box.dimensions = Vector2f(scaled_dimensions); + + RMLUI_ASSERT(box.dimensions.x >= 0 && box.dimensions.y >= 0); + + character_boxes[character] = box; + + // Add the character's dimensions into the texture layout engine. + texture_layout.AddRectangle((int)character, glyph_dimensions); + } + + constexpr int max_texture_dimensions = 1024; + + // Generate the texture layout; this will position the glyph rectangles efficiently and + // allocate the texture data ready for writing. + if (!texture_layout.GenerateLayout(max_texture_dimensions)) + return false; + + // Iterate over each rectangle in the layout, copying the glyph data into the rectangle as + // appropriate and generating geometry. + for (int i = 0; i < texture_layout.GetNumRectangles(); ++i) + { + TextureLayoutRectangle& rectangle = texture_layout.GetRectangle(i); + const TextureLayoutTexture& texture = texture_layout.GetTexture(rectangle.GetTextureIndex()); + Character character = (Character)rectangle.GetId(); + RMLUI_ASSERT(character_boxes.find(character) != character_boxes.end()); + TextureBox& box = character_boxes[character]; + + // Set the character's texture index. + box.texture_index = rectangle.GetTextureIndex(); + + // Generate the character's texture coordinates. + box.texcoords[0].x = float(rectangle.GetPosition().x) / float(texture.GetDimensions().x); + box.texcoords[0].y = float(rectangle.GetPosition().y) / float(texture.GetDimensions().y); + box.texcoords[1].x = float(rectangle.GetPosition().x + rectangle.GetDimensions().x) / float(texture.GetDimensions().x); + box.texcoords[1].y = float(rectangle.GetPosition().y + rectangle.GetDimensions().y) / float(texture.GetDimensions().y); + } + + const FontEffect* effect_ptr = effect.get(); + const int handle_version = handle->GetVersion(); + + // Generate the textures. + for (int i = 0; i < texture_layout.GetNumTextures(); ++i) + { + const int texture_id = i; + + TextureCallback texture_callback = [handle, effect_ptr, texture_id, handle_version](RenderInterface* render_interface, + const String& /*name*/, TextureHandle& out_texture_handle, Vector2i& out_dimensions) -> bool { + UniquePtr data; + if (!handle->GenerateLayerTexture(data, out_dimensions, effect_ptr, texture_id, handle_version) || !data) + return false; + if (!render_interface->GenerateTexture(out_texture_handle, data.get(), out_dimensions)) + return false; + return true; + }; + + Texture texture; + texture.Set("font-face-layer", texture_callback); + textures.push_back(texture); + } + } + + return true; +} + +bool FontFaceLayer::GenerateTexture(UniquePtr& texture_data, Vector2i& texture_dimensions, int texture_id, const FontGlyphMap& glyphs) +{ + if (texture_id < 0 || texture_id > texture_layout.GetNumTextures()) + return false; + + // Generate the texture data. + texture_data = texture_layout.GetTexture(texture_id).AllocateTexture(); + texture_dimensions = texture_layout.GetTexture(texture_id).GetDimensions(); + + for (int i = 0; i < texture_layout.GetNumRectangles(); ++i) + { + TextureLayoutRectangle& rectangle = texture_layout.GetRectangle(i); + Character character = (Character)rectangle.GetId(); + RMLUI_ASSERT(character_boxes.find(character) != character_boxes.end()); + + TextureBox& box = character_boxes[character]; + + if (box.texture_index != texture_id) + continue; + + auto it = glyphs.find((Character)rectangle.GetId()); + if (it == glyphs.end()) + continue; + + const FontGlyph& glyph = it->second; + + if (effect == nullptr) + { + // Copy the glyph's bitmap data into its allocated texture. + if (glyph.bitmap_data) + { + byte* destination = rectangle.GetTextureData(); + const byte* source = glyph.bitmap_data; + const int num_bytes_per_line = glyph.bitmap_dimensions.x * (glyph.color_format == ColorFormat::RGBA8 ? 4 : 1); + + for (int j = 0; j < glyph.bitmap_dimensions.y; ++j) + { + switch (glyph.color_format) + { + case ColorFormat::A8: + { + for (int k = 0; k < num_bytes_per_line; ++k) + destination[k * 4 + 3] = source[k]; + } + break; + case ColorFormat::RGBA8: + { + memcpy(destination, source, num_bytes_per_line); + } + break; + } + + destination += rectangle.GetTextureStride(); + source += num_bytes_per_line; + } + } + } + else + { + effect->GenerateGlyphTexture(rectangle.GetTextureData(), Vector2i(box.dimensions), rectangle.GetTextureStride(), glyph); + } + } + + return true; +} + +const FontEffect* FontFaceLayer::GetFontEffect() const +{ + return effect.get(); +} + +const Texture* FontFaceLayer::GetTexture(int index) +{ + RMLUI_ASSERT(index >= 0); + RMLUI_ASSERT(index < GetNumTextures()); + + return &(textures[index]); +} + +int FontFaceLayer::GetNumTextures() const +{ + return (int)textures.size(); +} + +Colourb FontFaceLayer::GetColour() const +{ + return colour; +} + +} // namespace Rml diff --git a/src/ui/FontEngineScaled/FontFaceLayer.h b/src/ui/FontEngineScaled/FontFaceLayer.h new file mode 100644 index 0000000..7c98a5d --- /dev/null +++ b/src/ui/FontEngineScaled/FontFaceLayer.h @@ -0,0 +1,138 @@ +/* + * This source file is modified from a part of RmlUi, the HTML/CSS Interface Middleware + * + * For the latest information, see http://github.com/mikke89/RmlUi + * + * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd + * Copyright (c) 2019-2023 The RmlUi Team, and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef RMLUI_CORE_FONTENGINESCALED_FONTFACELAYER_H +#define RMLUI_CORE_FONTENGINESCALED_FONTFACELAYER_H + +#include "RmlUi/Core.h" +#include "RmlUi/Core/FontGlyph.h" +#include "RmlUi/../../Source/Core/TextureLayout.h" + +namespace Rml { + class FontEffect; +} + +namespace RecompRml { + +using namespace Rml; + +class FontFaceHandleScaled; + +/** + A textured layer stored as part of a font face handle. Each handle will have at least a base + layer for the standard font. Further layers can be added to allow rendering of text effects. + + @author Peter Curry + */ + +class FontFaceLayer { +public: + FontFaceLayer(const SharedPtr& _effect); + ~FontFaceLayer(); + + /// Generates or re-generates the character and texture data for the layer. + /// @param[in] handle The handle generating this layer. + /// @param[in] effect The effect to initialise the layer with. + /// @param[in] clone The layer to optionally clone geometry and texture data from. + /// @return True if the layer was generated successfully, false if not. + bool Generate(const FontFaceHandleScaled* handle, const FontFaceLayer* clone = nullptr, bool clone_glyph_origins = false); + + /// Generates the texture data for a layer (for the texture database). + /// @param[out] texture_data The pointer to be set to the generated texture data. + /// @param[out] texture_dimensions The dimensions of the texture. + /// @param[in] texture_id The index of the texture within the layer to generate. + /// @param[in] glyphs The glyphs required by the font face handle. + bool GenerateTexture(UniquePtr& texture_data, Vector2i& texture_dimensions, int texture_id, const FontGlyphMap& glyphs); + + /// Generates the geometry required to render a single character. + /// @param[out] geometry An array of geometries this layer will write to. It must be at least as big as the number of textures in this layer. + /// @param[in] character_code The character to generate geometry for. + /// @param[in] position The position of the baseline. + /// @param[in] colour The colour of the string. + inline void GenerateGeometry(Geometry* geometry, const Character character_code, const Vector2f position, const Colourb colour) const + { + auto it = character_boxes.find(character_code); + if (it == character_boxes.end()) + return; + + const TextureBox& box = it->second; + + if (box.texture_index < 0) + return; + + // Generate the geometry for the character. + Vector& character_vertices = geometry[box.texture_index].GetVertices(); + Vector& character_indices = geometry[box.texture_index].GetIndices(); + + character_vertices.resize(character_vertices.size() + 4); + character_indices.resize(character_indices.size() + 6); + GeometryUtilities::GenerateQuad(&character_vertices[0] + (character_vertices.size() - 4), + &character_indices[0] + (character_indices.size() - 6), Vector2f(position.x + box.origin.x, position.y + box.origin.y).Round(), + box.dimensions, colour, box.texcoords[0], box.texcoords[1], (int)character_vertices.size() - 4); + } + + /// Returns the effect used to generate the layer. + const FontEffect* GetFontEffect() const; + + /// Returns one of the layer's textures. + const Texture* GetTexture(int index); + /// Returns the number of textures employed by this layer. + int GetNumTextures() const; + + /// Returns the layer's colour. + Colourb GetColour() const; + +private: + struct TextureBox { + TextureBox() : texture_index(-1) {} + + // The offset, in pixels, of the baseline from the start of this character's geometry. + Vector2f origin; + // The width and height, in pixels, of this character's geometry. + Vector2f dimensions; + // The texture coordinates for the character's geometry. + Vector2f texcoords[2]; + + // The texture this character renders from. + int texture_index; + }; + + using CharacterMap = UnorderedMap; + using TextureList = Vector; + + SharedPtr effect; + + TextureLayout texture_layout; + + CharacterMap character_boxes; + TextureList textures; + Colourb colour; +}; + +} // namespace Rml +#endif diff --git a/src/ui/FontEngineScaled/FontFamily.cpp b/src/ui/FontEngineScaled/FontFamily.cpp new file mode 100644 index 0000000..661f6e7 --- /dev/null +++ b/src/ui/FontEngineScaled/FontFamily.cpp @@ -0,0 +1,97 @@ +/* + * This source file is modified from a part of RmlUi, the HTML/CSS Interface Middleware + * + * For the latest information, see http://github.com/mikke89/RmlUi + * + * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd + * Copyright (c) 2019-2023 The RmlUi Team, and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include "FontFamily.h" +#include "RmlUi/Core/ComputedValues.h" +#include "RmlUi/Core/Math.h" +#include "FontFace.h" +#include + +namespace RecompRml { + +using namespace Rml; + +FontFamily::FontFamily(const String& name) : name(name) {} + +FontFamily::~FontFamily() +{ + // Multiple face entries may share memory within a single font family, although only one of them owns it. Here we make sure that all the face + // destructors are run before all the memory is released. This way we don't leave any hanging references to invalidated memory. + for (FontFaceEntry& entry : font_faces) + entry.face.reset(); +} + +FontFaceHandleScaled* FontFamily::GetFaceHandle(Style::FontStyle style, Style::FontWeight weight, int size) +{ + int best_dist = INT_MAX; + FontFace* matching_face = nullptr; + for (size_t i = 0; i < font_faces.size(); i++) + { + FontFace* face = font_faces[i].face.get(); + + if (face->GetStyle() == style) + { + const int dist = Math::Absolute((int)face->GetWeight() - (int)weight); + if (dist == 0) + { + // Direct match for weight, break the loop early. + matching_face = face; + break; + } + else if (dist < best_dist) + { + // Best match so far for weight, store the face and dist. + matching_face = face; + best_dist = dist; + } + } + } + + if (!matching_face) + return nullptr; + + return matching_face->GetHandle(size, true); +} + +FontFace* FontFamily::AddFace(FontFaceHandleFreetype ft_face, Style::FontStyle style, Style::FontWeight weight, UniquePtr face_memory) +{ + auto face = MakeUnique(ft_face, style, weight); + FontFace* result = face.get(); + + font_faces.push_back(FontFaceEntry{std::move(face), std::move(face_memory)}); + + return result; +} + +void FontFamily::ReleaseFontResources() +{ + for (auto& entry : font_faces) + entry.face->ReleaseFontResources(); +} + +} // namespace Rml diff --git a/src/ui/FontEngineScaled/FontFamily.h b/src/ui/FontEngineScaled/FontFamily.h new file mode 100644 index 0000000..1ce2127 --- /dev/null +++ b/src/ui/FontEngineScaled/FontFamily.h @@ -0,0 +1,82 @@ +/* + * This source file is modified from a part of RmlUi, the HTML/CSS Interface Middleware + * + * For the latest information, see http://github.com/mikke89/RmlUi + * + * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd + * Copyright (c) 2019-2023 The RmlUi Team, and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef RMLUI_CORE_FONTENGINESCALED_FONTFAMILY_H +#define RMLUI_CORE_FONTENGINESCALED_FONTFAMILY_H + +#include "FontTypes.h" + +namespace RecompRml { + +using namespace Rml; + +class FontFace; +class FontFaceHandleScaled; + +/** + @author Peter Curry + */ + +class FontFamily { +public: + FontFamily(const String& name); + ~FontFamily(); + + /// Returns a handle to the most appropriate font in the family, at the correct size. + /// @param[in] style The style of the desired handle. + /// @param[in] weight The weight of the desired handle. + /// @param[in] size The size of desired handle, in points. + /// @return A valid handle if a matching (or closely matching) font face was found, nullptr otherwise. + FontFaceHandleScaled* GetFaceHandle(Style::FontStyle style, Style::FontWeight weight, int size); + + /// Adds a new face to the family. + /// @param[in] ft_face The previously loaded FreeType face. + /// @param[in] style The style of the new face. + /// @param[in] weight The weight of the new face. + /// @param[in] face_memory Optionally pass ownership of the face's memory to the face itself, automatically releasing it on destruction. + /// @return True if the face was loaded successfully, false otherwise. + FontFace* AddFace(FontFaceHandleFreetype ft_face, Style::FontStyle style, Style::FontWeight weight, UniquePtr face_memory); + + /// Releases resources owned by sized font faces, including their textures and rendered glyphs. + void ReleaseFontResources(); + +protected: + String name; + + struct FontFaceEntry { + UniquePtr face; + // Only filled if we own the memory used by the face's FreeType handle. May be shared with other faces in this family. + UniquePtr face_memory; + }; + + using FontFaceList = Vector; + FontFaceList font_faces; +}; + +} // namespace Rml +#endif diff --git a/src/ui/FontEngineScaled/FontProvider.cpp b/src/ui/FontEngineScaled/FontProvider.cpp new file mode 100644 index 0000000..e41c842 --- /dev/null +++ b/src/ui/FontEngineScaled/FontProvider.cpp @@ -0,0 +1,268 @@ +/* + * This source file is modified from a part of RmlUi, the HTML/CSS Interface Middleware + * + * For the latest information, see http://github.com/mikke89/RmlUi + * + * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd + * Copyright (c) 2019-2023 The RmlUi Team, and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include "FontProvider.h" +#include "RmlUi/Core/Core.h" +#include "RmlUi/Core/FileInterface.h" +#include "RmlUi/Core/Log.h" +#include "RmlUi/Core/Math.h" +#include "RmlUi/Core/StringUtilities.h" +#include "RmlUi/../../Source/Core/ComputeProperty.h" +#include "FontFace.h" +#include "FontFamily.h" +#include "FreeTypeInterface.h" +#include + +namespace RecompRml { + +using namespace Rml; + +static FontProvider* g_font_provider = nullptr; + +FontProvider::FontProvider() +{ + RMLUI_ASSERT(!g_font_provider); +} + +FontProvider::~FontProvider() +{ + RMLUI_ASSERT(g_font_provider == this); +} + +bool FontProvider::Initialise() +{ + RMLUI_ASSERT(!g_font_provider); + if (!FreeType::Initialise()) + return false; + g_font_provider = new FontProvider; + return true; +} + +void FontProvider::Shutdown() +{ + RMLUI_ASSERT(g_font_provider); + delete g_font_provider; + g_font_provider = nullptr; + FreeType::Shutdown(); +} + +FontProvider& FontProvider::Get() +{ + RMLUI_ASSERT(g_font_provider); + return *g_font_provider; +} + +FontFaceHandleScaled* FontProvider::GetFontFaceHandle(const String& family, Style::FontStyle style, Style::FontWeight weight, int size) +{ + RMLUI_ASSERTMSG(family == StringUtilities::ToLower(family), "Font family name must be converted to lowercase before entering here."); + + FontFamilyMap& families = Get().font_families; + + auto it = families.find(family); + if (it == families.end()) + return nullptr; + + return it->second->GetFaceHandle(style, weight, size); +} + +int FontProvider::CountFallbackFontFaces() +{ + return (int)Get().fallback_font_faces.size(); +} + +FontFaceHandleScaled* FontProvider::GetFallbackFontFace(int index, int font_size) +{ + auto& faces = FontProvider::Get().fallback_font_faces; + + if (index >= 0 && index < (int)faces.size()) + return faces[index]->GetHandle(font_size, false); + + return nullptr; +} + +void FontProvider::ReleaseFontResources() +{ + RMLUI_ASSERT(g_font_provider); + for (auto& name_family : g_font_provider->font_families) + name_family.second->ReleaseFontResources(); +} + +bool FontProvider::LoadFontFace(const String& file_name, bool fallback_face, Style::FontWeight weight) +{ + FileInterface* file_interface = GetFileInterface(); + FileHandle handle = file_interface->Open(file_name); + + if (!handle) + { + Log::Message(Log::LT_ERROR, "Failed to load font face from %s, could not open file.", file_name.c_str()); + return false; + } + + size_t length = file_interface->Length(handle); + + auto buffer_ptr = UniquePtr(new byte[length]); + byte* buffer = buffer_ptr.get(); + file_interface->Read(buffer, length, handle); + file_interface->Close(handle); + + bool result = Get().LoadFontFace(buffer, (int)length, fallback_face, std::move(buffer_ptr), file_name, {}, Style::FontStyle::Normal, weight); + + return result; +} + +bool FontProvider::LoadFontFace(const byte* data, int data_size, const String& font_family, Style::FontStyle style, Style::FontWeight weight, + bool fallback_face) +{ + const String source = "memory"; + + bool result = Get().LoadFontFace(data, data_size, fallback_face, nullptr, source, font_family, style, weight); + + return result; +} + +bool FontProvider::LoadFontFace(const byte* data, int data_size, bool fallback_face, UniquePtr face_memory, const String& source, + String font_family, Style::FontStyle style, Style::FontWeight weight) +{ + using Style::FontWeight; + + Vector face_variations; + if (!FreeType::GetFaceVariations(data, data_size, face_variations)) + { + Log::Message(Log::LT_ERROR, "Failed to load font face from '%s': Invalid or unsupported font face file format.", source.c_str()); + return false; + } + + Vector load_variations; + if (face_variations.empty()) + { + load_variations.push_back(FaceVariation{Style::FontWeight::Auto, 0, 0}); + } + else + { + // Iterate through all the face variations and pick the ones to load. The list is already sorted by (weight, width). When weight is set to + // 'auto' we load all the weights of the face. However, we only want to load one width for each weight. + for (auto it = face_variations.begin(); it != face_variations.end();) + { + if (weight != FontWeight::Auto && it->weight != weight) + { + ++it; + continue; + } + + // We don't currently have any way for users to select widths, so we search for a regular (medium) value here. + constexpr int search_width = 100; + const FontWeight current_weight = it->weight; + + int best_width_distance = Math::Absolute((int)it->width - search_width); + auto it_best_width = it; + + // Search forward to find the best 'width' with the same weight. + for (++it; it != face_variations.end(); ++it) + { + if (it->weight != current_weight) + break; + + const int width_distance = Math::Absolute((int)it->width - search_width); + if (width_distance < best_width_distance) + { + best_width_distance = width_distance; + it_best_width = it; + } + } + + load_variations.push_back(*it_best_width); + } + } + + if (load_variations.empty()) + { + Log::Message(Log::LT_ERROR, "Failed to load font face from '%s': Could not locate face with weight %d.", source.c_str(), (int)weight); + return false; + } + + for (const FaceVariation& variation : load_variations) + { + FontFaceHandleFreetype ft_face = FreeType::LoadFace(data, data_size, source, variation.named_instance_index); + if (!ft_face) + return false; + + if (font_family.empty()) + FreeType::GetFaceStyle(ft_face, &font_family, &style, nullptr); + if (weight == FontWeight::Auto) + FreeType::GetFaceStyle(ft_face, nullptr, nullptr, &weight); + + const FontWeight variation_weight = (variation.weight == FontWeight::Auto ? weight : variation.weight); + const String font_face_description = GetFontFaceDescription(font_family, style, variation_weight); + + if (!AddFace(ft_face, font_family, style, variation_weight, fallback_face, std::move(face_memory))) + { + Log::Message(Log::LT_ERROR, "Failed to load font face %s from '%s'.", font_face_description.c_str(), source.c_str()); + return false; + } + + Log::Message(Log::LT_INFO, "Loaded font face %s from '%s'.", font_face_description.c_str(), source.c_str()); + } + + return true; +} + +bool FontProvider::AddFace(FontFaceHandleFreetype face, const String& family, Style::FontStyle style, Style::FontWeight weight, bool fallback_face, + UniquePtr face_memory) +{ + if (family.empty() || weight == Style::FontWeight::Auto) + return false; + + String family_lower = StringUtilities::ToLower(family); + FontFamily* font_family = nullptr; + auto it = font_families.find(family_lower); + if (it != font_families.end()) + { + font_family = (FontFamily*)it->second.get(); + } + else + { + auto font_family_ptr = MakeUnique(family_lower); + font_family = font_family_ptr.get(); + font_families[family_lower] = std::move(font_family_ptr); + } + + FontFace* font_face_result = font_family->AddFace(face, style, weight, std::move(face_memory)); + + if (font_face_result && fallback_face) + { + auto it_fallback_face = std::find(fallback_font_faces.begin(), fallback_font_faces.end(), font_face_result); + if (it_fallback_face == fallback_font_faces.end()) + { + fallback_font_faces.push_back(font_face_result); + } + } + + return static_cast(font_face_result); +} + +} // namespace Rml diff --git a/src/ui/FontEngineScaled/FontProvider.h b/src/ui/FontEngineScaled/FontProvider.h new file mode 100644 index 0000000..ca52dc4 --- /dev/null +++ b/src/ui/FontEngineScaled/FontProvider.h @@ -0,0 +1,102 @@ +/* + * This source file is modified from a part of RmlUi, the HTML/CSS Interface Middleware + * + * For the latest information, see http://github.com/mikke89/RmlUi + * + * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd + * Copyright (c) 2019-2023 The RmlUi Team, and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef RMLUI_CORE_FONTENGINESCALED_FONTPROVIDER_H +#define RMLUI_CORE_FONTENGINESCALED_FONTPROVIDER_H + +#include "RmlUi/Core/StyleTypes.h" +#include "RmlUi/Core/Types.h" +#include "FontTypes.h" + +namespace RecompRml { + +using namespace Rml; + +class FontFace; +class FontFamily; +class FontFaceHandleScaled; + +/** + The font provider contains all font families currently in use by RmlUi. + @author Peter Curry + */ + +class FontProvider { +public: + static bool Initialise(); + static void Shutdown(); + + /// Returns a handle to a font face that can be used to position and render text. This will return the closest match + /// it can find, but in the event a font family is requested that does not exist, nullptr will be returned instead of a + /// valid handle. + /// @param[in] family The family of the desired font handle. + /// @param[in] style The style of the desired font handle. + /// @param[in] weight The weight of the desired font handle. + /// @param[in] size The size of desired handle, in points. + /// @return A valid handle if a matching (or closely matching) font face was found, nullptr otherwise. + static FontFaceHandleScaled* GetFontFaceHandle(const String& family, Style::FontStyle style, Style::FontWeight weight, int size); + + /// Adds a new font face to the database. The face's family, style and weight will be determined from the face itself. + static bool LoadFontFace(const String& file_name, bool fallback_face, Style::FontWeight weight = Style::FontWeight::Auto); + + /// Adds a new font face from memory. + static bool LoadFontFace(const byte* data, int data_size, const String& font_family, Style::FontStyle style, Style::FontWeight weight, + bool fallback_face); + + /// Return the number of fallback font faces. + static int CountFallbackFontFaces(); + + /// Return a font face handle with the given index, at the given font size. + static FontFaceHandleScaled* GetFallbackFontFace(int index, int font_size); + + /// Releases resources owned by sized font faces, including their textures and rendered glyphs. + static void ReleaseFontResources(); + +private: + FontProvider(); + ~FontProvider(); + + static FontProvider& Get(); + + bool LoadFontFace(const byte* data, int data_size, bool fallback_face, UniquePtr face_memory, const String& source, String font_family, + Style::FontStyle style, Style::FontWeight weight); + + bool AddFace(FontFaceHandleFreetype face, const String& family, Style::FontStyle style, Style::FontWeight weight, bool fallback_face, + UniquePtr face_memory); + + using FontFaceList = Vector; + using FontFamilyMap = UnorderedMap>; + + FontFamilyMap font_families; + FontFaceList fallback_font_faces; + + static const String debugger_font_family_name; +}; + +} // namespace Rml +#endif diff --git a/src/ui/FontEngineScaled/FontTypes.h b/src/ui/FontEngineScaled/FontTypes.h new file mode 100644 index 0000000..9a4af5d --- /dev/null +++ b/src/ui/FontEngineScaled/FontTypes.h @@ -0,0 +1,58 @@ +/* + * This source file is modified from a part of RmlUi, the HTML/CSS Interface Middleware + * + * For the latest information, see http://github.com/mikke89/RmlUi + * + * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd + * Copyright (c) 2019-2023 The RmlUi Team, and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef RMLUI_CORE_FONTENGINESCALED_FONTTYPES_H +#define RMLUI_CORE_FONTENGINESCALED_FONTTYPES_H + +#include "RmlUi/Core/FontGlyph.h" +#include "RmlUi/Core/StyleTypes.h" +#include "RmlUi/Core/Types.h" + +namespace RecompRml { + +using namespace Rml; + +constexpr int global_font_scale = 4; + +using FontFaceHandleFreetype = uintptr_t; + +struct FaceVariation { + Style::FontWeight weight; + uint16_t width; + int named_instance_index; +}; + +inline bool operator<(const FaceVariation& a, const FaceVariation& b) +{ + if (a.weight == b.weight) + return a.width < b.width; + return a.weight < b.weight; +} + +} // namespace Rml +#endif diff --git a/src/ui/FontEngineScaled/FreeTypeInterface.cpp b/src/ui/FontEngineScaled/FreeTypeInterface.cpp new file mode 100644 index 0000000..b8cdd7b --- /dev/null +++ b/src/ui/FontEngineScaled/FreeTypeInterface.cpp @@ -0,0 +1,602 @@ +/* + * This source file is modified from a part of RmlUi, the HTML/CSS Interface Middleware + * + * For the latest information, see http://github.com/mikke89/RmlUi + * + * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd + * Copyright (c) 2019-2023 The RmlUi Team, and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include "FreeTypeInterface.h" +#include "RmlUi/Core/ComputedValues.h" +#include "RmlUi/Core/FontMetrics.h" +#include "RmlUi/Core/Log.h" +#include +#include +#include +#include +#include FT_FREETYPE_H +#include FT_MULTIPLE_MASTERS_H +#include FT_TRUETYPE_TABLES_H + +namespace RecompRml { + +using namespace Rml; + +static FT_Library ft_library = nullptr; + +static bool BuildGlyph(FT_Face ft_face, Character character, FontGlyphMap& glyphs, float bitmap_scaling_factor); +static void BuildGlyphMap(FT_Face ft_face, int size, FontGlyphMap& glyphs, float bitmap_scaling_factor, bool load_default_glyphs); +static void GenerateMetrics(FT_Face ft_face, FontMetrics& metrics, float bitmap_scaling_factor); +static bool SetFontSize(FT_Face ft_face, int font_size, float& out_bitmap_scaling_factor); +static void BitmapDownscale(byte* bitmap_new, int new_width, int new_height, const byte* bitmap_source, int width, int height, int pitch, + ColorFormat color_format); + +static int ConvertFixed16_16ToInt(int32_t fx) +{ + return fx / 0x10000; +} + +bool FreeType::Initialise() +{ + RMLUI_ASSERT(!ft_library); + + FT_Error result = FT_Init_FreeType(&ft_library); + if (result != 0) + { + Log::Message(Log::LT_ERROR, "Failed to initialise FreeType, error %d.", result); + Shutdown(); + return false; + } + + return true; +} + +void FreeType::Shutdown() +{ + if (ft_library != nullptr) + { + FT_Done_FreeType(ft_library); + ft_library = nullptr; + } +} + +bool FreeType::GetFaceVariations(const byte* data, int data_length, Vector& out_face_variations) +{ + RMLUI_ASSERT(ft_library); + + FT_Face face = nullptr; + FT_Error error = FT_New_Memory_Face(ft_library, (const FT_Byte*)data, data_length, 0, &face); + if (error) + return false; + + FT_MM_Var* var = nullptr; + error = FT_Get_MM_Var(face, &var); + if (error) + return true; + + unsigned int axis_index_weight = 0; + unsigned int axis_index_width = 1; + + const unsigned int num_axis = var->num_axis; + for (unsigned int i = 0; i < num_axis; i++) + { + switch (var->axis[i].tag) + { + case 0x77676874: // 'wght' + axis_index_weight = i; + break; + case 0x77647468: // 'wdth' + axis_index_width = i; + break; + } + } + + if (num_axis > 0) + { + for (unsigned int i = 0; i < var->num_namedstyles; i++) + { + uint16_t weight = (axis_index_weight < num_axis ? (uint16_t)ConvertFixed16_16ToInt(var->namedstyle[i].coords[axis_index_weight]) : 0); + uint16_t width = (axis_index_width < num_axis ? (uint16_t)ConvertFixed16_16ToInt(var->namedstyle[i].coords[axis_index_width]) : 0); + int named_instance_index = (i + 1); + + out_face_variations.push_back(FaceVariation{weight == 0 ? Style::FontWeight::Normal : (Style::FontWeight)weight, + width == 0 ? (uint16_t)100 : width, named_instance_index}); + } + } + + std::sort(out_face_variations.begin(), out_face_variations.end()); + +#if FREETYPE_MAJOR >= 2 && FREETYPE_MINOR >= 9 + FT_Done_MM_Var(ft_library, var); +#endif + + FT_Done_Face(face); + + return true; +} + +FontFaceHandleFreetype FreeType::LoadFace(const byte* data, int data_length, const String& source, int named_style_index) +{ + RMLUI_ASSERT(ft_library); + + FT_Face face = nullptr; + FT_Error error = FT_New_Memory_Face(ft_library, (const FT_Byte*)data, data_length, (named_style_index << 16), &face); + if (error) + { + Log::Message(Log::LT_ERROR, "FreeType error %d while loading face from %s.", error, source.c_str()); + return 0; + } + + // Initialise the character mapping on the face. + if (face->charmap == nullptr) + { + FT_Select_Charmap(face, FT_ENCODING_APPLE_ROMAN); + if (face->charmap == nullptr) + { + Log::Message(Log::LT_ERROR, "Font face (from %s) does not contain a Unicode or Apple Roman character map.", source.c_str()); + FT_Done_Face(face); + return 0; + } + } + + return (FontFaceHandleFreetype)face; +} + +bool FreeType::ReleaseFace(FontFaceHandleFreetype in_face) +{ + FT_Face face = (FT_Face)in_face; + FT_Error error = FT_Done_Face(face); + + return (error == 0); +} + +void FreeType::GetFaceStyle(FontFaceHandleFreetype in_face, String* font_family, Style::FontStyle* style, Style::FontWeight* weight) +{ + FT_Face face = (FT_Face)in_face; + + if (font_family) + *font_family = face->family_name; + if (style) + *style = face->style_flags & FT_STYLE_FLAG_ITALIC ? Style::FontStyle::Italic : Style::FontStyle::Normal; + + if (weight) + { + TT_OS2* font_table = (TT_OS2*)FT_Get_Sfnt_Table(face, FT_SFNT_OS2); + if (font_table && font_table->usWeightClass != 0) + *weight = (Style::FontWeight)font_table->usWeightClass; + else + *weight = (face->style_flags & FT_STYLE_FLAG_BOLD) == FT_STYLE_FLAG_BOLD ? Style::FontWeight::Bold : Style::FontWeight::Normal; + } +} + +bool FreeType::InitialiseFaceHandle(FontFaceHandleFreetype face, int font_size, FontGlyphMap& glyphs, FontMetrics& metrics, bool load_default_glyphs) +{ + FT_Face ft_face = (FT_Face)face; + + metrics.size = font_size; + + float bitmap_scaling_factor = 1.0f; + if (!SetFontSize(ft_face, font_size, bitmap_scaling_factor)) + return false; + + // Construct the initial list of glyphs. + BuildGlyphMap(ft_face, font_size / 4, glyphs, bitmap_scaling_factor, load_default_glyphs); + + // Generate the metrics for the handle. + GenerateMetrics(ft_face, metrics, bitmap_scaling_factor); + + return true; +} + +bool FreeType::AppendGlyph(FontFaceHandleFreetype face, int font_size, Character character, FontGlyphMap& glyphs) +{ + FT_Face ft_face = (FT_Face)face; + + RMLUI_ASSERT(glyphs.find(character) == glyphs.end()); + RMLUI_ASSERT(ft_face); + + // Set face size again in case it was used at another size in another font face handle. + float bitmap_scaling_factor = 1.0f; + if (!SetFontSize(ft_face, font_size, bitmap_scaling_factor)) + return false; + + if (!BuildGlyph(ft_face, character, glyphs, bitmap_scaling_factor)) + return false; + + return true; +} + +int FreeType::GetKerning(FontFaceHandleFreetype face, int font_size, Character lhs, Character rhs) +{ + FT_Face ft_face = (FT_Face)face; + + RMLUI_ASSERT(FT_HAS_KERNING(ft_face)); + + // Set face size again in case it was used at another size in another font face handle. + // Font size value of zero assumes it is already set. + if (font_size > 0) + { + float bitmap_scaling_factor = 1.0f; + if (!SetFontSize(ft_face, font_size, bitmap_scaling_factor) || bitmap_scaling_factor != 1.0f) + return 0; + } + + FT_Vector ft_kerning; + + FT_Error ft_error = FT_Get_Kerning(ft_face, FT_Get_Char_Index(ft_face, (FT_ULong)lhs), FT_Get_Char_Index(ft_face, (FT_ULong)rhs), + FT_KERNING_DEFAULT, &ft_kerning); + + if (ft_error) + return 0; + + int kerning = ft_kerning.x >> 6; + return kerning; +} + +bool FreeType::HasKerning(FontFaceHandleFreetype face) +{ + FT_Face ft_face = (FT_Face)face; + + return FT_HAS_KERNING(ft_face); +} + +static void BuildGlyphMap(FT_Face ft_face, int size, FontGlyphMap& glyphs, const float bitmap_scaling_factor, const bool load_default_glyphs) +{ + if (load_default_glyphs) + { + glyphs.reserve(128); + + // Add the ASCII characters now. Other characters are added later as needed. + FT_ULong code_min = 32; + FT_ULong code_max = 126; + + for (FT_ULong character_code = code_min; character_code <= code_max; ++character_code) + BuildGlyph(ft_face, (Character)character_code, glyphs, bitmap_scaling_factor); + } + + // Add a replacement character for rendering unknown characters. + Character replacement_character = Character::Replacement; + auto it = glyphs.find(replacement_character); + if (it == glyphs.end()) + { + FontGlyph glyph; + glyph.dimensions = {size / 3, (size * 2) / 3}; + glyph.bitmap_dimensions = glyph.dimensions; + glyph.advance = glyph.dimensions.x + 2; + glyph.bearing = {1, glyph.dimensions.y}; + + glyph.bitmap_owned_data.reset(new byte[glyph.bitmap_dimensions.x * glyph.bitmap_dimensions.y]); + glyph.bitmap_data = glyph.bitmap_owned_data.get(); + + for (int y = 0; y < glyph.bitmap_dimensions.y; y++) + { + for (int x = 0; x < glyph.bitmap_dimensions.x; x++) + { + constexpr int stroke = 1; + int i = y * glyph.bitmap_dimensions.x + x; + bool near_edge = (x < stroke || x >= glyph.bitmap_dimensions.x - stroke || y < stroke || y >= glyph.bitmap_dimensions.y - stroke); + glyph.bitmap_owned_data[i] = (near_edge ? 0xdd : 0); + } + } + + glyphs[replacement_character] = std::move(glyph); + } +} + +static bool BuildGlyph(FT_Face ft_face, const Character character, FontGlyphMap& glyphs, const float bitmap_scaling_factor) +{ + FT_UInt index = FT_Get_Char_Index(ft_face, (FT_ULong)character); + if (index == 0) + return false; + + FT_Error error = FT_Load_Glyph(ft_face, index, FT_LOAD_COLOR); + if (error != 0) + { + Log::Message(Log::LT_WARNING, "Unable to load glyph for character '%u' on the font face '%s %s'; error code: %d.", (unsigned int)character, + ft_face->family_name, ft_face->style_name, error); + return false; + } + + error = FT_Render_Glyph(ft_face->glyph, FT_RENDER_MODE_NORMAL); + if (error != 0) + { + Log::Message(Log::LT_WARNING, "Unable to render glyph for character '%u' on the font face '%s %s'; error code: %d.", (unsigned int)character, + ft_face->family_name, ft_face->style_name, error); + return false; + } + + auto result = glyphs.emplace(character, FontGlyph{}); + if (!result.second) + { + Log::Message(Log::LT_WARNING, "Glyph character '%u' is already loaded in the font face '%s %s'.", (unsigned int)character, + ft_face->family_name, ft_face->style_name); + return false; + } + + FontGlyph& glyph = result.first->second; + + FT_GlyphSlot ft_glyph = ft_face->glyph; + + // Set the glyph's dimensions. + glyph.dimensions.x = (ft_glyph->metrics.width * global_font_scale) >> 6; + glyph.dimensions.y = (ft_glyph->metrics.height * global_font_scale) >> 6; + + // Set the glyph's bearing. + glyph.bearing.x = (ft_glyph->metrics.horiBearingX * global_font_scale) >> 6; + glyph.bearing.y = (ft_glyph->metrics.horiBearingY * global_font_scale) >> 6; + + // Set the glyph's advance. + glyph.advance = (ft_glyph->metrics.horiAdvance * global_font_scale) >> 6; + + // Set the glyph's bitmap dimensions. + glyph.bitmap_dimensions.x = ft_glyph->bitmap.width; + glyph.bitmap_dimensions.y = ft_glyph->bitmap.rows; + + // Determine new metrics if we need to scale the bitmap received from FreeType. Only allow bitmap downscaling. + const bool scale_bitmap = (bitmap_scaling_factor < 1.f); + if (scale_bitmap) + { + glyph.dimensions = Vector2i(Vector2f(glyph.dimensions) * bitmap_scaling_factor); + glyph.bearing = Vector2i(Vector2f(glyph.bearing) * bitmap_scaling_factor); + glyph.advance = int(float(glyph.advance) * bitmap_scaling_factor); + glyph.bitmap_dimensions = Vector2i(Vector2f(glyph.bitmap_dimensions) * bitmap_scaling_factor); + } + + // Copy the glyph's bitmap data from the FreeType glyph handle to our glyph handle. + if (glyph.bitmap_dimensions.x * glyph.bitmap_dimensions.y != 0) + { + // Check if the pixel mode is supported. + if (ft_glyph->bitmap.pixel_mode != FT_PIXEL_MODE_MONO && ft_glyph->bitmap.pixel_mode != FT_PIXEL_MODE_GRAY && + ft_glyph->bitmap.pixel_mode != FT_PIXEL_MODE_BGRA) + { + Log::Message(Log::LT_WARNING, "Unable to render glyph on the font face '%s %s': unsupported pixel mode (%d).", + ft_glyph->face->family_name, ft_glyph->face->style_name, ft_glyph->bitmap.pixel_mode); + } + else if (ft_glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO && scale_bitmap) + { + Log::Message(Log::LT_WARNING, "Unable to render glyph on the font face '%s %s': bitmap scaling unsupported in mono pixel mode.", + ft_glyph->face->family_name, ft_glyph->face->style_name); + } + else + { + const int num_bytes_per_pixel = (ft_glyph->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA ? 4 : 1); + glyph.color_format = (ft_glyph->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA ? ColorFormat::RGBA8 : ColorFormat::A8); + + glyph.bitmap_owned_data.reset(new byte[glyph.bitmap_dimensions.x * glyph.bitmap_dimensions.y * num_bytes_per_pixel]); + glyph.bitmap_data = glyph.bitmap_owned_data.get(); + byte* destination_bitmap = glyph.bitmap_owned_data.get(); + const byte* source_bitmap = ft_glyph->bitmap.buffer; + + // Copy the bitmap data into the newly-allocated space on our glyph. + switch (ft_glyph->bitmap.pixel_mode) + { + case FT_PIXEL_MODE_MONO: + { + // Unpack 1-bit data into 8-bit. + for (int i = 0; i < glyph.bitmap_dimensions.y; ++i) + { + int mask = 0x80; + const byte* source_byte = source_bitmap; + for (int j = 0; j < glyph.bitmap_dimensions.x; ++j) + { + if ((*source_byte & mask) == mask) + destination_bitmap[j] = 255; + else + destination_bitmap[j] = 0; + + mask >>= 1; + if (mask <= 0) + { + mask = 0x80; + ++source_byte; + } + } + + destination_bitmap += glyph.bitmap_dimensions.x; + source_bitmap += ft_glyph->bitmap.pitch; + } + } + break; + case FT_PIXEL_MODE_GRAY: + case FT_PIXEL_MODE_BGRA: + { + if (scale_bitmap) + { + // Resize the glyph data to the new dimensions. + BitmapDownscale(destination_bitmap, glyph.bitmap_dimensions.x, glyph.bitmap_dimensions.y, source_bitmap, + (int)ft_glyph->bitmap.width, (int)ft_glyph->bitmap.rows, ft_glyph->bitmap.pitch, glyph.color_format); + } + else + { + // Copy the glyph data directly. + const int num_bytes_per_line = glyph.bitmap_dimensions.x * num_bytes_per_pixel; + for (int i = 0; i < glyph.bitmap_dimensions.y; ++i) + { + memcpy(destination_bitmap, source_bitmap, num_bytes_per_line); + destination_bitmap += num_bytes_per_line; + source_bitmap += ft_glyph->bitmap.pitch; + } + } + + if (glyph.color_format == ColorFormat::RGBA8) + { + // Swizzle channels (BGRA -> RGBA) and un-premultiply alpha. + destination_bitmap = glyph.bitmap_owned_data.get(); + + for (int k = 0; k < glyph.bitmap_dimensions.x * glyph.bitmap_dimensions.y * num_bytes_per_pixel; k += 4) + { + byte b = destination_bitmap[k]; + byte g = destination_bitmap[k + 1]; + byte r = destination_bitmap[k + 2]; + const byte alpha = destination_bitmap[k + 3]; + RMLUI_ASSERTMSG(b <= alpha && g <= alpha && r <= alpha, "Assumption of glyph data being premultiplied is broken."); + + if (alpha > 0 && alpha < 255) + { + b = byte((b * 255) / alpha); + g = byte((g * 255) / alpha); + r = byte((r * 255) / alpha); + } + + destination_bitmap[k] = r; + destination_bitmap[k + 1] = g; + destination_bitmap[k + 2] = b; + destination_bitmap[k + 3] = alpha; + } + } + } + break; + } + } + } + + return true; +} + +static void GenerateMetrics(FT_Face ft_face, FontMetrics& metrics, float bitmap_scaling_factor) +{ + metrics.ascent = ft_face->size->metrics.ascender * bitmap_scaling_factor * global_font_scale / float(1 << 6); + metrics.descent = -ft_face->size->metrics.descender * bitmap_scaling_factor * global_font_scale / float(1 << 6); + metrics.line_spacing = ft_face->size->metrics.height * bitmap_scaling_factor * global_font_scale / float(1 << 6); + + metrics.underline_position = FT_MulFix(-ft_face->underline_position, ft_face->size->metrics.y_scale) * bitmap_scaling_factor * global_font_scale / float(1 << 6); + metrics.underline_thickness = FT_MulFix(ft_face->underline_thickness, ft_face->size->metrics.y_scale) * bitmap_scaling_factor * global_font_scale / float(1 << 6); + metrics.underline_thickness = Math::Max(metrics.underline_thickness, 1.0f); + + // Determine the x-height of this font face. + FT_UInt index = FT_Get_Char_Index(ft_face, 'x'); + if (index != 0 && FT_Load_Glyph(ft_face, index, 0) == 0) + metrics.x_height = ft_face->glyph->metrics.height * bitmap_scaling_factor * global_font_scale / float(1 << 6); + else + metrics.x_height = 0.5f * metrics.line_spacing; +} + +static bool SetFontSize(FT_Face ft_face, int font_size, float& out_bitmap_scaling_factor) +{ + RMLUI_ASSERT(out_bitmap_scaling_factor == 1.f); + font_size /= global_font_scale; + + FT_Error error = 0; + + // Set the character size on the font face. + error = FT_Set_Char_Size(ft_face, 0, font_size << 6, 0, 0); + + // If setting char size fails, try to select a bitmap strike instead when available. + if (error != 0 && FT_HAS_FIXED_SIZES(ft_face)) + { + constexpr int a_big_number = INT_MAX / 2; + int heuristic_min = INT_MAX; + int index_min = -1; + + // Select the bitmap strike with the smallest size *above* font_size, or else the largest size. + for (int i = 0; i < ft_face->num_fixed_sizes; i++) + { + const int size_diff = ft_face->available_sizes[i].height - font_size; + const int heuristic = (size_diff < 0 ? a_big_number - size_diff : size_diff); + + if (heuristic < heuristic_min) + { + index_min = i; + heuristic_min = heuristic; + } + } + + if (index_min >= 0) + { + out_bitmap_scaling_factor = float(font_size) / ft_face->available_sizes[index_min].height; + + // Add some tolerance to the scaling factor to avoid unnecessary scaling. Only allow downscaling. + constexpr float bitmap_scaling_factor_threshold = 0.95f; + if (out_bitmap_scaling_factor >= bitmap_scaling_factor_threshold) + out_bitmap_scaling_factor = 1.f; + + error = FT_Select_Size(ft_face, index_min); + } + } + + if (error != 0) + { + Log::Message(Log::LT_ERROR, "Unable to set the character size '%d' on the font face '%s %s'.", font_size, ft_face->family_name, + ft_face->style_name); + return false; + } + + return true; +} + +static void BitmapDownscale(byte* bitmap_new, const int new_width, const int new_height, const byte* bitmap_source, const int width, const int height, + const int pitch, const ColorFormat color_format) +{ + // Average filter for downscaling bitmap images, based on https://stackoverflow.com/a/9571580 + constexpr int max_num_channels = 4; + const int num_channels = (color_format == ColorFormat::RGBA8 ? 4 : 1); + + const float xscale = float(new_width) / width; + const float yscale = float(new_height) / height; + const float sumscale = xscale * yscale; + + float yend = 0.0f; + for (int f = 0; f < new_height; f++) // y on output + { + const float ystart = yend; + yend = (f + 1) * (1.f / yscale); + if (yend >= height) + yend = height - 0.001f; + + float xend = 0.0; + for (int g = 0; g < new_width; g++) // x on output + { + float xstart = xend; + xend = (g + 1) * (1.f / xscale); + if (xend >= width) + xend = width - 0.001f; + + float sum[max_num_channels] = {}; + for (int y = (int)ystart; y <= (int)yend; ++y) + { + float yportion = 1.0f; + if (y == (int)ystart) + yportion -= ystart - y; + if (y == (int)yend) + yportion -= y + 1 - yend; + + for (int x = (int)xstart; x <= (int)xend; ++x) + { + float xportion = 1.0f; + if (x == (int)xstart) + xportion -= xstart - x; + if (x == (int)xend) + xportion -= x + 1 - xend; + + for (int i = 0; i < num_channels; i++) + sum[i] += bitmap_source[y * pitch + x * num_channels + i] * yportion * xportion; + } + } + + for (int i = 0; i < num_channels; i++) + bitmap_new[(f * new_width + g) * num_channels + i] = (byte)Math::Min(sum[i] * sumscale, 255.f); + } + } +} + +} // namespace Rml diff --git a/src/ui/FontEngineScaled/FreeTypeInterface.h b/src/ui/FontEngineScaled/FreeTypeInterface.h new file mode 100644 index 0000000..fa26755 --- /dev/null +++ b/src/ui/FontEngineScaled/FreeTypeInterface.h @@ -0,0 +1,73 @@ +/* + * This source file is modified from a part of RmlUi, the HTML/CSS Interface Middleware + * + * For the latest information, see http://github.com/mikke89/RmlUi + * + * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd + * Copyright (c) 2019-2023 The RmlUi Team, and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef RMLUI_CORE_FONTENGINESCALED_FREETYPEINTERFACE_H +#define RMLUI_CORE_FONTENGINESCALED_FREETYPEINTERFACE_H + +#include "RmlUi/Core/FontMetrics.h" +#include "FontTypes.h" + +namespace RecompRml { + +using namespace Rml; + +namespace FreeType { + + // Initialize FreeType library. + bool Initialise(); + // Shutdown FreeType library. + void Shutdown(); + + // Returns a sorted list of available font variations for the font face located in memory. + bool GetFaceVariations(const byte* data, int data_length, Vector& out_face_variations); + + // Loads a FreeType face from memory, 'source' is only used for logging. + FontFaceHandleFreetype LoadFace(const byte* data, int data_length, const String& source, int named_instance_index = 0); + + // Releases the FreeType face. + bool ReleaseFace(FontFaceHandleFreetype face); + + // Retrieves the font family, style and weight of the given font face. Use nullptr to ignore a property. + void GetFaceStyle(FontFaceHandleFreetype face, String* font_family, Style::FontStyle* style, Style::FontWeight* weight); + + // Initializes a face for a given font size. Glyphs are filled with the ASCII subset, and the font face metrics are set. + bool InitialiseFaceHandle(FontFaceHandleFreetype face, int font_size, FontGlyphMap& glyphs, FontMetrics& metrics, bool load_default_glyphs); + + // Build a new glyph representing the given code point and append to 'glyphs'. + bool AppendGlyph(FontFaceHandleFreetype face, int font_size, Character character, FontGlyphMap& glyphs); + + // Returns the kerning between two characters. + // 'font_size' value of zero assumes the font size is already set on the face, and skips this step for performance reasons. + int GetKerning(FontFaceHandleFreetype face, int font_size, Character lhs, Character rhs); + + // Returns true if the font face has kerning. + bool HasKerning(FontFaceHandleFreetype face); + +} // namespace FreeType +} // namespace Rml +#endif diff --git a/src/ui/ui_renderer.cpp b/src/ui/ui_renderer.cpp index 5f92781..6ec0fe3 100644 --- a/src/ui/ui_renderer.cpp +++ b/src/ui/ui_renderer.cpp @@ -26,6 +26,8 @@ # include "InterfacePS.hlsl.dxil.h" #endif +#include "FontEngineScaled/FontEngineInterfaceScaled.h" + #ifdef _WIN32 # define GET_SHADER_BLOB(name, format) \ ((format) == RT64::RenderShaderFormat::SPIRV ? name##BlobSPIRV : \ @@ -735,6 +737,7 @@ struct { public: std::unique_ptr system_interface; std::unique_ptr render_interface; + std::unique_ptr font_interface; Rml::Context* context; recomp::UiEventListenerInstancer event_listener_instancer; int32_t ui_scale = 4; @@ -887,6 +890,9 @@ void init_hook(RT64::RenderInterface* interface, RT64::RenderDevice* device) { Rml::SetSystemInterface(UIContext.rml.system_interface.get()); Rml::SetRenderInterface(UIContext.rml.render_interface.get()); Rml::Factory::RegisterEventListenerInstancer(&UIContext.rml.event_listener_instancer); + + UIContext.rml.font_interface = std::make_unique(); + Rml::SetFontEngineInterface(UIContext.rml.font_interface.get()); Rml::Initialise();