Last active
August 16, 2025 16:39
-
-
Save migueldeicaza/2e701f9ce2e53a422f66cfe5c0fa0fff to your computer and use it in GitHub Desktop.
Patch to use CoreText in Godot instead of OpenType.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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..a066dedfc5 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,68 @@ 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); | |
| + | |
| + // Create bitmap from context | |
| + CGImageRef image = CGBitmapContextCreateImage(context); | |
| + if (image) { | |
| + gl = rasterize_coretext_bitmap(fd, 1, image, Vector2(advance.width, advance.height), bbox); | |
| + CGImageRelease(image); | |
| + } | |
| + | |
| + 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 +1476,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 +1520,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; | |
| @@ -1401,6 +1548,104 @@ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_font_data, const | |
| fd->size = p_size; | |
| if (p_font_data->data_ptr && (p_font_data->data_size > 0)) { | |
| // Init dynamic font. | |
| + if (_should_use_coretext()) { | |
| +#ifdef CORETEXT_ENABLED | |
| + // Use CoreText backend | |
| + // Create CoreText font from data | |
| + CGDataProviderRef data_provider = CGDataProviderCreateWithData(nullptr, p_font_data->data_ptr, p_font_data->data_size, nullptr); | |
| + 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; | |
| + } | |
| +#endif | |
| + } else { | |
| #ifdef MODULE_FREETYPE_ENABLED | |
| int error = 0; | |
| { | |
| @@ -1415,7 +1660,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 +2163,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 { | |
| @@ -2101,7 +2348,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 +2375,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 +3245,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 +8452,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..0de40799f3 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 | |
| } | |
| }; | |
| @@ -382,11 +417,14 @@ class TextServerAdvanced : public TextServerExtension { | |
| }; | |
| _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; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment