.fnt file to image labels (Custom fonts in Studio)

Hello!

This is my approach to allowing custom font files in Roblox.

image

Table of Contents:

Preamble
Section 1: Introduction and Generating a .fnt file
Section 2: Actually doing things
Section 3: “Feed” functions
Section 4: Debugging
Section 5: Documentation

Preamble

I am not responsible for any use of this resource aside from my own. What you do with this resource is beyond my control and I take no responsibility for any moderation actions that happen on your account if you decide to use this in a way that is not appropriate and/or violates Roblox’s terms of service. You should also educate yourself on the acceptable uses of the font files you are using with respect to copyright. If you are violating any copyright laws, you are taking full responsibility for any legal actions that may be taken against you if you so choose to violate these laws.

I grant permission for you to use and manipulate this resource in any way you wish, you may redistribute as you please, I really don’t care honestly.

Section 1: Introduction and generating a .fnt file

This is a very bare-bones system and requires you to do some things by hand, there is no interface, however I will be going over how to do things and use this properly. My example generates a label using the specified graphemes. You will have to copy this frame, or modify the source if you want to use it in a way that isn’t a single frame in the top-left corner.

You might be asking what a .fnt file is. It basically allows you to take a spritesheet (particularly one for a font), and stores positioning and sizing properties. There are multiple benefits to doing this over manually uploading every single glyph: only one file has to load, therefore less time is spent waiting for images to load, and if one file is moderated and you need to appeal, you don’t have to copy every single moderated decal’s ID to appeal.

First off, we need to get a .fnt file, you can do this how you wish but I personally used snowb.org, a resource that does this without any external programs. If you have your own program, skip to section 2.

For this example, I’m using the font “Bradley Hand ITC”, however any font file should work.
When you get to the website, it should look something like this, pretty simple but I’ll still go over how to use it:

Over on the lefthand side, press the add font file button, you should navigate your file explorer and find the font file you want to use. When you do this, the preview should change to whichever font you used.
image

If you’re just using English characters and symbols, it should be fine to use this without any modification, however if there is any missing glyph, add this on the textbox on the left, the one labeled as “Glyphs”
image

If you want to manipulate the colour of your text in Studio, you should change the font colour to white, which can be done on the righthand side. Ensure “Fill” is selected.
image

Change it to white, type in R 255, G 255, and B 255, or use hex code #FFFFFF
image

This should make the preview white
image

Now, it is crucial that you verify that in the generated file, there isn’t anything profane or visually inappropriate because Roblox will moderate your file, regardless of intention. If your map does contain anything not considered appropriate, you can mess with layout settings which will change the order of the characters:

Now, once you’re happy, click “export” on the top bar:
image

This window should pop up:
image

You can choose any file type except xml. I’m using .fnt just for consistency purposes. Press “save”, it should download a .zip file.
image

I assume you know how to extract the .zip file so do that, if you don’t know how, google it.

Once you’ve extracted the .zip file, you should see two files: a .png file, and a .fnt/txt file depending on what you downloaded.
image

Section 2: Actually doing things

This is the most substantial part of this post I guess. I am terrible at introductions so I’ll just pick up where I left off.
You should have 2 files: a .png file and a .txt/.fnt file (hereon referred to as .fnt file). Upload the .png file to Roblox as a decal. If you don’t know how to do that, google it.

Now, get the model, which is the “interpreter” even though it’s basic:

Insert it somewhere, I’m using ServerScriptService which is what you should be using for server scripts anyway.

Open your .fnt file with notepad or some other text editor, it really doesn’t matter what you use because you aren’t manipulating it.

Open the “main” script, it should look something like this:

Select the full .fnt file source (usually ctrl + A or cmd + A), and paste it inside of the two sets of square brackets, right next to the “s” variable.

It should look something like this:

Now, scroll down to the “fontMap” variable, paste the decal ID from above.
image

Now, run the script however you please (through run or play), and it should create a folder, as the direct child of the script, this will be a folder that contains each of the glyphs.

image

Section 3: “Feed” functions

I don’t know what to call these functions so I’m going to call them “feed” functions because you’re feeding a string to the “interpreter”.

This “feed” function is simply a function that turns the string into an actual visual label. It does this by iterating through the graphemes, cloning the frame from the folder to a ScreenGui.

IMPORTANT: If you are working with non-ASCII characters (characters whose decimal codes are not beween 0 and 255), it is important that you use the UTF-8 library, not the string library for splitting and manipulating strings. I’m using the UTF-8 library in my example so I highly recommend you do this with yours as well.

I’m going to be providing my feed function with comments. You can use this or create your own. A feed function should handle kernings as well, which is essentially any removed space between the current and next letter

local function feed(str: string)
	-- example feed function:

	-- first off, we have a function that will return a table of graphemes
	local splitString = (function()
		local graphemes = {}
		for i,v in utf8.graphemes(str) do -- for each of the graphemes,
			table.insert(graphemes, str:sub(i,v)) -- we have to use string.sub because utf8 returns a pair where i is the beginning of the grapheme and v is the end of the grapheme. We must use this because we're working with a non-ASCII character (№).
		end
		return graphemes -- returns the table of graphemes
	end)() -- ensure you call this function so it actually assigns the graphemes table to the splitString variable
	local newFrame = Instance.new('Frame') -- create a new frame that will store the glyphs
	
	local newUIListLayout = Instance.new('UIListLayout')
	newUIListLayout.Parent = newFrame
	newUIListLayout.SortOrder = Enum.SortOrder.LayoutOrder -- we don't want them to be aligned vertically
	newUIListLayout.FillDirection = Enum.FillDirection.Horizontal -- align them horizontally from left to right
	newUIListLayout.Padding = UDim.new(0,0) -- we don't want any padding
	
	for i,v in ipairs(splitString) do -- where i is the index and v is the glyph
		local thisLetter = stringFolder:FindFirstChild(v) -- look inside the folder for a frame with the glyph's name
		if thisLetter then -- if it found a frame with this name,
			local newLetter = thisLetter:Clone() -- clone it and parent it to the frame
			newLetter.LayoutOrder = i
			newLetter.Parent = newFrame
			
			local next = splitString[i + 1] -- look for the next character to handle kernings
			if next then
				local kerningThisLetter = info.kernings[v] -- inside the kernings table, look for the kerning for this letter and the next letter
				if kerningThisLetter then -- if there is a kerning table for this letter then
					local kerningNextLetter = kerningThisLetter[next] -- if inside that kerning table there is a kerning for the next letter
					if kerningNextLetter then -- if there's a kerning then
						newFrame.Size -= UDim2.fromOffset(0,kerningNextLetter) -- subtract it from the frame's size, not the actual letter label's size because we don't want to clip the letter
					end
				end
			end
		end
	end
	return newFrame
end

feed('№test test№').Parent = game:GetService('StarterGui'):WaitForChild('ScreenGui')

Section 4: Debugging

