Problems with proper use of TextServer

Godot Version

Ver 4.3 mono windows 64

Question

I’m trying to create a Texture2D through code, to use with a custom draw function. I’ve been trying to use TextServer to draw directly to an image in memory, but I can’t get it to work.

Here’s my image generation code:

public static ImageTexture GenerateTestImage()
{
    Image img = Image.CreateEmpty(128, 128, false, Image.Format.Rgba8);
    img.Fill(new Color(1, 1, 1, 1));

    ImageTexture texture = ImageTexture.CreateFromImage(img);
    Rid textureRID = texture.GetRid();

    TextServer ts = TextServerManager.GetPrimaryInterface();

    Font font = ThemeDB.FallbackFont;
    Godot.Collections.Array<Rid> fontRidsArray = new Godot.Collections.Array<Rid>() { font.GetRid() };

    Rid shapedText = ts.CreateShapedText(TextServer.Direction.Ltr, TextServer.Orientation.Horizontal);
    ts.ShapedTextAddString(shapedText, "test", fontRidsArray, ThemeDB.FallbackFontSize);
    ts.ShapedTextDraw(shapedText, textureRID, new Vector2(10, 10), -1, -1, new Color(0, 0, 0, 1));

    return texture;
}

When I ran it the TextServer.ShapedTextAddString call threw this error:

E 0:00:01:0274   NativeCalls.cs:11146 @ Godot.NativeInterop.godot_bool Godot.NativeCalls.godot_icall_7_1201(nint, nint, Godot.Rid, string, Godot.NativeInterop.godot_array, long, Godot.NativeInterop.godot_dictionary, string, Godot.Variant): Parameter "_get_font_data(p_fonts[i])" is null.
  <C++ Source>   modules/text_server_adv/text_server_adv.cpp:4307 @ _shaped_text_add_string()
  <Stack Trace>  NativeCalls.cs:11146 @ Godot.NativeInterop.godot_bool Godot.NativeCalls.godot_icall_7_1201(nint, nint, Godot.Rid, string, Godot.NativeInterop.godot_array, long, Godot.NativeInterop.godot_dictionary, string, Godot.Variant)
                 TextServer.cs:2252 @ bool Godot.TextServer.ShapedTextAddString(Godot.Rid, string, Godot.Collections.Array`1[Godot.Rid], long, Godot.Collections.Dictionary, string, Godot.Variant)
                 SudokuImageGenerator.cs:69 @ Godot.ImageTexture SudokuImageGenerator.GenerateTestImage()
                 SudokuImageGrid.cs:33 @ void SudokuImageGrid._Ready()
                 Node.cs:2401 @ bool Godot.Node.InvokeGodotClassMethod(Godot.NativeInterop.godot_string_name&, Godot.NativeInterop.NativeVariantPtrArgs, Godot.NativeInterop.godot_variant&)
                 CanvasItem.cs:1505 @ bool Godot.CanvasItem.InvokeGodotClassMethod(Godot.NativeInterop.godot_string_name&, Godot.NativeInterop.NativeVariantPtrArgs, Godot.NativeInterop.godot_variant&)
                 Control.cs:2912 @ bool Godot.Control.InvokeGodotClassMethod(Godot.NativeInterop.godot_string_name&, Godot.NativeInterop.NativeVariantPtrArgs, Godot.NativeInterop.godot_variant&)
                 SudokuImageGrid_ScriptMethods.generated.cs:58 @ bool SudokuImageGrid.InvokeGodotClassMethod(Godot.NativeInterop.godot_string_name&, Godot.NativeInterop.NativeVariantPtrArgs, Godot.NativeInterop.godot_variant&)
                 CSharpInstanceBridge.cs:24 @ Godot.NativeInterop.godot_bool Godot.Bridge.CSharpInstanceBridge.Call(nint, Godot.NativeInterop.godot_string_name*, Godot.NativeInterop.godot_variant**, int, Godot.NativeInterop.godot_variant_call_error*, Godot.NativeInterop.godot_variant*)

When I called the text drawing function with an empty font rid array, TextServer.CreateShapedText errored instead:

E 0:00:01:0168   NativeCalls.cs:11345 @ void Godot.NativeCalls.godot_icall_6_1222(nint, nint, Godot.Rid, Godot.Rid, Godot.Vector2*, double, double, Godot.Color*): Parameter "canvas_item" is null.
  <C++ Source>   servers/rendering/renderer_canvas_cull.cpp:1154 @ canvas_item_add_rect()
  <Stack Trace>  NativeCalls.cs:11345 @ void Godot.NativeCalls.godot_icall_6_1222(nint, nint, Godot.Rid, Godot.Rid, Godot.Vector2*, double, double, Godot.Color*)
                 TextServer.cs:2763 @ void Godot.TextServer.ShapedTextDraw(Godot.Rid, Godot.Rid, Godot.Vector2, double, double, System.Nullable`1[Godot.Color])
                 SudokuImageGenerator.cs:70 @ Godot.ImageTexture SudokuImageGenerator.GenerateTestImage()
                 SudokuImageGrid.cs:33 @ void SudokuImageGrid._Ready()
                 Node.cs:2401 @ bool Godot.Node.InvokeGodotClassMethod(Godot.NativeInterop.godot_string_name&, Godot.NativeInterop.NativeVariantPtrArgs, Godot.NativeInterop.godot_variant&)
                 CanvasItem.cs:1505 @ bool Godot.CanvasItem.InvokeGodotClassMethod(Godot.NativeInterop.godot_string_name&, Godot.NativeInterop.NativeVariantPtrArgs, Godot.NativeInterop.godot_variant&)
                 Control.cs:2912 @ bool Godot.Control.InvokeGodotClassMethod(Godot.NativeInterop.godot_string_name&, Godot.NativeInterop.NativeVariantPtrArgs, Godot.NativeInterop.godot_variant&)
                 SudokuImageGrid_ScriptMethods.generated.cs:58 @ bool SudokuImageGrid.InvokeGodotClassMethod(Godot.NativeInterop.godot_string_name&, Godot.NativeInterop.NativeVariantPtrArgs, Godot.NativeInterop.godot_variant&)
                 CSharpInstanceBridge.cs:24 @ Godot.NativeInterop.godot_bool Godot.Bridge.CSharpInstanceBridge.Call(nint, Godot.NativeInterop.godot_string_name*, Godot.NativeInterop.godot_variant**, int, Godot.NativeInterop.godot_variant_call_error*, Godot.NativeInterop.godot_variant*)

How can I properly use TextServer? Or is there a better way to draw text to an image? Thanks.

TextServer.shaped_text_draw() expects a canvas item RID as its second parameter to draw to not a texture RID

You can use a SubViewport node with a Label and do whatever you need to do with that. Then you can get its texture with Viewport.get_texture()

I’m pretty sure that doing the same with servers only (TextServer and RenderingServer) is also possible but I’ve not managed to get it to work.

Edit: Yep, it’s possible, just a lot to setup:

extends Node


func _ready() -> void:
	# Create a viewport, canvas and canvas item
	var vp = RenderingServer.viewport_create()
	var canvas = RenderingServer.canvas_create()
	var ci = RenderingServer.canvas_item_create()

	# Setup viewport with size 256x256 and transparent background
	RenderingServer.viewport_set_size(vp, 256, 256)
	RenderingServer.viewport_set_transparent_background(vp, true)

	# Make it active and attach the canvas
	RenderingServer.viewport_set_active(vp, true)
	RenderingServer.viewport_attach_canvas(vp, canvas)

	# Set the viewport to update once as we won't need it anymore
	RenderingServer.viewport_set_update_mode(vp, RenderingServer.VIEWPORT_UPDATE_ONCE)

	# Set the canvas as the parent of the canvas item
	RenderingServer.canvas_item_set_parent(ci, canvas)

	# Get the default font
	var font = get_window().get_theme_default_font()

	# Get the primary text server
	var ts = TextServerManager.get_primary_interface()

	# Create the shaped text and add a string
	var st = ts.create_shaped_text()
	ts.shaped_text_add_string(st, "Hello", font.get_rids(), 16)

	# Draw the shaped text to the canvas item (Note: y pos is baseline)
	ts.shaped_text_draw(st, ci, Vector2(10, 22), -1, -1, Color.RED)
	# We are done with it, free it
	ts.free_rid(st)

	# Wait a frame
	await RenderingServer.frame_post_draw

	# Get the viewport's texture and get the Image from that texture
	var vp_tex = RenderingServer.viewport_get_texture(vp)
	var vp_img = RenderingServer.texture_2d_get(vp_tex)

	# Create a ImageTexture from that texture and assign it to the sprite's texture
	var texture = ImageTexture.create_from_image(vp_img)
	$Sprite2D.texture = texture

	# Free all the resources we used
	RenderingServer.free_rid(ci)
	RenderingServer.free_rid(canvas)
	RenderingServer.free_rid(vp)

Result: