Skip to content

Instantly share code, notes, and snippets.

@migueldeicaza
Last active August 19, 2025 14:28
Show Gist options
  • Select an option

  • Save migueldeicaza/e2e657a7f67770e364b2d666d63a8801 to your computer and use it in GitHub Desktop.

Select an option

Save migueldeicaza/e2e657a7f67770e364b2d666d63a8801 to your computer and use it in GitHub Desktop.
Ugly and contains two unrelated things, a CoreText backend (which I broke) and using CoreText-fonts to render fonts in the regular codepath.
diff --git a/editor/project_manager/project_manager.cpp b/editor/project_manager/project_manager.cpp
index 507931c744..ac2539a3ba 100644
--- a/editor/project_manager/project_manager.cpp
+++ b/editor/project_manager/project_manager.cpp
@@ -1498,7 +1498,7 @@ ProjectManager::ProjectManager() {
filter_option->connect(SceneStringName(item_selected), callable_mp(this, &ProjectManager::_on_order_option_changed));
hb->add_child(filter_option);
- filter_option->add_item(TTRC("Last Edited"));
+ filter_option->add_item(TTRC("😀Last Edited"));
filter_option->add_item(TTRC("Name"));
filter_option->add_item(TTRC("Path"));
filter_option->add_item(TTRC("Tags"));
diff --git a/editor/themes/editor_fonts.cpp b/editor/themes/editor_fonts.cpp
index c972ded2dd..72de0c8709 100644
--- a/editor/themes/editor_fonts.cpp
+++ b/editor/themes/editor_fonts.cpp
@@ -97,6 +97,41 @@ Ref<FontFile> load_internal_font(const uint8_t *p_data, size_t p_size, TextServe
return font;
}
+// Load a system font by name using OperatingSystemFont.
+// This uses the operating system's font APIs (e.g., CoreText on macOS) to load fonts
+// installed on the system, rather than loading font files from disk.
+// Example usage:
+// auto helvetica = load_operating_system_font("Helvetica");
+// auto arial_bold = load_operating_system_font("Arial Bold");
+Ref<OperatingSystemFont> load_operating_system_font(const String &p_font_name, TextServer::Hinting p_hinting, TextServer::FontAntialiasing p_aa, bool p_autohint, TextServer::SubpixelPositioning p_font_subpixel_positioning, bool p_font_disable_embedded_bitmaps, TypedArray<Font> *r_fallbacks) {
+ Ref<OperatingSystemFont> font;
+ font.instantiate();
+
+ font->set_font_name(p_font_name);
+ font->set_antialiasing(p_aa);
+ font->set_hinting(p_hinting);
+ font->set_force_autohinter(p_autohint);
+ font->set_subpixel_positioning(p_font_subpixel_positioning);
+ font->set_disable_embedded_bitmaps(p_font_disable_embedded_bitmaps);
+
+ if (r_fallbacks != nullptr) {
+ r_fallbacks->push_back(font);
+ }
+ return font;
+}
+
+Ref<OperatingSystemFont> load_operating_system_font(const String &p_font_name) {
+ return load_operating_system_font(
+ p_font_name,
+ TextServer::HINTING_LIGHT,
+ TextServer::FONT_ANTIALIASING_GRAY,
+ true, // autohint
+ TextServer::SUBPIXEL_POSITIONING_AUTO,
+ true, // disable embedded bitmaps
+ nullptr // no fallbacks
+ );
+}
+
Ref<FontVariation> make_bold_font(const Ref<Font> &p_font, double p_embolden, TypedArray<Font> *r_fallbacks = nullptr) {
Ref<FontVariation> font_var;
font_var.instantiate();
@@ -153,7 +188,8 @@ void editor_register_fonts(const Ref<Theme> &p_theme) {
const int default_font_size = int(EDITOR_GET("interface/editor/main_font_size")) * EDSCALE;
const float embolden_strength = 0.6;
- Ref<Font> default_font = load_internal_font(_font_NotoSans_Regular, _font_NotoSans_Regular_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, false);
+ //Ref<Font> default_font = load_internal_font(_font_NotoSans_Regular, _font_NotoSans_Regular_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, false);
+ Ref<Font> default_font = load_operating_system_font("SF Pro", font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, nullptr);
Ref<Font> default_font_msdf = load_internal_font(_font_NotoSans_Regular, _font_NotoSans_Regular_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, font_allow_msdf);
String noto_cjk_path;
@@ -169,6 +205,7 @@ void editor_register_fonts(const Ref<Theme> &p_theme) {
}
TypedArray<Font> fallbacks;
+ #if false
Ref<FontFile> arabic_font = load_internal_font(_font_Vazirmatn_Regular, _font_Vazirmatn_Regular_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, false, &fallbacks);
Ref<FontFile> bengali_font = load_internal_font(_font_NotoSansBengaliUI_Regular, _font_NotoSansBengaliUI_Regular_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, false, &fallbacks);
Ref<FontFile> devanagari_font = load_internal_font(_font_NotoSansDevanagariUI_Regular, _font_NotoSansDevanagariUI_Regular_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, false, &fallbacks);
@@ -187,11 +224,13 @@ void editor_register_fonts(const Ref<Theme> &p_theme) {
Ref<FontFile> japanese_font = load_internal_font(_font_DroidSansJapanese, _font_DroidSansJapanese_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, false, &fallbacks);
default_font->set_fallbacks(fallbacks);
default_font_msdf->set_fallbacks(fallbacks);
-
- Ref<FontFile> default_font_bold = load_internal_font(_font_NotoSans_Bold, _font_NotoSans_Bold_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, false);
- Ref<FontFile> default_font_bold_msdf = load_internal_font(_font_NotoSans_Bold, _font_NotoSans_Bold_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, font_allow_msdf);
+#endif
+ //Ref<FontFile> default_font_bold = load_internal_font(_font_NotoSans_Bold, _font_NotoSans_Bold_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, false);
+ Ref<Font> default_font_bold = load_operating_system_font("Palatino Bold", font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, nullptr);
+ Ref<Font> default_font_bold_msdf = load_internal_font(_font_NotoSans_Bold, _font_NotoSans_Bold_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, font_allow_msdf);
TypedArray<Font> fallbacks_bold;
+ #if false
Ref<FontFile> arabic_font_bold = load_internal_font(_font_Vazirmatn_Bold, _font_Vazirmatn_Bold_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, false, &fallbacks_bold);
Ref<FontFile> bengali_font_bold = load_internal_font(_font_NotoSansBengaliUI_Bold, _font_NotoSansBengaliUI_Bold_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, false, &fallbacks_bold);
Ref<FontFile> devanagari_font_bold = load_internal_font(_font_NotoSansDevanagariUI_Bold, _font_NotoSansDevanagariUI_Bold_size, font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, false, &fallbacks_bold);
@@ -209,6 +248,7 @@ void editor_register_fonts(const Ref<Theme> &p_theme) {
Ref<FontVariation> fallback_font_bold = make_bold_font(fallback_font, embolden_strength, &fallbacks_bold);
Ref<FontVariation> japanese_font_bold = make_bold_font(japanese_font, embolden_strength, &fallbacks_bold);
+ #if true
if (OS::get_singleton()->has_feature("system_fonts")) {
PackedStringArray emoji_font_names = {
"Apple Color Emoji",
@@ -222,11 +262,21 @@ void editor_register_fonts(const Ref<Theme> &p_theme) {
fallbacks.push_back(emoji_font);
fallbacks_bold.push_back(emoji_font);
}
+ #else
+ Ref<SystemFont> emoji_font = load_operating_system_font("Apple Color Emoji", font_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, nullptr);
+ fallbacks.push_back(emoji_font);
+ fallbacks_bold.push_back(emoji_font);
+ #endif
default_font_bold->set_fallbacks(fallbacks_bold);
default_font_bold_msdf->set_fallbacks(fallbacks_bold);
-
- Ref<FontFile> default_font_mono = load_internal_font(_font_JetBrainsMono_Regular, _font_JetBrainsMono_Regular_size, font_mono_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps);
+#endif
+ //Ref<FontFile> default_font_mono = load_internal_font(_font_JetBrainsMono_Regular, _font_JetBrainsMono_Regular_size, font_mono_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps);
+ Ref<Font> default_font_mono = load_operating_system_font("SF Mono", font_mono_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, nullptr);
+ if (!default_font_mono.is_valid()) {
+ // Fallback to another font if "SF Mono" is not available.
+ default_font_mono = load_operating_system_font("Courier New", font_mono_hinting, font_antialiasing, true, font_subpixel_positioning, font_disable_embedded_bitmaps, nullptr);
+ }
default_font_mono->set_fallbacks(fallbacks);
// Init base font configs and load custom fonts.
diff --git a/editor/themes/editor_fonts.h b/editor/themes/editor_fonts.h
index d252d35b90..961de44993 100644
--- a/editor/themes/editor_fonts.h
+++ b/editor/themes/editor_fonts.h
@@ -32,4 +32,16 @@
#include "scene/resources/theme.h"
+class Font;
+class OperatingSystemFont;
+template <typename T>
+class Ref;
+template <typename T>
+class TypedArray;
+
+Ref<OperatingSystemFont> load_operating_system_font(const String &p_font_name, TextServer::Hinting p_hinting, TextServer::FontAntialiasing p_aa, bool p_autohint, TextServer::SubpixelPositioning p_font_subpixel_positioning, bool p_font_disable_embedded_bitmaps, TypedArray<Font> *r_fallbacks = nullptr);
+
+// Convenience function with common defaults for loading system fonts
+Ref<OperatingSystemFont> load_operating_system_font(const String &p_font_name);
+
void editor_register_fonts(const Ref<Theme> &p_theme);
diff --git a/modules/text_server_adv/SCsub b/modules/text_server_adv/SCsub
index 1167fbc187..fb90e8689d 100644
--- a/modules/text_server_adv/SCsub
+++ b/modules/text_server_adv/SCsub
@@ -12,8 +12,13 @@ env_text_server_adv = env_modules.Clone()
thirdparty_obj = []
freetype_enabled = "freetype" in env.module_list
+coretext_enabled = env.get("coretext", False) and env["platform"] == "macos"
msdfgen_enabled = "msdfgen" in env.module_list
+if coretext_enabled:
+ env_text_server_adv.Append(CPPDEFINES=["CORETEXT_ENABLED"])
+ env_text_server_adv.Append(FRAMEWORKS=["CoreText", "CoreGraphics", "CoreFoundation"])
+
if "svg" in env.module_list:
env_text_server_adv.Prepend(
CPPEXTPATH=[
@@ -118,6 +123,13 @@ if env["builtin_harfbuzz"]:
thirdparty_sources += [
"src/hb-graphite2.cc",
]
+
+ if coretext_enabled:
+ thirdparty_sources += [
+ "src/hb-coretext.cc",
+ "src/hb-coretext-font.cc",
+ "src/hb-coretext-shape.cc",
+ ]
thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
env_harfbuzz.Prepend(CPPEXTPATH=["#thirdparty/harfbuzz/src"])
@@ -151,6 +163,14 @@ if env["builtin_harfbuzz"]:
if env["builtin_graphite"] and env["graphite"]:
env_harfbuzz.Prepend(CPPEXTPATH=["#thirdparty/graphite/include"])
env_harfbuzz.Append(CCFLAGS=["-DGRAPHITE2_STATIC"])
+
+ if coretext_enabled:
+ env_harfbuzz.Append(
+ CCFLAGS=[
+ "-DHAVE_CORETEXT",
+ ]
+ )
+ env_harfbuzz.Append(FRAMEWORKS=["CoreText", "CoreGraphics", "CoreFoundation"])
if env["platform"] in ["android", "linuxbsd", "web"]:
env_harfbuzz.Append(CCFLAGS=["-DHAVE_PTHREAD"])
@@ -533,7 +553,13 @@ if env["builtin_freetype"] and freetype_enabled:
if env["builtin_graphite"] and freetype_enabled and env["graphite"]:
env_text_server_adv.Prepend(CPPEXTPATH=["#thirdparty/graphite/include"])
-env_text_server_adv.add_source_files(module_obj, "*.cpp")
+if coretext_enabled and not freetype_enabled:
+ # Exclude SVG-in-OT file when using CoreText without FreeType since it depends on FreeType
+ source_files = Glob("*.cpp")
+ filtered_sources = [f for f in source_files if not str(f).endswith("thorvg_svg_in_ot.cpp")]
+ env_text_server_adv.add_source_files(module_obj, filtered_sources)
+else:
+ env_text_server_adv.add_source_files(module_obj, "*.cpp")
env.modules_sources += module_obj
# Needed to force rebuilding the module files when the thirdparty library is updated.
diff --git a/modules/text_server_adv/config.py b/modules/text_server_adv/config.py
index 08051398d1..f0d9990e06 100644
--- a/modules/text_server_adv/config.py
+++ b/modules/text_server_adv/config.py
@@ -1,4 +1,5 @@
def can_build(env, platform):
+ # Always include FreeType dependency, CoreText is optional on macOS
env.module_add_dependencies("text_server_adv", ["freetype", "msdfgen", "svg"], True)
return True
@@ -6,9 +7,14 @@ def can_build(env, platform):
def get_opts(platform):
from SCons.Variables import BoolVariable
- return [
+ opts = [
BoolVariable("graphite", "Enable SIL Graphite smart fonts support", True),
]
+
+ if platform == "macos":
+ opts.append(BoolVariable("coretext", "Use Apple's CoreText instead of FreeType for font rendering", False))
+
+ return opts
def configure(env):
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index b8a9e44aaa..e340ed9dd7 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -65,7 +65,7 @@ using namespace godot;
// Thirdparty headers.
-#ifdef MODULE_MSDFGEN_ENABLED
+#if defined(MODULE_MSDFGEN_ENABLED) && defined(MODULE_FREETYPE_ENABLED)
#include <core/EdgeHolder.h>
#include <core/ShapeDistanceFinder.h>
#include <core/contour-combiners.h>
@@ -353,10 +353,10 @@ bool TextServerAdvanced::_has_feature(Feature p_feature) const {
case FEATURE_KASHIDA_JUSTIFICATION:
case FEATURE_BREAK_ITERATORS:
case FEATURE_FONT_BITMAP:
-#ifdef MODULE_FREETYPE_ENABLED
+#if defined(MODULE_FREETYPE_ENABLED) || defined(CORETEXT_ENABLED)
case FEATURE_FONT_DYNAMIC:
#endif
-#ifdef MODULE_MSDFGEN_ENABLED
+#if defined(MODULE_MSDFGEN_ENABLED) && defined(MODULE_FREETYPE_ENABLED)
case FEATURE_FONT_MSDF:
#endif
case FEATURE_FONT_VARIABLE:
@@ -381,10 +381,10 @@ String TextServerAdvanced::_get_name() const {
int64_t TextServerAdvanced::_get_features() const {
int64_t interface_features = FEATURE_SIMPLE_LAYOUT | FEATURE_BIDI_LAYOUT | FEATURE_VERTICAL_LAYOUT | FEATURE_SHAPING | FEATURE_KASHIDA_JUSTIFICATION | FEATURE_BREAK_ITERATORS | FEATURE_FONT_BITMAP | FEATURE_FONT_VARIABLE | FEATURE_CONTEXT_SENSITIVE_CASE_CONVERSION | FEATURE_USE_SUPPORT_DATA;
-#ifdef MODULE_FREETYPE_ENABLED
+#if defined(MODULE_FREETYPE_ENABLED) || defined(CORETEXT_ENABLED)
interface_features |= FEATURE_FONT_DYNAMIC;
#endif
-#ifdef MODULE_MSDFGEN_ENABLED
+#if defined(MODULE_MSDFGEN_ENABLED) && defined(MODULE_FREETYPE_ENABLED)
interface_features |= FEATURE_FONT_MSDF;
#endif
@@ -905,7 +905,7 @@ _FORCE_INLINE_ TextServerAdvanced::FontTexturePosition TextServerAdvanced::find_
return ret;
}
-#ifdef MODULE_MSDFGEN_ENABLED
+#if defined(MODULE_MSDFGEN_ENABLED) && defined(MODULE_FREETYPE_ENABLED)
struct MSContext {
msdfgen::Point2 position;
@@ -1210,6 +1210,90 @@ _FORCE_INLINE_ TextServerAdvanced::FontGlyph TextServerAdvanced::rasterize_bitma
}
#endif
+#ifdef CORETEXT_ENABLED
+_FORCE_INLINE_ TextServerAdvanced::FontGlyph TextServerAdvanced::rasterize_coretext_bitmap(FontForSizeAdvanced *p_data, int p_rect_margin, CGImageRef p_image, const Vector2 &p_advance, const CGRect &p_bbox) const {
+ FontGlyph chr;
+ chr.advance = p_advance;
+ chr.found = true;
+
+ size_t w = CGImageGetWidth(p_image);
+ size_t h = CGImageGetHeight(p_image);
+
+ if (w == 0 || h == 0) {
+ chr.texture_idx = -1;
+ chr.uv_rect = Rect2();
+ chr.rect = Rect2();
+ return chr;
+ }
+
+ int mw = w + p_rect_margin * 4;
+ int mh = h + p_rect_margin * 4;
+
+ FontTexturePosition tex_pos = find_texture_pos_for_glyph(p_data, 2, Image::FORMAT_LA8, mw, mh, false);
+ ERR_FAIL_COND_V(tex_pos.index < 0, FontGlyph());
+
+ // Ensure we have enough textures
+ if (tex_pos.index >= p_data->textures.size()) {
+ p_data->textures.resize(tex_pos.index + 1);
+ }
+
+ ShelfPackTexture &tex = p_data->textures.write[tex_pos.index];
+
+ if (!tex.image.is_valid()) {
+ tex.image.instantiate();
+ tex.image->initialize_data(tex.texture_w, tex.texture_h, false, Image::FORMAT_LA8);
+ }
+
+ // Get the image data from CoreGraphics
+ CFDataRef imageData = CGDataProviderCopyData(CGImageGetDataProvider(p_image));
+ if (imageData) {
+ const uint8_t *source_data = CFDataGetBytePtr(imageData);
+ size_t bytes_per_row = CGImageGetBytesPerRow(p_image);
+ size_t bits_per_pixel = CGImageGetBitsPerPixel(p_image);
+ size_t bits_per_component = CGImageGetBitsPerComponent(p_image);
+
+ // Debug: Check the actual image format
+ // Expected: 8 bits per pixel (grayscale), 8 bits per component
+ if (bits_per_pixel == 8 && bits_per_component == 8) {
+ // Copy the CoreText rendered glyph to our texture (grayscale data used as alpha)
+ for (int y = 0; y < h; y++) {
+ for (int x = 0; x < w; x++) {
+ int src_index = y * bytes_per_row + x;
+ uint8_t grayscale = source_data[src_index];
+
+ // Use grayscale value as alpha (white text on transparent background)
+ tex.image->set_pixel(tex_pos.x + x + p_rect_margin, tex_pos.y + y + p_rect_margin, Color(1.0, 1.0, 1.0, grayscale / 255.0));
+ }
+ }
+ } else {
+ // Fallback for unexpected format - assume it's grayscale
+ ERR_PRINT(vformat("Unexpected CoreText image format: %d bits per pixel, %d bits per component", (int)bits_per_pixel, (int)bits_per_component));
+ for (int y = 0; y < h; y++) {
+ for (int x = 0; x < w; x++) {
+ int src_index = y * bytes_per_row + x;
+ uint8_t grayscale = source_data[src_index];
+
+ // Use grayscale value as alpha (white text on transparent background)
+ tex.image->set_pixel(tex_pos.x + x + p_rect_margin, tex_pos.y + y + p_rect_margin, Color(1.0, 1.0, 1.0, grayscale / 255.0));
+ }
+ }
+ }
+
+ CFRelease(imageData);
+ }
+
+ 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);
+ // Use CoreText bbox directly as bearing values
+ // bbox.origin gives us the bottom-left corner of the glyph relative to its origin
+ chr.rect.position = Vector2(p_bbox.origin.x - p_rect_margin, -(p_bbox.origin.y + p_bbox.size.height) - p_rect_margin) * p_data->scale;
+ chr.rect.size = chr.uv_rect.size * p_data->scale;
+ return chr;
+}
+#endif
+
/*************************************************************************/
/* Font Cache */
/*************************************************************************/
@@ -1232,8 +1316,73 @@ bool TextServerAdvanced::_ensure_glyph(FontAdvanced *p_font_data, const Vector2i
return true;
}
-#ifdef MODULE_FREETYPE_ENABLED
FontGlyph gl;
+
+ if (_should_use_coretext()) {
+#ifdef CORETEXT_ENABLED
+ // CoreText backend implementation
+ if (fd->ct_font) {
+ CGGlyph cg_glyph = (CGGlyph)glyph_index;
+ CGSize advance;
+ CTFontGetAdvancesForGlyphs(fd->ct_font, kCTFontOrientationDefault, &cg_glyph, &advance, 1);
+
+ // Get glyph bounding box
+ CGRect bbox;
+ CTFontGetBoundingRectsForGlyphs(fd->ct_font, kCTFontOrientationDefault, &cg_glyph, &bbox, 1);
+
+ if (bbox.size.width > 0 && bbox.size.height > 0) {
+ // Create bitmap context to render glyph
+ int width = (int)ceil(bbox.size.width) + 4; // Add padding
+ int height = (int)ceil(bbox.size.height) + 4;
+
+ CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericGray);
+ CGContextRef context = CGBitmapContextCreate(nullptr, width, height, 8, width, colorSpace, kCGImageAlphaNone);
+ CGColorSpaceRelease(colorSpace);
+
+ if (context) {
+ // Set up context for rendering
+ CGContextSetTextMatrix(context, CGAffineTransformIdentity);
+ CGContextSetTextDrawingMode(context, kCGTextFill);
+ CGContextSetGrayFillColor(context, 1.0, 1.0); // White fill with full alpha
+
+ // Position and render the glyph
+ CGPoint position = CGPointMake(-bbox.origin.x + 2, -bbox.origin.y + 2);
+ CTFontDrawGlyphs(fd->ct_font, &cg_glyph, &position, 1, context);
+
+ // Get bitmap data from context
+ const uint8_t *bitmap_data = (const uint8_t*)CGBitmapContextGetData(context);
+ if (bitmap_data) {
+ // Convert CoreText bitmap to Godot's FontGlyph format
+ gl.advance = Vector2(advance.width, advance.height);
+ gl.rect = Rect2(bbox.origin.x, bbox.origin.y, width, height);
+ gl.found = true;
+
+ // For now, don't store texture data - this would need proper texture management
+ // This is a simplified implementation for demonstration
+ }
+
+ CGContextRelease(context);
+ }
+
+ if (!gl.found) {
+ // Fallback for failed rasterization
+ gl.advance = Vector2(advance.width, advance.height);
+ gl.found = true;
+ }
+ } else {
+ // Handle zero-size glyphs (like spaces)
+ gl.advance = Vector2(advance.width, advance.height) * fd->scale;
+ gl.found = true;
+ }
+ }
+
+ E = fd->glyph_map.insert(p_glyph, gl);
+ r_glyph = E->value;
+ return gl.found;
+#endif
+ } else {
+#ifdef MODULE_FREETYPE_ENABLED
+ // FreeType backend implementation
if (fd->face) {
FT_Int32 flags = FT_LOAD_DEFAULT;
@@ -1332,7 +1481,7 @@ bool TextServerAdvanced::_ensure_glyph(FontAdvanced *p_font_data, const Vector2i
}
if (!error) {
if (p_font_data->msdf) {
-#ifdef MODULE_MSDFGEN_ENABLED
+#if defined(MODULE_MSDFGEN_ENABLED) && defined(MODULE_FREETYPE_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();
@@ -1376,6 +1525,9 @@ bool TextServerAdvanced::_ensure_glyph(FontAdvanced *p_font_data, const Vector2i
return gl.found;
}
#endif
+ } // End runtime backend selection
+
+ // Fallback if no backend handled the glyph
E = fd->glyph_map.insert(p_glyph, FontGlyph());
r_glyph = E->value;
return false;
@@ -1399,8 +1551,151 @@ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_font_data, const
FontForSizeAdvanced *fd = memnew(FontForSizeAdvanced);
fd->size = p_size;
- if (p_font_data->data_ptr && (p_font_data->data_size > 0)) {
+ if ((p_font_data->data_ptr && (p_font_data->data_size > 0)) || (_should_use_coretext() && !p_font_data->system_font_name.is_empty())) {
// Init dynamic font.
+ if (_should_use_coretext()) {
+#ifdef CORETEXT_ENABLED
+ // Check if this is a system font or a font with data
+ if (!p_font_data->system_font_name.is_empty()) {
+ // Handle system fonts by name - use the CoreText font from the FontAdvanced object
+ if (p_font_data->ct_font) {
+ double sz = double(fd->size.x) / 64.0;
+ if (p_font_data->msdf) {
+ sz = p_font_data->msdf_source_size;
+ }
+
+ // Create a CoreText font for this specific size
+ fd->ct_font = CTFontCreateCopyWithAttributes(p_font_data->ct_font, sz, nullptr, nullptr);
+ if (!fd->ct_font) {
+ memdelete(fd);
+ if (p_silent) {
+ return false;
+ } else {
+ ERR_FAIL_V_MSG(false, "CoreText: Failed to create sized CTFont!");
+ }
+ }
+
+ fd->scale = 1.0; // CoreText handles scaling internally
+
+ // Create HarfBuzz font
+ fd->hb_handle = hb_coretext_font_create(fd->ct_font);
+ hb_font_set_scale(fd->hb_handle, sz * 64, sz * 64);
+
+ // Get font metrics
+ fd->ascent = CTFontGetAscent(fd->ct_font);
+ fd->descent = CTFontGetDescent(fd->ct_font);
+ fd->underline_position = -CTFontGetUnderlinePosition(fd->ct_font);
+ fd->underline_thickness = CTFontGetUnderlineThickness(fd->ct_font);
+ } else {
+ memdelete(fd);
+ if (p_silent) {
+ return false;
+ } else {
+ ERR_FAIL_V_MSG(false, "CoreText: System font not properly initialized!");
+ }
+ }
+ } else {
+ // Use CoreText backend
+ // Create CoreText font from data
+ CGDataProviderRef data_provider = CGDataProviderCreateWithData(nullptr, p_font_data->data_ptr, p_font_data->data_size, nullptr);
+ print_line(p_font_data->font_name);
+ if(p_font_data->font_name == "Apple Color Emoji") {
+ //print_line("Emoji font detected");
+ }
+ //printf("Created a data provider with size: %zu bytes\n", p_font_data->data_size);
+ if (!data_provider) {
+ memdelete(fd);
+ if (p_silent) {
+ return false;
+ } else {
+ ERR_FAIL_V_MSG(false, "CoreText: Failed to create data provider!");
+ }
+ }
+
+ fd->cg_font = CGFontCreateWithDataProvider(data_provider);
+ CGDataProviderRelease(data_provider);
+
+ if (!fd->cg_font) {
+ memdelete(fd);
+ if (p_silent) {
+ return false;
+ } else {
+ ERR_FAIL_V_MSG(false, "CoreText: Failed to create CGFont!");
+ }
+ }
+
+ double sz = double(fd->size.x) / 64.0;
+ if (p_font_data->msdf) {
+ sz = p_font_data->msdf_source_size;
+ }
+
+ fd->ct_font = CTFontCreateWithGraphicsFont(fd->cg_font, sz, nullptr, nullptr);
+ if (!fd->ct_font) {
+ CFRelease(fd->cg_font);
+ fd->cg_font = nullptr;
+ memdelete(fd);
+ if (p_silent) {
+ return false;
+ } else {
+ ERR_FAIL_V_MSG(false, "CoreText: Failed to create CTFont!");
+ }
+ }
+
+ fd->scale = 1.0; // CoreText handles scaling internally
+
+ // Create HarfBuzz font
+ fd->hb_handle = hb_coretext_font_create(fd->ct_font);
+ // Set HarfBuzz font scale - CoreText units need to be converted to HarfBuzz units (26.6 fractional pixels)
+ hb_font_set_scale(fd->hb_handle, sz * 64, sz * 64);
+
+ // Get font metrics
+ fd->ascent = CTFontGetAscent(fd->ct_font);
+ fd->descent = CTFontGetDescent(fd->ct_font);
+ fd->underline_position = -CTFontGetUnderlinePosition(fd->ct_font);
+ fd->underline_thickness = CTFontGetUnderlineThickness(fd->ct_font);
+
+ if (!p_font_data->face_init) {
+ // Get font name
+ CFStringRef font_name = CTFontCopyFamilyName(fd->ct_font);
+ if (font_name) {
+ char buffer[256];
+ if (CFStringGetCString(font_name, buffer, sizeof(buffer), kCFStringEncodingUTF8)) {
+ p_font_data->font_name = String::utf8(buffer);
+ }
+ CFRelease(font_name);
+ }
+
+ // Get style name
+ CFStringRef style_name = CTFontCopyName(fd->ct_font, kCTFontStyleNameKey);
+ if (style_name) {
+ char buffer[256];
+ if (CFStringGetCString(style_name, buffer, sizeof(buffer), kCFStringEncodingUTF8)) {
+ p_font_data->style_name = String::utf8(buffer);
+ }
+ CFRelease(style_name);
+ }
+
+ // Get font weight and style flags
+ 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;
+
+ CTFontSymbolicTraits traits = CTFontGetSymbolicTraits(fd->ct_font);
+ if (traits & kCTFontTraitBold) {
+ p_font_data->style_flags.set_flag(FONT_BOLD);
+ }
+ if (traits & kCTFontTraitItalic) {
+ p_font_data->style_flags.set_flag(FONT_ITALIC);
+ }
+ if (traits & kCTFontTraitMonoSpace) {
+ p_font_data->style_flags.set_flag(FONT_FIXED_WIDTH);
+ }
+
+ p_font_data->face_init = true;
+ }
+ } // End else block for font data
+#endif
+ } else {
#ifdef MODULE_FREETYPE_ENABLED
int error = 0;
{
@@ -1415,7 +1710,7 @@ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_font_data, const
ERR_FAIL_V_MSG(false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'.");
}
}
-#ifdef MODULE_SVG_ENABLED
+#if defined(MODULE_SVG_ENABLED) && defined(MODULE_FREETYPE_ENABLED)
FT_Property_Set(ft_library, "ot-svg", "svg-hooks", get_tvg_svg_in_ot_hooks());
#endif
}
@@ -1918,12 +2213,14 @@ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_font_data, const
hb_font_set_variations(fd->hb_handle, hb_vars.is_empty() ? nullptr : &hb_vars[0], hb_vars.size());
FT_Done_MM_Var(ft_library, amaster);
}
-#else
+#endif
+ } // End FreeType backend
+#if !defined(MODULE_FREETYPE_ENABLED) && !defined(CORETEXT_ENABLED)
memdelete(fd);
if (p_silent) {
return false;
} else {
- ERR_FAIL_V_MSG(false, "FreeType: Can't load dynamic font, engine is compiled without FreeType support!");
+ ERR_FAIL_V_MSG(false, "No font backend available (FreeType or CoreText)!");
}
#endif
} else {
@@ -2043,6 +2340,60 @@ RID TextServerAdvanced::_create_font_linked_variation(const RID &p_font_rid) {
return font_var_owner.make_rid(new_fdv);
}
+RID TextServerAdvanced::_create_font_system(const String &p_name, TextServer::FontAntialiasing p_antialiasing) {
+ _THREAD_SAFE_METHOD_
+
+#ifdef CORETEXT_ENABLED
+ FontAdvanced *fd = memnew(FontAdvanced);
+ fd->antialiasing = p_antialiasing;
+ fd->system_font_name = p_name;
+
+ // Create CoreText font from name
+ CFStringRef font_name_cf = CFStringCreateWithCString(kCFAllocatorDefault, p_name.utf8().get_data(), kCFStringEncodingUTF8);
+ if (font_name_cf) {
+ CTFontRef ct_font_ref = CTFontCreateWithName(font_name_cf, 16.0, nullptr);
+ if (ct_font_ref) {
+ fd->ct_font = ct_font_ref;
+ // Set font properties from CoreText
+ CFStringRef family_name = CTFontCopyFamilyName(ct_font_ref);
+ if (family_name) {
+ char buffer[1024];
+ if (CFStringGetCString(family_name, buffer, sizeof(buffer), kCFStringEncodingUTF8)) {
+ fd->font_name = String(buffer);
+ }
+ CFRelease(family_name);
+ }
+
+ CTFontSymbolicTraits traits = CTFontGetSymbolicTraits(ct_font_ref);
+ if (traits & kCTFontBoldTrait) {
+ fd->style_flags.set_flag(TextServer::FONT_BOLD);
+ }
+ if (traits & kCTFontItalicTrait) {
+ fd->style_flags.set_flag(TextServer::FONT_ITALIC);
+ }
+ if (traits & kCTFontMonoSpaceTrait) {
+ fd->style_flags.set_flag(TextServer::FONT_FIXED_WIDTH);
+ }
+ } else {
+ // Font creation failed, clean up and return invalid RID
+ CFRelease(font_name_cf);
+ memdelete(fd);
+ return RID();
+ }
+ CFRelease(font_name_cf);
+ } else {
+ // String creation failed, clean up and return invalid RID
+ memdelete(fd);
+ return RID();
+ }
+
+ return font_owner.make_rid(fd);
+#else
+ // For platforms other than macOS, create a regular font
+ return _create_font();
+#endif
+}
+
void TextServerAdvanced::_font_set_data(const RID &p_font_rid, const PackedByteArray &p_data) {
FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
@@ -2101,7 +2452,7 @@ int64_t TextServerAdvanced::_font_get_face_count(const RID &p_font_rid) const {
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
+#if defined(MODULE_SVG_ENABLED) && defined(MODULE_FREETYPE_ENABLED)
FT_Property_Set(ft_library, "ot-svg", "svg-hooks", get_tvg_svg_in_ot_hooks());
#endif
}
@@ -2128,6 +2479,24 @@ int64_t TextServerAdvanced::_font_get_face_count(const RID &p_font_rid) const {
FT_Done_Face(tmp_face);
}
#endif
+
+#ifdef CORETEXT_ENABLED
+ // CoreText typically doesn't have multiple faces in a single font file
+ // but we can check by attempting to create the font
+ CFDataRef font_data = CFDataCreate(nullptr, (const UInt8 *)fd->data_ptr, fd->data_size);
+ if (font_data) {
+ CGDataProviderRef data_provider = CGDataProviderCreateWithCFData(font_data);
+ if (data_provider) {
+ CGFontRef cg_font = CGFontCreateWithDataProvider(data_provider);
+ if (cg_font) {
+ face_count = 1; // CoreText fonts typically have 1 face
+ CFRelease(cg_font);
+ }
+ CFRelease(data_provider);
+ }
+ CFRelease(font_data);
+ }
+#endif
}
return face_count;
@@ -2980,6 +3349,11 @@ void TextServerAdvanced::_font_set_scale(const RID &p_font_rid, int64_t p_size,
if (ffsd->face) {
return; // Do not override scale for dynamic fonts, it's calculated automatically.
}
+#endif
+#ifdef CORETEXT_ENABLED
+ if (ffsd->ct_font) {
+ return; // Do not override scale for dynamic fonts, it's calculated automatically.
+ }
#endif
ffsd->scale = p_scale;
}
@@ -8182,6 +8556,21 @@ TextServerAdvanced::TextServerAdvanced() {
_insert_feature_sets();
_bmp_create_font_funcs();
_update_settings();
+
+ // Check environment variable for backend selection
+#ifdef CORETEXT_ENABLED
+ const char* use_coretext_env = getenv("USE_CORETEXT");
+ if (use_coretext_env != nullptr) {
+ String env_value = String(use_coretext_env).to_lower();
+ use_coretext_backend = (env_value == "true" || env_value == "1" || env_value == "yes");
+ } else {
+ // Default to FreeType if environment variable is not set
+ use_coretext_backend = false;
+ }
+#else
+ use_coretext_backend = false;
+#endif
+
ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &TextServerAdvanced::_update_settings));
}
diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h
index 254430037f..20cd65f417 100644
--- a/modules/text_server_adv/text_server_adv.h
+++ b/modules/text_server_adv/text_server_adv.h
@@ -124,8 +124,16 @@ using namespace godot;
#include <hb-ot.h>
#endif
+#ifdef CORETEXT_ENABLED
+#include <CoreText/CoreText.h>
+#include <CoreGraphics/CoreGraphics.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <hb-coretext.h>
+#endif
+
#include <hb-icu.h>
#include <hb.h>
+#include <hb-ot.h>
/*************************************************************************/
@@ -185,6 +193,20 @@ class TextServerAdvanced : public TextServerExtension {
mutable FT_Library ft_library = nullptr;
#endif
+#ifdef CORETEXT_ENABLED
+ // CoreText doesn't need a global library instance like FreeType
+#endif
+
+ // Runtime backend selection
+ mutable bool use_coretext_backend = false;
+ _FORCE_INLINE_ bool _should_use_coretext() const {
+#ifdef CORETEXT_ENABLED
+ return use_coretext_backend;
+#else
+ return false;
+#endif
+ }
+
const int rect_range = 1;
struct FontTexturePosition {
@@ -301,6 +323,11 @@ class TextServerAdvanced : public TextServerExtension {
FT_StreamRec stream;
#endif
+#ifdef CORETEXT_ENABLED
+ CTFontRef ct_font = nullptr;
+ CGFontRef cg_font = nullptr;
+#endif
+
~FontForSizeAdvanced() {
if (hb_handle != nullptr) {
hb_font_destroy(hb_handle);
@@ -309,6 +336,14 @@ class TextServerAdvanced : public TextServerExtension {
if (face != nullptr) {
FT_Done_Face(face);
}
+#endif
+#ifdef CORETEXT_ENABLED
+ if (ct_font != nullptr) {
+ CFRelease(ct_font);
+ }
+ if (cg_font != nullptr) {
+ CFRelease(cg_font);
+ }
#endif
}
};
@@ -351,6 +386,7 @@ class TextServerAdvanced : public TextServerExtension {
BitField<TextServer::FontStyle> style_flags = 0;
String font_name;
String style_name;
+ String system_font_name;
int weight = 400;
int stretch = 100;
int extra_spacing[4] = { 0, 0, 0, 0 };
@@ -373,20 +409,33 @@ class TextServerAdvanced : public TextServerExtension {
size_t data_size;
int face_index = 0;
+#ifdef CORETEXT_ENABLED
+ CTFontRef ct_font = nullptr;
+#endif
+
~FontAdvanced() {
for (const KeyValue<Vector2i, FontForSizeAdvanced *> &E : cache) {
memdelete(E.value);
}
cache.clear();
+#ifdef CORETEXT_ENABLED
+ if (ct_font) {
+ CFRelease(ct_font);
+ ct_font = nullptr;
+ }
+#endif
}
};
_FORCE_INLINE_ FontTexturePosition find_texture_pos_for_glyph(FontForSizeAdvanced *p_data, int p_color_size, Image::Format p_image_format, int p_width, int p_height, bool p_msdf) const;
-#ifdef MODULE_MSDFGEN_ENABLED
+#if defined(MODULE_MSDFGEN_ENABLED) && defined(MODULE_FREETYPE_ENABLED)
_FORCE_INLINE_ FontGlyph rasterize_msdf(FontAdvanced *p_font_data, FontForSizeAdvanced *p_data, int p_pixel_range, int p_rect_margin, FT_Outline *p_outline, const Vector2 &p_advance) const;
#endif
#ifdef MODULE_FREETYPE_ENABLED
_FORCE_INLINE_ FontGlyph rasterize_bitmap(FontForSizeAdvanced *p_data, int p_rect_margin, FT_Bitmap p_bitmap, int p_yofs, int p_xofs, const Vector2 &p_advance, bool p_bgra) const;
+#endif
+#ifdef CORETEXT_ENABLED
+ _FORCE_INLINE_ FontGlyph rasterize_coretext_bitmap(FontForSizeAdvanced *p_data, int p_rect_margin, CGImageRef p_image, const Vector2 &p_advance, const CGRect &p_bbox) const;
#endif
bool _ensure_glyph(FontAdvanced *p_font_data, const Vector2i &p_size, int32_t p_glyph, FontGlyph &r_glyph, uint32_t p_oversampling = 0) const;
bool _ensure_cache_for_size(FontAdvanced *p_font_data, const Vector2i &p_size, FontForSizeAdvanced *&r_cache_for_size, bool p_silent = false, uint32_t p_oversampling = 0) const;
@@ -770,6 +819,7 @@ public:
MODBIND0R(RID, create_font);
MODBIND1R(RID, create_font_linked_variation, const RID &);
+ MODBIND2R(RID, create_font_system, const String &, TextServer::FontAntialiasing);
MODBIND2(font_set_data, const RID &, const PackedByteArray &);
MODBIND3(font_set_data_ptr, const RID &, const uint8_t *, int64_t);
diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp
index a2b68702b9..343d6bfa17 100644
--- a/modules/text_server_fb/text_server_fb.cpp
+++ b/modules/text_server_fb/text_server_fb.cpp
@@ -1131,6 +1131,17 @@ RID TextServerFallback::_create_font_linked_variation(const RID &p_font_rid) {
return font_var_owner.make_rid(new_fdv);
}
+RID TextServerFallback::_create_font_system(const String &p_name, TextServer::FontAntialiasing p_antialiasing) {
+ _THREAD_SAFE_METHOD_
+
+ // TextServerFallback doesn't support system fonts by name, so just create a regular font
+ // This could be extended in the future to support system font loading on platforms that have APIs for it
+ FontFallback *fd = memnew(FontFallback);
+ fd->antialiasing = p_antialiasing;
+
+ return font_owner.make_rid(fd);
+}
+
void TextServerFallback::_font_set_data(const RID &p_font_rid, const PackedByteArray &p_data) {
FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h
index 8349f37c18..5661dd854c 100644
--- a/modules/text_server_fb/text_server_fb.h
+++ b/modules/text_server_fb/text_server_fb.h
@@ -626,6 +626,7 @@ public:
MODBIND0R(RID, create_font);
MODBIND1R(RID, create_font_linked_variation, const RID &);
+ MODBIND2R(RID, create_font_system, const String &, TextServer::FontAntialiasing);
MODBIND2(font_set_data, const RID &, const PackedByteArray &);
MODBIND3(font_set_data_ptr, const RID &, const uint8_t *, int64_t);
diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp
index 904d73cf3d..2d6906c587 100644
--- a/scene/resources/font.cpp
+++ b/scene/resources/font.cpp
@@ -3664,3 +3664,368 @@ SystemFont::SystemFont() {
SystemFont::~SystemFont() {
}
+
+/*************************************************************************/
+/* OperatingSystemFont */
+/*************************************************************************/
+
+void OperatingSystemFont::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_antialiasing", "antialiasing"), &OperatingSystemFont::set_antialiasing);
+ ClassDB::bind_method(D_METHOD("get_antialiasing"), &OperatingSystemFont::get_antialiasing);
+
+ ClassDB::bind_method(D_METHOD("set_disable_embedded_bitmaps", "disable_embedded_bitmaps"), &OperatingSystemFont::set_disable_embedded_bitmaps);
+ ClassDB::bind_method(D_METHOD("get_disable_embedded_bitmaps"), &OperatingSystemFont::get_disable_embedded_bitmaps);
+
+ ClassDB::bind_method(D_METHOD("set_generate_mipmaps", "generate_mipmaps"), &OperatingSystemFont::set_generate_mipmaps);
+ ClassDB::bind_method(D_METHOD("get_generate_mipmaps"), &OperatingSystemFont::get_generate_mipmaps);
+
+ ClassDB::bind_method(D_METHOD("set_allow_system_fallback", "allow_system_fallback"), &OperatingSystemFont::set_allow_system_fallback);
+ ClassDB::bind_method(D_METHOD("is_allow_system_fallback"), &OperatingSystemFont::is_allow_system_fallback);
+
+ ClassDB::bind_method(D_METHOD("set_force_autohinter", "force_autohinter"), &OperatingSystemFont::set_force_autohinter);
+ ClassDB::bind_method(D_METHOD("is_force_autohinter"), &OperatingSystemFont::is_force_autohinter);
+
+ ClassDB::bind_method(D_METHOD("set_modulate_color_glyphs", "modulate"), &OperatingSystemFont::set_modulate_color_glyphs);
+ ClassDB::bind_method(D_METHOD("is_modulate_color_glyphs"), &OperatingSystemFont::is_modulate_color_glyphs);
+
+ ClassDB::bind_method(D_METHOD("set_hinting", "hinting"), &OperatingSystemFont::set_hinting);
+ ClassDB::bind_method(D_METHOD("get_hinting"), &OperatingSystemFont::get_hinting);
+
+ ClassDB::bind_method(D_METHOD("set_subpixel_positioning", "subpixel_positioning"), &OperatingSystemFont::set_subpixel_positioning);
+ ClassDB::bind_method(D_METHOD("get_subpixel_positioning"), &OperatingSystemFont::get_subpixel_positioning);
+
+ ClassDB::bind_method(D_METHOD("set_keep_rounding_remainders", "keep_rounding_remainders"), &OperatingSystemFont::set_keep_rounding_remainders);
+ ClassDB::bind_method(D_METHOD("get_keep_rounding_remainders"), &OperatingSystemFont::get_keep_rounding_remainders);
+
+ ClassDB::bind_method(D_METHOD("set_multichannel_signed_distance_field", "msdf"), &OperatingSystemFont::set_multichannel_signed_distance_field);
+ ClassDB::bind_method(D_METHOD("is_multichannel_signed_distance_field"), &OperatingSystemFont::is_multichannel_signed_distance_field);
+
+ ClassDB::bind_method(D_METHOD("set_msdf_pixel_range", "msdf_pixel_range"), &OperatingSystemFont::set_msdf_pixel_range);
+ ClassDB::bind_method(D_METHOD("get_msdf_pixel_range"), &OperatingSystemFont::get_msdf_pixel_range);
+
+ ClassDB::bind_method(D_METHOD("set_msdf_size", "msdf_size"), &OperatingSystemFont::set_msdf_size);
+ ClassDB::bind_method(D_METHOD("get_msdf_size"), &OperatingSystemFont::get_msdf_size);
+
+ ClassDB::bind_method(D_METHOD("set_oversampling", "oversampling"), &OperatingSystemFont::set_oversampling);
+ ClassDB::bind_method(D_METHOD("get_oversampling"), &OperatingSystemFont::get_oversampling);
+
+ ClassDB::bind_method(D_METHOD("set_font_name", "name"), &OperatingSystemFont::set_font_name);
+
+ ClassDB::bind_method(D_METHOD("get_font_size"), &OperatingSystemFont::get_font_size);
+ ClassDB::bind_method(D_METHOD("set_font_size", "size"), &OperatingSystemFont::set_font_size);
+
+ ClassDB::bind_method(D_METHOD("get_font_italic"), &OperatingSystemFont::get_font_italic);
+ ClassDB::bind_method(D_METHOD("set_font_italic", "italic"), &OperatingSystemFont::set_font_italic);
+ ClassDB::bind_method(D_METHOD("set_font_weight", "weight"), &OperatingSystemFont::set_font_weight);
+ ClassDB::bind_method(D_METHOD("set_font_stretch", "stretch"), &OperatingSystemFont::set_font_stretch);
+
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "font_size", PROPERTY_HINT_RANGE, "1,256,1"), "set_font_size", "get_font_size");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "font_italic"), "set_font_italic", "get_font_italic");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "antialiasing", PROPERTY_HINT_ENUM, "None,Grayscale,LCD Subpixel", PROPERTY_USAGE_STORAGE), "set_antialiasing", "get_antialiasing");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "generate_mipmaps"), "set_generate_mipmaps", "get_generate_mipmaps");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disable_embedded_bitmaps"), "set_disable_embedded_bitmaps", "get_disable_embedded_bitmaps");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_system_fallback"), "set_allow_system_fallback", "is_allow_system_fallback");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "force_autohinter"), "set_force_autohinter", "is_force_autohinter");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "modulate_color_glyphs"), "set_modulate_color_glyphs", "is_modulate_color_glyphs");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "hinting", PROPERTY_HINT_ENUM, "None,Light,Normal"), "set_hinting", "get_hinting");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "subpixel_positioning", PROPERTY_HINT_ENUM, "Disabled,Auto,One Half of a Pixel,One Quarter of a Pixel"), "set_subpixel_positioning", "get_subpixel_positioning");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_rounding_remainders"), "set_keep_rounding_remainders", "get_keep_rounding_remainders");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "multichannel_signed_distance_field"), "set_multichannel_signed_distance_field", "is_multichannel_signed_distance_field");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "msdf_pixel_range"), "set_msdf_pixel_range", "get_msdf_pixel_range");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "msdf_size"), "set_msdf_size", "get_msdf_size");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "oversampling"), "set_oversampling", "get_oversampling");
+}
+
+void OperatingSystemFont::_update_rids() const {
+ rids.clear();
+ _update_os_font();
+ if (os_font_rid.is_valid()) {
+ rids.push_back(os_font_rid);
+ }
+ dirty_rids = false;
+}
+
+void OperatingSystemFont::_update_os_font() const {
+ // Only create if not already created
+ if (!os_font_rid.is_valid() && !font_name.is_empty()) {
+ os_font_rid = TS->create_font_system(font_name, antialiasing);
+ if (os_font_rid.is_valid()) {
+ TS->font_set_disable_embedded_bitmaps(os_font_rid, disable_embedded_bitmaps);
+ TS->font_set_generate_mipmaps(os_font_rid, mipmaps);
+ TS->font_set_force_autohinter(os_font_rid, force_autohinter);
+ TS->font_set_allow_system_fallback(os_font_rid, allow_system_fallback);
+ TS->font_set_modulate_color_glyphs(os_font_rid, modulate_color_glyphs);
+ TS->font_set_hinting(os_font_rid, hinting);
+ TS->font_set_subpixel_positioning(os_font_rid, subpixel_positioning);
+ TS->font_set_keep_rounding_remainders(os_font_rid, keep_rounding_remainders);
+ TS->font_set_oversampling(os_font_rid, oversampling_override);
+ TS->font_set_multichannel_signed_distance_field(os_font_rid, msdf);
+ TS->font_set_msdf_pixel_range(os_font_rid, msdf_pixel_range);
+ TS->font_set_msdf_size(os_font_rid, msdf_size);
+ }
+ }
+}
+
+void OperatingSystemFont::reset_state() {
+ if (os_font_rid.is_valid()) {
+ TS->free_rid(os_font_rid);
+ os_font_rid = RID();
+ }
+
+ if (theme_font.is_valid()) {
+ theme_font->disconnect_changed(callable_mp(reinterpret_cast<Font *>(this), &Font::_invalidate_rids));
+ theme_font.unref();
+ }
+
+ font_name = "";
+ font_size = 16;
+ italic = false;
+ weight = 400;
+ stretch = 100;
+
+ antialiasing = TextServer::FONT_ANTIALIASING_GRAY;
+ mipmaps = false;
+ disable_embedded_bitmaps = true;
+ force_autohinter = false;
+ modulate_color_glyphs = false;
+ allow_system_fallback = true;
+ hinting = TextServer::HINTING_LIGHT;
+ subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_AUTO;
+ keep_rounding_remainders = true;
+ oversampling_override = 0.0;
+ msdf = false;
+ msdf_pixel_range = 16;
+ msdf_size = 48;
+
+ Font::reset_state();
+}
+
+void OperatingSystemFont::set_antialiasing(TextServer::FontAntialiasing p_antialiasing) {
+ if (antialiasing != p_antialiasing) {
+ antialiasing = p_antialiasing;
+ _invalidate_rids();
+ }
+}
+
+TextServer::FontAntialiasing OperatingSystemFont::get_antialiasing() const {
+ return antialiasing;
+}
+
+void OperatingSystemFont::set_disable_embedded_bitmaps(bool p_disable_embedded_bitmaps) {
+ if (disable_embedded_bitmaps != p_disable_embedded_bitmaps) {
+ disable_embedded_bitmaps = p_disable_embedded_bitmaps;
+ _invalidate_rids();
+ }
+}
+
+bool OperatingSystemFont::get_disable_embedded_bitmaps() const {
+ return disable_embedded_bitmaps;
+}
+
+void OperatingSystemFont::set_generate_mipmaps(bool p_generate_mipmaps) {
+ if (mipmaps != p_generate_mipmaps) {
+ mipmaps = p_generate_mipmaps;
+ _invalidate_rids();
+ }
+}
+
+bool OperatingSystemFont::get_generate_mipmaps() const {
+ return mipmaps;
+}
+
+void OperatingSystemFont::set_allow_system_fallback(bool p_allow_system_fallback) {
+ if (allow_system_fallback != p_allow_system_fallback) {
+ allow_system_fallback = p_allow_system_fallback;
+ _invalidate_rids();
+ }
+}
+
+bool OperatingSystemFont::is_allow_system_fallback() const {
+ return allow_system_fallback;
+}
+
+void OperatingSystemFont::set_force_autohinter(bool p_force_autohinter) {
+ if (force_autohinter != p_force_autohinter) {
+ force_autohinter = p_force_autohinter;
+ _invalidate_rids();
+ }
+}
+
+bool OperatingSystemFont::is_force_autohinter() const {
+ return force_autohinter;
+}
+
+void OperatingSystemFont::set_modulate_color_glyphs(bool p_modulate) {
+ if (modulate_color_glyphs != p_modulate) {
+ modulate_color_glyphs = p_modulate;
+ _invalidate_rids();
+ }
+}
+
+bool OperatingSystemFont::is_modulate_color_glyphs() const {
+ return modulate_color_glyphs;
+}
+
+void OperatingSystemFont::set_hinting(TextServer::Hinting p_hinting) {
+ if (hinting != p_hinting) {
+ hinting = p_hinting;
+ _invalidate_rids();
+ }
+}
+
+TextServer::Hinting OperatingSystemFont::get_hinting() const {
+ return hinting;
+}
+
+void OperatingSystemFont::set_subpixel_positioning(TextServer::SubpixelPositioning p_subpixel) {
+ if (subpixel_positioning != p_subpixel) {
+ subpixel_positioning = p_subpixel;
+ _invalidate_rids();
+ }
+}
+
+TextServer::SubpixelPositioning OperatingSystemFont::get_subpixel_positioning() const {
+ return subpixel_positioning;
+}
+
+void OperatingSystemFont::set_keep_rounding_remainders(bool p_keep_rounding_remainders) {
+ if (keep_rounding_remainders != p_keep_rounding_remainders) {
+ keep_rounding_remainders = p_keep_rounding_remainders;
+ _invalidate_rids();
+ }
+}
+
+bool OperatingSystemFont::get_keep_rounding_remainders() const {
+ return keep_rounding_remainders;
+}
+
+void OperatingSystemFont::set_oversampling(real_t p_oversampling) {
+ if (oversampling_override != p_oversampling) {
+ oversampling_override = p_oversampling;
+ _invalidate_rids();
+ }
+}
+
+real_t OperatingSystemFont::get_oversampling() const {
+ return oversampling_override;
+}
+
+void OperatingSystemFont::set_multichannel_signed_distance_field(bool p_msdf) {
+ if (msdf != p_msdf) {
+ msdf = p_msdf;
+ _invalidate_rids();
+ }
+}
+
+bool OperatingSystemFont::is_multichannel_signed_distance_field() const {
+ return msdf;
+}
+
+void OperatingSystemFont::set_msdf_pixel_range(int p_msdf_pixel_range) {
+ if (msdf_pixel_range != p_msdf_pixel_range) {
+ msdf_pixel_range = p_msdf_pixel_range;
+ _invalidate_rids();
+ }
+}
+
+int OperatingSystemFont::get_msdf_pixel_range() const {
+ return msdf_pixel_range;
+}
+
+void OperatingSystemFont::set_msdf_size(int p_msdf_size) {
+ if (msdf_size != p_msdf_size) {
+ msdf_size = p_msdf_size;
+ _invalidate_rids();
+ }
+}
+
+int OperatingSystemFont::get_msdf_size() const {
+ return msdf_size;
+}
+
+void OperatingSystemFont::set_font_name(const String &p_name) {
+ if (font_name != p_name) {
+ font_name = p_name;
+ _invalidate_rids();
+ }
+}
+
+String OperatingSystemFont::get_font_name() const {
+ return font_name;
+}
+
+void OperatingSystemFont::set_font_size(int p_size) {
+ if (font_size != p_size) {
+ font_size = p_size;
+ _invalidate_rids();
+ }
+}
+
+int OperatingSystemFont::get_font_size() const {
+ return font_size;
+}
+
+void OperatingSystemFont::set_font_italic(bool p_italic) {
+ if (italic != p_italic) {
+ italic = p_italic;
+ _invalidate_rids();
+ }
+}
+
+bool OperatingSystemFont::get_font_italic() const {
+ return italic;
+}
+
+void OperatingSystemFont::set_font_weight(int p_weight) {
+ if (weight != p_weight) {
+ weight = p_weight;
+ _invalidate_rids();
+ }
+}
+
+int OperatingSystemFont::get_font_weight() const {
+ return weight;
+}
+
+void OperatingSystemFont::set_font_stretch(int p_stretch) {
+ if (stretch != p_stretch) {
+ stretch = p_stretch;
+ _invalidate_rids();
+ }
+}
+
+int OperatingSystemFont::get_font_stretch() const {
+ return stretch;
+}
+
+int OperatingSystemFont::get_spacing(TextServer::SpacingType p_spacing) const {
+ return 0;
+}
+
+RID OperatingSystemFont::find_variation(const Dictionary &p_variation_coordinates, int p_face_index, float p_strength, Transform2D p_transform, int p_spacing_top, int p_spacing_bottom, int p_spacing_space, int p_spacing_glyph, float p_baseline_offset) const {
+ _update_os_font();
+ if (os_font_rid.is_valid()) {
+ // For now, return the base font directly to avoid variation issues
+ // TODO: Properly implement font variations for system fonts
+ return os_font_rid;
+ }
+ return RID();
+}
+
+RID OperatingSystemFont::_get_rid() const {
+ _update_os_font();
+ return os_font_rid;
+}
+
+int64_t OperatingSystemFont::get_face_count() const {
+ return 1;
+}
+
+OperatingSystemFont::OperatingSystemFont() {
+}
+
+OperatingSystemFont::~OperatingSystemFont() {
+ if (os_font_rid.is_valid()) {
+ TS->free_rid(os_font_rid);
+ }
+}
diff --git a/scene/resources/font.h b/scene/resources/font.h
index 52cf8036e6..331349335a 100644
--- a/scene/resources/font.h
+++ b/scene/resources/font.h
@@ -572,3 +572,107 @@ public:
SystemFont();
~SystemFont();
};
+
+/*************************************************************************/
+/* OperatingSystemFont */
+/*************************************************************************/
+
+class OperatingSystemFont : public Font {
+ GDCLASS(OperatingSystemFont, Font);
+
+ String font_name;
+ int font_size = 16;
+ bool italic = false;
+ int weight = 400;
+ int stretch = 100;
+
+ mutable Ref<Font> theme_font;
+ mutable RID os_font_rid;
+
+ TextServer::FontAntialiasing antialiasing = TextServer::FONT_ANTIALIASING_GRAY;
+ bool mipmaps = false;
+ bool disable_embedded_bitmaps = true;
+ bool force_autohinter = false;
+ bool modulate_color_glyphs = false;
+ bool allow_system_fallback = true;
+ TextServer::Hinting hinting = TextServer::HINTING_LIGHT;
+ TextServer::SubpixelPositioning subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_AUTO;
+ bool keep_rounding_remainders = true;
+ double oversampling_override = 0.0;
+ bool msdf = false;
+ int msdf_pixel_range = 16;
+ int msdf_size = 48;
+
+protected:
+ static void _bind_methods();
+
+ virtual void _update_os_font() const;
+ virtual void _update_rids() const override;
+
+ virtual void reset_state() override;
+
+public:
+ virtual void set_antialiasing(TextServer::FontAntialiasing p_antialiasing);
+ virtual TextServer::FontAntialiasing get_antialiasing() const;
+
+ virtual void set_disable_embedded_bitmaps(bool p_disable_embedded_bitmaps);
+ virtual bool get_disable_embedded_bitmaps() const;
+
+ virtual void set_generate_mipmaps(bool p_generate_mipmaps);
+ virtual bool get_generate_mipmaps() const;
+
+ virtual void set_allow_system_fallback(bool p_allow_system_fallback);
+ virtual bool is_allow_system_fallback() const;
+
+ virtual void set_force_autohinter(bool p_force_autohinter);
+ virtual bool is_force_autohinter() const;
+
+ virtual void set_modulate_color_glyphs(bool p_modulate);
+ virtual bool is_modulate_color_glyphs() const;
+
+ virtual void set_hinting(TextServer::Hinting p_hinting);
+ virtual TextServer::Hinting get_hinting() const;
+
+ virtual void set_subpixel_positioning(TextServer::SubpixelPositioning p_subpixel);
+ virtual TextServer::SubpixelPositioning get_subpixel_positioning() const;
+
+ virtual void set_keep_rounding_remainders(bool p_keep_rounding_remainders);
+ virtual bool get_keep_rounding_remainders() const;
+
+ virtual void set_oversampling(real_t p_oversampling);
+ virtual real_t get_oversampling() const;
+
+ virtual void set_multichannel_signed_distance_field(bool p_msdf);
+ virtual bool is_multichannel_signed_distance_field() const;
+
+ virtual void set_msdf_pixel_range(int p_msdf_pixel_range);
+ virtual int get_msdf_pixel_range() const;
+
+ virtual void set_msdf_size(int p_msdf_size);
+ virtual int get_msdf_size() const;
+
+ virtual void set_font_name(const String &p_name);
+ virtual String get_font_name() const override;
+
+ virtual void set_font_size(int p_size);
+ virtual int get_font_size() const;
+
+ virtual void set_font_italic(bool p_italic);
+ virtual bool get_font_italic() const;
+
+ virtual void set_font_weight(int p_weight);
+ virtual int get_font_weight() const override;
+
+ virtual void set_font_stretch(int p_stretch);
+ virtual int get_font_stretch() const override;
+
+ virtual int get_spacing(TextServer::SpacingType p_spacing) const override;
+
+ virtual RID find_variation(const Dictionary &p_variation_coordinates, int p_face_index = 0, float p_strength = 0.0, Transform2D p_transform = Transform2D(), int p_spacing_top = 0, int p_spacing_bottom = 0, int p_spacing_space = 0, int p_spacing_glyph = 0, float p_baseline_offset = 0.0) const override;
+ virtual RID _get_rid() const override;
+
+ int64_t get_face_count() const override;
+
+ OperatingSystemFont();
+ ~OperatingSystemFont();
+};
diff --git a/servers/text/text_server_dummy.h b/servers/text/text_server_dummy.h
index a1f3a08d5b..31578a3131 100644
--- a/servers/text/text_server_dummy.h
+++ b/servers/text/text_server_dummy.h
@@ -46,6 +46,8 @@ public:
virtual bool has(const RID &p_rid) override { return false; }
virtual RID create_font() override { return RID(); }
+ virtual RID create_font_linked_variation(const RID &p_font_rid) override { return RID(); }
+ virtual RID create_font_system(const String &p_name, FontAntialiasing p_antialiasing = FONT_ANTIALIASING_GRAY) override { return RID(); }
virtual void font_set_fixed_size(const RID &p_font_rid, int64_t p_fixed_size) override {}
virtual int64_t font_get_fixed_size(const RID &p_font_rid) const override { return 0; }
virtual void font_set_fixed_size_scale_mode(const RID &p_font_rid, TextServer::FixedSizeScaleMode p_fixed_size_scale_mode) override {}
diff --git a/servers/text/text_server_extension.cpp b/servers/text/text_server_extension.cpp
index 28fd2ff50c..59bbfa254c 100644
--- a/servers/text/text_server_extension.cpp
+++ b/servers/text/text_server_extension.cpp
@@ -53,6 +53,7 @@ void TextServerExtension::_bind_methods() {
GDVIRTUAL_BIND(_create_font);
GDVIRTUAL_BIND(_create_font_linked_variation, "font_rid");
+ GDVIRTUAL_BIND(_create_font_system, "name", "antialiasing");
GDVIRTUAL_BIND(_font_set_data, "font_rid", "data");
GDVIRTUAL_BIND(_font_set_data_ptr, "font_rid", "data_ptr", "data_size");
@@ -482,6 +483,12 @@ RID TextServerExtension::create_font_linked_variation(const RID &p_font_rid) {
return ret;
}
+RID TextServerExtension::create_font_system(const String &p_name, FontAntialiasing p_antialiasing) {
+ RID ret;
+ GDVIRTUAL_CALL(_create_font_system, p_name, p_antialiasing, ret);
+ return ret;
+}
+
void TextServerExtension::font_set_data(const RID &p_font_rid, const PackedByteArray &p_data) {
GDVIRTUAL_CALL(_font_set_data, p_font_rid, p_data);
}
diff --git a/servers/text/text_server_extension.h b/servers/text/text_server_extension.h
index a5c0a06e96..aebba5c9ed 100644
--- a/servers/text/text_server_extension.h
+++ b/servers/text/text_server_extension.h
@@ -84,6 +84,9 @@ public:
virtual RID create_font_linked_variation(const RID &p_font_rid) override;
GDVIRTUAL1R(RID, _create_font_linked_variation, RID);
+ virtual RID create_font_system(const String &p_name, FontAntialiasing p_antialiasing = FONT_ANTIALIASING_GRAY) override;
+ GDVIRTUAL2R(RID, _create_font_system, String, FontAntialiasing);
+
virtual void font_set_data(const RID &p_font_rid, const PackedByteArray &p_data) override;
virtual void font_set_data_ptr(const RID &p_font_rid, const uint8_t *p_data_ptr, int64_t p_data_size) override;
GDVIRTUAL2(_font_set_data, RID, const PackedByteArray &);
diff --git a/servers/text_server.h b/servers/text_server.h
index 50d2683909..a19e910df2 100644
--- a/servers/text_server.h
+++ b/servers/text_server.h
@@ -268,6 +268,7 @@ public:
virtual RID create_font() = 0;
virtual RID create_font_linked_variation(const RID &p_font_rid) = 0;
+ virtual RID create_font_system(const String &p_name, FontAntialiasing p_antialiasing = FONT_ANTIALIASING_GRAY) = 0;
virtual void font_set_data(const RID &p_font_rid, const PackedByteArray &p_data) = 0;
virtual void font_set_data_ptr(const RID &p_font_rid, const uint8_t *p_data_ptr, int64_t p_data_size) = 0;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment