Skip to content

Instantly share code, notes, and snippets.

@zr-tex8r zr-tex8r/zrdecompttc.lua
Last active Jan 27, 2019

Embed
What would you like to do?
Converts TTC font files to TTF files
-- 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
You can’t perform that action at this time.