From 4b0245dc8beef73378720c6eb3660da266a7dc30 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Rollo Date: Fri, 10 Oct 2025 11:42:58 +0200 Subject: [PATCH] WIP params --- extra_fonts/params_test.lua | 68 ++++ .../sources/pixeldroidBoticRegular.otf | Bin 0 -> 25720 bytes font_api/tools/make_font.lua | 351 +++++++++--------- font_api/tools/make_font/params.example | 83 +++++ font_metro/metro-sans.otf/license.txt | 5 + font_metro/metro-sans.otf/metro-sans.otf | Bin 0 -> 81948 bytes font_metro/metro-sans.otf/readme.txt | 16 + 7 files changed, 357 insertions(+), 166 deletions(-) create mode 100644 extra_fonts/params_test.lua create mode 100644 extra_fonts/sources/pixeldroidBoticRegular.otf create mode 100644 font_api/tools/make_font/params.example create mode 100644 font_metro/metro-sans.otf/license.txt create mode 100644 font_metro/metro-sans.otf/metro-sans.otf create mode 100644 font_metro/metro-sans.otf/readme.txt diff --git a/extra_fonts/params_test.lua b/extra_fonts/params_test.lua new file mode 100644 index 0000000..a443156 --- /dev/null +++ b/extra_fonts/params_test.lua @@ -0,0 +1,68 @@ + +-- This is an example parameter file for make_font.lua +-- Copy this file as params.lua. +-- Replace values between brakets <> to your choices. +-- Launch make_font.lua params.lua + +params = { + -- Resulting mod name (required) + -- As this name will be use as texture prefix and so repeated many times, + -- please avoid long names but keep explicit anyway. + mod_name = "fonts_extra", + + -- If only one font, have font in title, like "xxx font" + mod_title = "Extra fonts", + + -- A good description would be "... fonts for font_api" + mod_descriptiion = "Extra fonts for font_api: botic", + + -- List of fons to include to the mod. + fonts = { + { + -- Registered font name (required) + -- As this name will be use as texture prefix and so repeated many times, + -- avoid long names. A good name would be a single world all lowercase. + name = "botic", + + -- Registered font label (optional, default capitalized name) + -- This is the display name for this font. No need to be concise. + label = "Botic", + + -- True type font file to get glyphs from (required) + file = "sources/pixeldroidBoticRegular.ttf", + + -- Render pointsize (integer, required) + -- Try to find a proper value for a good rendering + pointsize = 16, + + -- Shoud chars be trimmed? (boolean, required) + -- Set it to true to reduce texture size + -- and increase `char_spacing` accordingly. + -- If results are weird, you may try to set it to false. + trim = true, + + -- Margin added on top of text textures with this font (integer, optional, default 0) + margin_top = 3, + + -- Space between consecutive lines (integer, optional, default 0) + -- Space may be negative to make lines closer. + line_spacing = -2, + + -- Space between consecutive chars (integer, optional, default 0) + char_spacing = 2, + + -- Extra codepoints to include to font mod (optional, default none) + -- Codepoints from 0x0020 to 0x007f (ASCII) are always included. + -- Codepoint 0x0020 is always considered as a space. + -- Codepoints not existing in font file will be ignored. + -- Refer to https://en.wikipedia.org/wiki/Unicode + codepoints = { + -- 00a0-00ff Latin-1 Supplement (full) + { from=0x00a0, to=0x00ff }, + + -- 20a0-20cf Currency Symbols (Limited to Euro symbol) + { from=0x20ac, 0x20ac }, + }, + } + } +} diff --git a/extra_fonts/sources/pixeldroidBoticRegular.otf b/extra_fonts/sources/pixeldroidBoticRegular.otf new file mode 100644 index 0000000000000000000000000000000000000000..513b57c72e99028e8702e247991f59f3e8fd13fe GIT binary patch literal 25720 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 p-/dev/null | grep \"/dev/null | grep \" font_height then font_height = h end - glyph_widths[codepoint] = w + local w, h = measure(font, codepoint) + if h > font.glyphs_height then font.glyphs_height = h end + font.glyph_widths[codepoint] = w -- Tile width local tile_w = tile_width(w) - if by_width[tile_w] == nil then - by_width[tile_w] = {} - table.insert(tile_widths, tile_w) + if font.by_width[tile_w] == nil then + font.by_width[tile_w] = {} + table.insert(font.tile_widths, tile_w) end - table.insert(by_width[tile_w], codepoint) + table.insert(font.by_width[tile_w], codepoint) end end end --- Characteristics of [sheet:NxM:x,y --- M is always the same and depends on font and texture height. -local glyph_xs = {} -- x for each glyph -local glyph_ys = {} -- y for each glyph -local glyph_ns = {} -- n of tiles in sheet for each glyph (=texturewidth / tilewidth) +-- Make font texture +-- Font must have all its codepoints added +local function make_final_texture(font) + local texture_file = string.format("%/textures/font_%s.png", + mod_dir, font.name) -local texture_height + -- We start with a single line + font.texture_height = font.glyphs_height -local function make_final_texture(filename) + -- Characteristics of [sheet:NxM:x,y + -- M is always the same and depends on font and texture height. + font.glyph_xs = {} -- x for each glyph + font.glyph_ys = {} -- y for each glyph + font.glyph_ns = {} -- n of tiles in sheet for each glyph (=texturewidth / tilewidth) - texture_height = font_height - local x = 0 -- cursor x local glyph_y = 0 - table.sort(tile_widths) + table.sort(font.tile_widths) -- Compute positions - for _, tile_width in ipairs(tile_widths) do - for _, codepoint in ipairs(by_width[tile_width]) do + for _, tile_width in ipairs(font.tile_widths) do + for _, codepoint in ipairs(font.by_width[tile_width]) do local glyph_x = math.ceil(x / tile_width) x = glyph_x * tile_width - if x + tile_width > texture_width then -- no space left on current line + if x + tile_width > font.texture_width then -- no space left on current line x = 0 glyph_x = 0 glyph_y = glyph_y + 1 - texture_height = texture_height + font_height + font.texture_height = font.texture_height + font.glyphs_height end - glyph_xs[codepoint] = glyph_x - glyph_ys[codepoint] = glyph_y - glyph_ns[codepoint] = math.floor(texture_width / tile_width) + font.glyph_xs[codepoint] = glyph_x + font.glyph_ys[codepoint] = glyph_y + font.glyph_ns[codepoint] = math.floor(texture_width / tile_width) x = x + tile_width end end @@ -216,13 +206,13 @@ local function make_final_texture(filename) -- Compose texture command(string.format( "convert -size %dx%d xc:transparent %s", - texture_width, texture_height, filename + texture_width, texture_height, texture_file )) for codepoint, n in pairs(glyph_ns) do local w = math.floor(texture_width / n) local x = w * glyph_xs[codepoint] - local y = font_height * glyph_ys[codepoint] + local y = font.glyphs_height * glyph_ys[codepoint] local cmd -- Subtexture subcommand @@ -232,7 +222,7 @@ local function make_final_texture(filename) "convert %s" .. " -stroke black -fill transparent -strokewidth 1 " .. " -draw \"rectangle %d,%d %d,%d\" %s", - filename, x, y, x + w, y + font_height, filename + texture_file, x, y, x + w, y + font.glyphs_height, texture_file ) else -- Other glyhp chars @@ -241,82 +231,91 @@ local function make_final_texture(filename) " -background none -font \"%s\" -pointsize %d label:\"%s\"" .. " -define trim:edges=east,west -trim" .. " -repage +%d+%d \\) -flatten %s", - filename, fontfile, fontsize, escape(utf8.char(codepoint)), - x, y, filename + texture_file, font.file, font.pointsize, escape(utf8.char(codepoint)), + x, y, texture_file ) end command(cmd) end - command(string.format("convert %s -channel alpha -threshold 50%% %s", filename, filename)) + command(string.format("convert %s -channel alpha -threshold 50%% %s", texture_file, texture_file)) +end + +local function process_font(font) + print(string.format("Processing font \"%s\" (%s)", font.label, font.name) + + font.by_width = {} -- Codepoints by tile width + font.tile_widths = {} -- Used tile widths + font.glyph_widths = {} -- Exact width of reach glyph + font.glyphs_height = 0 -- Max height of all glyphs + + print("Read available glyphs") + + -- Available codepoints from file + font.cp = read_available_codepoints(font.file) + + print("Compute glyphs properties") + + -- Special char: unknown char + -- We use size of glyph "0" (rounded) but it would be better to get size from ttx + + -- 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) + + local w = tile_width(measure(font, 0x0030)) + font.glyph_widths[0] = w + font.by_width[w] = { 0 } + font.tile_widths = { w } + + -- Mandatory codepoints (ASCII) + add_codepoints(font, 0x0021, 0x007f) + + -- Extra codepoints + if font.codepoints then + for _, range in ipairs(font.codepoints) do + add_codepoints(font, range.from, range.to) + end + end + + print("Create final texture") + + make_final_texture(font) + + -- Add invisible chars : Spaces + -- TODO: Should be computed from ttx + -- TODO: manage half/quater spaces + glyph_widths[0x0020] = glyph_widths[0x0030] end -print("Compute glyphs properties") +local function get_font_registration_lua(font) --- 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) --- + local glyphs = "" + local curlinesize = 1000 --- Special char: unknown char --- We use size of glyph "0" (rounded) but it would be better to get size from ttx -local w = tile_width(measure(0x0030)) -glyph_widths[0] = w -by_width[w] = { 0 } -tile_widths = { w } + for codepoint, w in pairs(glyph_widths) do + local glyph + local x = glyph_xs[codepoint] + local y = glyph_ys[codepoint] + local n = glyph_ns[codepoint] + if x ~= nil and y ~=nil and n ~= nil then + glyph = string.format("[%d] = { %d, %d, %d, %d },", codepoint, w, n, x, y) + else + glyph = string.format("[%d] = { %d },", codepoint, w) + end --- Mandatory chars -add_codepoints(0x0021, 0x007f) + curlinesize = curlinesize + glyph:len() + 1 + if curlinesize > 80 then + glyphs = glyphs + "\n\t\t\t" + glyph + curlinesize = 12 + glyph:len() + else + glyphs = glyphs + " " + glyph + end + end --- 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(moddir .. "/textures/font_" .. modname .. ".png") - --- Invisible chars : Spaces -- Should be computed from ttx -glyph_widths[0x0020] = glyph_widths[0x0030] - --- --- Write init.lua --- - -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 + return string.format([[ +-- Font generated from file %s with pointsize %d font_api.register_font( '%s', { @@ -328,19 +327,41 @@ font_api.register_font( texture_height = %d, glyphs_height = %d, glyphs = { -]], - fontname, texture_height, font_height) -) -for codepoint, w in pairs(glyph_widths) do - local x = glyph_xs[codepoint] - local y = glyph_ys[codepoint] - local n = glyph_ns[codepoint] - if x ~= nil and y ~=nil and n ~= nil then - file:write(string.format(" [%d] = { %d, %d, %d, %d },\n", codepoint, w, n, x, y)) - else - file:write(string.format(" [%d] = { %d },\n", codepoint, w)) - end +%s + } +]], font.file, font.pointsize, font.texture_height, font.glyphd_height, glyphs) end + +-- +-- Main code +-- + +-- Defaults (TODO) +if not font.label then + font.label = font.name:gsub("^%l", string.upper) +end + +for _, font in ipairs(font.fonts) do + process_font(font) +end + +-- Write init.lua +file = io.open(mod_dir .. "/init.lua", "w") + +file:write(string.format([[ +-- +-- %s: A font mod for font_api +-- +-- This file was generated by `%s` on %s. +-- + +]], params.mod_name, arg[0], os.date("%Y-%m-%d at %H:%M") +)) + +for _, font in ipairs(font.fonts) do + file:write(get_font_registration_lua(font)) +end + file:write([[ } } @@ -348,19 +369,17 @@ file:write([[ ]]) file:close() - -- -- Write mod.conf -- -local fontlabel = fontname:gsub("^%l", string.upper) -file = io.open(moddir .. "/mod.conf", "w") +file = io.open(mod_dir .. "/mod.conf", "w") file:write(string.format([[ -name = font_%s -title = %s Font -description = %s font for font_api +name = %s +title = %s +description = %s depends = font_api -]], fontname, fontlabel, fontlabel)) +]], params.mod_name, params.mod_title, params.mod_description)) diff --git a/font_api/tools/make_font/params.example b/font_api/tools/make_font/params.example new file mode 100644 index 0000000..2400413 --- /dev/null +++ b/font_api/tools/make_font/params.example @@ -0,0 +1,83 @@ + +-- This is an example parameter file for make_font.lua +-- Copy this file as params.lua. +-- Replace values between brakets <> to your choices. +-- Launch make_font.lua params.lua + +params = { + -- Resulting mod name (required) + -- As this name will be use as texture prefix and so repeated many times, + -- please avoid long names but keep explicit anyway. + mod_name = "", + + -- If only one font, have font in title, like "xxx font" + mod_title = "", + + -- A good description would be "... fonts for font_api" + mod_descriptiion = "", + + -- List of fons to include to the mod. + fonts = { + { + -- Registered font name (required) + -- As this name will be use as texture prefix and so repeated many times, + -- avoid long names. A good name would be a single world all lowercase. + name = "", + + -- Registered font label (optional, default capitalized name) + -- This is the display name for this font. No need to be concise. + label = "", + + -- True type font file to get glyphs from (required) + file = "", + + -- Render pointsize (integer, required) + -- Try to find a proper value for a good rendering + pointsize = , + + -- Shoud chars be trimmed? (boolean, required) + -- Set it to true to reduce texture size + -- and increase `char_spacing` accordingly. + -- If results are weird, you may try to set it to false. + trim = true, + + -- Margin added on top of text textures with this font (integer, optional, default 0) + margin_top = 3, + + -- Space between consecutive lines (integer, optional, default 0) + -- Space may be negative to make lines closer. + line_spacing = -2, + + -- Space between consecutive chars (integer, optional, default 0) + char_spacing = 2, + + -- Extra codepoints to include to font mod (optional, default none) + -- Codepoints from 0x0020 to 0x007f (ASCII) are always included. + -- Codepoint 0x0020 is always considered as a space. + -- Codepoints not existing in font file will be ignored. + -- Refer to https://en.wikipedia.org/wiki/Unicode + codepoints = { + -- 00a0-00ff Latin-1 Supplement (full) + { from=0x00a0, to=0x00ff }, + + -- 0100-017f Latin Extended-A (full) + { from=0x0100, 0x017f }, + + -- 0370-03ff Greek (full) + { from=0x0370, 0x03ff }, + + -- 0400-04ff Cyrilic (full) + { from=0x0400, 0x04ff }, + + -- 2000-206f General Punctuation (Limited to Dashes) + { from=0x2010, 0x2015 }, + + -- 2000-206f General Punctuation (Limited to Quotes) + { from=0x2018, 0x201F }, + + -- 20a0-20cf Currency Symbols (Limited to Euro symbol) + { from=0x20ac, 0x20ac }, + }, + } + } +} diff --git a/font_metro/metro-sans.otf/license.txt b/font_metro/metro-sans.otf/license.txt new file mode 100644 index 0000000..935c1fd --- /dev/null +++ b/font_metro/metro-sans.otf/license.txt @@ -0,0 +1,5 @@ +The FontStruction “Metro Sans” +(https://fontstruct.com/fontstructions/show/723864) by Christian Munk is +licensed under a Creative Commons Attribution Share Alike license +(http://creativecommons.org/licenses/by-sa/3.0/). +[ancestry] \ No newline at end of file diff --git a/font_metro/metro-sans.otf/metro-sans.otf b/font_metro/metro-sans.otf/metro-sans.otf new file mode 100644 index 0000000000000000000000000000000000000000..a3bd4043439f016d567f052d88b5eba30b296d98 GIT binary patch literal 81948 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