You might find that there is unappealing spacing between characters, or you might find weird positioning etc. This shouldn’t happen but in case, modifying is easily done thankfully because of how .fnt files are laid out. You must find the character’s code point (type print(utf8.codepoint('some character'), changing the some character to a character and from there you can change the properties of each character). See documentation of how each property is used.

Section 5: Documentation

Now, you might have noticed a table called “info” in the script, this is a table that contains information about the font, and about each glyph:

Legend:
(0/1): boolean, where 0 is false and 1 is true.
(u): unused

info.characterTable

Table of more tables containing information about each glyph. Inside of these sub-tables, contains more properties:

charId: character ID passed to utf8.char
x: X position of the grapheme inside of the character sheet
y: Y position of the grapheme inside of the character sheet
width: visual width of the grapheme
height: visual height of the grapheme
xOffset: distance from the left of the frame
yOffset: distance from the top of the frame
xAdvance: like the absolute width of the character, distance between the left of the current and left of the next character
page: for informative purposes (u)
chnl: for informative purposes (u)

fontInfo

Dictionary of information relating to the font
face: name of the true type font
size: the size of the true type font
bold: the font is bold (0/1) (u)
italic: The font is italic (0/1) (u)
charset: the name of the OEM charset used (when not unicode). (u)
unicode: set to 1 if it is the unicode charset. (0/1) (u)
stretchH: the font height stretch in percentage. 100% means no stretch. (u)
smooth: set to 1 if smoothing was turned on. (0/1) (u)
aa: The supersampling level used. 1 means no supersampling was used. (0/1) (u)
padding: the padding for each character (up, right, down, left). (u)
spacing: the spacing for each character (horizontal, vertical). (u)
outline: the outline thickness for the characters. (u)

info.kernings

Table indicating the space to subtract between each character (if any) where the index is the first character, and the value is a dictionary where the index is the second character and the value is the space in pixels to subtract

You can get the source here:

image

111 Likes

If you want to try it out without going through generating the .fnt and .png files, you can use this source:

source
local s = [[info face="BradleyHandITC" size=64 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=1 aa=1 padding=1,1,1,1 spacing=1,1
common lineHeight=64 base=54 scaleW=1023 scaleH=150 pages=1 packed=0
page id=0 file="Unnamed.png"
chars count=91
char id=32 x=0 y=0 width=0 height=0 xoffset=0 yoffset=0 xadvance=17 page=0 chnl=15
char id=33 x=814 y=72 width=6 height=37 xoffset=7 yoffset=19 xadvance=19 page=0 chnl=15
char id=34 x=852 y=114 width=11 height=15 xoffset=3 yoffset=21 xadvance=19 page=0 chnl=15
char id=35 x=608 y=0 width=42 height=47 xoffset=-1 yoffset=19 xadvance=40 page=0 chnl=15
char id=36 x=103 y=0 width=41 height=60 xoffset=-2 yoffset=12 xadvance=42 page=0 chnl=15
char id=37 x=27 y=0 width=37 height=67 xoffset=2 yoffset=10 xadvance=44 page=0 chnl=15
char id=38 x=340 y=0 width=36 height=52 xoffset=-1 yoffset=19 xadvance=38 page=0 chnl=15
char id=39 x=864 y=114 width=6 height=15 xoffset=3 yoffset=21 xadvance=13 page=0 chnl=15
char id=40 x=269 y=0 width=15 height=54 xoffset=10 yoffset=11 xadvance=26 page=0 chnl=15
char id=41 x=285 y=0 width=15 height=54 xoffset=2 yoffset=11 xadvance=26 page=0 chnl=15
char id=42 x=717 y=114 width=30 height=27 xoffset=2 yoffset=12 xadvance=35 page=0 chnl=15
char id=43 x=170 y=72 width=39 height=40 xoffset=7 yoffset=16 xadvance=54 page=0 chnl=15
char id=44 x=838 y=114 width=13 height=16 xoffset=-1 yoffset=48 xadvance=16 page=0 chnl=15
char id=45 x=983 y=36 width=18 height=5 xoffset=2 yoffset=39 xadvance=22 page=0 chnl=15
char id=46 x=871 y=114 width=7 height=8 xoffset=4 yoffset=49 xadvance=16 page=0 chnl=15
char id=47 x=953 y=0 width=29 height=42 xoffset=-2 yoffset=17 xadvance=27 page=0 chnl=15
char id=48 x=88 y=114 width=31 height=35 xoffset=1 yoffset=22 xadvance=34 page=0 chnl=15
char id=49 x=216 y=114 width=32 height=34 xoffset=-1 yoffset=23 xadvance=32 page=0 chnl=15
char id=50 x=821 y=72 width=37 height=36 xoffset=0 yoffset=22 xadvance=37 page=0 chnl=15
char id=51 x=382 y=114 width=34 height=32 xoffset=0 yoffset=25 xadvance=37 page=0 chnl=15
char id=52 x=210 y=72 width=36 height=39 xoffset=0 yoffset=21 xadvance=41 page=0 chnl=15
char id=53 x=859 y=72 width=43 height=36 xoffset=-1 yoffset=21 xadvance=37 page=0 chnl=15
char id=54 x=903 y=72 width=35 height=36 xoffset=2 yoffset=21 xadvance=39 page=0 chnl=15
char id=55 x=491 y=0 width=47 height=48 xoffset=-4 yoffset=20 xadvance=42 page=0 chnl=15
char id=56 x=247 y=72 width=30 height=39 xoffset=3 yoffset=19 xadvance=34 page=0 chnl=15
char id=57 x=188 y=0 width=47 height=56 xoffset=-18 yoffset=19 xadvance=34 page=0 chnl=15
char id=58 x=748 y=114 width=8 height=26 xoffset=4 yoffset=31 xadvance=16 page=0 chnl=15
char id=59 x=341 y=114 width=14 height=34 xoffset=-1 yoffset=30 xadvance=16 page=0 chnl=15
char id=61 x=798 y=114 width=39 height=16 xoffset=7 yoffset=28 xadvance=54 page=0 chnl=15
char id=63 x=557 y=72 width=19 height=38 xoffset=2 yoffset=19 xadvance=24 page=0 chnl=15
char id=64 x=893 y=0 width=42 height=44 xoffset=1 yoffset=18 xadvance=44 page=0 chnl=15
char id=65 x=0 y=72 width=46 height=41 xoffset=-2 yoffset=18 xadvance=46 page=0 chnl=15
char id=66 x=402 y=72 width=43 height=38 xoffset=-2 yoffset=19 xadvance=43 page=0 chnl=15
char id=67 x=577 y=72 width=37 height=37 xoffset=3 yoffset=20 xadvance=39 page=0 chnl=15
char id=68 x=973 y=72 width=38 height=36 xoffset=0 yoffset=22 xadvance=40 page=0 chnl=15
char id=69 x=615 y=72 width=33 height=37 xoffset=4 yoffset=20 xadvance=39 page=0 chnl=15
char id=70 x=47 y=72 width=33 height=40 xoffset=2 yoffset=20 xadvance=35 page=0 chnl=15
char id=71 x=687 y=0 width=42 height=46 xoffset=2 yoffset=22 xadvance=47 page=0 chnl=15
char id=72 x=81 y=72 width=45 height=40 xoffset=5 yoffset=20 xadvance=47 page=0 chnl=15
char id=73 x=1012 y=72 width=7 height=36 xoffset=5 yoffset=21 xadvance=17 page=0 chnl=15
char id=74 x=301 y=0 width=38 height=52 xoffset=-26 yoffset=20 xadvance=17 page=0 chnl=15
char id=75 x=278 y=72 width=43 height=39 xoffset=5 yoffset=20 xadvance=47 page=0 chnl=15
char id=76 x=0 y=114 width=36 height=36 xoffset=5 yoffset=20 xadvance=40 page=0 chnl=15
char id=77 x=37 y=114 width=50 height=36 xoffset=2 yoffset=21 xadvance=55 page=0 chnl=15
char id=78 x=649 y=72 width=38 height=37 xoffset=6 yoffset=21 xadvance=49 page=0 chnl=15
char id=79 x=983 y=0 width=40 height=35 xoffset=3 yoffset=22 xadvance=45 page=0 chnl=15
char id=80 x=688 y=72 width=37 height=37 xoffset=-1 yoffset=20 xadvance=36 page=0 chnl=15
char id=81 x=446 y=72 width=50 height=38 xoffset=3 yoffset=21 xadvance=46 page=0 chnl=15
char id=82 x=120 y=114 width=50 height=35 xoffset=0 yoffset=22 xadvance=48 page=0 chnl=15
char id=83 x=171 y=114 width=44 height=35 xoffset=-1 yoffset=21 xadvance=47 page=0 chnl=15
char id=84 x=726 y=72 width=43 height=37 xoffset=0 yoffset=21 xadvance=38 page=0 chnl=15
char id=85 x=770 y=72 width=43 height=37 xoffset=5 yoffset=23 xadvance=46 page=0 chnl=15
char id=86 x=302 y=114 width=38 height=34 xoffset=1 yoffset=23 xadvance=41 page=0 chnl=15
char id=87 x=497 y=72 width=59 height=38 xoffset=3 yoffset=20 xadvance=62 page=0 chnl=15
char id=88 x=127 y=72 width=42 height=40 xoffset=-2 yoffset=20 xadvance=41 page=0 chnl=15
char id=89 x=145 y=0 width=42 height=58 xoffset=1 yoffset=19 xadvance=43 page=0 chnl=15
char id=90 x=322 y=72 width=53 height=39 xoffset=0 yoffset=19 xadvance=57 page=0 chnl=15
char id=91 x=407 y=0 width=21 height=51 xoffset=6 yoffset=13 xadvance=26 page=0 chnl=15
char id=93 x=429 y=0 width=21 height=51 xoffset=-2 yoffset=13 xadvance=26 page=0 chnl=15
char id=94 x=757 y=114 width=40 height=20 xoffset=12 yoffset=8 xadvance=64 page=0 chnl=15
char id=95 x=103 y=61 width=28 height=4 xoffset=-1 yoffset=61 xadvance=26 page=0 chnl=15
char id=97 x=356 y=114 width=25 height=33 xoffset=2 yoffset=24 xadvance=32 page=0 chnl=15
char id=98 x=730 y=0 width=23 height=45 xoffset=4 yoffset=12 xadvance=30 page=0 chnl=15
char id=99 x=545 y=114 width=23 height=30 xoffset=2 yoffset=27 xadvance=27 page=0 chnl=15
char id=100 x=754 y=0 width=29 height=45 xoffset=2 yoffset=12 xadvance=35 page=0 chnl=15
char id=101 x=249 y=114 width=20 height=34 xoffset=2 yoffset=25 xadvance=23 page=0 chnl=15
char id=102 x=70 y=0 width=32 height=65 xoffset=-1 yoffset=9 xadvance=26 page=0 chnl=15
char id=103 x=539 y=0 width=31 height=48 xoffset=2 yoffset=25 xadvance=38 page=0 chnl=15
char id=104 x=866 y=0 width=26 height=44 xoffset=3 yoffset=14 xadvance=33 page=0 chnl=15
char id=105 x=784 y=0 width=18 height=45 xoffset=2 yoffset=12 xadvance=20 page=0 chnl=15
char id=106 x=0 y=0 width=26 height=71 xoffset=-14 yoffset=7 xadvance=17 page=0 chnl=15
char id=107 x=651 y=0 width=35 height=46 xoffset=4 yoffset=14 xadvance=39 page=0 chnl=15
char id=108 x=936 y=0 width=16 height=43 xoffset=3 yoffset=14 xadvance=20 page=0 chnl=15
char id=109 x=593 y=114 width=51 height=29 xoffset=4 yoffset=27 xadvance=56 page=0 chnl=15
char id=110 x=645 y=114 width=37 height=28 xoffset=4 yoffset=29 xadvance=40 page=0 chnl=15
char id=111 x=417 y=114 width=22 height=32 xoffset=2 yoffset=27 xadvance=27 page=0 chnl=15
char id=112 x=377 y=0 width=29 height=51 xoffset=-3 yoffset=26 xadvance=29 page=0 chnl=15
char id=113 x=571 y=0 width=36 height=48 xoffset=2 yoffset=27 xadvance=38 page=0 chnl=15
char id=114 x=569 y=114 width=23 height=30 xoffset=3 yoffset=28 xadvance=24 page=0 chnl=15
char id=115 x=483 y=114 width=26 height=31 xoffset=0 yoffset=26 xadvance=28 page=0 chnl=15
char id=116 x=376 y=72 width=25 height=38 xoffset=-2 yoffset=18 xadvance=23 page=0 chnl=15
char id=117 x=683 y=114 width=33 height=28 xoffset=3 yoffset=28 xadvance=37 page=0 chnl=15
char id=118 x=270 y=114 width=31 height=34 xoffset=0 yoffset=23 xadvance=31 page=0 chnl=15
char id=119 x=440 y=114 width=42 height=32 xoffset=0 yoffset=25 xadvance=42 page=0 chnl=15
char id=120 x=939 y=72 width=33 height=36 xoffset=-4 yoffset=26 xadvance=30 page=0 chnl=15
char id=121 x=236 y=0 width=32 height=55 xoffset=3 yoffset=26 xadvance=41 page=0 chnl=15
char id=122 x=510 y=114 width=34 height=31 xoffset=-2 yoffset=27 xadvance=35 page=0 chnl=15
char id=123 x=451 y=0 width=19 height=50 xoffset=5 yoffset=12 xadvance=26 page=0 chnl=15
char id=124 x=65 y=0 width=4 height=66 xoffset=14 yoffset=5 xadvance=32 page=0 chnl=15
char id=125 x=471 y=0 width=19 height=50 xoffset=2 yoffset=12 xadvance=26 page=0 chnl=15
char id=8470 x=803 y=0 width=62 height=45 xoffset=-1 yoffset=2 xadvance=62 page=0 chnl=15
]]

local fontMap = 'rbxassetid://8951676396'

-----

local info = {}

info.fontInfo = {}
info.characterTable = {}
info.kernings = {}

local init, _ = s:find('kernings')
if init then
	local kernings = s:sub(init, s:len()):split('\n')

	local kerningsTable = info.kernings

	for i,v in ipairs(kernings) do
		local first, second, amount = v:match('kerning first=([%-?%.?%d?]+) second=([%-?%.?%d?]+) amount=([%-?%.?%d?]+)')
		if first then
			kerningsTable[utf8.char(first)] = kerningsTable[utf8.char(first)] or {}
			kerningsTable[utf8.char(first)][utf8.char(second)] = amount
		end
	end
	s = s:sub(1, init - 1)
end
local split = s:split('\n')

local characterTable = info.characterTable

for i = 3, 1, -1 do
	local infoThisIteration = split[i]:split(' ')
	for i,v in ipairs(infoThisIteration) do
		local field, value = unpack(v:split('='))
		if field and value then
			field, value = field:gsub('"', ''), value:gsub('"', '')
			info.fontInfo[field] = tonumber(value) or value
		end
	end
	table.remove(split, i)
end
table.remove(split, 1)
print(split)

for i = #split, 1, -1 do
	print(i,split[i])
	local v = split[i]
	local charId, x, y, width, height, xOffset, yOffset, xAdvance, page, chnl = v:match('char id=([%-?%.?%d?]+) x=([%-?%.?%d?]+) y=([%-?%.?%d?]+) width=([%-?%.?%d?]+) height=([%-?%.?%d?]+) xoffset=([%-?%.?%d?]+) yoffset=([%-?%.?%d?]+) xadvance=([%-?%.?%d?]+) page=([%-?%.?%d?]+) chnl=([%-?%.?%d?]+)')
	if charId then
		table.remove(split,i)
		table.insert(characterTable, {
			charId = charId;
			x = x;
			y = y;
			width = width;
			height = height;
			xOffset = xOffset;
			yOffset = yOffset;
			xAdvance = xAdvance;
			page = page;
			chnl = chnl
		})
	end
end

local size = 72

local stringFolder = Instance.new('Folder')
stringFolder.Name = info.fontInfo.face
stringFolder.Parent = script

for i,v in ipairs(characterTable) do
	local mainFrame = Instance.new('Frame')
	mainFrame.Size = UDim2.fromOffset(v.xAdvance or v.width, size)
	mainFrame.BackgroundTransparency = 1
	mainFrame.Name = utf8.char(v.charId)
	mainFrame.BackgroundTransparency = 1
	local newLabel = Instance.new('ImageLabel')
	newLabel.Image = fontMap
	newLabel.Size = UDim2.fromOffset(v.width, v.height)
	newLabel.Parent = mainFrame
	newLabel.Name = utf8.char(v.charId)
	newLabel.Position = UDim2.fromOffset(v.xOffset, v.yOffset)
	newLabel.ImageRectSize = Vector2.new(v.width, v.height)
	newLabel.ImageRectOffset = Vector2.new(v.x, v.y)
	newLabel.Parent = mainFrame
	newLabel.BackgroundTransparency = 1
	newLabel.ScaleType = Enum.ScaleType.Fit
	newLabel.BackgroundTransparency = 1
	mainFrame.Parent = stringFolder
end

-----

local function feed(str: string)
	-- example feed function:

	-- first off, we have a function that will return a table of graphemes
	local splitString = (function()
		local graphemes = {}
		for i,v in utf8.graphemes(str) do -- for each of the graphemes,
			table.insert(graphemes, str:sub(i,v)) -- we have to use string.sub because utf8 returns a pair where i is the beginning of the grapheme and v is the end of the grapheme. We must use this because we're working with a non-ASCII character (№).
		end
		return graphemes -- returns the table of graphemes
	end)() -- ensure you call this function so it actually assigns the graphemes table to the splitString variable
	local newFrame = Instance.new('Frame') -- create a new frame that will store the glyphs
	
	local newUIListLayout = Instance.new('UIListLayout')
	newUIListLayout.Parent = newFrame
	newUIListLayout.SortOrder = Enum.SortOrder.LayoutOrder -- we don't want them to be aligned vertically
	newUIListLayout.FillDirection = Enum.FillDirection.Horizontal -- align them horizontally from left to right
	newUIListLayout.Padding = UDim.new(0,0) -- we don't want any padding
	
	for i,v in ipairs(splitString) do -- where i is the index and v is the glyph
		local thisLetter = stringFolder:FindFirstChild(v) -- look inside the folder for a frame with the glyph's name
		if thisLetter then -- if it found a frame with this name,
			local newLetter = thisLetter:Clone() -- clone it and parent it to the frame
			newLetter.LayoutOrder = i
			newLetter.Parent = newFrame
			
			local next = splitString[i + 1] -- look for the next character to handle kernings
			if next then
				local kerningThisLetter = info.kernings[v] -- inside the kernings table, look for the kerning for this letter and the next letter
				if kerningThisLetter then -- if there is a kerning table for this letter then
					local kerningNextLetter = kerningThisLetter[next] -- if inside that kerning table there is a kerning for the next letter
					if kerningNextLetter then -- if there's a kerning then
						newFrame.Size -= UDim2.fromOffset(0,kerningNextLetter) -- subtract it from the frame's size, not the actual letter label's size because we don't want to clip the letter
					end
				end
			end
		end
	end
	return newFrame
end

feed('wow custom fonts so cool right').Parent = game.StarterGui.ScreenGui
9 Likes

this is awesome! love this resource

4 Likes

Wonderful! Imagine the amount of customization and in-game animations we can do with this module

4 Likes

Before I try this, I just want to check, every player sees this font in the game, right? There are many tutorials on how to use cutom fonts, but most of them only allow you to see them.

1 Like

Yep, it uses image labels and a font map that is uploaded on Roblox by you as a decal. Only issue is that you need to do your own implementation for text boxes and whatnot.

6 Likes

can you give add place file???

1 Like

Nope, don’t have one on hand sorry. You can copy and paste the code from my first reply to the post though.

1 Like

Does this support MaxVisibleGraphemes? (typewriting) and RichText? and does this update every time the actual text is changed, like egomoose’s old project? (for example you change the actual textlabel’s text, color etc and the custom font characters change as well automatically?)

1 Like

Nope, it just makes image labels cropped and correctly positioned. You can add support for that if you want though.

1 Like

How do we use that with TextLabels?

1 Like

You don’t, you’d have to make your own custom UI objects. Roblox is adding support for more fonts anyway so it’s not really worth it I’d say

1 Like

i need to use coolvetica with textlabels.

2 Likes

Sorry to necropost, but For whatever reason my text is rendering backwards. Not the image is backwards, but it places it backwards, like “Welcome” is “emocleW”. Any fixes?

EDIT: Scratch that, it generates gibberish. it uses the letters, but it was just "coeemlW!

i have 0 clue why this is happening

1 Like

Could be a couple things, but try setting the LayoutOrder of the ImageLabel to reorder it. I can do that in my original post if you want.

1 Like

okay, i will try this solution. thank you

wait, the imagelabel? or the frame? also, what do you mean by “recorder it”?

Oh it might be the frames that hold the image labels, it would just be whatever is the sibling of your UIListLayout. I haven’t touched this module in a while but I can look into the issue soon.

1 Like

i think i found a pattern. it displays the text given alphabetically, but in reverse. the string “abcdefg” returns “gfedcba”, which is why ! and W were the end and the E’s were at the front when i inputted “Welcome!”

it also counts down from 9 when inputting “1234567890”, but some gets cut off.