/*
* This source file is 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 "FontEngineBitmap.h"
#include
#include
#include
#include
#include
namespace FontProviderBitmap {
static Rml::Vector> fonts;
void Initialise() {}
void Shutdown()
{
fonts.clear();
}
bool LoadFontFace(const String& file_name)
{
// Load the xml meta file into memory
Rml::UniquePtr data;
size_t length = 0;
{
auto file_interface = Rml::GetFileInterface();
auto handle = file_interface->Open(file_name);
if (!handle)
return false;
length = file_interface->Length(handle);
data.reset(new byte[length]);
size_t read_length = file_interface->Read(data.get(), length, handle);
file_interface->Close(handle);
if (read_length != length || !data)
return false;
}
// Parse the xml font description
FontParserBitmap parser;
{
auto stream = Rml::MakeUnique(data.get(), length);
stream->SetSourceURL(file_name);
parser.Parse(stream.get());
if (parser.family.empty() || parser.glyphs.empty() || parser.texture_name.empty() || parser.metrics.size == 0)
return false;
// Fill the remaining metrics
parser.metrics.underline_position = 3.f;
parser.metrics.underline_thickness = 1.f;
}
Texture texture;
texture.Set(parser.texture_name, file_name);
// Construct and add the font face
fonts.push_back(Rml::MakeUnique(parser.family, parser.style, parser.weight, parser.metrics, texture, parser.texture_dimensions,
std::move(parser.glyphs), std::move(parser.kerning)));
return true;
}
FontFaceBitmap* GetFontFaceHandle(const String& family, FontStyle style, FontWeight weight, int size)
{
FontFaceBitmap* best_match = nullptr;
int best_score = 0;
// Normally, we'd want to only match the font family exactly, but for this demo we create a very lenient heuristic.
for (const auto& font : fonts)
{
int score = 1;
if (font->GetFamily() == family)
score += 100;
score += 10 - std::min(10, std::abs(font->GetMetrics().size - size));
if (font->GetStyle() == style)
score += 2;
if (font->GetWeight() == weight)
score += 1;
if (score > best_score)
{
best_match = font.get();
best_score = score;
}
}
return best_match;
}
} // namespace FontProviderBitmap
FontFaceBitmap::FontFaceBitmap(String family, FontStyle style, FontWeight weight, FontMetrics metrics, Texture texture, Vector2f texture_dimensions,
FontGlyphs&& glyphs, FontKerning&& kerning) :
family(family),
style(style), weight(weight), metrics(metrics), texture(texture), texture_dimensions(texture_dimensions), glyphs(std::move(glyphs)),
kerning(std::move(kerning))
{}
int FontFaceBitmap::GetStringWidth(const String& string, Character previous_character)
{
int width = 0;
for (auto it_char = Rml::StringIteratorU8(string); it_char; ++it_char)
{
Character character = *it_char;
auto it_glyph = glyphs.find(character);
if (it_glyph == glyphs.end())
continue;
const BitmapGlyph& glyph = it_glyph->second;
int kerning = GetKerning(previous_character, character);
width += glyph.advance + kerning;
previous_character = character;
}
return width;
}
int FontFaceBitmap::GenerateString(const String& string, const Vector2f& string_position, const Colourb& colour, GeometryList& geometry_list)
{
int width = 0;
geometry_list.resize(1);
Rml::Geometry& geometry = geometry_list[0];
geometry.SetTexture(&texture);
auto& vertices = geometry.GetVertices();
auto& indices = geometry.GetIndices();
vertices.reserve(string.size() * 4);
indices.reserve(string.size() * 6);
Vector2f position = string_position.Round();
Character previous_character = Character::Null;
for (auto it_char = Rml::StringIteratorU8(string); it_char; ++it_char)
{
Character character = *it_char;
auto it_glyph = glyphs.find(character);
if (it_glyph == glyphs.end())
continue;
int kerning = GetKerning(previous_character, character);
width += kerning;
position.x += kerning;
const BitmapGlyph& glyph = it_glyph->second;
// Generate the geometry for the character.
vertices.resize(vertices.size() + 4);
indices.resize(indices.size() + 6);
Vector2f uv_top_left = glyph.position / texture_dimensions;
Vector2f uv_bottom_right = (glyph.position + glyph.dimension) / texture_dimensions;
Rml::GeometryUtilities::GenerateQuad(&vertices[0] + (vertices.size() - 4), &indices[0] + (indices.size() - 6),
Vector2f(position + glyph.offset).Round(), glyph.dimension, colour, uv_top_left, uv_bottom_right, (int)vertices.size() - 4);
width += glyph.advance;
position.x += glyph.advance;
previous_character = character;
}
return width;
}
int FontFaceBitmap::GetKerning(Character left, Character right) const
{
uint64_t key = (((uint64_t)left << 32) | (uint64_t)right);
auto it = kerning.find(key);
if (it != kerning.end())
return it->second;
return 0;
}
FontParserBitmap::~FontParserBitmap() {}
void FontParserBitmap::HandleElementStart(const String& name, const Rml::XMLAttributes& attributes)
{
if (name == "info")
{
family = Rml::StringUtilities::ToLower(Get(attributes, "face", String()));
metrics.size = Get(attributes, "size", 0);
style = Get(attributes, "italic", 0) == 1 ? FontStyle::Italic : FontStyle::Normal;
weight = Get(attributes, "bold", 0) == 1 ? FontWeight::Bold : FontWeight::Normal;
}
else if (name == "common")
{
metrics.line_spacing = Get(attributes, "lineHeight", 0.f);
metrics.ascent = Get(attributes, "base", 0.f);
metrics.descent = metrics.line_spacing - metrics.ascent;
texture_dimensions.x = Get(attributes, "scaleW", 0.f);
texture_dimensions.y = Get(attributes, "scaleH", 0.f);
}
else if (name == "page")
{
int id = Get(attributes, "id", -1);
if (id != 0)
{
Rml::Log::Message(Rml::Log::LT_WARNING, "Only single font textures are supported in Bitmap Font Engine");
return;
}
texture_name = Get(attributes, "file", String());
}
else if (name == "char")
{
Character character = (Character)Get(attributes, "id", 0);
if (character == Character::Null)
return;
BitmapGlyph& glyph = glyphs[character];
glyph.offset.x = Get(attributes, "xoffset", 0.f);
glyph.offset.y = Get(attributes, "yoffset", 0.f) - metrics.ascent; // Shift y-origin from top to baseline
glyph.advance = Get(attributes, "xadvance", 0);
glyph.position.x = Get(attributes, "x", 0.f);
glyph.position.y = Get(attributes, "y", 0.f);
glyph.dimension.x = Get(attributes, "width", 0.f);
glyph.dimension.y = Get(attributes, "height", 0.f);
if (character == (Character)'x')
metrics.x_height = glyph.dimension.y;
}
else if (name == "kerning")
{
uint64_t first = (uint64_t)Get(attributes, "first", 0);
uint64_t second = (uint64_t)Get(attributes, "second", 0);
int amount = Get(attributes, "amount", 0);
if (first != 0 && second != 0 && amount != 0)
{
uint64_t key = ((first << 32) | second);
kerning[key] = amount;
}
}
}
void FontParserBitmap::HandleElementEnd(const String& /*name*/) {}
void FontParserBitmap::HandleData(const String& /*data*/, Rml::XMLDataType /*type*/) {}