Created
September 18, 2025 13:48
-
-
Save Be1zebub/f6a3bc9cb6d6cdc8051ce5869a64385c to your computer and use it in GitHub Desktop.
GMod fonts list & inspector (opentype.js based parser)
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
| 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 |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
based on https://gist.github.com/sn-0w/46baa6db41d2ba76aa255383cbef32db