godot/modules/text_server_fb/text_server_fb.cpp
Rémi Verschelde 25b2f1780a
Style: Harmonize header includes in modules
This applies our existing style guide, and adds a new rule to that style
guide for modular components such as platform ports and modules:

Includes from the platform port or module ("local" includes) should be listed
first in their own block using relative paths, before Godot's "core" includes
which use "absolute" (project folder relative) paths, and finally thirdparty
includes.

Includes in `#ifdef`s come after their relevant section, i.e. the overall
structure is:

- Local includes
  * Conditional local includes
- Core includes
  * Conditional core includes
- Thirdparty includes
  * Conditional thirdparty includes
2023-06-15 14:35:45 +02:00

4178 lines
140 KiB
C++

/**************************************************************************/
/* text_server_fb.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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 "text_server_fb.h"
#ifdef GDEXTENSION
// Headers for building as GDExtension plug-in.
#include <godot_cpp/classes/file_access.hpp>
#include <godot_cpp/classes/os.hpp>
#include <godot_cpp/classes/project_settings.hpp>
#include <godot_cpp/classes/rendering_server.hpp>
#include <godot_cpp/classes/translation_server.hpp>
#include <godot_cpp/core/error_macros.hpp>
using namespace godot;
#define GLOBAL_GET(m_var) ProjectSettings::get_singleton()->get_setting_with_override(m_var)
#else
// Headers for building as built-in module.
#include "core/config/project_settings.h"
#include "core/error/error_macros.h"
#include "core/string/print_string.h"
#include "core/string/translation.h"
#include "modules/modules_enabled.gen.h" // For freetype, msdfgen, svg.
#endif
// Thirdparty headers.
#ifdef MODULE_MSDFGEN_ENABLED
#include <core/ShapeDistanceFinder.h>
#include <core/contour-combiners.h>
#include <core/edge-selectors.h>
#include <msdfgen.h>
#endif
#ifdef MODULE_SVG_ENABLED
#ifdef MODULE_FREETYPE_ENABLED
#include "thorvg_svg_in_ot.h"
#endif
#endif
/*************************************************************************/
#define OT_TAG(c1, c2, c3, c4) ((int32_t)((((uint32_t)(c1)&0xff) << 24) | (((uint32_t)(c2)&0xff) << 16) | (((uint32_t)(c3)&0xff) << 8) | ((uint32_t)(c4)&0xff)))
bool TextServerFallback::_has_feature(Feature p_feature) const {
switch (p_feature) {
case FEATURE_SIMPLE_LAYOUT:
case FEATURE_FONT_BITMAP:
#ifdef MODULE_FREETYPE_ENABLED
case FEATURE_FONT_DYNAMIC:
#endif
#ifdef MODULE_MSDFGEN_ENABLED
case FEATURE_FONT_MSDF:
#endif
return true;
default: {
}
}
return false;
}
String TextServerFallback::_get_name() const {
#ifdef GDEXTENSION
return "Fallback (GDExtension)";
#else
return "Fallback (Built-in)";
#endif
}
int64_t TextServerFallback::_get_features() const {
int64_t interface_features = FEATURE_SIMPLE_LAYOUT | FEATURE_FONT_BITMAP;
#ifdef MODULE_FREETYPE_ENABLED
interface_features |= FEATURE_FONT_DYNAMIC;
#endif
#ifdef MODULE_MSDFGEN_ENABLED
interface_features |= FEATURE_FONT_MSDF;
#endif
return interface_features;
}
void TextServerFallback::_free_rid(const RID &p_rid) {
_THREAD_SAFE_METHOD_
if (font_owner.owns(p_rid)) {
MutexLock ftlock(ft_mutex);
FontFallback *fd = font_owner.get_or_null(p_rid);
{
MutexLock lock(fd->mutex);
font_owner.free(p_rid);
}
memdelete(fd);
} else if (shaped_owner.owns(p_rid)) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_rid);
{
MutexLock lock(sd->mutex);
shaped_owner.free(p_rid);
}
memdelete(sd);
}
}
bool TextServerFallback::_has(const RID &p_rid) {
_THREAD_SAFE_METHOD_
return font_owner.owns(p_rid) || shaped_owner.owns(p_rid);
}
String TextServerFallback::_get_support_data_filename() const {
return "";
};
String TextServerFallback::_get_support_data_info() const {
return "Not supported";
};
bool TextServerFallback::_load_support_data(const String &p_filename) {
return false; // No extra data used.
}
bool TextServerFallback::_save_support_data(const String &p_filename) const {
return false; // No extra data used.
}
bool TextServerFallback::_is_locale_right_to_left(const String &p_locale) const {
return false; // No RTL support.
}
_FORCE_INLINE_ void TextServerFallback::_insert_feature(const StringName &p_name, int32_t p_tag) {
feature_sets.insert(p_name, p_tag);
feature_sets_inv.insert(p_tag, p_name);
}
void TextServerFallback::_insert_feature_sets() {
// Registered OpenType variation tag.
_insert_feature("italic", OT_TAG('i', 't', 'a', 'l'));
_insert_feature("optical_size", OT_TAG('o', 'p', 's', 'z'));
_insert_feature("slant", OT_TAG('s', 'l', 'n', 't'));
_insert_feature("width", OT_TAG('w', 'd', 't', 'h'));
_insert_feature("weight", OT_TAG('w', 'g', 'h', 't'));
}
_FORCE_INLINE_ int32_t ot_tag_from_string(const char *p_str, int p_len) {
char tag[4];
uint32_t i;
if (!p_str || !p_len || !*p_str) {
return OT_TAG(0, 0, 0, 0);
}
if (p_len < 0 || p_len > 4) {
p_len = 4;
}
for (i = 0; i < (uint32_t)p_len && p_str[i]; i++) {
tag[i] = p_str[i];
}
for (; i < 4; i++) {
tag[i] = ' ';
}
return OT_TAG(tag[0], tag[1], tag[2], tag[3]);
}
int64_t TextServerFallback::_name_to_tag(const String &p_name) const {
if (feature_sets.has(p_name)) {
return feature_sets[p_name];
}
// No readable name, use tag string.
return ot_tag_from_string(p_name.replace("custom_", "").ascii().get_data(), -1);
}
_FORCE_INLINE_ void ot_tag_to_string(int32_t p_tag, char *p_buf) {
p_buf[0] = (char)(uint8_t)(p_tag >> 24);
p_buf[1] = (char)(uint8_t)(p_tag >> 16);
p_buf[2] = (char)(uint8_t)(p_tag >> 8);
p_buf[3] = (char)(uint8_t)(p_tag >> 0);
}
String TextServerFallback::_tag_to_name(int64_t p_tag) const {
if (feature_sets_inv.has(p_tag)) {
return feature_sets_inv[p_tag];
}
// No readable name, use tag string.
char name[5];
memset(name, 0, 5);
ot_tag_to_string(p_tag, name);
return String("custom_") + String(name);
}
/*************************************************************************/
/* Font Glyph Rendering */
/*************************************************************************/
_FORCE_INLINE_ TextServerFallback::FontTexturePosition TextServerFallback::find_texture_pos_for_glyph(FontForSizeFallback *p_data, int p_color_size, Image::Format p_image_format, int p_width, int p_height, bool p_msdf) const {
FontTexturePosition ret;
int mw = p_width;
int mh = p_height;
ShelfPackTexture *ct = p_data->textures.ptrw();
for (int32_t i = 0; i < p_data->textures.size(); i++) {
if (p_image_format != ct[i].format) {
continue;
}
if (mw > ct[i].texture_w || mh > ct[i].texture_h) { // Too big for this texture.
continue;
}
ret = ct[i].pack_rect(i, mh, mw);
if (ret.index != -1) {
break;
}
}
if (ret.index == -1) {
// Could not find texture to fit, create one.
int texsize = MAX(p_data->size.x * p_data->oversampling * 8, 256);
texsize = next_power_of_2(texsize);
if (p_msdf) {
texsize = MIN(texsize, 2048);
} else {
texsize = MIN(texsize, 1024);
}
if (mw > texsize) { // Special case, adapt to it?
texsize = next_power_of_2(mw);
}
if (mh > texsize) { // Special case, adapt to it?
texsize = next_power_of_2(mh);
}
ShelfPackTexture tex = ShelfPackTexture(texsize, texsize);
tex.format = p_image_format;
tex.imgdata.resize(texsize * texsize * p_color_size);
{
// Zero texture.
uint8_t *w = tex.imgdata.ptrw();
ERR_FAIL_COND_V(texsize * texsize * p_color_size > tex.imgdata.size(), ret);
// Initialize the texture to all-white pixels to prevent artifacts when the
// font is displayed at a non-default scale with filtering enabled.
if (p_color_size == 2) {
for (int i = 0; i < texsize * texsize * p_color_size; i += 2) { // FORMAT_LA8, BW font.
w[i + 0] = 255;
w[i + 1] = 0;
}
} else if (p_color_size == 4) {
for (int i = 0; i < texsize * texsize * p_color_size; i += 4) { // FORMAT_RGBA8, Color font, Multichannel(+True) SDF.
w[i + 0] = 255;
w[i + 1] = 255;
w[i + 2] = 255;
w[i + 3] = 0;
}
} else {
ERR_FAIL_V(ret);
}
}
p_data->textures.push_back(tex);
int32_t idx = p_data->textures.size() - 1;
ret = p_data->textures.write[idx].pack_rect(idx, mh, mw);
}
return ret;
}
#ifdef MODULE_MSDFGEN_ENABLED
struct MSContext {
msdfgen::Point2 position;
msdfgen::Shape *shape = nullptr;
msdfgen::Contour *contour = nullptr;
};
class DistancePixelConversion {
double invRange;
public:
_FORCE_INLINE_ explicit DistancePixelConversion(double range) :
invRange(1 / range) {}
_FORCE_INLINE_ void operator()(float *pixels, const msdfgen::MultiAndTrueDistance &distance) const {
pixels[0] = float(invRange * distance.r + .5);
pixels[1] = float(invRange * distance.g + .5);
pixels[2] = float(invRange * distance.b + .5);
pixels[3] = float(invRange * distance.a + .5);
}
};
struct MSDFThreadData {
msdfgen::Bitmap<float, 4> *output;
msdfgen::Shape *shape;
msdfgen::Projection *projection;
DistancePixelConversion *distancePixelConversion;
};
static msdfgen::Point2 ft_point2(const FT_Vector &vector) {
return msdfgen::Point2(vector.x / 60.0f, vector.y / 60.0f);
}
static int ft_move_to(const FT_Vector *to, void *user) {
MSContext *context = static_cast<MSContext *>(user);
if (!(context->contour && context->contour->edges.empty())) {
context->contour = &context->shape->addContour();
}
context->position = ft_point2(*to);
return 0;
}
static int ft_line_to(const FT_Vector *to, void *user) {
MSContext *context = static_cast<MSContext *>(user);
msdfgen::Point2 endpoint = ft_point2(*to);
if (endpoint != context->position) {
context->contour->addEdge(new msdfgen::LinearSegment(context->position, endpoint));
context->position = endpoint;
}
return 0;
}
static int ft_conic_to(const FT_Vector *control, const FT_Vector *to, void *user) {
MSContext *context = static_cast<MSContext *>(user);
context->contour->addEdge(new msdfgen::QuadraticSegment(context->position, ft_point2(*control), ft_point2(*to)));
context->position = ft_point2(*to);
return 0;
}
static int ft_cubic_to(const FT_Vector *control1, const FT_Vector *control2, const FT_Vector *to, void *user) {
MSContext *context = static_cast<MSContext *>(user);
context->contour->addEdge(new msdfgen::CubicSegment(context->position, ft_point2(*control1), ft_point2(*control2), ft_point2(*to)));
context->position = ft_point2(*to);
return 0;
}
void TextServerFallback::_generateMTSDF_threaded(void *p_td, uint32_t p_y) {
MSDFThreadData *td = static_cast<MSDFThreadData *>(p_td);
msdfgen::ShapeDistanceFinder<msdfgen::OverlappingContourCombiner<msdfgen::MultiAndTrueDistanceSelector>> distanceFinder(*td->shape);
int row = td->shape->inverseYAxis ? td->output->height() - p_y - 1 : p_y;
for (int col = 0; col < td->output->width(); ++col) {
int x = (p_y % 2) ? td->output->width() - col - 1 : col;
msdfgen::Point2 p = td->projection->unproject(msdfgen::Point2(x + .5, p_y + .5));
msdfgen::MultiAndTrueDistance distance = distanceFinder.distance(p);
td->distancePixelConversion->operator()(td->output->operator()(x, row), distance);
}
}
_FORCE_INLINE_ TextServerFallback::FontGlyph TextServerFallback::rasterize_msdf(FontFallback *p_font_data, FontForSizeFallback *p_data, int p_pixel_range, int p_rect_margin, FT_Outline *outline, const Vector2 &advance) const {
msdfgen::Shape shape;
shape.contours.clear();
shape.inverseYAxis = false;
MSContext context = {};
context.shape = &shape;
FT_Outline_Funcs ft_functions;
ft_functions.move_to = &ft_move_to;
ft_functions.line_to = &ft_line_to;
ft_functions.conic_to = &ft_conic_to;
ft_functions.cubic_to = &ft_cubic_to;
ft_functions.shift = 0;
ft_functions.delta = 0;
int error = FT_Outline_Decompose(outline, &ft_functions, &context);
ERR_FAIL_COND_V_MSG(error, FontGlyph(), "FreeType: Outline decomposition error: '" + String(FT_Error_String(error)) + "'.");
if (!shape.contours.empty() && shape.contours.back().edges.empty()) {
shape.contours.pop_back();
}
if (FT_Outline_Get_Orientation(outline) == 1) {
for (int i = 0; i < (int)shape.contours.size(); ++i) {
shape.contours[i].reverse();
}
}
shape.inverseYAxis = true;
shape.normalize();
msdfgen::Shape::Bounds bounds = shape.getBounds(p_pixel_range);
FontGlyph chr;
chr.found = true;
chr.advance = advance;
if (shape.validate() && shape.contours.size() > 0) {
int w = (bounds.r - bounds.l);
int h = (bounds.t - bounds.b);
int mw = w + p_rect_margin * 4;
int mh = h + p_rect_margin * 4;
ERR_FAIL_COND_V(mw > 4096, FontGlyph());
ERR_FAIL_COND_V(mh > 4096, FontGlyph());
FontTexturePosition tex_pos = find_texture_pos_for_glyph(p_data, 4, Image::FORMAT_RGBA8, mw, mh, true);
ERR_FAIL_COND_V(tex_pos.index < 0, FontGlyph());
ShelfPackTexture &tex = p_data->textures.write[tex_pos.index];
edgeColoringSimple(shape, 3.0); // Max. angle.
msdfgen::Bitmap<float, 4> image(w, h); // Texture size.
DistancePixelConversion distancePixelConversion(p_pixel_range);
msdfgen::Projection projection(msdfgen::Vector2(1.0, 1.0), msdfgen::Vector2(-bounds.l, -bounds.b));
msdfgen::MSDFGeneratorConfig config(true, msdfgen::ErrorCorrectionConfig());
MSDFThreadData td;
td.output = &image;
td.shape = &shape;
td.projection = &projection;
td.distancePixelConversion = &distancePixelConversion;
WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_native_group_task(&TextServerFallback::_generateMTSDF_threaded, &td, h, -1, true, String("TextServerFBRenderMSDF"));
WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task);
msdfgen::msdfErrorCorrection(image, shape, projection, p_pixel_range, config);
{
uint8_t *wr = tex.imgdata.ptrw();
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
int ofs = ((i + tex_pos.y + p_rect_margin * 2) * tex.texture_w + j + tex_pos.x + p_rect_margin * 2) * 4;
ERR_FAIL_COND_V(ofs >= tex.imgdata.size(), FontGlyph());
wr[ofs + 0] = (uint8_t)(CLAMP(image(j, i)[0] * 256.f, 0.f, 255.f));
wr[ofs + 1] = (uint8_t)(CLAMP(image(j, i)[1] * 256.f, 0.f, 255.f));
wr[ofs + 2] = (uint8_t)(CLAMP(image(j, i)[2] * 256.f, 0.f, 255.f));
wr[ofs + 3] = (uint8_t)(CLAMP(image(j, i)[3] * 256.f, 0.f, 255.f));
}
}
}
tex.dirty = true;
chr.texture_idx = tex_pos.index;
chr.uv_rect = Rect2(tex_pos.x + p_rect_margin, tex_pos.y + p_rect_margin, w + p_rect_margin * 2, h + p_rect_margin * 2);
chr.rect.position = Vector2(bounds.l - p_rect_margin, -bounds.t - p_rect_margin);
chr.rect.size = chr.uv_rect.size;
}
return chr;
}
#endif
#ifdef MODULE_FREETYPE_ENABLED
_FORCE_INLINE_ TextServerFallback::FontGlyph TextServerFallback::rasterize_bitmap(FontForSizeFallback *p_data, int p_rect_margin, FT_Bitmap bitmap, int yofs, int xofs, const Vector2 &advance, bool p_bgra) const {
int w = bitmap.width;
int h = bitmap.rows;
int color_size = 2;
switch (bitmap.pixel_mode) {
case FT_PIXEL_MODE_MONO:
case FT_PIXEL_MODE_GRAY: {
color_size = 2;
} break;
case FT_PIXEL_MODE_BGRA: {
color_size = 4;
} break;
case FT_PIXEL_MODE_LCD: {
color_size = 4;
w /= 3;
} break;
case FT_PIXEL_MODE_LCD_V: {
color_size = 4;
h /= 3;
} break;
}
int mw = w + p_rect_margin * 4;
int mh = h + p_rect_margin * 4;
ERR_FAIL_COND_V(mw > 4096, FontGlyph());
ERR_FAIL_COND_V(mh > 4096, FontGlyph());
Image::Format require_format = color_size == 4 ? Image::FORMAT_RGBA8 : Image::FORMAT_LA8;
FontTexturePosition tex_pos = find_texture_pos_for_glyph(p_data, color_size, require_format, mw, mh, false);
ERR_FAIL_COND_V(tex_pos.index < 0, FontGlyph());
// Fit character in char texture.
ShelfPackTexture &tex = p_data->textures.write[tex_pos.index];
{
uint8_t *wr = tex.imgdata.ptrw();
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
int ofs = ((i + tex_pos.y + p_rect_margin * 2) * tex.texture_w + j + tex_pos.x + p_rect_margin * 2) * color_size;
ERR_FAIL_COND_V(ofs >= tex.imgdata.size(), FontGlyph());
switch (bitmap.pixel_mode) {
case FT_PIXEL_MODE_MONO: {
int byte = i * bitmap.pitch + (j >> 3);
int bit = 1 << (7 - (j % 8));
wr[ofs + 0] = 255; // grayscale as 1
wr[ofs + 1] = (bitmap.buffer[byte] & bit) ? 255 : 0;
} break;
case FT_PIXEL_MODE_GRAY:
wr[ofs + 0] = 255; // grayscale as 1
wr[ofs + 1] = bitmap.buffer[i * bitmap.pitch + j];
break;
case FT_PIXEL_MODE_BGRA: {
int ofs_color = i * bitmap.pitch + (j << 2);
wr[ofs + 2] = bitmap.buffer[ofs_color + 0];
wr[ofs + 1] = bitmap.buffer[ofs_color + 1];
wr[ofs + 0] = bitmap.buffer[ofs_color + 2];
wr[ofs + 3] = bitmap.buffer[ofs_color + 3];
} break;
case FT_PIXEL_MODE_LCD: {
int ofs_color = i * bitmap.pitch + (j * 3);
if (p_bgra) {
wr[ofs + 0] = bitmap.buffer[ofs_color + 0];
wr[ofs + 1] = bitmap.buffer[ofs_color + 1];
wr[ofs + 2] = bitmap.buffer[ofs_color + 2];
wr[ofs + 3] = 255;
} else {
wr[ofs + 0] = bitmap.buffer[ofs_color + 2];
wr[ofs + 1] = bitmap.buffer[ofs_color + 1];
wr[ofs + 2] = bitmap.buffer[ofs_color + 0];
wr[ofs + 3] = 255;
}
} break;
case FT_PIXEL_MODE_LCD_V: {
int ofs_color = i * bitmap.pitch * 3 + j;
if (p_bgra) {
wr[ofs + 0] = bitmap.buffer[ofs_color + bitmap.pitch * 2];
wr[ofs + 1] = bitmap.buffer[ofs_color + bitmap.pitch];
wr[ofs + 2] = bitmap.buffer[ofs_color + 0];
wr[ofs + 3] = 255;
} else {
wr[ofs + 0] = bitmap.buffer[ofs_color + 0];
wr[ofs + 1] = bitmap.buffer[ofs_color + bitmap.pitch];
wr[ofs + 2] = bitmap.buffer[ofs_color + bitmap.pitch * 2];
wr[ofs + 3] = 255;
}
} break;
default:
ERR_FAIL_V_MSG(FontGlyph(), "Font uses unsupported pixel format: " + String::num_int64(bitmap.pixel_mode) + ".");
break;
}
}
}
}
tex.dirty = true;
FontGlyph chr;
chr.advance = advance * p_data->scale / p_data->oversampling;
chr.texture_idx = tex_pos.index;
chr.found = true;
chr.uv_rect = Rect2(tex_pos.x + p_rect_margin, tex_pos.y + p_rect_margin, w + p_rect_margin * 2, h + p_rect_margin * 2);
chr.rect.position = Vector2(xofs - p_rect_margin, -yofs - p_rect_margin) * p_data->scale / p_data->oversampling;
chr.rect.size = chr.uv_rect.size * p_data->scale / p_data->oversampling;
return chr;
}
#endif
/*************************************************************************/
/* Font Cache */
/*************************************************************************/
_FORCE_INLINE_ bool TextServerFallback::_ensure_glyph(FontFallback *p_font_data, const Vector2i &p_size, int32_t p_glyph) const {
ERR_FAIL_COND_V(!_ensure_cache_for_size(p_font_data, p_size), false);
int32_t glyph_index = p_glyph & 0xffffff; // Remove subpixel shifts.
FontForSizeFallback *fd = p_font_data->cache[p_size];
if (fd->glyph_map.has(p_glyph)) {
return fd->glyph_map[p_glyph].found;
}
if (glyph_index == 0) { // Non graphical or invalid glyph, do not render.
fd->glyph_map[p_glyph] = FontGlyph();
return true;
}
#ifdef MODULE_FREETYPE_ENABLED
FontGlyph gl;
if (fd->face) {
FT_Int32 flags = FT_LOAD_DEFAULT;
bool outline = p_size.y > 0;
switch (p_font_data->hinting) {
case TextServer::HINTING_NONE:
flags |= FT_LOAD_NO_HINTING;
break;
case TextServer::HINTING_LIGHT:
flags |= FT_LOAD_TARGET_LIGHT;
break;
default:
flags |= FT_LOAD_TARGET_NORMAL;
break;
}
if (p_font_data->force_autohinter) {
flags |= FT_LOAD_FORCE_AUTOHINT;
}
if (outline) {
flags |= FT_LOAD_NO_BITMAP;
} else if (FT_HAS_COLOR(fd->face)) {
flags |= FT_LOAD_COLOR;
}
glyph_index = FT_Get_Char_Index(fd->face, glyph_index);
FT_Fixed v, h;
FT_Get_Advance(fd->face, glyph_index, flags, &h);
FT_Get_Advance(fd->face, glyph_index, flags | FT_LOAD_VERTICAL_LAYOUT, &v);
int error = FT_Load_Glyph(fd->face, glyph_index, flags);
if (error) {
fd->glyph_map[p_glyph] = FontGlyph();
return false;
}
if (!p_font_data->msdf) {
if ((p_font_data->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (p_font_data->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && p_size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
FT_Pos xshift = (int)((p_glyph >> 27) & 3) << 4;
FT_Outline_Translate(&fd->face->glyph->outline, xshift, 0);
} else if ((p_font_data->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (p_font_data->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && p_size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
FT_Pos xshift = (int)((p_glyph >> 27) & 3) << 5;
FT_Outline_Translate(&fd->face->glyph->outline, xshift, 0);
}
}
if (p_font_data->embolden != 0.f) {
FT_Pos strength = p_font_data->embolden * p_size.x * 4; // 26.6 fractional units (1 / 64).
FT_Outline_Embolden(&fd->face->glyph->outline, strength);
}
if (p_font_data->transform != Transform2D()) {
FT_Matrix mat = { FT_Fixed(p_font_data->transform[0][0] * 65536), FT_Fixed(p_font_data->transform[0][1] * 65536), FT_Fixed(p_font_data->transform[1][0] * 65536), FT_Fixed(p_font_data->transform[1][1] * 65536) }; // 16.16 fractional units (1 / 65536).
FT_Outline_Transform(&fd->face->glyph->outline, &mat);
}
FT_Render_Mode aa_mode = FT_RENDER_MODE_NORMAL;
bool bgra = false;
switch (p_font_data->antialiasing) {
case FONT_ANTIALIASING_NONE: {
aa_mode = FT_RENDER_MODE_MONO;
} break;
case FONT_ANTIALIASING_GRAY: {
aa_mode = FT_RENDER_MODE_NORMAL;
} break;
case FONT_ANTIALIASING_LCD: {
int aa_layout = (int)((p_glyph >> 24) & 7);
switch (aa_layout) {
case FONT_LCD_SUBPIXEL_LAYOUT_HRGB: {
aa_mode = FT_RENDER_MODE_LCD;
bgra = false;
} break;
case FONT_LCD_SUBPIXEL_LAYOUT_HBGR: {
aa_mode = FT_RENDER_MODE_LCD;
bgra = true;
} break;
case FONT_LCD_SUBPIXEL_LAYOUT_VRGB: {
aa_mode = FT_RENDER_MODE_LCD_V;
bgra = false;
} break;
case FONT_LCD_SUBPIXEL_LAYOUT_VBGR: {
aa_mode = FT_RENDER_MODE_LCD_V;
bgra = true;
} break;
default: {
aa_mode = FT_RENDER_MODE_NORMAL;
} break;
}
} break;
}
if (!outline) {
if (!p_font_data->msdf) {
error = FT_Render_Glyph(fd->face->glyph, aa_mode);
}
FT_GlyphSlot slot = fd->face->glyph;
if (!error) {
if (p_font_data->msdf) {
#ifdef MODULE_MSDFGEN_ENABLED
gl = rasterize_msdf(p_font_data, fd, p_font_data->msdf_range, rect_range, &slot->outline, Vector2((h + (1 << 9)) >> 10, (v + (1 << 9)) >> 10) / 64.0);
#else
fd->glyph_map[p_glyph] = FontGlyph();
ERR_FAIL_V_MSG(false, "Compiled without MSDFGEN support!");
#endif
} else {
gl = rasterize_bitmap(fd, rect_range, slot->bitmap, slot->bitmap_top, slot->bitmap_left, Vector2((h + (1 << 9)) >> 10, (v + (1 << 9)) >> 10) / 64.0, bgra);
}
}
} else {
FT_Stroker stroker;
if (FT_Stroker_New(ft_library, &stroker) != 0) {
fd->glyph_map[p_glyph] = FontGlyph();
ERR_FAIL_V_MSG(false, "FreeType: Failed to load glyph stroker.");
}
FT_Stroker_Set(stroker, (int)(fd->size.y * fd->oversampling * 16.0), FT_STROKER_LINECAP_BUTT, FT_STROKER_LINEJOIN_ROUND, 0);
FT_Glyph glyph;
FT_BitmapGlyph glyph_bitmap;
if (FT_Get_Glyph(fd->face->glyph, &glyph) != 0) {
goto cleanup_stroker;
}
if (FT_Glyph_Stroke(&glyph, stroker, 1) != 0) {
goto cleanup_glyph;
}
if (FT_Glyph_To_Bitmap(&glyph, aa_mode, nullptr, 1) != 0) {
goto cleanup_glyph;
}
glyph_bitmap = (FT_BitmapGlyph)glyph;
gl = rasterize_bitmap(fd, rect_range, glyph_bitmap->bitmap, glyph_bitmap->top, glyph_bitmap->left, Vector2(), bgra);
cleanup_glyph:
FT_Done_Glyph(glyph);
cleanup_stroker:
FT_Stroker_Done(stroker);
}
fd->glyph_map[p_glyph] = gl;
return gl.found;
}
#endif
fd->glyph_map[p_glyph] = FontGlyph();
return false;
}
_FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_font_data, const Vector2i &p_size) const {
ERR_FAIL_COND_V(p_size.x <= 0, false);
if (p_font_data->cache.has(p_size)) {
return true;
}
FontForSizeFallback *fd = memnew(FontForSizeFallback);
fd->size = p_size;
if (p_font_data->data_ptr && (p_font_data->data_size > 0)) {
// Init dynamic font.
#ifdef MODULE_FREETYPE_ENABLED
int error = 0;
{
MutexLock ftlock(ft_mutex);
if (!ft_library) {
error = FT_Init_FreeType(&ft_library);
if (error != 0) {
memdelete(fd);
ERR_FAIL_V_MSG(false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'.");
}
#ifdef MODULE_SVG_ENABLED
FT_Property_Set(ft_library, "ot-svg", "svg-hooks", get_tvg_svg_in_ot_hooks());
#endif
}
memset(&fd->stream, 0, sizeof(FT_StreamRec));
fd->stream.base = (unsigned char *)p_font_data->data_ptr;
fd->stream.size = p_font_data->data_size;
fd->stream.pos = 0;
FT_Open_Args fargs;
memset(&fargs, 0, sizeof(FT_Open_Args));
fargs.memory_base = (unsigned char *)p_font_data->data_ptr;
fargs.memory_size = p_font_data->data_size;
fargs.flags = FT_OPEN_MEMORY;
fargs.stream = &fd->stream;
int max_index = 0;
FT_Face tmp_face = nullptr;
error = FT_Open_Face(ft_library, &fargs, -1, &tmp_face);
if (tmp_face && error == 0) {
max_index = tmp_face->num_faces - 1;
}
if (tmp_face) {
FT_Done_Face(tmp_face);
}
error = FT_Open_Face(ft_library, &fargs, CLAMP(p_font_data->face_index, 0, max_index), &fd->face);
if (error) {
FT_Done_Face(fd->face);
fd->face = nullptr;
memdelete(fd);
ERR_FAIL_V_MSG(false, "FreeType: Error loading font: '" + String(FT_Error_String(error)) + "'.");
}
}
if (p_font_data->msdf) {
fd->oversampling = 1.0;
fd->size.x = p_font_data->msdf_source_size;
} else if (p_font_data->oversampling <= 0.0) {
fd->oversampling = _font_get_global_oversampling();
} else {
fd->oversampling = p_font_data->oversampling;
}
if (FT_HAS_COLOR(fd->face) && fd->face->num_fixed_sizes > 0) {
int best_match = 0;
int diff = ABS(fd->size.x - ((int64_t)fd->face->available_sizes[0].width));
fd->scale = double(fd->size.x * fd->oversampling) / fd->face->available_sizes[0].width;
for (int i = 1; i < fd->face->num_fixed_sizes; i++) {
int ndiff = ABS(fd->size.x - ((int64_t)fd->face->available_sizes[i].width));
if (ndiff < diff) {
best_match = i;
diff = ndiff;
fd->scale = double(fd->size.x * fd->oversampling) / fd->face->available_sizes[i].width;
}
}
FT_Select_Size(fd->face, best_match);
} else {
FT_Set_Pixel_Sizes(fd->face, 0, Math::round(fd->size.x * fd->oversampling));
if (fd->face->size->metrics.y_ppem != 0) {
fd->scale = ((double)fd->size.x * fd->oversampling) / (double)fd->face->size->metrics.y_ppem;
}
}
fd->ascent = (fd->face->size->metrics.ascender / 64.0) / fd->oversampling * fd->scale;
fd->descent = (-fd->face->size->metrics.descender / 64.0) / fd->oversampling * fd->scale;
fd->underline_position = (-FT_MulFix(fd->face->underline_position, fd->face->size->metrics.y_scale) / 64.0) / fd->oversampling * fd->scale;
fd->underline_thickness = (FT_MulFix(fd->face->underline_thickness, fd->face->size->metrics.y_scale) / 64.0) / fd->oversampling * fd->scale;
if (!p_font_data->face_init) {
// Get style flags and name.
if (fd->face->family_name != nullptr) {
p_font_data->font_name = String::utf8((const char *)fd->face->family_name);
}
if (fd->face->style_name != nullptr) {
p_font_data->style_name = String::utf8((const char *)fd->face->style_name);
}
p_font_data->weight = _font_get_weight_by_name(p_font_data->style_name.to_lower());
p_font_data->stretch = _font_get_stretch_by_name(p_font_data->style_name.to_lower());
p_font_data->style_flags = 0;
if ((fd->face->style_flags & FT_STYLE_FLAG_BOLD) || p_font_data->weight >= 700) {
p_font_data->style_flags.set_flag(FONT_BOLD);
}
if ((fd->face->style_flags & FT_STYLE_FLAG_ITALIC) || _is_ital_style(p_font_data->style_name.to_lower())) {
p_font_data->style_flags.set_flag(FONT_ITALIC);
}
if (fd->face->face_flags & FT_FACE_FLAG_FIXED_WIDTH) {
p_font_data->style_flags.set_flag(FONT_FIXED_WIDTH);
}
// Read OpenType variations.
p_font_data->supported_varaitions.clear();
if (fd->face->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS) {
FT_MM_Var *amaster;
FT_Get_MM_Var(fd->face, &amaster);
for (FT_UInt i = 0; i < amaster->num_axis; i++) {
p_font_data->supported_varaitions[(int32_t)amaster->axis[i].tag] = Vector3i(amaster->axis[i].minimum / 65536, amaster->axis[i].maximum / 65536, amaster->axis[i].def / 65536);
}
FT_Done_MM_Var(ft_library, amaster);
}
p_font_data->face_init = true;
}
// Write variations.
if (fd->face->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS) {
FT_MM_Var *amaster;
FT_Get_MM_Var(fd->face, &amaster);
Vector<FT_Fixed> coords;
coords.resize(amaster->num_axis);
FT_Get_Var_Design_Coordinates(fd->face, coords.size(), coords.ptrw());
for (FT_UInt i = 0; i < amaster->num_axis; i++) {
// Reset to default.
int32_t var_tag = amaster->axis[i].tag;
double var_value = (double)amaster->axis[i].def / 65536.0;
coords.write[i] = amaster->axis[i].def;
if (p_font_data->variation_coordinates.has(var_tag)) {
var_value = p_font_data->variation_coordinates[var_tag];
coords.write[i] = CLAMP(var_value * 65536.0, amaster->axis[i].minimum, amaster->axis[i].maximum);
}
if (p_font_data->variation_coordinates.has(_tag_to_name(var_tag))) {
var_value = p_font_data->variation_coordinates[_tag_to_name(var_tag)];
coords.write[i] = CLAMP(var_value * 65536.0, amaster->axis[i].minimum, amaster->axis[i].maximum);
}
}
FT_Set_Var_Design_Coordinates(fd->face, coords.size(), coords.ptrw());
FT_Done_MM_Var(ft_library, amaster);
}
#else
memdelete(fd);
ERR_FAIL_V_MSG(false, "FreeType: Can't load dynamic font, engine is compiled without FreeType support!");
#endif
}
p_font_data->cache[p_size] = fd;
return true;
}
_FORCE_INLINE_ void TextServerFallback::_font_clear_cache(FontFallback *p_font_data) {
MutexLock ftlock(ft_mutex);
for (const KeyValue<Vector2i, FontForSizeFallback *> &E : p_font_data->cache) {
memdelete(E.value);
}
p_font_data->cache.clear();
p_font_data->face_init = false;
p_font_data->supported_varaitions.clear();
}
RID TextServerFallback::_create_font() {
_THREAD_SAFE_METHOD_
FontFallback *fd = memnew(FontFallback);
return font_owner.make_rid(fd);
}
void TextServerFallback::_font_set_data(const RID &p_font_rid, const PackedByteArray &p_data) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
_font_clear_cache(fd);
fd->data = p_data;
fd->data_ptr = fd->data.ptr();
fd->data_size = fd->data.size();
}
void TextServerFallback::_font_set_data_ptr(const RID &p_font_rid, const uint8_t *p_data_ptr, int64_t p_data_size) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
_font_clear_cache(fd);
fd->data.resize(0);
fd->data_ptr = p_data_ptr;
fd->data_size = p_data_size;
}
void TextServerFallback::_font_set_style(const RID &p_font_rid, BitField<FontStyle> p_style) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
fd->style_flags = p_style;
}
void TextServerFallback::_font_set_face_index(const RID &p_font_rid, int64_t p_face_index) {
ERR_FAIL_COND(p_face_index < 0);
ERR_FAIL_COND(p_face_index >= 0x7FFF);
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
if (fd->face_index != p_face_index) {
fd->face_index = p_face_index;
_font_clear_cache(fd);
}
}
int64_t TextServerFallback::_font_get_face_index(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, 0);
MutexLock lock(fd->mutex);
return fd->face_index;
}
int64_t TextServerFallback::_font_get_face_count(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, 0);
MutexLock lock(fd->mutex);
int face_count = 0;
if (fd->data_ptr && (fd->data_size > 0)) {
// Init dynamic font.
#ifdef MODULE_FREETYPE_ENABLED
int error = 0;
if (!ft_library) {
error = FT_Init_FreeType(&ft_library);
ERR_FAIL_COND_V_MSG(error != 0, false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'.");
#ifdef MODULE_SVG_ENABLED
FT_Property_Set(ft_library, "ot-svg", "svg-hooks", get_tvg_svg_in_ot_hooks());
#endif
}
FT_StreamRec stream;
memset(&stream, 0, sizeof(FT_StreamRec));
stream.base = (unsigned char *)fd->data_ptr;
stream.size = fd->data_size;
stream.pos = 0;
FT_Open_Args fargs;
memset(&fargs, 0, sizeof(FT_Open_Args));
fargs.memory_base = (unsigned char *)fd->data_ptr;
fargs.memory_size = fd->data_size;
fargs.flags = FT_OPEN_MEMORY;
fargs.stream = &stream;
MutexLock ftlock(ft_mutex);
FT_Face tmp_face = nullptr;
error = FT_Open_Face(ft_library, &fargs, -1, &tmp_face);
if (error == 0) {
face_count = tmp_face->num_faces;
FT_Done_Face(tmp_face);
}
#endif
}
return face_count;
}
BitField<TextServer::FontStyle> TextServerFallback::_font_get_style(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, 0);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0);
return fd->style_flags;
}
void TextServerFallback::_font_set_style_name(const RID &p_font_rid, const String &p_name) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
fd->style_name = p_name;
}
String TextServerFallback::_font_get_style_name(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, String());
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), String());
return fd->style_name;
}
void TextServerFallback::_font_set_weight(const RID &p_font_rid, int64_t p_weight) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
fd->weight = CLAMP(p_weight, 100, 999);
}
int64_t TextServerFallback::_font_get_weight(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, 400);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 400);
return fd->weight;
}
void TextServerFallback::_font_set_stretch(const RID &p_font_rid, int64_t p_stretch) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
fd->stretch = CLAMP(p_stretch, 50, 200);
}
int64_t TextServerFallback::_font_get_stretch(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, 100);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 100);
return fd->stretch;
}
void TextServerFallback::_font_set_name(const RID &p_font_rid, const String &p_name) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
fd->font_name = p_name;
}
String TextServerFallback::_font_get_name(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, String());
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), String());
return fd->font_name;
}
void TextServerFallback::_font_set_antialiasing(const RID &p_font_rid, TextServer::FontAntialiasing p_antialiasing) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
if (fd->antialiasing != p_antialiasing) {
_font_clear_cache(fd);
fd->antialiasing = p_antialiasing;
}
}
TextServer::FontAntialiasing TextServerFallback::_font_get_antialiasing(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, TextServer::FONT_ANTIALIASING_NONE);
MutexLock lock(fd->mutex);
return fd->antialiasing;
}
void TextServerFallback::_font_set_generate_mipmaps(const RID &p_font_rid, bool p_generate_mipmaps) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
if (fd->mipmaps != p_generate_mipmaps) {
for (KeyValue<Vector2i, FontForSizeFallback *> &E : fd->cache) {
for (int i = 0; i < E.value->textures.size(); i++) {
E.value->textures.write[i].dirty = true;
E.value->textures.write[i].texture = Ref<ImageTexture>();
}
}
fd->mipmaps = p_generate_mipmaps;
}
}
bool TextServerFallback::_font_get_generate_mipmaps(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, false);
MutexLock lock(fd->mutex);
return fd->mipmaps;
}
void TextServerFallback::_font_set_multichannel_signed_distance_field(const RID &p_font_rid, bool p_msdf) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
if (fd->msdf != p_msdf) {
_font_clear_cache(fd);
fd->msdf = p_msdf;
}
}
bool TextServerFallback::_font_is_multichannel_signed_distance_field(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, false);
MutexLock lock(fd->mutex);
return fd->msdf;
}
void TextServerFallback::_font_set_msdf_pixel_range(const RID &p_font_rid, int64_t p_msdf_pixel_range) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
if (fd->msdf_range != p_msdf_pixel_range) {
_font_clear_cache(fd);
fd->msdf_range = p_msdf_pixel_range;
}
}
int64_t TextServerFallback::_font_get_msdf_pixel_range(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, false);
MutexLock lock(fd->mutex);
return fd->msdf_range;
}
void TextServerFallback::_font_set_msdf_size(const RID &p_font_rid, int64_t p_msdf_size) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
if (fd->msdf_source_size != p_msdf_size) {
_font_clear_cache(fd);
fd->msdf_source_size = p_msdf_size;
}
}
int64_t TextServerFallback::_font_get_msdf_size(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, false);
MutexLock lock(fd->mutex);
return fd->msdf_source_size;
}
void TextServerFallback::_font_set_fixed_size(const RID &p_font_rid, int64_t p_fixed_size) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
fd->fixed_size = p_fixed_size;
}
int64_t TextServerFallback::_font_get_fixed_size(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, false);
MutexLock lock(fd->mutex);
return fd->fixed_size;
}
void TextServerFallback::_font_set_allow_system_fallback(const RID &p_font_rid, bool p_allow_system_fallback) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
fd->allow_system_fallback = p_allow_system_fallback;
}
bool TextServerFallback::_font_is_allow_system_fallback(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, false);
MutexLock lock(fd->mutex);
return fd->allow_system_fallback;
}
void TextServerFallback::_font_set_force_autohinter(const RID &p_font_rid, bool p_force_autohinter) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
if (fd->force_autohinter != p_force_autohinter) {
_font_clear_cache(fd);
fd->force_autohinter = p_force_autohinter;
}
}
bool TextServerFallback::_font_is_force_autohinter(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, false);
MutexLock lock(fd->mutex);
return fd->force_autohinter;
}
void TextServerFallback::_font_set_hinting(const RID &p_font_rid, TextServer::Hinting p_hinting) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
if (fd->hinting != p_hinting) {
_font_clear_cache(fd);
fd->hinting = p_hinting;
}
}
TextServer::Hinting TextServerFallback::_font_get_hinting(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, HINTING_NONE);
MutexLock lock(fd->mutex);
return fd->hinting;
}
void TextServerFallback::_font_set_subpixel_positioning(const RID &p_font_rid, TextServer::SubpixelPositioning p_subpixel) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
fd->subpixel_positioning = p_subpixel;
}
TextServer::SubpixelPositioning TextServerFallback::_font_get_subpixel_positioning(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, SUBPIXEL_POSITIONING_DISABLED);
MutexLock lock(fd->mutex);
return fd->subpixel_positioning;
}
void TextServerFallback::_font_set_embolden(const RID &p_font_rid, double p_strength) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
if (fd->embolden != p_strength) {
_font_clear_cache(fd);
fd->embolden = p_strength;
}
}
double TextServerFallback::_font_get_embolden(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, 0.0);
MutexLock lock(fd->mutex);
return fd->embolden;
}
void TextServerFallback::_font_set_transform(const RID &p_font_rid, const Transform2D &p_transform) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
if (fd->transform != p_transform) {
_font_clear_cache(fd);
fd->transform = p_transform;
}
}
Transform2D TextServerFallback::_font_get_transform(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, Transform2D());
MutexLock lock(fd->mutex);
return fd->transform;
}
void TextServerFallback::_font_set_variation_coordinates(const RID &p_font_rid, const Dictionary &p_variation_coordinates) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
if (fd->variation_coordinates != p_variation_coordinates) {
_font_clear_cache(fd);
fd->variation_coordinates = p_variation_coordinates;
}
}
Dictionary TextServerFallback::_font_get_variation_coordinates(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, Dictionary());
MutexLock lock(fd->mutex);
return fd->variation_coordinates;
}
void TextServerFallback::_font_set_oversampling(const RID &p_font_rid, double p_oversampling) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
if (fd->oversampling != p_oversampling) {
_font_clear_cache(fd);
fd->oversampling = p_oversampling;
}
}
double TextServerFallback::_font_get_oversampling(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, 0.0);
MutexLock lock(fd->mutex);
return fd->oversampling;
}
TypedArray<Vector2i> TextServerFallback::_font_get_size_cache_list(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, TypedArray<Vector2i>());
MutexLock lock(fd->mutex);
TypedArray<Vector2i> ret;
for (const KeyValue<Vector2i, FontForSizeFallback *> &E : fd->cache) {
ret.push_back(E.key);
}
return ret;
}
void TextServerFallback::_font_clear_size_cache(const RID &p_font_rid) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
MutexLock ftlock(ft_mutex);
for (const KeyValue<Vector2i, FontForSizeFallback *> &E : fd->cache) {
memdelete(E.value);
}
fd->cache.clear();
}
void TextServerFallback::_font_remove_size_cache(const RID &p_font_rid, const Vector2i &p_size) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
MutexLock ftlock(ft_mutex);
if (fd->cache.has(p_size)) {
memdelete(fd->cache[p_size]);
fd->cache.erase(p_size);
}
}
void TextServerFallback::_font_set_ascent(const RID &p_font_rid, int64_t p_size, double p_ascent) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
fd->cache[size]->ascent = p_ascent;
}
double TextServerFallback::_font_get_ascent(const RID &p_font_rid, int64_t p_size) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, 0.0);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
if (fd->msdf) {
return fd->cache[size]->ascent * (double)p_size / (double)fd->msdf_source_size;
} else {
return fd->cache[size]->ascent;
}
}
void TextServerFallback::_font_set_descent(const RID &p_font_rid, int64_t p_size, double p_descent) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
Vector2i size = _get_size(fd, p_size);
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
fd->cache[size]->descent = p_descent;
}
double TextServerFallback::_font_get_descent(const RID &p_font_rid, int64_t p_size) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, 0.0);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
if (fd->msdf) {
return fd->cache[size]->descent * (double)p_size / (double)fd->msdf_source_size;
} else {
return fd->cache[size]->descent;
}
}
void TextServerFallback::_font_set_underline_position(const RID &p_font_rid, int64_t p_size, double p_underline_position) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
fd->cache[size]->underline_position = p_underline_position;
}
double TextServerFallback::_font_get_underline_position(const RID &p_font_rid, int64_t p_size) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, 0.0);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
if (fd->msdf) {
return fd->cache[size]->underline_position * (double)p_size / (double)fd->msdf_source_size;
} else {
return fd->cache[size]->underline_position;
}
}
void TextServerFallback::_font_set_underline_thickness(const RID &p_font_rid, int64_t p_size, double p_underline_thickness) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
fd->cache[size]->underline_thickness = p_underline_thickness;
}
double TextServerFallback::_font_get_underline_thickness(const RID &p_font_rid, int64_t p_size) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, 0.0);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
if (fd->msdf) {
return fd->cache[size]->underline_thickness * (double)p_size / (double)fd->msdf_source_size;
} else {
return fd->cache[size]->underline_thickness;
}
}
void TextServerFallback::_font_set_scale(const RID &p_font_rid, int64_t p_size, double p_scale) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
#ifdef MODULE_FREETYPE_ENABLED
if (fd->cache[size]->face) {
return; // Do not override scale for dynamic fonts, it's calculated automatically.
}
#endif
fd->cache[size]->scale = p_scale;
}
double TextServerFallback::_font_get_scale(const RID &p_font_rid, int64_t p_size) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, 0.0);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0);
if (fd->msdf) {
return fd->cache[size]->scale * (double)p_size / (double)fd->msdf_source_size;
} else {
return fd->cache[size]->scale / fd->cache[size]->oversampling;
}
}
int64_t TextServerFallback::_font_get_texture_count(const RID &p_font_rid, const Vector2i &p_size) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, 0);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0);
return fd->cache[size]->textures.size();
}
void TextServerFallback::_font_clear_textures(const RID &p_font_rid, const Vector2i &p_size) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
fd->cache[size]->textures.clear();
}
void TextServerFallback::_font_remove_texture(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
ERR_FAIL_INDEX(p_texture_index, fd->cache[size]->textures.size());
fd->cache[size]->textures.remove_at(p_texture_index);
}
void TextServerFallback::_font_set_texture_image(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index, const Ref<Image> &p_image) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
ERR_FAIL_COND(p_image.is_null());
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
ERR_FAIL_COND(p_texture_index < 0);
if (p_texture_index >= fd->cache[size]->textures.size()) {
fd->cache[size]->textures.resize(p_texture_index + 1);
}
ShelfPackTexture &tex = fd->cache[size]->textures.write[p_texture_index];
tex.imgdata = p_image->get_data();
tex.texture_w = p_image->get_width();
tex.texture_h = p_image->get_height();
tex.format = p_image->get_format();
Ref<Image> img = Image::create_from_data(tex.texture_w, tex.texture_h, false, tex.format, tex.imgdata);
if (fd->mipmaps) {
img->generate_mipmaps();
}
tex.texture = ImageTexture::create_from_image(img);
tex.dirty = false;
}
Ref<Image> TextServerFallback::_font_get_texture_image(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, Ref<Image>());
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Ref<Image>());
ERR_FAIL_INDEX_V(p_texture_index, fd->cache[size]->textures.size(), Ref<Image>());
const ShelfPackTexture &tex = fd->cache[size]->textures[p_texture_index];
return Image::create_from_data(tex.texture_w, tex.texture_h, false, tex.format, tex.imgdata);
}
void TextServerFallback::_font_set_texture_offsets(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index, const PackedInt32Array &p_offsets) {
ERR_FAIL_COND(p_offsets.size() % 4 != 0);
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
ERR_FAIL_COND(p_texture_index < 0);
if (p_texture_index >= fd->cache[size]->textures.size()) {
fd->cache[size]->textures.resize(p_texture_index + 1);
}
ShelfPackTexture &tex = fd->cache[size]->textures.write[p_texture_index];
tex.shelves.clear();
for (int32_t i = 0; i < p_offsets.size(); i += 4) {
tex.shelves.push_back(Shelf(p_offsets[i], p_offsets[i + 1], p_offsets[i + 2], p_offsets[i + 3]));
}
}
PackedInt32Array TextServerFallback::_font_get_texture_offsets(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, PackedInt32Array());
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), PackedInt32Array());
ERR_FAIL_INDEX_V(p_texture_index, fd->cache[size]->textures.size(), PackedInt32Array());
const ShelfPackTexture &tex = fd->cache[size]->textures[p_texture_index];
PackedInt32Array ret;
ret.resize(tex.shelves.size() * 4);
int32_t *wr = ret.ptrw();
int32_t i = 0;
for (const Shelf &E : tex.shelves) {
wr[i * 4] = E.x;
wr[i * 4 + 1] = E.y;
wr[i * 4 + 2] = E.w;
wr[i * 4 + 3] = E.h;
i++;
}
return ret;
}
PackedInt32Array TextServerFallback::_font_get_glyph_list(const RID &p_font_rid, const Vector2i &p_size) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, PackedInt32Array());
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), PackedInt32Array());
PackedInt32Array ret;
const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
for (const KeyValue<int32_t, FontGlyph> &E : gl) {
ret.push_back(E.key);
}
return ret;
}
void TextServerFallback::_font_clear_glyphs(const RID &p_font_rid, const Vector2i &p_size) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
fd->cache[size]->glyph_map.clear();
}
void TextServerFallback::_font_remove_glyph(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
fd->cache[size]->glyph_map.erase(p_glyph);
}
Vector2 TextServerFallback::_font_get_glyph_advance(const RID &p_font_rid, int64_t p_size, int64_t p_glyph) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, Vector2());
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
if (!_ensure_glyph(fd, size, p_glyph | mod)) {
return Vector2(); // Invalid or non graphicl glyph, do not display errors.
}
const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
Vector2 ea;
if (fd->embolden != 0.0) {
ea.x = fd->embolden * double(size.x) / 64.0;
}
double scale = _font_get_scale(p_font_rid, p_size);
if (fd->msdf) {
return (gl[p_glyph | mod].advance + ea) * (double)p_size / (double)fd->msdf_source_size;
} else if ((scale == 1.0) && ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_DISABLED) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x > SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE))) {
return (gl[p_glyph | mod].advance + ea).round();
} else {
return gl[p_glyph | mod].advance + ea;
}
}
void TextServerFallback::_font_set_glyph_advance(const RID &p_font_rid, int64_t p_size, int64_t p_glyph, const Vector2 &p_advance) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
gl[p_glyph].advance = p_advance;
gl[p_glyph].found = true;
}
Vector2 TextServerFallback::_font_get_glyph_offset(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, Vector2());
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
if (!_ensure_glyph(fd, size, p_glyph | mod)) {
return Vector2(); // Invalid or non graphicl glyph, do not display errors.
}
const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
if (fd->msdf) {
return gl[p_glyph | mod].rect.position * (double)p_size.x / (double)fd->msdf_source_size;
} else {
return gl[p_glyph | mod].rect.position;
}
}
void TextServerFallback::_font_set_glyph_offset(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Vector2 &p_offset) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
gl[p_glyph].rect.position = p_offset;
gl[p_glyph].found = true;
}
Vector2 TextServerFallback::_font_get_glyph_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, Vector2());
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
if (!_ensure_glyph(fd, size, p_glyph | mod)) {
return Vector2(); // Invalid or non graphicl glyph, do not display errors.
}
const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
if (fd->msdf) {
return gl[p_glyph | mod].rect.size * (double)p_size.x / (double)fd->msdf_source_size;
} else {
return gl[p_glyph | mod].rect.size;
}
}
void TextServerFallback::_font_set_glyph_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Vector2 &p_gl_size) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
gl[p_glyph].rect.size = p_gl_size;
gl[p_glyph].found = true;
}
Rect2 TextServerFallback::_font_get_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, Rect2());
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Rect2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
if (!_ensure_glyph(fd, size, p_glyph | mod)) {
return Rect2(); // Invalid or non graphicl glyph, do not display errors.
}
const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
return gl[p_glyph | mod].uv_rect;
}
void TextServerFallback::_font_set_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Rect2 &p_uv_rect) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
gl[p_glyph].uv_rect = p_uv_rect;
gl[p_glyph].found = true;
}
int64_t TextServerFallback::_font_get_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, -1);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), -1);
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
if (!_ensure_glyph(fd, size, p_glyph | mod)) {
return -1; // Invalid or non graphicl glyph, do not display errors.
}
const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
return gl[p_glyph | mod].texture_idx;
}
void TextServerFallback::_font_set_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, int64_t p_texture_idx) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
gl[p_glyph].texture_idx = p_texture_idx;
gl[p_glyph].found = true;
}
RID TextServerFallback::_font_get_glyph_texture_rid(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, RID());
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), RID());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
if (!_ensure_glyph(fd, size, p_glyph | mod)) {
return RID(); // Invalid or non graphicl glyph, do not display errors.
}
const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
ERR_FAIL_COND_V(gl[p_glyph | mod].texture_idx < -1 || gl[p_glyph | mod].texture_idx >= fd->cache[size]->textures.size(), RID());
if (RenderingServer::get_singleton() != nullptr) {
if (gl[p_glyph | mod].texture_idx != -1) {
if (fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].dirty) {
ShelfPackTexture &tex = fd->cache[size]->textures.write[gl[p_glyph | mod].texture_idx];
Ref<Image> img = Image::create_from_data(tex.texture_w, tex.texture_h, false, tex.format, tex.imgdata);
if (fd->mipmaps) {
img->generate_mipmaps();
}
if (tex.texture.is_null()) {
tex.texture = ImageTexture::create_from_image(img);
} else {
tex.texture->update(img);
}
tex.dirty = false;
}
return fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].texture->get_rid();
}
}
return RID();
}
Size2 TextServerFallback::_font_get_glyph_texture_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, Size2());
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Size2());
int mod = 0;
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
mod = (layout << 24);
}
}
if (!_ensure_glyph(fd, size, p_glyph | mod)) {
return Size2(); // Invalid or non graphicl glyph, do not display errors.
}
const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map;
ERR_FAIL_COND_V(gl[p_glyph | mod].texture_idx < -1 || gl[p_glyph | mod].texture_idx >= fd->cache[size]->textures.size(), Size2());
if (RenderingServer::get_singleton() != nullptr) {
if (gl[p_glyph | mod].texture_idx != -1) {
if (fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].dirty) {
ShelfPackTexture &tex = fd->cache[size]->textures.write[gl[p_glyph | mod].texture_idx];
Ref<Image> img = Image::create_from_data(tex.texture_w, tex.texture_h, false, tex.format, tex.imgdata);
if (fd->mipmaps) {
img->generate_mipmaps();
}
if (tex.texture.is_null()) {
tex.texture = ImageTexture::create_from_image(img);
} else {
tex.texture->update(img);
}
tex.dirty = false;
}
return fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].texture->get_size();
}
}
return Size2();
}
Dictionary TextServerFallback::_font_get_glyph_contours(const RID &p_font_rid, int64_t p_size, int64_t p_index) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, Dictionary());
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary());
#ifdef MODULE_FREETYPE_ENABLED
PackedVector3Array points;
PackedInt32Array contours;
int32_t index = p_index & 0xffffff; // Remove subpixel shifts.
int error = FT_Load_Glyph(fd->cache[size]->face, FT_Get_Char_Index(fd->cache[size]->face, index), FT_LOAD_NO_BITMAP | (fd->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0));
ERR_FAIL_COND_V(error, Dictionary());
if (fd->embolden != 0.f) {
FT_Pos strength = fd->embolden * p_size * 4; // 26.6 fractional units (1 / 64).
FT_Outline_Embolden(&fd->cache[size]->face->glyph->outline, strength);
}
if (fd->transform != Transform2D()) {
FT_Matrix mat = { FT_Fixed(fd->transform[0][0] * 65536), FT_Fixed(fd->transform[0][1] * 65536), FT_Fixed(fd->transform[1][0] * 65536), FT_Fixed(fd->transform[1][1] * 65536) }; // 16.16 fractional units (1 / 65536).
FT_Outline_Transform(&fd->cache[size]->face->glyph->outline, &mat);
}
double scale = (1.0 / 64.0) / fd->cache[size]->oversampling * fd->cache[size]->scale;
if (fd->msdf) {
scale = scale * (double)p_size / (double)fd->msdf_source_size;
}
for (short i = 0; i < fd->cache[size]->face->glyph->outline.n_points; i++) {
points.push_back(Vector3(fd->cache[size]->face->glyph->outline.points[i].x * scale, -fd->cache[size]->face->glyph->outline.points[i].y * scale, FT_CURVE_TAG(fd->cache[size]->face->glyph->outline.tags[i])));
}
for (short i = 0; i < fd->cache[size]->face->glyph->outline.n_contours; i++) {
contours.push_back(fd->cache[size]->face->glyph->outline.contours[i]);
}
bool orientation = (FT_Outline_Get_Orientation(&fd->cache[size]->face->glyph->outline) == FT_ORIENTATION_FILL_RIGHT);
Dictionary out;
out["points"] = points;
out["contours"] = contours;
out["orientation"] = orientation;
return out;
#else
return Dictionary();
#endif
}
TypedArray<Vector2i> TextServerFallback::_font_get_kerning_list(const RID &p_font_rid, int64_t p_size) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, TypedArray<Vector2i>());
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), TypedArray<Vector2i>());
TypedArray<Vector2i> ret;
for (const KeyValue<Vector2i, Vector2> &E : fd->cache[size]->kerning_map) {
ret.push_back(E.key);
}
return ret;
}
void TextServerFallback::_font_clear_kerning_map(const RID &p_font_rid, int64_t p_size) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
fd->cache[size]->kerning_map.clear();
}
void TextServerFallback::_font_remove_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
fd->cache[size]->kerning_map.erase(p_glyph_pair);
}
void TextServerFallback::_font_set_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
fd->cache[size]->kerning_map[p_glyph_pair] = p_kerning;
}
Vector2 TextServerFallback::_font_get_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, Vector2());
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2());
const HashMap<Vector2i, Vector2> &kern = fd->cache[size]->kerning_map;
if (kern.has(p_glyph_pair)) {
if (fd->msdf) {
return kern[p_glyph_pair] * (double)p_size / (double)fd->msdf_source_size;
} else {
return kern[p_glyph_pair];
}
} else {
#ifdef MODULE_FREETYPE_ENABLED
if (fd->cache[size]->face) {
FT_Vector delta;
int32_t glyph_a = FT_Get_Char_Index(fd->cache[size]->face, p_glyph_pair.x);
int32_t glyph_b = FT_Get_Char_Index(fd->cache[size]->face, p_glyph_pair.y);
FT_Get_Kerning(fd->cache[size]->face, glyph_a, glyph_b, FT_KERNING_DEFAULT, &delta);
if (fd->msdf) {
return Vector2(delta.x, delta.y) * (double)p_size / (double)fd->msdf_source_size;
} else {
return Vector2(delta.x, delta.y);
}
}
#endif
}
return Vector2();
}
int64_t TextServerFallback::_font_get_glyph_index(const RID &p_font_rid, int64_t p_size, int64_t p_char, int64_t p_variation_selector) const {
ERR_FAIL_COND_V_MSG((p_char >= 0xd800 && p_char <= 0xdfff) || (p_char > 0x10ffff), 0, "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_char, 16) + ".");
return (int64_t)p_char;
}
int64_t TextServerFallback::_font_get_char_from_glyph_index(const RID &p_font_rid, int64_t p_size, int64_t p_glyph_index) const {
return p_glyph_index;
}
bool TextServerFallback::_font_has_char(const RID &p_font_rid, int64_t p_char) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V_MSG((p_char >= 0xd800 && p_char <= 0xdfff) || (p_char > 0x10ffff), false, "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_char, 16) + ".");
if (!fd) {
return false;
}
MutexLock lock(fd->mutex);
if (fd->cache.is_empty()) {
ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), false);
}
FontForSizeFallback *at_size = fd->cache.begin()->value;
#ifdef MODULE_FREETYPE_ENABLED
if (at_size && at_size->face) {
return FT_Get_Char_Index(at_size->face, p_char) != 0;
}
#endif
return (at_size) ? at_size->glyph_map.has((int32_t)p_char) : false;
}
String TextServerFallback::_font_get_supported_chars(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, String());
MutexLock lock(fd->mutex);
if (fd->cache.is_empty()) {
ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), String());
}
FontForSizeFallback *at_size = fd->cache.begin()->value;
String chars;
#ifdef MODULE_FREETYPE_ENABLED
if (at_size && at_size->face) {
FT_UInt gindex;
FT_ULong charcode = FT_Get_First_Char(at_size->face, &gindex);
while (gindex != 0) {
if (charcode != 0) {
chars = chars + String::chr(charcode);
}
charcode = FT_Get_Next_Char(at_size->face, charcode, &gindex);
}
return chars;
}
#endif
if (at_size) {
const HashMap<int32_t, FontGlyph> &gl = at_size->glyph_map;
for (const KeyValue<int32_t, FontGlyph> &E : gl) {
chars = chars + String::chr(E.key);
}
}
return chars;
}
void TextServerFallback::_font_render_range(const RID &p_font_rid, const Vector2i &p_size, int64_t p_start, int64_t p_end) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
ERR_FAIL_COND_MSG((p_start >= 0xd800 && p_start <= 0xdfff) || (p_start > 0x10ffff), "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_start, 16) + ".");
ERR_FAIL_COND_MSG((p_end >= 0xd800 && p_end <= 0xdfff) || (p_end > 0x10ffff), "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_end, 16) + ".");
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
for (int64_t i = p_start; i <= p_end; i++) {
#ifdef MODULE_FREETYPE_ENABLED
int32_t idx = i;
if (fd->cache[size]->face) {
if (fd->msdf) {
_ensure_glyph(fd, size, (int32_t)idx);
} else {
for (int aa = 0; aa < ((fd->antialiasing == FONT_ANTIALIASING_LCD) ? FONT_LCD_SUBPIXEL_LAYOUT_MAX : 1); aa++) {
if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
_ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24));
_ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24));
_ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24));
_ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24));
} else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
_ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24));
_ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24));
} else {
_ensure_glyph(fd, size, (int32_t)idx | (aa << 24));
}
}
}
}
#endif
}
}
void TextServerFallback::_font_render_glyph(const RID &p_font_rid, const Vector2i &p_size, int64_t p_index) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
#ifdef MODULE_FREETYPE_ENABLED
int32_t idx = p_index & 0xffffff; // Remove subpixel shifts.
if (fd->cache[size]->face) {
if (fd->msdf) {
_ensure_glyph(fd, size, (int32_t)idx);
} else {
for (int aa = 0; aa < ((fd->antialiasing == FONT_ANTIALIASING_LCD) ? FONT_LCD_SUBPIXEL_LAYOUT_MAX : 1); aa++) {
if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
_ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24));
_ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24));
_ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24));
_ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24));
} else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
_ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24));
_ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24));
} else {
_ensure_glyph(fd, size, (int32_t)idx | (aa << 24));
}
}
}
}
#endif
}
void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_canvas, int64_t p_size, const Vector2 &p_pos, int64_t p_index, const Color &p_color) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, p_size);
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
int32_t index = p_index & 0xffffff; // Remove subpixel shifts.
bool lcd_aa = false;
#ifdef MODULE_FREETYPE_ENABLED
if (!fd->msdf && fd->cache[size]->face) {
// LCD layout, bits 24, 25, 26
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
lcd_aa = true;
index = index | (layout << 24);
}
}
// Subpixel X-shift, bits 27, 28
if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
int xshift = (int)(Math::floor(4 * (p_pos.x + 0.125)) - 4 * Math::floor(p_pos.x + 0.125));
index = index | (xshift << 27);
} else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
int xshift = (int)(Math::floor(2 * (p_pos.x + 0.25)) - 2 * Math::floor(p_pos.x + 0.25));
index = index | (xshift << 27);
}
}
#endif
if (!_ensure_glyph(fd, size, index)) {
return; // Invalid or non-graphical glyph, do not display errors, nothing to draw.
}
const FontGlyph &gl = fd->cache[size]->glyph_map[index];
if (gl.found) {
ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size());
if (gl.texture_idx != -1) {
Color modulate = p_color;
#ifdef MODULE_FREETYPE_ENABLED
if (fd->cache[size]->face && (fd->cache[size]->textures[gl.texture_idx].format == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) {
modulate.r = modulate.g = modulate.b = 1.0;
}
#endif
if (RenderingServer::get_singleton() != nullptr) {
if (fd->cache[size]->textures[gl.texture_idx].dirty) {
ShelfPackTexture &tex = fd->cache[size]->textures.write[gl.texture_idx];
Ref<Image> img = Image::create_from_data(tex.texture_w, tex.texture_h, false, tex.format, tex.imgdata);
if (fd->mipmaps) {
img->generate_mipmaps();
}
if (tex.texture.is_null()) {
tex.texture = ImageTexture::create_from_image(img);
} else {
tex.texture->update(img);
}
tex.dirty = false;
}
RID texture = fd->cache[size]->textures[gl.texture_idx].texture->get_rid();
if (fd->msdf) {
Point2 cpos = p_pos;
cpos += gl.rect.position * (double)p_size / (double)fd->msdf_source_size;
Size2 csize = gl.rect.size * (double)p_size / (double)fd->msdf_source_size;
RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, 0, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size);
} else {
Point2 cpos = p_pos;
double scale = _font_get_scale(p_font_rid, p_size);
if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
cpos.x = cpos.x + 0.125;
} else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
cpos.x = cpos.x + 0.25;
}
if (scale == 1.0) {
cpos.y = Math::floor(cpos.y);
cpos.x = Math::floor(cpos.x);
}
cpos += gl.rect.position;
Size2 csize = gl.rect.size;
if (lcd_aa) {
RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate);
} else {
RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, false, false);
}
}
}
}
}
}
void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const RID &p_canvas, int64_t p_size, int64_t p_outline_size, const Vector2 &p_pos, int64_t p_index, const Color &p_color) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, Vector2i(p_size, p_outline_size));
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
int32_t index = p_index & 0xffffff; // Remove subpixel shifts.
bool lcd_aa = false;
#ifdef MODULE_FREETYPE_ENABLED
if (!fd->msdf && fd->cache[size]->face) {
// LCD layout, bits 24, 25, 26
if (fd->antialiasing == FONT_ANTIALIASING_LCD) {
TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout");
if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) {
lcd_aa = true;
index = index | (layout << 24);
}
}
// Subpixel X-shift, bits 27, 28
if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
int xshift = (int)(Math::floor(4 * (p_pos.x + 0.125)) - 4 * Math::floor(p_pos.x + 0.125));
index = index | (xshift << 27);
} else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
int xshift = (int)(Math::floor(2 * (p_pos.x + 0.25)) - 2 * Math::floor(p_pos.x + 0.25));
index = index | (xshift << 27);
}
}
#endif
if (!_ensure_glyph(fd, size, index)) {
return; // Invalid or non-graphical glyph, do not display errors, nothing to draw.
}
const FontGlyph &gl = fd->cache[size]->glyph_map[index];
if (gl.found) {
ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size());
if (gl.texture_idx != -1) {
Color modulate = p_color;
#ifdef MODULE_FREETYPE_ENABLED
if (fd->cache[size]->face && (fd->cache[size]->textures[gl.texture_idx].format == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) {
modulate.r = modulate.g = modulate.b = 1.0;
}
#endif
if (RenderingServer::get_singleton() != nullptr) {
if (fd->cache[size]->textures[gl.texture_idx].dirty) {
ShelfPackTexture &tex = fd->cache[size]->textures.write[gl.texture_idx];
Ref<Image> img = Image::create_from_data(tex.texture_w, tex.texture_h, false, tex.format, tex.imgdata);
if (fd->mipmaps) {
img->generate_mipmaps();
}
if (tex.texture.is_null()) {
tex.texture = ImageTexture::create_from_image(img);
} else {
tex.texture->update(img);
}
tex.dirty = false;
}
RID texture = fd->cache[size]->textures[gl.texture_idx].texture->get_rid();
if (fd->msdf) {
Point2 cpos = p_pos;
cpos += gl.rect.position * (double)p_size / (double)fd->msdf_source_size;
Size2 csize = gl.rect.size * (double)p_size / (double)fd->msdf_source_size;
RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, p_outline_size, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size);
} else {
Point2 cpos = p_pos;
double scale = _font_get_scale(p_font_rid, p_size);
if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) {
cpos.x = cpos.x + 0.125;
} else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) {
cpos.x = cpos.x + 0.25;
}
if (scale == 1.0) {
cpos.y = Math::floor(cpos.y);
cpos.x = Math::floor(cpos.x);
}
cpos += gl.rect.position;
Size2 csize = gl.rect.size;
if (lcd_aa) {
RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate);
} else {
RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, false, false);
}
}
}
}
}
}
bool TextServerFallback::_font_is_language_supported(const RID &p_font_rid, const String &p_language) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, false);
MutexLock lock(fd->mutex);
if (fd->language_support_overrides.has(p_language)) {
return fd->language_support_overrides[p_language];
} else {
return true;
}
}
void TextServerFallback::_font_set_language_support_override(const RID &p_font_rid, const String &p_language, bool p_supported) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
fd->language_support_overrides[p_language] = p_supported;
}
bool TextServerFallback::_font_get_language_support_override(const RID &p_font_rid, const String &p_language) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, false);
MutexLock lock(fd->mutex);
return fd->language_support_overrides[p_language];
}
void TextServerFallback::_font_remove_language_support_override(const RID &p_font_rid, const String &p_language) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
fd->language_support_overrides.erase(p_language);
}
PackedStringArray TextServerFallback::_font_get_language_support_overrides(const RID &p_font_rid) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, PackedStringArray());
MutexLock lock(fd->mutex);
PackedStringArray out;
for (const KeyValue<String, bool> &E : fd->language_support_overrides) {
out.push_back(E.key);
}
return out;
}
bool TextServerFallback::_font_is_script_supported(const RID &p_font_rid, const String &p_script) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, false);
MutexLock lock(fd->mutex);
if (fd->script_support_overrides.has(p_script)) {
return fd->script_support_overrides[p_script];
} else {
return true;
}
}
void TextServerFallback::_font_set_script_support_override(const RID &p_font_rid, const String &p_script, bool p_supported) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
fd->script_support_overrides[p_script] = p_supported;
}
bool TextServerFallback::_font_get_script_support_override(const RID &p_font_rid, const String &p_script) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, false);
MutexLock lock(fd->mutex);
return fd->script_support_overrides[p_script];
}
void TextServerFallback::_font_remove_script_support_override(const RID &p_font_rid, const String &p_script) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
fd->script_support_overrides.erase(p_script);
}
PackedStringArray TextServerFallback::_font_get_script_support_overrides(const RID &p_font_rid) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, PackedStringArray());
MutexLock lock(fd->mutex);
PackedStringArray out;
for (const KeyValue<String, bool> &E : fd->script_support_overrides) {
out.push_back(E.key);
}
return out;
}
void TextServerFallback::_font_set_opentype_feature_overrides(const RID &p_font_rid, const Dictionary &p_overrides) {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND(!fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
ERR_FAIL_COND(!_ensure_cache_for_size(fd, size));
fd->feature_overrides = p_overrides;
}
Dictionary TextServerFallback::_font_get_opentype_feature_overrides(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, Dictionary());
MutexLock lock(fd->mutex);
return fd->feature_overrides;
}
Dictionary TextServerFallback::_font_supported_feature_list(const RID &p_font_rid) const {
return Dictionary();
}
Dictionary TextServerFallback::_font_supported_variation_list(const RID &p_font_rid) const {
FontFallback *fd = font_owner.get_or_null(p_font_rid);
ERR_FAIL_COND_V(!fd, Dictionary());
MutexLock lock(fd->mutex);
Vector2i size = _get_size(fd, 16);
ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary());
return fd->supported_varaitions;
}
double TextServerFallback::_font_get_global_oversampling() const {
return oversampling;
}
void TextServerFallback::_font_set_global_oversampling(double p_oversampling) {
_THREAD_SAFE_METHOD_
if (oversampling != p_oversampling) {
oversampling = p_oversampling;
List<RID> fonts;
font_owner.get_owned_list(&fonts);
bool font_cleared = false;
for (const RID &E : fonts) {
if (!_font_is_multichannel_signed_distance_field(E) && _font_get_oversampling(E) <= 0) {
_font_clear_size_cache(E);
font_cleared = true;
}
}
if (font_cleared) {
List<RID> text_bufs;
shaped_owner.get_owned_list(&text_bufs);
for (const RID &E : text_bufs) {
invalidate(shaped_owner.get_or_null(E));
}
}
}
}
/*************************************************************************/
/* Shaped text buffer interface */
/*************************************************************************/
void TextServerFallback::invalidate(ShapedTextDataFallback *p_shaped) {
p_shaped->valid = false;
p_shaped->sort_valid = false;
p_shaped->line_breaks_valid = false;
p_shaped->justification_ops_valid = false;
p_shaped->ascent = 0.0;
p_shaped->descent = 0.0;
p_shaped->width = 0.0;
p_shaped->upos = 0.0;
p_shaped->uthk = 0.0;
p_shaped->glyphs.clear();
p_shaped->glyphs_logical.clear();
}
void TextServerFallback::full_copy(ShapedTextDataFallback *p_shaped) {
ShapedTextDataFallback *parent = shaped_owner.get_or_null(p_shaped->parent);
for (const KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : parent->objects) {
if (E.value.pos >= p_shaped->start && E.value.pos < p_shaped->end) {
p_shaped->objects[E.key] = E.value;
}
}
for (int k = 0; k < parent->spans.size(); k++) {
ShapedTextDataFallback::Span span = parent->spans[k];
if (span.start >= p_shaped->end || span.end <= p_shaped->start) {
continue;
}
span.start = MAX(p_shaped->start, span.start);
span.end = MIN(p_shaped->end, span.end);
p_shaped->spans.push_back(span);
}
p_shaped->parent = RID();
}
RID TextServerFallback::_create_shaped_text(TextServer::Direction p_direction, TextServer::Orientation p_orientation) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND_V_MSG(p_direction == DIRECTION_INHERITED, RID(), "Invalid text direction.");
ShapedTextDataFallback *sd = memnew(ShapedTextDataFallback);
sd->direction = p_direction;
sd->orientation = p_orientation;
return shaped_owner.make_rid(sd);
}
void TextServerFallback::_shaped_text_clear(const RID &p_shaped) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND(!sd);
MutexLock lock(sd->mutex);
sd->parent = RID();
sd->start = 0;
sd->end = 0;
sd->text = String();
sd->spans.clear();
sd->objects.clear();
invalidate(sd);
}
void TextServerFallback::_shaped_text_set_direction(const RID &p_shaped, TextServer::Direction p_direction) {
ERR_FAIL_COND_MSG(p_direction == DIRECTION_INHERITED, "Invalid text direction.");
if (p_direction == DIRECTION_RTL) {
ERR_PRINT_ONCE("Right-to-left layout is not supported by this text server.");
}
}
TextServer::Direction TextServerFallback::_shaped_text_get_direction(const RID &p_shaped) const {
return TextServer::DIRECTION_LTR;
}
TextServer::Direction TextServerFallback::_shaped_text_get_inferred_direction(const RID &p_shaped) const {
return TextServer::DIRECTION_LTR;
}
void TextServerFallback::_shaped_text_set_custom_punctuation(const RID &p_shaped, const String &p_punct) {
_THREAD_SAFE_METHOD_
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND(!sd);
if (sd->custom_punct != p_punct) {
if (sd->parent != RID()) {
full_copy(sd);
}
sd->custom_punct = p_punct;
invalidate(sd);
}
}
String TextServerFallback::_shaped_text_get_custom_punctuation(const RID &p_shaped) const {
_THREAD_SAFE_METHOD_
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, String());
return sd->custom_punct;
}
void TextServerFallback::_shaped_text_set_orientation(const RID &p_shaped, TextServer::Orientation p_orientation) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND(!sd);
MutexLock lock(sd->mutex);
if (sd->orientation != p_orientation) {
if (sd->parent != RID()) {
full_copy(sd);
}
sd->orientation = p_orientation;
invalidate(sd);
}
}
void TextServerFallback::_shaped_text_set_bidi_override(const RID &p_shaped, const Array &p_override) {
// No BiDi support, ignore.
}
TextServer::Orientation TextServerFallback::_shaped_text_get_orientation(const RID &p_shaped) const {
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, TextServer::ORIENTATION_HORIZONTAL);
MutexLock lock(sd->mutex);
return sd->orientation;
}
void TextServerFallback::_shaped_text_set_preserve_invalid(const RID &p_shaped, bool p_enabled) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
MutexLock lock(sd->mutex);
ERR_FAIL_COND(!sd);
if (sd->preserve_invalid != p_enabled) {
if (sd->parent != RID()) {
full_copy(sd);
}
sd->preserve_invalid = p_enabled;
invalidate(sd);
}
}
bool TextServerFallback::_shaped_text_get_preserve_invalid(const RID &p_shaped) const {
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, false);
MutexLock lock(sd->mutex);
return sd->preserve_invalid;
}
void TextServerFallback::_shaped_text_set_preserve_control(const RID &p_shaped, bool p_enabled) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND(!sd);
MutexLock lock(sd->mutex);
if (sd->preserve_control != p_enabled) {
if (sd->parent != RID()) {
full_copy(sd);
}
sd->preserve_control = p_enabled;
invalidate(sd);
}
}
bool TextServerFallback::_shaped_text_get_preserve_control(const RID &p_shaped) const {
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, false);
MutexLock lock(sd->mutex);
return sd->preserve_control;
}
void TextServerFallback::_shaped_text_set_spacing(const RID &p_shaped, SpacingType p_spacing, int64_t p_value) {
ERR_FAIL_INDEX((int)p_spacing, 4);
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND(!sd);
MutexLock lock(sd->mutex);
if (sd->extra_spacing[p_spacing] != p_value) {
if (sd->parent != RID()) {
full_copy(sd);
}
sd->extra_spacing[p_spacing] = p_value;
invalidate(sd);
}
}
int64_t TextServerFallback::_shaped_text_get_spacing(const RID &p_shaped, SpacingType p_spacing) const {
ERR_FAIL_INDEX_V((int)p_spacing, 4, 0);
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, 0);
MutexLock lock(sd->mutex);
return sd->extra_spacing[p_spacing];
}
int64_t TextServerFallback::_shaped_get_span_count(const RID &p_shaped) const {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, 0);
return sd->spans.size();
}
Variant TextServerFallback::_shaped_get_span_meta(const RID &p_shaped, int64_t p_index) const {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, Variant());
ERR_FAIL_INDEX_V(p_index, sd->spans.size(), Variant());
return sd->spans[p_index].meta;
}
void TextServerFallback::_shaped_set_span_update_font(const RID &p_shaped, int64_t p_index, const TypedArray<RID> &p_fonts, int64_t p_size, const Dictionary &p_opentype_features) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND(!sd);
ERR_FAIL_INDEX(p_index, sd->spans.size());
ShapedTextDataFallback::Span &span = sd->spans.ptrw()[p_index];
span.fonts.clear();
// Pre-sort fonts, push fonts with the language support first.
Array fonts_no_match;
int font_count = p_fonts.size();
for (int i = 0; i < font_count; i++) {
if (_font_is_language_supported(p_fonts[i], span.language)) {
span.fonts.push_back(p_fonts[i]);
} else {
fonts_no_match.push_back(p_fonts[i]);
}
}
span.fonts.append_array(fonts_no_match);
span.font_size = p_size;
span.features = p_opentype_features;
sd->valid = false;
}
bool TextServerFallback::_shaped_text_add_string(const RID &p_shaped, const String &p_text, const TypedArray<RID> &p_fonts, int64_t p_size, const Dictionary &p_opentype_features, const String &p_language, const Variant &p_meta) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, false);
MutexLock lock(sd->mutex);
ERR_FAIL_COND_V(p_size <= 0, false);
for (int i = 0; i < p_fonts.size(); i++) {
ERR_FAIL_COND_V(!font_owner.get_or_null(p_fonts[i]), false);
}
if (p_text.is_empty()) {
return true;
}
if (sd->parent != RID()) {
full_copy(sd);
}
ShapedTextDataFallback::Span span;
span.start = sd->text.length();
span.end = span.start + p_text.length();
// Pre-sort fonts, push fonts with the language support first.
Array fonts_no_match;
int font_count = p_fonts.size();
if (font_count > 0) {
span.fonts.push_back(p_fonts[0]);
}
for (int i = 1; i < font_count; i++) {
if (_font_is_language_supported(p_fonts[i], p_language)) {
span.fonts.push_back(p_fonts[i]);
} else {
fonts_no_match.push_back(p_fonts[i]);
}
}
span.fonts.append_array(fonts_no_match);
ERR_FAIL_COND_V(span.fonts.is_empty(), false);
span.font_size = p_size;
span.language = p_language;
span.meta = p_meta;
sd->spans.push_back(span);
sd->text = sd->text + p_text;
sd->end += p_text.length();
invalidate(sd);
return true;
}
bool TextServerFallback::_shaped_text_add_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align, int64_t p_length, double p_baseline) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, false);
MutexLock lock(sd->mutex);
ERR_FAIL_COND_V(p_key == Variant(), false);
ERR_FAIL_COND_V(sd->objects.has(p_key), false);
if (sd->parent != RID()) {
full_copy(sd);
}
ShapedTextDataFallback::Span span;
span.start = sd->start + sd->text.length();
span.end = span.start + p_length;
span.embedded_key = p_key;
ShapedTextDataFallback::EmbeddedObject obj;
obj.inline_align = p_inline_align;
obj.rect.size = p_size;
obj.pos = span.start;
obj.baseline = p_baseline;
sd->spans.push_back(span);
sd->text = sd->text + String::chr(0xfffc).repeat(p_length);
sd->end += p_length;
sd->objects[p_key] = obj;
invalidate(sd);
return true;
}
bool TextServerFallback::_shaped_text_resize_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align, double p_baseline) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, false);
MutexLock lock(sd->mutex);
ERR_FAIL_COND_V(!sd->objects.has(p_key), false);
sd->objects[p_key].rect.size = p_size;
sd->objects[p_key].inline_align = p_inline_align;
sd->objects[p_key].baseline = p_baseline;
if (sd->valid) {
// Recalc string metrics.
sd->ascent = 0;
sd->descent = 0;
sd->width = 0;
sd->upos = 0;
sd->uthk = 0;
int sd_size = sd->glyphs.size();
for (int i = 0; i < sd_size; i++) {
Glyph gl = sd->glyphs[i];
Variant key;
if (gl.count == 1) {
for (const KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : sd->objects) {
if (E.value.pos == gl.start) {
key = E.key;
break;
}
}
}
if (key != Variant()) {
if (sd->orientation == ORIENTATION_HORIZONTAL) {
sd->objects[key].rect.position.x = sd->width;
sd->width += sd->objects[key].rect.size.x;
sd->glyphs.write[i].advance = sd->objects[key].rect.size.x;
} else {
sd->objects[key].rect.position.y = sd->width;
sd->width += sd->objects[key].rect.size.y;
sd->glyphs.write[i].advance = sd->objects[key].rect.size.y;
}
} else {
if (gl.font_rid.is_valid()) {
if (sd->orientation == ORIENTATION_HORIZONTAL) {
sd->ascent = MAX(sd->ascent, _font_get_ascent(gl.font_rid, gl.font_size));
sd->descent = MAX(sd->descent, _font_get_descent(gl.font_rid, gl.font_size));
} else {
sd->ascent = MAX(sd->ascent, Math::round(_font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x * 0.5));
sd->descent = MAX(sd->descent, Math::round(_font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x * 0.5));
}
sd->upos = MAX(sd->upos, _font_get_underline_position(gl.font_rid, gl.font_size));
sd->uthk = MAX(sd->uthk, _font_get_underline_thickness(gl.font_rid, gl.font_size));
} else if (sd->preserve_invalid || (sd->preserve_control && is_control(gl.index))) {
// Glyph not found, replace with hex code box.
if (sd->orientation == ORIENTATION_HORIZONTAL) {
sd->ascent = MAX(sd->ascent, get_hex_code_box_size(gl.font_size, gl.index).y);
} else {
sd->ascent = MAX(sd->ascent, Math::round(get_hex_code_box_size(gl.font_size, gl.index).x * 0.5));
sd->descent = MAX(sd->descent, Math::round(get_hex_code_box_size(gl.font_size, gl.index).x * 0.5));
}
}
sd->width += gl.advance * gl.repeat;
}
}
_realign(sd);
}
return true;
}
void TextServerFallback::_realign(ShapedTextDataFallback *p_sd) const {
// Align embedded objects to baseline.
double full_ascent = p_sd->ascent;
double full_descent = p_sd->descent;
for (KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : p_sd->objects) {
if ((E.value.pos >= p_sd->start) && (E.value.pos < p_sd->end)) {
if (p_sd->orientation == ORIENTATION_HORIZONTAL) {
switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) {
case INLINE_ALIGNMENT_TO_TOP: {
E.value.rect.position.y = -p_sd->ascent;
} break;
case INLINE_ALIGNMENT_TO_CENTER: {
E.value.rect.position.y = (-p_sd->ascent + p_sd->descent) / 2;
} break;
case INLINE_ALIGNMENT_TO_BASELINE: {
E.value.rect.position.y = 0;
} break;
case INLINE_ALIGNMENT_TO_BOTTOM: {
E.value.rect.position.y = p_sd->descent;
} break;
}
switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) {
case INLINE_ALIGNMENT_BOTTOM_TO: {
E.value.rect.position.y -= E.value.rect.size.y;
} break;
case INLINE_ALIGNMENT_CENTER_TO: {
E.value.rect.position.y -= E.value.rect.size.y / 2;
} break;
case INLINE_ALIGNMENT_BASELINE_TO: {
E.value.rect.position.y -= E.value.baseline;
} break;
case INLINE_ALIGNMENT_TOP_TO: {
// NOP
} break;
}
full_ascent = MAX(full_ascent, -E.value.rect.position.y);
full_descent = MAX(full_descent, E.value.rect.position.y + E.value.rect.size.y);
} else {
switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) {
case INLINE_ALIGNMENT_TO_TOP: {
E.value.rect.position.x = -p_sd->ascent;
} break;
case INLINE_ALIGNMENT_TO_CENTER: {
E.value.rect.position.x = (-p_sd->ascent + p_sd->descent) / 2;
} break;
case INLINE_ALIGNMENT_TO_BASELINE: {
E.value.rect.position.x = 0;
} break;
case INLINE_ALIGNMENT_TO_BOTTOM: {
E.value.rect.position.x = p_sd->descent;
} break;
}
switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) {
case INLINE_ALIGNMENT_BOTTOM_TO: {
E.value.rect.position.x -= E.value.rect.size.x;
} break;
case INLINE_ALIGNMENT_CENTER_TO: {
E.value.rect.position.x -= E.value.rect.size.x / 2;
} break;
case INLINE_ALIGNMENT_BASELINE_TO: {
E.value.rect.position.x -= E.value.baseline;
} break;
case INLINE_ALIGNMENT_TOP_TO: {
// NOP
} break;
}
full_ascent = MAX(full_ascent, -E.value.rect.position.x);
full_descent = MAX(full_descent, E.value.rect.position.x + E.value.rect.size.x);
}
}
}
p_sd->ascent = full_ascent;
p_sd->descent = full_descent;
}
RID TextServerFallback::_shaped_text_substr(const RID &p_shaped, int64_t p_start, int64_t p_length) const {
_THREAD_SAFE_METHOD_
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, RID());
MutexLock lock(sd->mutex);
if (sd->parent != RID()) {
return _shaped_text_substr(sd->parent, p_start, p_length);
}
if (!sd->valid) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
ERR_FAIL_COND_V(p_start < 0 || p_length < 0, RID());
ERR_FAIL_COND_V(sd->start > p_start || sd->end < p_start, RID());
ERR_FAIL_COND_V(sd->end < p_start + p_length, RID());
ShapedTextDataFallback *new_sd = memnew(ShapedTextDataFallback);
new_sd->parent = p_shaped;
new_sd->start = p_start;
new_sd->end = p_start + p_length;
new_sd->orientation = sd->orientation;
new_sd->direction = sd->direction;
new_sd->custom_punct = sd->custom_punct;
new_sd->para_direction = sd->para_direction;
new_sd->line_breaks_valid = sd->line_breaks_valid;
new_sd->justification_ops_valid = sd->justification_ops_valid;
new_sd->sort_valid = false;
new_sd->upos = sd->upos;
new_sd->uthk = sd->uthk;
for (int i = 0; i < TextServer::SPACING_MAX; i++) {
new_sd->extra_spacing[i] = sd->extra_spacing[i];
}
if (p_length > 0) {
new_sd->text = sd->text.substr(p_start - sd->start, p_length);
int sd_size = sd->glyphs.size();
const Glyph *sd_glyphs = sd->glyphs.ptr();
for (int i = 0; i < sd_size; i++) {
if ((sd_glyphs[i].start >= new_sd->start) && (sd_glyphs[i].end <= new_sd->end)) {
Glyph gl = sd_glyphs[i];
Variant key;
bool find_embedded = false;
if (gl.count == 1) {
for (const KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : sd->objects) {
if (E.value.pos == gl.start) {
find_embedded = true;
key = E.key;
new_sd->objects[key] = E.value;
break;
}
}
}
if (find_embedded) {
if (new_sd->orientation == ORIENTATION_HORIZONTAL) {
new_sd->objects[key].rect.position.x = new_sd->width;
new_sd->width += new_sd->objects[key].rect.size.x;
} else {
new_sd->objects[key].rect.position.y = new_sd->width;
new_sd->width += new_sd->objects[key].rect.size.y;
}
} else {
if (gl.font_rid.is_valid()) {
if (new_sd->orientation == ORIENTATION_HORIZONTAL) {
new_sd->ascent = MAX(new_sd->ascent, _font_get_ascent(gl.font_rid, gl.font_size));
new_sd->descent = MAX(new_sd->descent, _font_get_descent(gl.font_rid, gl.font_size));
} else {
new_sd->ascent = MAX(new_sd->ascent, Math::round(_font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x * 0.5));
new_sd->descent = MAX(new_sd->descent, Math::round(_font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x * 0.5));
}
} else if (new_sd->preserve_invalid || (new_sd->preserve_control && is_control(gl.index))) {
// Glyph not found, replace with hex code box.
if (new_sd->orientation == ORIENTATION_HORIZONTAL) {
new_sd->ascent = MAX(new_sd->ascent, get_hex_code_box_size(gl.font_size, gl.index).y);
} else {
new_sd->ascent = MAX(new_sd->ascent, Math::round(get_hex_code_box_size(gl.font_size, gl.index).x * 0.5));
new_sd->descent = MAX(new_sd->descent, Math::round(get_hex_code_box_size(gl.font_size, gl.index).x * 0.5));
}
}
new_sd->width += gl.advance * gl.repeat;
}
new_sd->glyphs.push_back(gl);
}
}
_realign(new_sd);
}
new_sd->valid = true;
return shaped_owner.make_rid(new_sd);
}
RID TextServerFallback::_shaped_text_get_parent(const RID &p_shaped) const {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, RID());
MutexLock lock(sd->mutex);
return sd->parent;
}
double TextServerFallback::_shaped_text_fit_to_width(const RID &p_shaped, double p_width, BitField<JustificationFlag> p_jst_flags) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, 0.0);
MutexLock lock(sd->mutex);
if (!sd->valid) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
if (!sd->justification_ops_valid) {
const_cast<TextServerFallback *>(this)->_shaped_text_update_justification_ops(p_shaped);
}
int start_pos = 0;
int end_pos = sd->glyphs.size() - 1;
if (p_jst_flags.has_flag(JUSTIFICATION_AFTER_LAST_TAB)) {
int start, end, delta;
if (sd->para_direction == DIRECTION_LTR) {
start = sd->glyphs.size() - 1;
end = -1;
delta = -1;
} else {
start = 0;
end = sd->glyphs.size();
delta = +1;
}
for (int i = start; i != end; i += delta) {
if ((sd->glyphs[i].flags & GRAPHEME_IS_TAB) == GRAPHEME_IS_TAB) {
if (sd->para_direction == DIRECTION_LTR) {
start_pos = i;
break;
} else {
end_pos = i;
break;
}
}
}
}
double justification_width;
if (p_jst_flags.has_flag(JUSTIFICATION_CONSTRAIN_ELLIPSIS)) {
if (sd->overrun_trim_data.trim_pos >= 0) {
end_pos = sd->overrun_trim_data.trim_pos;
justification_width = sd->width_trimmed;
} else {
return Math::ceil(sd->width);
}
} else {
justification_width = sd->width;
}
if (p_jst_flags.has_flag(JUSTIFICATION_TRIM_EDGE_SPACES)) {
// Trim spaces.
while ((start_pos < end_pos) && ((sd->glyphs[start_pos].flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE || (sd->glyphs[start_pos].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD || (sd->glyphs[start_pos].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT)) {
justification_width -= sd->glyphs[start_pos].advance * sd->glyphs[start_pos].repeat;
sd->glyphs.write[start_pos].advance = 0;
start_pos += sd->glyphs[start_pos].count;
}
while ((start_pos < end_pos) && ((sd->glyphs[end_pos].flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE || (sd->glyphs[end_pos].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD || (sd->glyphs[end_pos].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT)) {
justification_width -= sd->glyphs[end_pos].advance * sd->glyphs[end_pos].repeat;
sd->glyphs.write[end_pos].advance = 0;
end_pos -= sd->glyphs[end_pos].count;
}
} else {
// Skip breaks, but do not reset size.
while ((start_pos < end_pos) && ((sd->glyphs[start_pos].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD)) {
start_pos += sd->glyphs[start_pos].count;
}
while ((start_pos < end_pos) && ((sd->glyphs[end_pos].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD)) {
end_pos -= sd->glyphs[end_pos].count;
}
}
int space_count = 0;
for (int i = start_pos; i <= end_pos; i++) {
const Glyph &gl = sd->glyphs[i];
if (gl.count > 0) {
if ((gl.flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE && (gl.flags & GRAPHEME_IS_PUNCTUATION) != GRAPHEME_IS_PUNCTUATION) {
space_count++;
}
}
}
if ((space_count > 0) && p_jst_flags.has_flag(JUSTIFICATION_WORD_BOUND)) {
double delta_width_per_space = (p_width - justification_width) / space_count;
for (int i = start_pos; i <= end_pos; i++) {
Glyph &gl = sd->glyphs.write[i];
if (gl.count > 0) {
if ((gl.flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE && (gl.flags & GRAPHEME_IS_PUNCTUATION) != GRAPHEME_IS_PUNCTUATION) {
double old_adv = gl.advance;
gl.advance = MAX(gl.advance + delta_width_per_space, Math::round(0.1 * gl.font_size));
justification_width += (gl.advance - old_adv);
}
}
}
}
if (Math::floor(p_width) < Math::floor(justification_width)) {
sd->fit_width_minimum_reached = true;
}
if (!p_jst_flags.has_flag(JUSTIFICATION_CONSTRAIN_ELLIPSIS)) {
sd->width = justification_width;
}
return Math::ceil(justification_width);
}
double TextServerFallback::_shaped_text_tab_align(const RID &p_shaped, const PackedFloat32Array &p_tab_stops) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, 0.0);
MutexLock lock(sd->mutex);
if (!sd->valid) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
if (!sd->line_breaks_valid) {
const_cast<TextServerFallback *>(this)->_shaped_text_update_breaks(p_shaped);
}
for (int i = 0; i < p_tab_stops.size(); i++) {
if (p_tab_stops[i] <= 0) {
return 0.0;
}
}
int tab_index = 0;
double off = 0.0;
int start, end, delta;
if (sd->para_direction == DIRECTION_LTR) {
start = 0;
end = sd->glyphs.size();
delta = +1;
} else {
start = sd->glyphs.size() - 1;
end = -1;
delta = -1;
}
Glyph *gl = sd->glyphs.ptrw();
for (int i = start; i != end; i += delta) {
if ((gl[i].flags & GRAPHEME_IS_TAB) == GRAPHEME_IS_TAB) {
double tab_off = 0.0;
while (tab_off <= off) {
tab_off += p_tab_stops[tab_index];
tab_index++;
if (tab_index >= p_tab_stops.size()) {
tab_index = 0;
}
}
double old_adv = gl[i].advance;
gl[i].advance = tab_off - off;
sd->width += gl[i].advance - old_adv;
off = 0;
continue;
}
off += gl[i].advance * gl[i].repeat;
}
return 0.0;
}
bool TextServerFallback::_shaped_text_update_breaks(const RID &p_shaped) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, false);
MutexLock lock(sd->mutex);
if (!sd->valid) {
_shaped_text_shape(p_shaped);
}
if (sd->line_breaks_valid) {
return true; // Nothing to do.
}
int sd_size = sd->glyphs.size();
Glyph *sd_glyphs = sd->glyphs.ptrw();
int c_punct_size = sd->custom_punct.length();
const char32_t *c_punct = sd->custom_punct.ptr();
for (int i = 0; i < sd_size; i++) {
if (sd_glyphs[i].count > 0) {
char32_t c = sd->text[sd_glyphs[i].start - sd->start];
if (c_punct_size == 0) {
if (is_punct(c) && c != 0x005F && c != ' ') {
sd_glyphs[i].flags |= GRAPHEME_IS_PUNCTUATION;
}
} else {
for (int j = 0; j < c_punct_size; j++) {
if (c_punct[j] == c) {
sd_glyphs[i].flags |= GRAPHEME_IS_PUNCTUATION;
break;
}
}
}
if (is_underscore(c)) {
sd->glyphs.write[i].flags |= GRAPHEME_IS_UNDERSCORE;
}
if (is_whitespace(c) && !is_linebreak(c)) {
sd_glyphs[i].flags |= GRAPHEME_IS_SPACE;
sd_glyphs[i].flags |= GRAPHEME_IS_BREAK_SOFT;
}
if (is_linebreak(c)) {
sd_glyphs[i].flags |= GRAPHEME_IS_SPACE;
sd_glyphs[i].flags |= GRAPHEME_IS_BREAK_HARD;
}
if (c == 0x0009 || c == 0x000b) {
sd_glyphs[i].flags |= GRAPHEME_IS_TAB;
}
i += (sd_glyphs[i].count - 1);
}
}
sd->line_breaks_valid = true;
return sd->line_breaks_valid;
}
bool TextServerFallback::_shaped_text_update_justification_ops(const RID &p_shaped) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, false);
MutexLock lock(sd->mutex);
if (!sd->valid) {
_shaped_text_shape(p_shaped);
}
if (!sd->line_breaks_valid) {
_shaped_text_update_breaks(p_shaped);
}
sd->justification_ops_valid = true; // Not supported by fallback server.
return true;
}
void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_line, double p_width, BitField<TextServer::TextOverrunFlag> p_trim_flags) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped_line);
ERR_FAIL_COND_MSG(!sd, "ShapedTextDataFallback invalid.");
MutexLock lock(sd->mutex);
if (!sd->valid) {
_shaped_text_shape(p_shaped_line);
}
sd->text_trimmed = false;
sd->overrun_trim_data.ellipsis_glyph_buf.clear();
bool add_ellipsis = p_trim_flags.has_flag(OVERRUN_ADD_ELLIPSIS);
bool cut_per_word = p_trim_flags.has_flag(OVERRUN_TRIM_WORD_ONLY);
bool enforce_ellipsis = p_trim_flags.has_flag(OVERRUN_ENFORCE_ELLIPSIS);
bool justification_aware = p_trim_flags.has_flag(OVERRUN_JUSTIFICATION_AWARE);
Glyph *sd_glyphs = sd->glyphs.ptrw();
if ((p_trim_flags & OVERRUN_TRIM) == OVERRUN_NO_TRIM || sd_glyphs == nullptr || p_width <= 0 || !(sd->width > p_width || enforce_ellipsis)) {
sd->overrun_trim_data.trim_pos = -1;
sd->overrun_trim_data.ellipsis_pos = -1;
return;
}
if (justification_aware && !sd->fit_width_minimum_reached) {
return;
}
Vector<ShapedTextDataFallback::Span> &spans = sd->spans;
if (sd->parent != RID()) {
ShapedTextDataFallback *parent_sd = shaped_owner.get_or_null(sd->parent);
ERR_FAIL_COND(!parent_sd->valid);
spans = parent_sd->spans;
}
if (spans.size() == 0) {
return;
}
int sd_size = sd->glyphs.size();
int last_gl_font_size = sd_glyphs[sd_size - 1].font_size;
// Find usable fonts, if fonts from the last glyph do not have required chars.
RID dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
if (!_font_has_char(dot_gl_font_rid, '.')) {
const Array &fonts = spans[spans.size() - 1].fonts;
for (int i = 0; i < fonts.size(); i++) {
if (_font_has_char(fonts[i], '.')) {
dot_gl_font_rid = fonts[i];
break;
}
}
}
RID whitespace_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
if (!_font_has_char(whitespace_gl_font_rid, '.')) {
const Array &fonts = spans[spans.size() - 1].fonts;
for (int i = 0; i < fonts.size(); i++) {
if (_font_has_char(fonts[i], ' ')) {
whitespace_gl_font_rid = fonts[i];
break;
}
}
}
int32_t dot_gl_idx = dot_gl_font_rid.is_valid() ? _font_get_glyph_index(dot_gl_font_rid, last_gl_font_size, '.', 0) : -10;
Vector2 dot_adv = dot_gl_font_rid.is_valid() ? _font_get_glyph_advance(dot_gl_font_rid, last_gl_font_size, dot_gl_idx) : Vector2();
int32_t whitespace_gl_idx = whitespace_gl_font_rid.is_valid() ? _font_get_glyph_index(whitespace_gl_font_rid, last_gl_font_size, ' ', 0) : -10;
Vector2 whitespace_adv = whitespace_gl_font_rid.is_valid() ? _font_get_glyph_advance(whitespace_gl_font_rid, last_gl_font_size, whitespace_gl_idx) : Vector2();
int ellipsis_width = 0;
if (add_ellipsis && whitespace_gl_font_rid.is_valid()) {
ellipsis_width = 3 * dot_adv.x + sd->extra_spacing[SPACING_GLYPH] + (cut_per_word ? whitespace_adv.x : 0);
}
int ell_min_characters = 6;
double width = sd->width;
int trim_pos = 0;
int ellipsis_pos = (enforce_ellipsis) ? 0 : -1;
int last_valid_cut = 0;
bool found = false;
if (enforce_ellipsis && (width + ellipsis_width <= p_width)) {
trim_pos = -1;
ellipsis_pos = sd_size;
} else {
for (int i = sd_size - 1; i != -1; i--) {
width -= sd_glyphs[i].advance * sd_glyphs[i].repeat;
if (sd_glyphs[i].count > 0) {
bool above_min_char_threshold = (i >= ell_min_characters);
if (width + (((above_min_char_threshold && add_ellipsis) || enforce_ellipsis) ? ellipsis_width : 0) <= p_width) {
if (cut_per_word && above_min_char_threshold) {
if ((sd_glyphs[i].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT) {
last_valid_cut = i;
found = true;
}
} else {
last_valid_cut = i;
found = true;
}
if (found) {
trim_pos = last_valid_cut;
if (add_ellipsis && (above_min_char_threshold || enforce_ellipsis) && width - ellipsis_width <= p_width) {
ellipsis_pos = trim_pos;
}
break;
}
}
}
}
}
sd->overrun_trim_data.trim_pos = trim_pos;
sd->overrun_trim_data.ellipsis_pos = ellipsis_pos;
if (trim_pos == 0 && enforce_ellipsis && add_ellipsis) {
sd->overrun_trim_data.ellipsis_pos = 0;
}
if ((trim_pos >= 0 && sd->width > p_width) || enforce_ellipsis) {
if (add_ellipsis && (ellipsis_pos > 0 || enforce_ellipsis)) {
// Insert an additional space when cutting word bound for aesthetics.
if (cut_per_word && (ellipsis_pos > 0)) {
Glyph gl;
gl.count = 1;
gl.advance = whitespace_adv.x;
gl.index = whitespace_gl_idx;
gl.font_rid = whitespace_gl_font_rid;
gl.font_size = last_gl_font_size;
gl.flags = GRAPHEME_IS_SPACE | GRAPHEME_IS_BREAK_SOFT | GRAPHEME_IS_VIRTUAL;
sd->overrun_trim_data.ellipsis_glyph_buf.append(gl);
}
// Add ellipsis dots.
if (dot_gl_idx != 0) {
Glyph gl;
gl.count = 1;
gl.repeat = 3;
gl.advance = dot_adv.x;
gl.index = dot_gl_idx;
gl.font_rid = dot_gl_font_rid;
gl.font_size = last_gl_font_size;
gl.flags = GRAPHEME_IS_PUNCTUATION | GRAPHEME_IS_VIRTUAL;
sd->overrun_trim_data.ellipsis_glyph_buf.append(gl);
}
}
sd->text_trimmed = true;
sd->width_trimmed = width + ((ellipsis_pos != -1) ? ellipsis_width : 0);
}
}
int64_t TextServerFallback::_shaped_text_get_trim_pos(const RID &p_shaped) const {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V_MSG(!sd, -1, "ShapedTextDataFallback invalid.");
MutexLock lock(sd->mutex);
return sd->overrun_trim_data.trim_pos;
}
int64_t TextServerFallback::_shaped_text_get_ellipsis_pos(const RID &p_shaped) const {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V_MSG(!sd, -1, "ShapedTextDataFallback invalid.");
MutexLock lock(sd->mutex);
return sd->overrun_trim_data.ellipsis_pos;
}
const Glyph *TextServerFallback::_shaped_text_get_ellipsis_glyphs(const RID &p_shaped) const {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V_MSG(!sd, nullptr, "ShapedTextDataFallback invalid.");
MutexLock lock(sd->mutex);
return sd->overrun_trim_data.ellipsis_glyph_buf.ptr();
}
int64_t TextServerFallback::_shaped_text_get_ellipsis_glyph_count(const RID &p_shaped) const {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V_MSG(!sd, 0, "ShapedTextDataFallback invalid.");
MutexLock lock(sd->mutex);
return sd->overrun_trim_data.ellipsis_glyph_buf.size();
}
bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, false);
MutexLock lock(sd->mutex);
if (sd->valid) {
return true;
}
if (sd->parent != RID()) {
full_copy(sd);
}
// Cleanup.
sd->justification_ops_valid = false;
sd->line_breaks_valid = false;
sd->ascent = 0.0;
sd->descent = 0.0;
sd->width = 0.0;
sd->glyphs.clear();
if (sd->text.length() == 0) {
sd->valid = true;
return true;
}
// "Shape" string.
for (int i = 0; i < sd->spans.size(); i++) {
const ShapedTextDataFallback::Span &span = sd->spans[i];
if (span.embedded_key != Variant()) {
// Embedded object.
if (sd->orientation == ORIENTATION_HORIZONTAL) {
sd->objects[span.embedded_key].rect.position.x = sd->width;
sd->width += sd->objects[span.embedded_key].rect.size.x;
} else {
sd->objects[span.embedded_key].rect.position.y = sd->width;
sd->width += sd->objects[span.embedded_key].rect.size.y;
}
Glyph gl;
gl.start = span.start;
gl.end = span.end;
gl.count = 1;
gl.index = 0;
gl.flags = GRAPHEME_IS_VALID | GRAPHEME_IS_EMBEDDED_OBJECT;
if (sd->orientation == ORIENTATION_HORIZONTAL) {
gl.advance = sd->objects[span.embedded_key].rect.size.x;
} else {
gl.advance = sd->objects[span.embedded_key].rect.size.y;
}
sd->glyphs.push_back(gl);
} else {
// Text span.
RID prev_font;
for (int j = span.start; j < span.end; j++) {
Glyph gl;
gl.start = j;
gl.end = j + 1;
gl.count = 1;
gl.font_size = span.font_size;
gl.index = (int32_t)sd->text[j - sd->start]; // Use codepoint.
if (gl.index == 0x0009 || gl.index == 0x000b) {
gl.index = 0x0020;
}
if (!sd->preserve_control && is_control(gl.index)) {
gl.index = 0x0020;
}
// Select first font which has character (font are already sorted by span language).
for (int k = 0; k < span.fonts.size(); k++) {
if (_font_has_char(span.fonts[k], gl.index)) {
gl.font_rid = span.fonts[k];
break;
}
}
if (!gl.font_rid.is_valid() && prev_font.is_valid()) {
if (_font_has_char(prev_font, gl.index)) {
gl.font_rid = prev_font;
}
}
if (!gl.font_rid.is_valid() && OS::get_singleton()->has_feature("system_fonts") && span.fonts.size() > 0) {
// Try system fallback.
RID fdef = span.fonts[0];
if (_font_is_allow_system_fallback(fdef)) {
String text = sd->text.substr(j, 1);
String font_name = _font_get_name(fdef);
BitField<FontStyle> font_style = _font_get_style(fdef);
int font_weight = _font_get_weight(fdef);
int font_stretch = _font_get_stretch(fdef);
Dictionary dvar = _font_get_variation_coordinates(fdef);
static int64_t wgth_tag = _name_to_tag("weight");
static int64_t wdth_tag = _name_to_tag("width");
static int64_t ital_tag = _name_to_tag("italic");
if (dvar.has(wgth_tag)) {
font_weight = dvar[wgth_tag].operator int();
}
if (dvar.has(wdth_tag)) {
font_stretch = dvar[wdth_tag].operator int();
}
if (dvar.has(ital_tag) && dvar[ital_tag].operator int() == 1) {
font_style.set_flag(TextServer::FONT_ITALIC);
}
String locale = (span.language.is_empty()) ? TranslationServer::get_singleton()->get_tool_locale() : span.language;
PackedStringArray fallback_font_name = OS::get_singleton()->get_system_font_path_for_text(font_name, text, locale, String(), font_weight, font_stretch, font_style & TextServer::FONT_ITALIC);
#ifdef GDEXTENSION
for (int fb = 0; fb < fallback_font_name.size(); fb++) {
const String &E = fallback_font_name[fb];
#else
for (const String &E : fallback_font_name) {
#endif
SystemFontKey key = SystemFontKey(E, font_style & TextServer::FONT_ITALIC, font_weight, font_stretch, fdef, this);
if (system_fonts.has(key)) {
const SystemFontCache &sysf_cache = system_fonts[key];
int best_score = 0;
int best_match = -1;
for (int face_idx = 0; face_idx < sysf_cache.var.size(); face_idx++) {
const SystemFontCacheRec &F = sysf_cache.var[face_idx];
if (unlikely(!_font_has_char(F.rid, text[0]))) {
continue;
}
BitField<FontStyle> style = _font_get_style(F.rid);
int weight = _font_get_weight(F.rid);
int stretch = _font_get_stretch(F.rid);
int score = (20 - Math::abs(weight - font_weight) / 50);
score += (20 - Math::abs(stretch - font_stretch) / 10);
if (bool(style & TextServer::FONT_ITALIC) == bool(font_style & TextServer::FONT_ITALIC)) {
score += 30;
}
if (score >= best_score) {
best_score = score;
best_match = face_idx;
}
if (best_score == 70) {
break;
}
}
if (best_match != -1) {
gl.font_rid = sysf_cache.var[best_match].rid;
}
}
if (!gl.font_rid.is_valid()) {
if (system_fonts.has(key)) {
const SystemFontCache &sysf_cache = system_fonts[key];
if (sysf_cache.max_var == sysf_cache.var.size()) {
// All subfonts already tested, skip.
continue;
}
}
if (!system_font_data.has(E)) {
system_font_data[E] = FileAccess::get_file_as_bytes(E);
}
const PackedByteArray &font_data = system_font_data[E];
SystemFontCacheRec sysf;
sysf.rid = _create_font();
_font_set_data_ptr(sysf.rid, font_data.ptr(), font_data.size());
Dictionary var = dvar;
// Select matching style from collection.
int best_score = 0;
int best_match = -1;
for (int face_idx = 0; face_idx < _font_get_face_count(sysf.rid); face_idx++) {
_font_set_face_index(sysf.rid, face_idx);
if (unlikely(!_font_has_char(sysf.rid, text[0]))) {
continue;
}
BitField<FontStyle> style = _font_get_style(sysf.rid);
int weight = _font_get_weight(sysf.rid);
int stretch = _font_get_stretch(sysf.rid);
int score = (20 - Math::abs(weight - font_weight) / 50);
score += (20 - Math::abs(stretch - font_stretch) / 10);
if (bool(style & TextServer::FONT_ITALIC) == bool(font_style & TextServer::FONT_ITALIC)) {
score += 30;
}
if (score >= best_score) {
best_score = score;
best_match = face_idx;
}
if (best_score == 70) {
break;
}
}
if (best_match == -1) {
_free_rid(sysf.rid);
continue;
} else {
_font_set_face_index(sysf.rid, best_match);
}
sysf.index = best_match;
// If it's a variable font, apply weight, stretch and italic coordinates to match requested style.
if (best_score != 70) {
Dictionary ftr = _font_supported_variation_list(sysf.rid);
if (ftr.has(wdth_tag)) {
var[wdth_tag] = font_stretch;
_font_set_stretch(sysf.rid, font_stretch);
}
if (ftr.has(wgth_tag)) {
var[wgth_tag] = font_weight;
_font_set_weight(sysf.rid, font_weight);
}
if ((font_style & TextServer::FONT_ITALIC) && ftr.has(ital_tag)) {
var[ital_tag] = 1;
_font_set_style(sysf.rid, _font_get_style(sysf.rid) | TextServer::FONT_ITALIC);
}
}
_font_set_antialiasing(sysf.rid, key.antialiasing);
_font_set_generate_mipmaps(sysf.rid, key.mipmaps);
_font_set_multichannel_signed_distance_field(sysf.rid, key.msdf);
_font_set_msdf_pixel_range(sysf.rid, key.msdf_range);
_font_set_msdf_size(sysf.rid, key.msdf_source_size);
_font_set_fixed_size(sysf.rid, key.fixed_size);
_font_set_force_autohinter(sysf.rid, key.force_autohinter);
_font_set_hinting(sysf.rid, key.hinting);
_font_set_subpixel_positioning(sysf.rid, key.subpixel_positioning);
_font_set_variation_coordinates(sysf.rid, var);
_font_set_oversampling(sysf.rid, key.oversampling);
_font_set_embolden(sysf.rid, key.embolden);
_font_set_transform(sysf.rid, key.transform);
if (system_fonts.has(key)) {
system_fonts[key].var.push_back(sysf);
} else {
SystemFontCache &sysf_cache = system_fonts[key];
sysf_cache.max_var = _font_get_face_count(sysf.rid);
sysf_cache.var.push_back(sysf);
}
gl.font_rid = sysf.rid;
}
break;
}
}
}
prev_font = gl.font_rid;
if (gl.font_rid.is_valid()) {
double scale = _font_get_scale(gl.font_rid, gl.font_size);
bool subpos = (scale != 1.0) || (_font_get_subpixel_positioning(gl.font_rid) == SUBPIXEL_POSITIONING_ONE_HALF) || (_font_get_subpixel_positioning(gl.font_rid) == SUBPIXEL_POSITIONING_ONE_QUARTER) || (_font_get_subpixel_positioning(gl.font_rid) == SUBPIXEL_POSITIONING_AUTO && gl.font_size <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE);
if (sd->text[j - sd->start] != 0 && !is_linebreak(sd->text[j - sd->start])) {
if (sd->orientation == ORIENTATION_HORIZONTAL) {
gl.advance = _font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x;
gl.x_off = 0;
gl.y_off = 0;
sd->ascent = MAX(sd->ascent, _font_get_ascent(gl.font_rid, gl.font_size));
sd->descent = MAX(sd->descent, _font_get_descent(gl.font_rid, gl.font_size));
} else {
gl.advance = _font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).y;
gl.x_off = -Math::round(_font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x * 0.5);
gl.y_off = _font_get_ascent(gl.font_rid, gl.font_size);
sd->ascent = MAX(sd->ascent, Math::round(_font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x * 0.5));
sd->descent = MAX(sd->descent, Math::round(_font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x * 0.5));
}
}
if (j < sd->end - 1) {
// Do not add extra spacing to the last glyph of the string.
if (is_whitespace(sd->text[j - sd->start])) {
gl.advance += sd->extra_spacing[SPACING_SPACE];
} else {
gl.advance += sd->extra_spacing[SPACING_GLYPH];
}
}
sd->upos = MAX(sd->upos, _font_get_underline_position(gl.font_rid, gl.font_size));
sd->uthk = MAX(sd->uthk, _font_get_underline_thickness(gl.font_rid, gl.font_size));
// Add kerning to previous glyph.
if (sd->glyphs.size() > 0) {
Glyph &prev_gl = sd->glyphs.write[sd->glyphs.size() - 1];
if (prev_gl.font_rid == gl.font_rid && prev_gl.font_size == gl.font_size) {
if (sd->orientation == ORIENTATION_HORIZONTAL) {
prev_gl.advance += _font_get_kerning(gl.font_rid, gl.font_size, Vector2i(prev_gl.index, gl.index)).x;
} else {
prev_gl.advance += _font_get_kerning(gl.font_rid, gl.font_size, Vector2i(prev_gl.index, gl.index)).y;
}
}
}
if (sd->orientation == ORIENTATION_HORIZONTAL && !subpos) {
gl.advance = Math::round(gl.advance);
}
} else if (sd->preserve_invalid || (sd->preserve_control && is_control(gl.index))) {
// Glyph not found, replace with hex code box.
if (sd->orientation == ORIENTATION_HORIZONTAL) {
gl.advance = get_hex_code_box_size(gl.font_size, gl.index).x;
sd->ascent = MAX(sd->ascent, get_hex_code_box_size(gl.font_size, gl.index).y);
} else {
gl.advance = get_hex_code_box_size(gl.font_size, gl.index).y;
sd->ascent = MAX(sd->ascent, Math::round(get_hex_code_box_size(gl.font_size, gl.index).x * 0.5));
sd->descent = MAX(sd->descent, Math::round(get_hex_code_box_size(gl.font_size, gl.index).x * 0.5));
}
}
sd->width += gl.advance;
sd->glyphs.push_back(gl);
}
}
}
// Align embedded objects to baseline.
_realign(sd);
sd->valid = true;
return sd->valid;
}
bool TextServerFallback::_shaped_text_is_ready(const RID &p_shaped) const {
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, false);
MutexLock lock(sd->mutex);
return sd->valid;
}
const Glyph *TextServerFallback::_shaped_text_get_glyphs(const RID &p_shaped) const {
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, nullptr);
MutexLock lock(sd->mutex);
if (!sd->valid) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
return sd->glyphs.ptr();
}
int64_t TextServerFallback::_shaped_text_get_glyph_count(const RID &p_shaped) const {
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, 0);
MutexLock lock(sd->mutex);
if (!sd->valid) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
return sd->glyphs.size();
}
const Glyph *TextServerFallback::_shaped_text_sort_logical(const RID &p_shaped) {
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, nullptr);
MutexLock lock(sd->mutex);
if (!sd->valid) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
return sd->glyphs.ptr(); // Already in the logical order, return as is.
}
Vector2i TextServerFallback::_shaped_text_get_range(const RID &p_shaped) const {
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, Vector2i());
MutexLock lock(sd->mutex);
return Vector2(sd->start, sd->end);
}
Array TextServerFallback::_shaped_text_get_objects(const RID &p_shaped) const {
Array ret;
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, ret);
MutexLock lock(sd->mutex);
for (const KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : sd->objects) {
ret.push_back(E.key);
}
return ret;
}
Rect2 TextServerFallback::_shaped_text_get_object_rect(const RID &p_shaped, const Variant &p_key) const {
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, Rect2());
MutexLock lock(sd->mutex);
ERR_FAIL_COND_V(!sd->objects.has(p_key), Rect2());
if (!sd->valid) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
return sd->objects[p_key].rect;
}
Size2 TextServerFallback::_shaped_text_get_size(const RID &p_shaped) const {
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, Size2());
MutexLock lock(sd->mutex);
if (!sd->valid) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
if (sd->orientation == TextServer::ORIENTATION_HORIZONTAL) {
return Size2(sd->width, sd->ascent + sd->descent + sd->extra_spacing[SPACING_TOP] + sd->extra_spacing[SPACING_BOTTOM]).ceil();
} else {
return Size2(sd->ascent + sd->descent + sd->extra_spacing[SPACING_TOP] + sd->extra_spacing[SPACING_BOTTOM], sd->width).ceil();
}
}
double TextServerFallback::_shaped_text_get_ascent(const RID &p_shaped) const {
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, 0.0);
MutexLock lock(sd->mutex);
if (!sd->valid) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
return sd->ascent + sd->extra_spacing[SPACING_TOP];
}
double TextServerFallback::_shaped_text_get_descent(const RID &p_shaped) const {
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, 0.0);
MutexLock lock(sd->mutex);
if (!sd->valid) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
return sd->descent + sd->extra_spacing[SPACING_BOTTOM];
}
double TextServerFallback::_shaped_text_get_width(const RID &p_shaped) const {
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, 0.0);
MutexLock lock(sd->mutex);
if (!sd->valid) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
return Math::ceil(sd->width);
}
double TextServerFallback::_shaped_text_get_underline_position(const RID &p_shaped) const {
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, 0.0);
MutexLock lock(sd->mutex);
if (!sd->valid) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
return sd->upos;
}
double TextServerFallback::_shaped_text_get_underline_thickness(const RID &p_shaped) const {
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, 0.0);
MutexLock lock(sd->mutex);
if (!sd->valid) {
const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped);
}
return sd->uthk;
}
String TextServerFallback::_string_to_upper(const String &p_string, const String &p_language) const {
return p_string.to_upper();
}
String TextServerFallback::_string_to_lower(const String &p_string, const String &p_language) const {
return p_string.to_lower();
}
PackedInt32Array TextServerFallback::_string_get_word_breaks(const String &p_string, const String &p_language, int64_t p_chars_per_line) const {
PackedInt32Array ret;
int line_start = 0;
int line_end = 0; // End of last word on current line.
int word_start = 0; // -1 if no word encountered. Leading spaces are part of a word.
int word_length = 0;
for (int i = 0; i < p_string.length(); i++) {
const char32_t c = p_string[i];
if (is_linebreak(c)) {
// Force newline.
ret.push_back(line_start);
ret.push_back(i);
line_start = i + 1;
line_end = line_start;
word_start = line_start;
word_length = 0;
} else if (c == 0xfffc) {
continue;
} else if ((is_punct(c) && c != 0x005F) || is_underscore(c) || c == '\t' || is_whitespace(c)) {
// A whitespace ends current word.
if (word_length > 0) {
line_end = i - 1;
word_start = -1;
word_length = 0;
}
} else {
if (word_start == -1) {
word_start = i;
if (p_chars_per_line <= 0) {
ret.push_back(line_start);
ret.push_back(line_end + 1);
line_start = word_start;
line_end = line_start;
}
}
word_length += 1;
if (p_chars_per_line > 0) {
if (word_length > p_chars_per_line) {
// Word too long: wrap before current character.
ret.push_back(line_start);
ret.push_back(i);
line_start = i;
line_end = i;
word_start = i;
word_length = 1;
} else if (i - line_start + 1 > p_chars_per_line) {
// Line too long: wrap after the last word.
ret.push_back(line_start);
ret.push_back(line_end + 1);
line_start = word_start;
line_end = line_start;
}
}
}
}
if (line_start < p_string.length()) {
ret.push_back(line_start);
ret.push_back(p_string.length());
}
return ret;
}
TextServerFallback::TextServerFallback() {
_insert_feature_sets();
};
void TextServerFallback::_cleanup() {
for (const KeyValue<SystemFontKey, SystemFontCache> &E : system_fonts) {
const Vector<SystemFontCacheRec> &sysf_cache = E.value.var;
for (const SystemFontCacheRec &F : sysf_cache) {
_free_rid(F.rid);
}
}
system_fonts.clear();
system_font_data.clear();
}
TextServerFallback::~TextServerFallback() {
#ifdef MODULE_FREETYPE_ENABLED
if (ft_library != nullptr) {
FT_Done_FreeType(ft_library);
}
#endif
};