Added custom font interface that scales font glyphs down to avoid downsampling

This commit is contained in:
Mr-Wiseguy 2024-01-19 18:14:42 -05:00
parent 3456b03b4d
commit d6e4e98c8b
17 changed files with 2698 additions and 0 deletions

View File

@ -128,6 +128,14 @@ set (SOURCES
${CMAKE_SOURCE_DIR}/src/ui/ui_config.cpp ${CMAKE_SOURCE_DIR}/src/ui/ui_config.cpp
${CMAKE_SOURCE_DIR}/src/ui/ui_color_hack.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/aspMain.cpp
${CMAKE_SOURCE_DIR}/rsp/njpgdspMain.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
${CMAKE_SOURCE_DIR}/lib/RT64-HLE/src/rhi ${CMAKE_SOURCE_DIR}/lib/RT64-HLE/src/rhi
${CMAKE_SOURCE_DIR}/lib/RT64-HLE/src/render ${CMAKE_SOURCE_DIR}/lib/RT64-HLE/src/render
${CMAKE_SOURCE_DIR}/lib/freetype-windows-binaries/include
${CMAKE_BINARY_DIR}/shaders ${CMAKE_BINARY_DIR}/shaders
) )

View File

@ -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<FontFaceHandle>(handle);
}
FontEffectsHandle FontEngineInterfaceScaled::PrepareFontEffects(FontFaceHandle handle, const FontEffectList& font_effects)
{
auto handle_scaled = reinterpret_cast<FontFaceHandleScaled*>(handle);
return (FontEffectsHandle)handle_scaled->GenerateLayerConfiguration(font_effects);
}
const FontMetrics& FontEngineInterfaceScaled::GetFontMetrics(FontFaceHandle handle)
{
auto handle_scaled = reinterpret_cast<FontFaceHandleScaled*>(handle);
return handle_scaled->GetFontMetrics();
}
int FontEngineInterfaceScaled::GetStringWidth(FontFaceHandle handle, const String& string, float letter_spacing, Character prior_character)
{
auto handle_scaled = reinterpret_cast<FontFaceHandleScaled*>(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<FontFaceHandleScaled*>(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<FontFaceHandleScaled*>(handle);
return handle_scaled->GetVersion();
}
void FontEngineInterfaceScaled::ReleaseFontResources()
{
FontProvider::ReleaseFontResources();
}
} // namespace Rml

View File

@ -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

View File

@ -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<FontFaceHandleScaled>();
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

View File

@ -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<int, UniquePtr<FontFaceHandleScaled>>;
HandleMap handles;
FontFaceHandleFreetype face;
};
} // namespace Rml
#endif

View File

@ -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 <algorithm>
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<const byte[]>& 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<const FontEffect>& 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<FontFaceLayer>(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

View File

@ -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<const byte[]>& 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<const FontEffect>& font_effect);
// (Re-)generate a layer in this font face handle.
bool GenerateLayer(FontFaceLayer* layer);
FontGlyphMap glyphs;
struct EffectLayerPair {
const FontEffect* font_effect;
UniquePtr<FontFaceLayer> layer;
};
using FontLayerMap = Vector<EffectLayerPair>;
using FontLayerCache = SmallUnorderedMap<size_t, FontFaceLayer*>;
using LayerConfiguration = Vector<FontFaceLayer*>;
using LayerConfigurationList = Vector<LayerConfiguration>;
// 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<AsciiPair, KerningIntType>;
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

View File

@ -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 <string.h>
namespace RecompRml {
using namespace Rml;
FontFaceLayer::FontFaceLayer(const SharedPtr<const FontEffect>& _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<const byte[]> 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<const byte[]>& 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

View File

@ -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<const FontEffect>& _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<const byte[]>& 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<Vertex>& character_vertices = geometry[box.texture_index].GetVertices();
Vector<int>& 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<Character, TextureBox>;
using TextureList = Vector<Texture>;
SharedPtr<const FontEffect> effect;
TextureLayout texture_layout;
CharacterMap character_boxes;
TextureList textures;
Colourb colour;
};
} // namespace Rml
#endif

View File

@ -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 <limits.h>
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<byte[]> face_memory)
{
auto face = MakeUnique<FontFace>(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

View File

@ -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<byte[]> face_memory);
/// Releases resources owned by sized font faces, including their textures and rendered glyphs.
void ReleaseFontResources();
protected:
String name;
struct FontFaceEntry {
UniquePtr<FontFace> 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<byte[]> face_memory;
};
using FontFaceList = Vector<FontFaceEntry>;
FontFaceList font_faces;
};
} // namespace Rml
#endif

View File

@ -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 <algorithm>
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<byte[]>(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<byte[]> face_memory, const String& source,
String font_family, Style::FontStyle style, Style::FontWeight weight)
{
using Style::FontWeight;
Vector<FaceVariation> 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<FaceVariation> 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<byte[]> 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<FontFamily>(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<bool>(font_face_result);
}
} // namespace Rml

View File

@ -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<byte[]> 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<byte[]> face_memory);
using FontFaceList = Vector<FontFace*>;
using FontFamilyMap = UnorderedMap<String, UniquePtr<FontFamily>>;
FontFamilyMap font_families;
FontFaceList fallback_font_faces;
static const String debugger_font_family_name;
};
} // namespace Rml
#endif

View File

@ -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

View File

@ -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 <algorithm>
#include <ft2build.h>
#include <limits.h>
#include <string.h>
#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<FaceVariation>& 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

View File

@ -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<FaceVariation>& 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

View File

@ -26,6 +26,8 @@
# include "InterfacePS.hlsl.dxil.h" # include "InterfacePS.hlsl.dxil.h"
#endif #endif
#include "FontEngineScaled/FontEngineInterfaceScaled.h"
#ifdef _WIN32 #ifdef _WIN32
# define GET_SHADER_BLOB(name, format) \ # define GET_SHADER_BLOB(name, format) \
((format) == RT64::RenderShaderFormat::SPIRV ? name##BlobSPIRV : \ ((format) == RT64::RenderShaderFormat::SPIRV ? name##BlobSPIRV : \
@ -735,6 +737,7 @@ struct {
public: public:
std::unique_ptr<SystemInterface_SDL> system_interface; std::unique_ptr<SystemInterface_SDL> system_interface;
std::unique_ptr<RmlRenderInterface_RT64> render_interface; std::unique_ptr<RmlRenderInterface_RT64> render_interface;
std::unique_ptr<Rml::FontEngineInterface> font_interface;
Rml::Context* context; Rml::Context* context;
recomp::UiEventListenerInstancer event_listener_instancer; recomp::UiEventListenerInstancer event_listener_instancer;
int32_t ui_scale = 4; 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::SetSystemInterface(UIContext.rml.system_interface.get());
Rml::SetRenderInterface(UIContext.rml.render_interface.get()); Rml::SetRenderInterface(UIContext.rml.render_interface.get());
Rml::Factory::RegisterEventListenerInstancer(&UIContext.rml.event_listener_instancer); Rml::Factory::RegisterEventListenerInstancer(&UIContext.rml.event_listener_instancer);
UIContext.rml.font_interface = std::make_unique<RecompRml::FontEngineInterfaceScaled>();
Rml::SetFontEngineInterface(UIContext.rml.font_interface.get());
Rml::Initialise(); Rml::Initialise();