Skip to content

Instantly share code, notes, and snippets.

@Be1zebub
Created September 18, 2025 13:48
Show Gist options
  • Save Be1zebub/f6a3bc9cb6d6cdc8051ce5869a64385c to your computer and use it in GitHub Desktop.
Save Be1zebub/f6a3bc9cb6d6cdc8051ce5869a64385c to your computer and use it in GitHub Desktop.
GMod fonts list & inspector (opentype.js based parser)
local HTML = [[<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/opentype.js@latest/dist/opentype.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/default.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js"></script>
<style>
html, body {
background-color: rgba(0, 0, 0, 0.1);
}
</style>
<script>
window.RenderOpenTypeData = (base64) => {
const binary = atob(base64)
const len = binary.length
const buffer = new Uint8Array(len)
for (let i = 0; i < len; i++) buffer[i] = binary.charCodeAt(i)
const fontData = opentype.parse(buffer.buffer)
const codeBlock = document.getElementById("json-block")
codeBlock.textContent = safeStringify(fontData)
hljs.highlightElement(codeBlock)
}
function safeStringify(obj) {
const seen = new WeakSet()
return JSON.stringify(obj, function(key, value) {
if (typeof value === "object" && value !== null) {
if (seen.has(value)) return "[=[circular]=]"
seen.add(value)
}
if (typeof value === "function") {
return "[=[function]=]"
}
return value
}, 2);
}
</script>
<body>
<pre>
<code class="json" id="json-block"></code>
</pre>
</body>
</head>
</html>
]]
if IsValid(g_FontsViewer) then g_FontsViewer:Remove() end
g_FontsViewer = vgui.Create("DFrame")
g_FontsViewer:SetSize(1280, 720)
g_FontsViewer:Center()
g_FontsViewer:MakePopup()
g_FontsViewer:SetTitle("Fonts Viewer")
g_FontsViewer.scroll = g_FontsViewer:Add("DScrollPanel")
g_FontsViewer.scroll:Dock(LEFT)
g_FontsViewer.scroll:SetWide(320)
g_FontsViewer.openTypeBridge = g_FontsViewer:Add("DHTML")
g_FontsViewer.openTypeBridge:Dock(FILL)
g_FontsViewer.openTypeBridge:SetHTML(HTML)
local function ReadUShortBE(f)
local b1 = f:ReadByte() or 0
local b2 = f:ReadByte() or 0
return b1 * 256 + b2
end
local function ReadULongBE(f)
local b1 = f:ReadByte() or 0
local b2 = f:ReadByte() or 0
local b3 = f:ReadByte() or 0
local b4 = f:ReadByte() or 0
return b1 * 16777216 + b2 * 65536 + b3 * 256 + b4
end
function GetTTFFontName(filepath)
if not file.Exists(filepath, "GAME") then return nil, "File does not exist" end
local f = file.Open(filepath, "rb", "GAME")
if not f then return nil, "Could not open file" end
f:Seek(4)
local numTables = ReadUShortBE(f)
f:Seek(f:Tell() + 6) --we skip searchRange, entrySelector, rangeShift
local nameOffset, nameLength
for i = 1, numTables do
local tag = f:Read(4)
local checkSum = ReadULongBE(f)
local offset = ReadULongBE(f)
local length = ReadULongBE(f)
if tag == "name" then
nameOffset, nameLength = offset, length
end
end
if not nameOffset then return nil, "No name table" end
f:Seek(nameOffset)
local version = ReadUShortBE(f)
local count = ReadUShortBE(f)
local storageOffset = ReadUShortBE(f)
for i = 1, count do
local platformID = ReadUShortBE(f)
local encodingID = ReadUShortBE(f)
local languageID = ReadUShortBE(f)
local nameID = ReadUShortBE(f)
local length = ReadUShortBE(f)
local stringOffset = ReadUShortBE(f)
if nameID == 4 or nameID == 1 then
local pos = nameOffset + stringOffset + storageOffset
f:Seek(pos)
local raw = f:Read(length)
if platformID == 0 or platformID == 3 then
local out = {}
for j = 1, #raw, 2 do
local hi = raw:byte(j)
local lo = raw:byte(j + 1)
local code = hi * 256 + lo
if code > 0 and code < 128 then
table.insert(out, string.char(code))
end
end
return table.concat(out)
else
return raw
end
end
end
return nil, "Font name not found"
end
local fontFiles = file.Find("resource/fonts/*", "GAME")
for index, fileName in ipairs(fontFiles) do
local fontName = fileName
if fileName:match("%.ttf$") then
g_FontNamesCache = g_FontNamesCache or {}
if g_FontNamesCache[fileName] == nil then
local name = GetTTFFontName("resource/fonts/" .. fileName)
g_FontNamesCache[fileName] = name and (name .. " (" .. fileName .. ")") or fileName
end
fontName = g_FontNamesCache[fileName]
end
local btn = g_FontsViewer.scroll:Add("DButton")
btn:Dock(TOP)
btn:SetText(fontName)
btn:DockMargin(0, 0, 0, 5)
btn:SetFont("DermaDefault")
btn:SetContentAlignment(4)
btn:SetTextInset(8, 0)
btn.DoClick = function()
local raw = file.Read("resource/fonts/" .. fileName, "GAME")
local b64 = util.Base64Encode(raw)
local js = string.format("window.RenderOpenTypeData(%q)", b64)
g_FontsViewer.openTypeBridge:QueueJavascript(js)
end
end
@Be1zebub
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment