1
0
mirror of https://github.com/pyrollo/display_modpack.git synced 2025-10-15 16:45:35 +02:00
Files
display_modpack/font_api/tools/make_font.lua
Pierre-Yves Rollo 4b0245dc8b WIP params
2025-10-10 11:42:58 +02:00

386 lines
8.9 KiB
Lua

--
-- A font mod generator for font_api
--
-- This files generates only code and textures - should not be translated
-- TODO : detect and manage fixed width fonts
--
-- 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
--
-- Argument & parameters management
--
local function usage()
print (arg[0] .. " takes two arguments:")
print (" - parameter file")
print (" - destination path")
end
if #arg ~= 2 then
usage()
os.exit(1)
end
print("Reading paramaters.")
dofile(arg[1])
local mod_dir = arg[2]
if os.execute("[ -d " .. mod_dir .. " ]") then
print ("Directory " .. mod_dir .. " already exists!")
-- os.exit(1)
end
os.execute("mkdir -p " .. mod_dir .. "/textures")
--
-- Available tile sizes management
--
local function compute_tile_sizes(texture_size)
results = {}
for size = 1, texture_size do
if texture_size % size == 0 then
table.insert(results, size)
end
end
return results
end
-- This will give enough tile width combinations (360 is 2 * 2 * 2 * 3 * 3 * 5)
local tile_widths = compute_tile_sizes(360)
-- Table width has to be sorted
table.sort(tile_widths)
local texture_width = tile_widths[#tile_widths]
-- Rounds glyph width up to available tile width (first width larger than given one)
local function tile_width(width)
for _, w in ipairs(tile_widths) do
if width < w then
return w
end
end
return texture_width
end
--
-- Helper functions
--
-- Issue an OS command and get its result
local function command(cmd)
local f = assert(io.popen(cmd, 'r'))
local s = assert(f:read('*a'))
f:close()
return s
end
-- Escape chars that could harm commands
local function escape(char)
if char == "\\" then return "\\\\\\\\" end
if char == "\"" then return "\\\"" end
if char == "`" then return "\\`" end
return char
end
--
-- Things start here
--
-- Measures a glyph, returs its height and width in pixels
local function measure(font, codepoint)
local char = utf8.char(codepoint)
local cmd = string.format(
"convert -font \"%s\" -pointsize %d label:\"%s\" -define trim:edges=east,west -trim info:",
font.file, font.height, escape(char)
)
local _, _, w, h = string.find(command(cmd), "([0-9]+)x([0-9]+)" )
return tonumber(w), tonumber(h)
end
-- Read all available codepoints from ttf file
local function read_available_codepoints(file)
local cmd, errmsg, status = io.popen(string.format(
"ttx -o - \"%s\" 2>/dev/null | grep \"<map code=\" | cut -d \\\" -f 2",
file), 'r')
if cmd == nil then
print(string.format(
"Could not open font file %s:\n%s", file, errmsg))
os.exit(status)
end
local codepoints = {}
local codepoint = cmd:read("*line")
while codepoint do
codepoints[tonumber(codepoint)] = true
codepoint = cmd:read("*line")
end
cmd:close()
return codepoints
end
-- Add codepoints to a font
local function add_codepoints(font, from, to)
for codepoint = from, to do
if font.cp[codepoint] then
-- Glyph size
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 font.by_width[tile_w] == nil then
font.by_width[tile_w] = {}
table.insert(font.tile_widths, tile_w)
end
table.insert(font.by_width[tile_w], codepoint)
end
end
end
-- 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)
-- We start with a single line
font.texture_height = font.glyphs_height
-- 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)
local x = 0 -- cursor x
local glyph_y = 0
table.sort(font.tile_widths)
-- Compute positions
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 > font.texture_width then -- no space left on current line
x = 0
glyph_x = 0
glyph_y = glyph_y + 1
font.texture_height = font.texture_height + font.glyphs_height
end
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
-- Compose texture
command(string.format(
"convert -size %dx%d xc:transparent %s",
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.glyphs_height * glyph_ys[codepoint]
local cmd
-- Subtexture subcommand
if codepoint == 0 then
-- The "unknown" char
cmd = string.format(
"convert %s" ..
" -stroke black -fill transparent -strokewidth 1 " ..
" -draw \"rectangle %d,%d %d,%d\" %s",
texture_file, x, y, x + w, y + font.glyphs_height, texture_file
)
else
-- Other glyhp chars
cmd = string.format(
"convert %s \\(" ..
" -background none -font \"%s\" -pointsize %d label:\"%s\"" ..
" -define trim:edges=east,west -trim" ..
" -repage +%d+%d \\) -flatten %s",
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", 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:
-- <mtx> 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
local function get_font_registration_lua(font)
local glyphs = ""
local curlinesize = 1000
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
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
return string.format([[
-- Font generated from file %s with pointsize %d
font_api.register_font(
'%s',
{
version = 2,
default = true,
margintop = 3,
linespacing = -2,
charspacing = 2,
texture_height = %d,
glyphs_height = %d,
glyphs = {
%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([[
}
}
);
]])
file:close()
--
-- Write mod.conf
--
file = io.open(mod_dir .. "/mod.conf", "w")
file:write(string.format([[
name = %s
title = %s
description = %s
depends = font_api
]], params.mod_name, params.mod_title, params.mod_description))