Import monospace image font at runtime?

Godot Version

4.0.2

Question

I’m wondering if there is a way to import an image as a monospace image font through code / at runtime. I would like the user of the software to be able to change the look of the font in the application if they want :slight_smile:

Hi! there are multiple ways to go about this, the simplest being that you can import a BMFont format font as described in the docs here

Unfortunately, while the engine does support importing a image such as a png as a font as well, it looks like the function to load that format during runtime hasn’t yet been added to FontFile.

The good news is, If you’re feeling (a little) brave, you can see more or less the entire process of how to use FontFile to set one up manually, just like the importer does in the engine source code. Since it’s only using FontFile you can do exactly the same thing in GDScript!

It’s in the ResourceImporterImageFont class’s import() function. Here is a link to that function on the github page.

Don’t let the c++ intimidate you if you aren’t familiar with it, I think all these methods are available on the ImageFont resource in GDScript as well, you’ll just need to get the image file as an Image first (like this)
The math for setting up each individual glyph should be the same in GDScript as it is in c++ as well.

1 Like

If you need help understanding why it’s looking at ranges it’s because that importer has you tell it where the characters are in the image by specifying ranges of characters, see in the docs here

1 Like

yay! big thanks for this detailed info
i’ll definetly take a shot at recreating it in gdscrpit :slight_smile:

1 Like

there we go! a word for word copy of the importer in gdscript. (for consistency i guess?)
works just as i need it to.
I did find that FontFile does not have the function set_fixed_size_scale_mode() or a variable counter part. That i think is the only difference? am also just using it to return the new font var instead of saving it. Thanks again @cammymoop :smiley:
seriously, i wouldn’t have thought to look in the score code, super cool. def have ta do that next time

func import_font_from_image(p_source_file : String, p_options : Dictionary) -> FontFile:
	
	print("Importing image font from: " + p_source_file)

	var columns : int = p_options["columns"]
	var rows : int = p_options["rows"]
	var ranges : Array = p_options["character_ranges"]
	var fallbacks : Array = p_options["fallbacks"]
	var img_margin : Rect2i = p_options["image_margin"]
	var char_margin : Rect2i = p_options["character_margin"]
#	var smode = p_options["scaling_mode"].operator int()

	var img = Image.load_from_file(p_source_file)
	assert(img != null, "Can't load font texture: \"%s\"." % p_source_file)
	
	var count : int = columns * rows
	var chr_cell_width : int = (img.get_width() - img_margin.position.x - img_margin.size.x) / columns
	var chr_cell_height : int = (img.get_height() - img_margin.position.y - img_margin.size.y) / rows
	assert(chr_cell_width > 0 and chr_cell_height > 0, "Image margin too big.")

	var chr_width : int = chr_cell_width - char_margin.position.x - char_margin.size.x
	var chr_height : int = chr_cell_height - char_margin.position.y - char_margin.size.y
	assert(chr_width > 0 and chr_height > 0, "Character margin too big.")

	var font = FontFile.new()
	font.antialiasing = TextServer.FONT_ANTIALIASING_NONE
	font.set_generate_mipmaps(false)
	font.set_multichannel_signed_distance_field(false)
	font.set_fixed_size(chr_height)
	font.subpixel_positioning  = TextServer.SUBPIXEL_POSITIONING_DISABLED
	font.set_force_autohinter(false)
	font.set_allow_system_fallback(false)
	font.hinting = TextServer.HINTING_NONE
	font.oversampling = 1.0
	font.set_fallbacks(fallbacks)
	font.set_texture_image(0, Vector2i(chr_height, 0), 0, img)
#	font.set_fixed_size_scale_mode(smode) This func don't exsist?

	var pos : int = 0
	for range in ranges:
		var tokens : Array = range.split("-")
		var start : int = int(tokens[0])
		var end : int = int(tokens[1])
		var idx = start
		while idx <= end:
			assert(pos < count, "Too many characters in range, should be " + str(columns * rows))
			var x : int = pos % columns
			var y : int = pos / columns
			font.set_glyph_advance(0, chr_height, idx, Vector2(chr_width, 0))
			font.set_glyph_offset(0, Vector2i(chr_height, 0), idx, Vector2i(0, -0.5 * chr_height))
			font.set_glyph_size(0, Vector2i(chr_height, 0), idx, Vector2(chr_width, chr_height))
			font.set_glyph_uv_rect(0, Vector2i(chr_height, 0), idx, Rect2(img_margin.position.x + chr_cell_width * x + char_margin.position.x, img_margin.position.y + chr_cell_height * y + char_margin.position.y, chr_width, chr_height))
			font.set_glyph_texture_idx(0, Vector2i(chr_height, 0), idx, 0)
			pos += 1
			idx += 1
		
	
	font.set_cache_ascent(0, chr_height, 0.5 * chr_height)
	font.set_cache_descent(0, chr_height, 0.5 * chr_height)
	
	return font
2 Likes

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.