From 45a1d89502cf79fc0629431ec17f5809829190d6 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Rollo Date: Thu, 2 Oct 2025 20:12:03 +0200 Subject: [PATCH] wip --- font_api/tools/make_font.lua | 282 +++++++++++++++++++++++++++ font_api/tools/make_font_textures.sh | 10 +- 2 files changed, 290 insertions(+), 2 deletions(-) create mode 100644 font_api/tools/make_font.lua diff --git a/font_api/tools/make_font.lua b/font_api/tools/make_font.lua new file mode 100644 index 0000000..3741a4a --- /dev/null +++ b/font_api/tools/make_font.lua @@ -0,0 +1,282 @@ +-- +-- A font mod generator for font_api +-- + +-- This files generates only code and textures - should not be translated + +-- +-- Argument management +-- + +local function usage() + print (arg[0].." takes tree arguments:") + print (" - font file name") + print (" - wanted font name") + print (" - wanted font height") +end + +if #arg ~= 3 then + usage() + os.exit(1) +end + +local fontfile=arg[1] +local fontname=arg[2] +local fontsize=arg[3] + +local modname = fontname + +-- +-- Dependancies check +-- + +local function check(cmd, msg) + if os.execute(cmd .. " > /dev/null 2>&1") then + return true + else + print(msg) + end +end + +if + not check("convert --version", "Error: This program requires convert from ImageMagick!") or + not check("identify --version", "Error: This program requires identify from ImageMagick!") or + not check("ttx --version", "Error: This program requires ttx from FontTools!") +then + print("Please fix above problem and retry.") + os.exit(1) +end + +-- +-- Prepare output directory +-- + +-- TODO: we should be able to choose basedir +local moddir = fontname + +if os.execute("[ -d " .. moddir .. " ]") then + print ("Directory " .. moddir .. " already exists!") +-- os.exit(1) +end + +os.execute("mkdir -p " .. moddir .. "/textures") + +-- +-- +-- + +local function command(cmd) + local f = assert(io.popen(cmd, 'r')) + local s = assert(f:read('*a')) + f:close() + return s +end + +local function escape(char) + if char == "\\" then return "\\\\\\\\" end + if char == "\"" then return "\\\"" end + if char == "`" then return "\\`" end + return char +end + +-- Measures a glyph, returs its height and width in pixels + +local function measure(codepoint) + local char = utf8.char(codepoint) + + local cmd = string.format( + "convert -font \"%s\" -pointsize %d label:\"%s\" -identify NULL:", + fontfile, fontsize, escape(char) + ) + + _, _, w, h = string.find(command(cmd), "([0-9]+)x([0-9]+)" ) + return tonumber(w), tonumber(h) +end + +-- Read all available codepoints +local cmd, errmsg, status = io.popen("ttx -o - \"" .. fontfile .. "\" 2>/dev/null | grep \" fontheight then fontheight = h end + + if bywidth[w] == nil then + bywidth[w] = {} + table.insert(widths, w) + end + + table.insert(bywidth[w], codepoint) + + end + end + table.sort(widths) +end + +local glyphxs = {} -- x (in grid) for each glyph +local glyphys = {} -- y (in grid) for each glyph +local glyphws = {} -- widths (in pixel) of each glyph + +local function make_final_texture(imagewidth, filename) + + local imageheight = fontheight + + local x = 0 -- cursor x + local glyphy = 0 + + -- Compute positions + for _, width in ipairs(widths) do + for _, codepoint in ipairs(bywidth[width]) do + local glyphx = math.ceil(x / width) + x = glyphx * width + if x + width > imagewidth then -- no space left on current line + x = 0 + glyphx = 0 + glyphy = glyphy + 1 + imageheight = imageheight + fontheight + end + glyphxs[codepoint] = glyphx + glyphys[codepoint] = glyphy + glyphws[codepoint] = width + x = x + width + end + end + + -- Compose texture + command(string.format( + "convert -channel alpha -colorspace gray -size %dx%d xc:transparent %s", + imagewidth, imageheight, filename + )) + + for codepoint, w in pairs(glyphws) do + local x = w * glyphxs[codepoint] + local y = fontheight * glyphys[codepoint] + + local cmd + + -- Subtexture subcommand + if codepoint == 0 then + -- The "unknown" char + cmd = string.format("-size %dx%d xc:transparent -colorspace gray -stroke black -fill transparent -strokewidth 1 -draw \"rectangle 0,0 %d,%d\"", + w, fontheight, w-1, fontheight-1 + ) + else + -- Other glyhp chars + cmd = string.format("-channel alpha -background none -colorspace gray -fill black -font \"%s\" -pointsize %d label:\"%s\"", + fontfile, fontsize, escape(utf8.char(codepoint)) + ) + end + -- Place subtexure in texture + cmd = string.format("convert %s \\( %s -repage +%d+%d \\) -flatten %s", filename, cmd, x, y, filename) + command(cmd) + end + + command(string.format("convert %s -threshold 50%% %s", filename, filename)) + +end + +print("Compute glyphs properties") + +-- TODO: We could get information from ttx: +-- gives a width always divisible by 125 for metro font (check if its somehow proportional to what magick gives) +-- + +-- Special char: unknown char +-- We use size of glyph "0" but it would be better to get size from ttx +local w, _ = measure(0x0030) +bywidth[w] = { 0 } +widths = { w } + +-- Mandatory chars +add_codepoints(0x0021, 0x007f) + +-- TODO: manage Space without texture! + half/quater spaces + +-- Optional Unicode pages (see https://en.wikipedia.org/wiki/Unicode) : + +-- 00a0-00ff Latin-1 Supplement (full) +--add_codepoints(0x00a0, 0x00ff) + +-- 0100-017f Latin Extended-A (full) +--add_codepoints(0x0100, 0x017f) + +-- 0370-03ff Greek (full) +--add_codepoints(0x0370, 0x03ff) + +-- 0400-04ff Cyrilic (full) +--add_codepoints(0x0400, 0x04ff) + +-- 2000-206f General Punctuation (Limited to Dashes) +--add_codepoints(0x2010, 0x2015) + +-- 2000-206f General Punctuation (Limited to Quotes) +--add_codepoints(0x2018, 0x201F) + +-- 20a0-20cf Currency Symbols (Limited to Euro symbol) +--add_codepoints(0x20ac, 0x20ac) + + +print("Prepare final texture") + +make_final_texture(200, moddir .. "/textures/" .. modname .. "_" .. fontname .. ".png") + +-- Invisible chars : Spaces +glyphws[0x0020] = glyphws[0] + + +file = io.open(moddir .. "/init.lua", "w") +file:write(string.format([[ +-- +-- %s: A '%s' font mod for font_api +-- +-- This file was generated by `%s` on %s from file `%s` with size %d. +-- + +]], modname, fontname, arg[0], os.date("%Y-%m-%d at %H:%M"), fontfile, fontsize +)) +file:write(string.format([[ +-- luacheck: ignore +font_api.register_font( + '%s', + { + default = true, + margintop = 3, + linespacing = -2, + height = %d, + glyphs = { +]], + fontname, fontheight) +) +for codepoint, w in pairs(glyphws) do + x = glyphxs[codepoint] + y = glyphys[codepoint] + if x ~= nil and y ~=nil then + file:write(string.format(" [%d] = { w = %d, x = %d, y = %d },\n", codepoint, w, x, y)) + else + file:write(string.format(" [%d] = { w = %d },\n", codepoint, w)) + end +end +file:write([[ + } + } +); +]]) +file:close() diff --git a/font_api/tools/make_font_textures.sh b/font_api/tools/make_font_textures.sh index 4a3191c..5f72315 100755 --- a/font_api/tools/make_font_textures.sh +++ b/font_api/tools/make_font_textures.sh @@ -31,14 +31,20 @@ fi # check imagemagick hash convert &>/dev/null if [ $? -eq 1 ]; then - echo -e "Error: This program requires convert from ImageMagick! Please install it by typing 'sudo apt-get install imagemagick' in terminal." + echo -e "Error: This program requires convert from ImageMagick!" + abort=1 +fi + +hash montage &>/dev/null +if [ $? -eq 1 ]; then + echo -e "Error: This program requires montage from ImageMagick!" abort=1 fi # check ttx hash ttx &>/dev/null if [ $? -eq 1 ]; then - echo -e "Error: This program requires ttx from FontTools! Please install it by typing 'sudo apt-get install fonttools' in terminal." + echo -e "Error: This program requires ttx from FontTools!" abort=1 fi