Last active
June 4, 2021 19:03
-
-
Save zr-tex8r/7058922 to your computer and use it in GitHub Desktop.
Converts TTC font files to TTF files
This file contains 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
-- zrdecompttc.lua | |
prog_name = "zrdecompttc" | |
version = "0.4" | |
mod_date = "2016/11/03" | |
verbose = false | |
dryrun = false | |
---------------------------------------- | |
ttc_file, ttf_base, ttf_ext, ttf_idb = nil | |
---------------------------------------- | |
do | |
local reader_meta = { | |
__tostring = function(self) | |
return "reader("..self.name..")" | |
end; | |
__index = { | |
cdata = function(self, ofs, len) | |
return make_cdata(self:read(ofs, len)) | |
end; | |
read = function(self, ofs, len) | |
self.file:seek("set", ofs) | |
local data = self.file:read(len) | |
sure(data:len() == len, 1) | |
return data | |
end; | |
close = function(self) | |
self.file:close() | |
end; | |
} | |
} | |
function make_reader(fname) | |
local file = io.open(fname, "rb") | |
sure(file, "cannot open for input", fname) | |
return setmetatable({ | |
name = fname, file = file | |
}, reader_meta) | |
end | |
end | |
---------------------------------------- | |
do | |
local cdata_meta = { | |
__tostring = function(self) | |
return "cdata(pos="..self._pos..")" | |
end; | |
__index = { | |
pos = function(self, p) | |
if not p then return self._pos end | |
self._pos = p | |
return self | |
end; | |
_unum = function(self, b) | |
local v, data = 0, self.data | |
sure(#data >= self._pos + b, 11) | |
for i = 1, b do | |
self._pos = self._pos + 1 | |
v = v * 256 + data:byte(self._pos) | |
end | |
return v | |
end; | |
_setunum = function(self, b, v) | |
local t, data = {}, self.data | |
t[1] = data:sub(1, self._pos) | |
self._pos = self._pos + b | |
sure(#data >= self._pos, 12) | |
t[b + 2] = data:sub(self._pos + 1) | |
for i = 1, b do | |
t[b + 2 - i] = string.char(v % 256) | |
v = math.floor(v / 256) | |
end | |
self.data = table.concat(t, "") | |
return self | |
end; | |
str = function(self, b) | |
local data = self.data | |
self._pos = self._pos + b | |
sure(#data >= self._pos, 13) | |
return data:sub(self._pos - b + 1, self._pos) | |
end; | |
setstr = function(self, s) | |
local t, data = {}, self.data | |
t[1], t[2] = data:sub(1, self._pos), s | |
self._pos = self._pos + #s | |
sure(#data >= self._pos, 14) | |
t[3] = data:sub(self._pos + 1) | |
self.data = table.concat(t, "") | |
return self | |
end; | |
ushort = function(self) | |
return self:_unum(2) | |
end; | |
ulong = function(self) | |
return self:_unum(4) | |
end; | |
setulong = function(self, v) | |
return self:_setunum(4, v) | |
end; | |
ulongs = function(self, num) | |
local t = {} | |
for i = 1, num do | |
t[i] = self:_unum(4) | |
end | |
return t | |
end; | |
} | |
} | |
function make_cdata(data) | |
return setmetatable({ | |
data = data, _pos = 0 | |
}, cdata_meta) | |
end | |
function empty_cdata(len) | |
return make_cdata(("\0"):rep(len)) | |
end | |
end | |
---------------------------------------- | |
do | |
local noop = function(self) end | |
local dumbfile_meta = { __index = { | |
write = noop; close = noop; | |
}} | |
function make_dumbfile() | |
return setmetatable({}, dumbfile_meta) | |
end | |
end | |
---------------------------------------- | |
do | |
local RDX = 0x10000 * 0x10000 | |
local MAGIC = 0x5F0F3CF5 | |
local SGOAL = 0xB1B0AFBA | |
function ttf_offset(reader) | |
local cd = reader:cdata(0, 12) | |
local tag = cd:str(4); info("tag", tag) | |
sure(tag == "ttcf", "TTC header missing") | |
local ver = cd:ulong(); info("version", ver) | |
local num = cd:ulong(); info("#fonts", num) | |
cd = reader:cdata(12, 4 * num) | |
local res = cd:ulongs(num); info("offset", stt(res)) | |
return res | |
end | |
local function check_sum(cd) | |
local sum, len = 0, #cd.data | |
sure(len % 4 == 0, 19) | |
cd:pos(0) | |
for i = 1, len / 4 do | |
sum = sum + cd:ulong() | |
if sum >= RDX then sum = sum - RDX end | |
end | |
return sum | |
end | |
local function madd(a, b) | |
local sum = a + b | |
if sum >= RDX then sum = sum - RDX end | |
return sum | |
end | |
local function msub(a, b) | |
local sum = a - b | |
if sum < 0 then sum = sum + RDX end | |
return sum | |
end | |
function ttf_flavor(reader, fofs) | |
local tag = reader:cdata(fofs, 4):str(4) | |
return (tag == "OTTO") and "otf" or "ttf" | |
end | |
function ttf_write(reader, fofs, out) | |
local cd_fh = reader:cdata(fofs, 12) | |
local tag = cd_fh:ulong(); info("tag", tag) | |
local num = cd_fh:ushort(); info("#tables", num) | |
local cd_d = reader:cdata(fofs + 12, 16 * num) | |
local dir, wofs, head = stt({}), 12 + 16 * num | |
for j = 1, num do | |
local t = stt({-- tag, csum, ofs, len | |
cd_d:str(4), cd_d:ulong(), cd_d:ulong(), cd_d:ulong() | |
}) | |
t[5] = math.ceil(t[4] / 4) * 4 | |
t[6] = wofs; wofs = wofs + t[5] | |
if t[1] == 'head' then head = j end | |
dir[j] = t | |
end | |
info("head table index", head) | |
local cd_h | |
do -- checksum adjustment | |
local t = dir[head] | |
cd_h = reader:cdata(t[3], t[5]) | |
sure(cd_h:pos(12):ulong() == MAGIC, 30) | |
cd_h:pos(8):setulong(0) | |
t[2] = check_sum(cd_h) | |
info("head checksum", t[2]) | |
end | |
local csumg, cd_od = 0, empty_cdata(16 * num) | |
for j = 1, num do | |
local t = dir[j] | |
cd_od:setstr(t[1]):setulong(t[2]) | |
cd_od:setulong(t[6]):setulong(t[4]) | |
csumg = madd(csumg, t[2]) | |
end | |
csumg = madd(csumg, check_sum(cd_fh)) | |
csumg = madd(csumg, check_sum(cd_od)) | |
csumg = msub(SGOAL, csumg) | |
cd_h:pos(8):setulong(csumg) | |
info("adjustment", csumg) | |
-- info(dir) | |
out:write(cd_fh.data) | |
out:write(cd_od.data) | |
for j = 1, num do | |
if j == head then | |
out:write(cd_h.data) | |
else | |
local t = dir[j] | |
out:write(reader:read(t[3], t[5])) | |
end | |
end | |
end | |
end | |
---------------------------------------- | |
do | |
local stt_meta = { | |
__tostring = function(self) | |
return "{"..concat(self, ",").."}" | |
end | |
} | |
function stt(tbl) | |
return setmetatable(tbl, stt_meta) | |
end | |
function concat(tbl, ...) | |
local t = {} | |
for i = 1, #tbl do t[i] = tostring(tbl[i]) end | |
return table.concat(t, ...) | |
end | |
function info(...) | |
if not verbose then return end | |
local t = { prog_name, ... } | |
io.stderr:write(concat(t, ": ").."\n") | |
end | |
function abort(...) | |
verbose = true; info(...) | |
os.exit(-1) | |
end | |
function sure(val, a1, ...) | |
if val then return val end | |
if type(a1) == "number" then | |
a1 = "error("..a1..")" | |
end | |
abort(a1, ...) | |
end | |
end | |
---------------------------------------- | |
do | |
local suffix = "_1" | |
local function show_usage() | |
io.stdout:write(([[ | |
This is %s v%s <%s> by 'ZR' | |
Usage: %s[.lua] [-v] [-o <out_name>] <ttc_file> | |
-v be verbose | |
-d dry run (implies -v) | |
-o output file name | |
ex: font_1.otf, 00FONT.TTF, font000 | |
]]):format(prog_name, version, mod_date, prog_name)) | |
os.exit(0) | |
end | |
local function out_name() | |
local base, t, ext = ttf_base | |
if base then | |
local _, _, b, e = base:find("^(.+)%.([a-zA-Z]+)$") | |
if b then base, ext = b, e end | |
t = not (base:find("%d")) | |
else | |
local ps = sure(ttc_file:find("[^/\\]+$"), 1) | |
base = ttc_file:sub(ps) | |
t = (base:sub(-4):lower() == ".ttc") and -5 or -1 | |
base, t = base:sub(1, t), true | |
end | |
if t then base = base..suffix end | |
local _, _, h, i, t = base:find("^(.-)(%d+)(%D*)$") | |
base = h:gsub("%%","%%%%").."%0"..(#i).."d".. | |
t:gsub("%%","%%%%")..".%s" | |
return base, i - 1, ext | |
end | |
function read_option() | |
if #arg == 0 then show_usage() end | |
local idx = 1 | |
while idx <= #arg do | |
local opt = arg[idx] | |
if opt:sub(1, 1) ~= "-" then break end | |
if opt == "-h" or opt == "--help" then | |
show_usage() | |
elseif opt == "-v" then | |
verbose = true | |
elseif opt == "-d" then | |
dryrun = true; verbose = true | |
elseif opt == "-o" then | |
idx = idx + 1 | |
ttf_base = sure(arg[idx], "output name is missing") | |
else abort("invalid option", opt) | |
end | |
idx = idx + 1 | |
end | |
sure(#arg == idx, "wrong number of arguments") | |
ttc_file = arg[idx] | |
ttf_base, ttf_idb, ttf_ext = out_name() | |
end | |
local function write_open(file) | |
if dryrun then return make_dumbfile() end | |
local ofile = io.open(file, "wb") | |
sure(ofile, "cannot open for output", file) | |
return ofile | |
end | |
function main() | |
read_option() | |
local reader = make_reader(ttc_file) | |
local tofs = ttf_offset(reader) | |
for i = 1, #tofs do | |
local ext = ttf_ext or ttf_flavor(reader, tofs[i]) | |
local ttf_name = ttf_base:format(ttf_idb + i, ext) | |
info("Font #"..i, ttf_name) | |
local ofile = write_open(ttf_name) | |
ttf_write(reader, tofs[i], ofile) | |
ofile:close() | |
end | |
reader:close() | |
end | |
end | |
---------------------------------------- | |
main() | |
-- EOF |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment