Initial commit
This commit is contained in:
commit
3815ab3963
36
luametalatex-back-pdf.lua
Normal file
36
luametalatex-back-pdf.lua
Normal file
@ -0,0 +1,36 @@
|
||||
local writer = require'luametalatex-nodewriter'
|
||||
local newpdf = require'luametalatex-pdf'
|
||||
local pfile = newpdf.open(tex.jobname .. '.pdf')
|
||||
local fontdirs = setmetatable({}, {__index=function(t, k)t[k] = pfile:getobj() return t[k] end})
|
||||
local usedglyphs = {}
|
||||
print(token, token.luacmd)
|
||||
token.luacmd("shipout", function()
|
||||
local voff = node.new'kern'
|
||||
voff.kern = tex.voffset + tex.sp'1in'
|
||||
voff.next = token.scan_list()
|
||||
voff.next.shift = tex.hoffset + tex.sp'1in'
|
||||
local list = node.vpack(voff)
|
||||
list.height = tex.pageheight
|
||||
list.width = tex.pagewidth
|
||||
local out, resources, annots = writer(pfile, list, fontdirs, usedglyphs)
|
||||
local page, parent = pfile:newpage()
|
||||
local content = pfile:stream(nil, '', out)
|
||||
pfile:indirect(page, string.format([[<</Type/Page/Parent %i 0 R/Contents %i 0 R/MediaBox[0 %i %i %i]/Resources%s%s>>]], parent, content, -math.ceil(list.depth/65781.76), math.ceil(list.width/65781.76), math.ceil(list.height/65781.76), resources, annots))
|
||||
token.put_next(token.create'immediateassignment', token.create'global', token.create'deadcycles', token.create(0x30), token.create'relax')
|
||||
token.scan_token()
|
||||
end, 'protected')
|
||||
callback.register("stop_run", function()
|
||||
for fid, id in pairs(fontdirs) do
|
||||
local f = font.getfont(fid)
|
||||
local psname = f.psname or f.fullname
|
||||
local sorted = {}
|
||||
for k,v in pairs(usedglyphs[fid]) do
|
||||
sorted[#sorted+1] = v
|
||||
end
|
||||
table.sort(sorted, function(a,b) return a[1] < b[1] end)
|
||||
pfile:indirect(id, require'luametalatex-pdf-font'(pfile, f, sorted))
|
||||
end
|
||||
pfile.root = pfile:getobj()
|
||||
pfile:indirect(pfile.root, string.format([[<</Type/Catalog/Version/1.7/Pages %i 0 R>>]], pfile:writepages()))
|
||||
pfile:close()
|
||||
end, "Finish PDF file")
|
49
luametalatex-baseregisters.tex
Normal file
49
luametalatex-baseregisters.tex
Normal file
@ -0,0 +1,49 @@
|
||||
\begingroup
|
||||
\catcode`\^^^^ffff=11
|
||||
\catcode`\@=11
|
||||
\toks0{%
|
||||
local dimen_cmd = token.command_id'assign_dimen'
|
||||
local tex_params = {}
|
||||
local texmeta = getmetatable(tex)
|
||||
local texmetaoldindex = texmeta.__index
|
||||
local texmetaoldnewindex = texmeta.__newindex
|
||||
function texmeta.__index(t, k)
|
||||
local v = tex_params[k]
|
||||
if v then
|
||||
if v.command == dimen_cmd then
|
||||
return tex.dimen[v.index]
|
||||
end
|
||||
else
|
||||
return texmetaoldindex(t, k)
|
||||
end
|
||||
end
|
||||
function texmeta.__newindex(t, k, v)
|
||||
local p = tex_params[k]
|
||||
if p then
|
||||
if p.command == dimen_cmd then
|
||||
tex.dimen[p.index] = v
|
||||
end
|
||||
else
|
||||
return texmetaoldnewindex(t, k, v)
|
||||
end
|
||||
end
|
||||
}
|
||||
\def\InternalAlloc#1#2#3#4#5{%
|
||||
\csname new#3\endcsname#1%
|
||||
\global#1=#5\relax
|
||||
\etoksapp0{#2_params["\luaescapestring{#4}"] = token.create"\luaescapestring{\csstring#1}"}
|
||||
}
|
||||
\def\internalAlloc#1#2#3{%
|
||||
\expandafter\InternalAlloc\csname ^^^^fffe#3@#1\endcsname{#1}{#2}{#3}%
|
||||
}
|
||||
\def\texAlloc#1#2{%
|
||||
\expandafter\InternalAlloc\csname #2\endcsname{tex}{#1}{#2}%
|
||||
}
|
||||
\texAlloc{dimen}{pageheight}{11in}
|
||||
\texAlloc{dimen}{pagewidth}{8.5in}
|
||||
% \internalAlloc{tex}{dimen}{pagewidth}
|
||||
\directlua{
|
||||
lua.prepared_code[\csstring#lua.prepared_code+1] = tex.toks[0]
|
||||
\the\toks0
|
||||
}
|
||||
\endgroup
|
21
luametalatex-bit32.lua
Normal file
21
luametalatex-bit32.lua
Normal file
@ -0,0 +1,21 @@
|
||||
local mask32 = 0xFFFFFFFF
|
||||
return {
|
||||
rshift = function(i, s)
|
||||
return (mask32 & i) >> s
|
||||
end,
|
||||
lshift = function(i, s)
|
||||
return mask32 & (i << s)
|
||||
end,
|
||||
band = function(i, j)
|
||||
return i & j & mask32
|
||||
end,
|
||||
bor = function(i, j)
|
||||
return (i | j) & mask32
|
||||
end,
|
||||
bor = function(i, j)
|
||||
return (i ^ j) & mask32
|
||||
end,
|
||||
extract = function(v, shift, count)
|
||||
return ((bit32 & v) >> shift) & ((1<<count)-1)
|
||||
end,
|
||||
}
|
179
luametalatex-firstcode.lua
Normal file
179
luametalatex-firstcode.lua
Normal file
@ -0,0 +1,179 @@
|
||||
local functions = lua.getfunctionstable()
|
||||
-- I am not sure why this is necessary, but otherwise LuaMetaTeX resets
|
||||
-- the functions table every time the getter is called
|
||||
function lua.get_functions_table() return functions end
|
||||
local set_lua = token.set_lua
|
||||
-- local new_luafunction = luatexbase.new_luafunction
|
||||
local i=12342
|
||||
local function new_luafunction(name)
|
||||
i = i+1
|
||||
return i
|
||||
end
|
||||
function token.luacmd(name, func, ...)
|
||||
local idx = new_luafunction(name)
|
||||
set_lua(name, idx, ...)
|
||||
functions[idx] = func
|
||||
end
|
||||
local properties = node.get_properties_table()
|
||||
-- setmetatable(node.direct.get_properties_table(), {
|
||||
-- __index = function(t, id)
|
||||
-- local new = {}
|
||||
-- t[id] = new
|
||||
-- return new
|
||||
-- end
|
||||
-- })
|
||||
|
||||
local whatsit_id = node.id'whatsit'
|
||||
local whatsits = {
|
||||
[0] = "open",
|
||||
"write",
|
||||
"close",
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
"save_pos",
|
||||
"late_lua",
|
||||
"user_defined",
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
"pdf_literal",
|
||||
"pdf_refobj",
|
||||
"pdf_annot",
|
||||
"pdf_start_link",
|
||||
"pdf_end_link",
|
||||
"pdf_dest",
|
||||
"pdf_action",
|
||||
"pdf_thread",
|
||||
"pdf_start_thread",
|
||||
"pdf_end_thread",
|
||||
"pdf_thread_data",
|
||||
"pdf_link_data",
|
||||
"pdf_colorstack",
|
||||
"pdf_setmatrix",
|
||||
"pdf_save",
|
||||
"pdf_restore",
|
||||
}
|
||||
whatsits[whatsits[0]] = 0
|
||||
for i = 0,#whatsits do
|
||||
local v = whatsits[i]
|
||||
if v then
|
||||
whatsits[v] = i
|
||||
end
|
||||
end
|
||||
function node.whatsits() return whatsits end
|
||||
function node.subtype(s) return type(s) == "string" and whatsits[s] or nil end
|
||||
local spacer_cmd, relax_cmd = token.command_id'spacer', token.command_id'relax'
|
||||
local function scan_filename()
|
||||
local name = {}
|
||||
local quoted = false
|
||||
local tok, cmd
|
||||
repeat
|
||||
tok = token.scan_token()
|
||||
cmd = tok.command
|
||||
until cmd ~= spacer_cmd and cmd ~= relax_cmd
|
||||
while (tok.command <= 12 and tok.mode <= token.biggest_char()
|
||||
or (token.put_next(tok) and false))
|
||||
and (quoted or tok.mode ~= string.byte' ') do
|
||||
if tok.mode == string.byte'"' then
|
||||
quoted = not quoted
|
||||
else
|
||||
name[#name+1] = tok.mode
|
||||
end
|
||||
tok = token.scan_token()
|
||||
end
|
||||
return utf8.char(table.unpack(name))
|
||||
end
|
||||
|
||||
local ofiles = {}
|
||||
local function do_openout(p)
|
||||
if ofiles[p.file] then
|
||||
error[[Existing file]]
|
||||
else
|
||||
local msg
|
||||
ofiles[p.file], msg = io.open(p.name, 'w')
|
||||
if not ofiles[p.file] then
|
||||
error(msg)
|
||||
end
|
||||
end
|
||||
end
|
||||
functions[39] = function(_, immediate) -- \openout
|
||||
local file = token.scan_int()
|
||||
token.scan_keyword'='
|
||||
local name = scan_filename()
|
||||
local props = {file = file, name = name, handle = do_openout}
|
||||
if immediate == "immediate" then
|
||||
do_openout(props)
|
||||
else
|
||||
local whatsit = node.new(whatsit_id, whatsits.open)
|
||||
properties[whatsit] = props
|
||||
node.write(whatsit)
|
||||
end
|
||||
end
|
||||
local function do_closeout(p)
|
||||
if ofiles[p.file] then
|
||||
ofiles[p.file]:close()
|
||||
ofiles[p.file] = nil
|
||||
end
|
||||
end
|
||||
functions[40] = function(_, immediate) -- \closeout
|
||||
local file = token.scan_int()
|
||||
local props = {file = file, handle = do_closeout}
|
||||
if immediate == "immediate" then
|
||||
do_closeout(props)
|
||||
else
|
||||
local whatsit = node.new(whatsit_id, whatsits.close)
|
||||
properties[whatsit] = props
|
||||
node.write(whatsit)
|
||||
end
|
||||
end
|
||||
local function do_write(p)
|
||||
local content = token.to_string(p.data) .. '\n'
|
||||
local file = ofiles[p.file]
|
||||
if file then
|
||||
file:write(content)
|
||||
else
|
||||
texio.write_nl(p.file < 0 and "log" or "term and log", content)
|
||||
end
|
||||
end
|
||||
functions[41] = function(_, immediate) -- \write
|
||||
local file = token.scan_int()
|
||||
local content = token.scan_tokenlist()
|
||||
local props = {file = file, data = content, handle = do_write}
|
||||
if immediate == "immediate" then
|
||||
do_write(props)
|
||||
else
|
||||
local whatsit = node.new(whatsit_id, whatsits.write)
|
||||
properties[whatsit] = props
|
||||
node.write(whatsit)
|
||||
end
|
||||
end
|
||||
local lua_call_cmd = token.command_id'lua_call'
|
||||
functions[42] = function() -- \immediate
|
||||
local next_tok = token.scan_token()
|
||||
if next_tok.command ~= lua_call_cmd then
|
||||
return token.put_next(next_tok)
|
||||
end
|
||||
local function_id = next_tok.index
|
||||
functions[function_id](function_id, 'immediate')
|
||||
end
|
||||
functions[43] = function() -- \pdfvariable
|
||||
local name = token.scan_string()
|
||||
print('Missing \\pdf' .. name)
|
||||
end
|
||||
token.set_lua("openout", 39, "protected")
|
||||
token.set_lua("closeout", 40, "protected")
|
||||
token.set_lua("write", 41, "protected")
|
||||
write_tok = token.create'write'
|
||||
token.set_lua("immediate", 42, "protected")
|
||||
token.set_lua("pdfvariable", 43)
|
||||
local prepared_code = lua.bytecode[1]
|
||||
if prepared_code then
|
||||
prepared_code()
|
||||
prepared_code, lua.bytecode[1] = nil, nil
|
||||
end
|
||||
require'luametalatex-back-pdf'
|
72
luametalatex-font-cff-data.lua
Normal file
72
luametalatex-font-cff-data.lua
Normal file
@ -0,0 +1,72 @@
|
||||
-- Copied from the CFF specification...
|
||||
local strings = {[".notdef"] = 0, [0] =
|
||||
".notdef", "space", "exclam", "quotedbl", "numbersign", "dollar", "percent",
|
||||
"ampersand", "quoteright", "parenleft", "parenright", "asterisk", "plus",
|
||||
"comma", "hyphen", "period", "slash", "zero", "one", "two", "three", "four",
|
||||
"five", "six", "seven", "eight", "nine", "colon", "semicolon", "less",
|
||||
"equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", "H",
|
||||
"I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W",
|
||||
"X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum",
|
||||
"underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j",
|
||||
"k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y",
|
||||
"z", "braceleft", "bar", "braceright", "asciitilde", "exclamdown", "cent",
|
||||
"sterling", "fraction", "yen", "florin", "section", "currency",
|
||||
"quotesingle", "quotedblleft", "guillemotleft", "guilsinglleft",
|
||||
"guilsinglright", "fi", "fl", "endash", "dagger", "daggerdbl",
|
||||
"periodcentered", "paragraph", "bullet", "quotesinglbase", "quotedblbase",
|
||||
"quotedblright", "guillemotright", "ellipsis", "perthousand", "questiondown",
|
||||
"grave", "acute", "circumflex", "tilde", "macron", "breve", "dotaccent",
|
||||
"dieresis", "ring", "cedilla", "hungarumlaut", "ogonek", "caron", "emdash",
|
||||
"AE", "ordfeminine", "Lslash", "Oslash", "OE", "ordmasculine", "ae",
|
||||
"dotlessi", "lslash", "oslash", "oe", "germandbls", "onesuperior",
|
||||
"logicalnot", "mu", "trademark", "Eth", "onehalf", "plusminus", "Thorn",
|
||||
"onequarter", "divide", "brokenbar", "degree", "thorn", "threequarters",
|
||||
"twosuperior", "registered", "minus", "eth", "multiply", "threesuperior",
|
||||
"copyright", "Aacute", "Acircumflex", "Adieresis", "Agrave", "Aring",
|
||||
"Atilde", "Ccedilla", "Eacute", "Ecircumflex", "Edieresis", "Egrave",
|
||||
"Iacute", "Icircumflex", "Idieresis", "Igrave", "Ntilde", "Oacute",
|
||||
"Ocircumflex", "Odieresis", "Ograve", "Otilde", "Scaron", "Uacute",
|
||||
"Ucircumflex", "Udieresis", "Ugrave", "Yacute", "Ydieresis", "Zcaron",
|
||||
"aacute", "acircumflex", "adieresis", "agrave", "aring", "atilde",
|
||||
"ccedilla", "eacute", "ecircumflex", "edieresis", "egrave", "iacute",
|
||||
"icircumflex", "idieresis", "igrave", "ntilde", "oacute", "ocircumflex",
|
||||
"odieresis", "ograve", "otilde", "scaron", "uacute", "ucircumflex",
|
||||
"udieresis", "ugrave", "yacute", "ydieresis", "zcaron", "exclamsmall",
|
||||
"Hungarumlautsmall", "dollaroldstyle", "dollarsuperior", "ampersandsmall",
|
||||
"Acutesmall", "parenleftsuperior", "parenrightsuperior", "twodotenleader",
|
||||
"onedotenleader", "zerooldstyle", "oneoldstyle", "twooldstyle",
|
||||
"threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle",
|
||||
"sevenoldstyle", "eightoldstyle", "nineoldstyle", "commasuperior",
|
||||
"threequartersemdash", "periodsuperior", "questionsmall", "asuperior",
|
||||
"bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior",
|
||||
"lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior",
|
||||
"tsuperior", "ff", "ffi", "ffl", "parenleftinferior", "parenrightinferior",
|
||||
"Circumflexsmall", "hyphensuperior", "Gravesmall", "Asmall", "Bsmall",
|
||||
"Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall", "Ismall",
|
||||
"Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall",
|
||||
"Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall",
|
||||
"Xsmall", "Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah",
|
||||
"Tildesmall", "exclamdownsmall", "centoldstyle", "Lslashsmall",
|
||||
"Scaronsmall", "Zcaronsmall", "Dieresissmall", "Brevesmall", "Caronsmall",
|
||||
"Dotaccentsmall", "Macronsmall", "figuredash", "hypheninferior",
|
||||
"Ogoneksmall", "Ringsmall", "Cedillasmall", "questiondownsmall", "oneeighth",
|
||||
"threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds",
|
||||
"zerosuperior", "foursuperior", "fivesuperior", "sixsuperior",
|
||||
"sevensuperior", "eightsuperior", "ninesuperior", "zeroinferior",
|
||||
"oneinferior", "twoinferior", "threeinferior", "fourinferior",
|
||||
"fiveinferior", "sixinferior", "seveninferior", "eightinferior",
|
||||
"nineinferior", "centinferior", "dollarinferior", "periodinferior",
|
||||
"commainferior", "Agravesmall", "Aacutesmall", "Acircumflexsmall",
|
||||
"Atildesmall", "Adieresissmall", "Aringsmall", "AEsmall", "Ccedillasmall",
|
||||
"Egravesmall", "Eacutesmall", "Ecircumflexsmall", "Edieresissmall",
|
||||
"Igravesmall", "Iacutesmall", "Icircumflexsmall", "Idieresissmall",
|
||||
"Ethsmall", "Ntildesmall", "Ogravesmall", "Oacutesmall", "Ocircumflexsmall",
|
||||
"Otildesmall", "Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall",
|
||||
"Uacutesmall", "Ucircumflexsmall", "Udieresissmall", "Yacutesmall",
|
||||
"Thornsmall", "Ydieresissmall", "001.000", "001.001", "001.002", "001.003",
|
||||
"Black", "Bold", "Book", "Light", "Medium", "Regular", "Roman", "Semibold"
|
||||
}
|
||||
for i,v in ipairs(strings) do
|
||||
strings[v] = i
|
||||
end
|
||||
return strings
|
321
luametalatex-font-cff.lua
Normal file
321
luametalatex-font-cff.lua
Normal file
@ -0,0 +1,321 @@
|
||||
local pack = string.pack
|
||||
local strings = require'luametalatex-font-cff-data'
|
||||
|
||||
local function getstring(cff, str)
|
||||
local i = strings[str] or cff.strings[str]
|
||||
if not i then
|
||||
i = #strings + #cff.strings + 1
|
||||
cff.strings[str] = i
|
||||
cff.strings[i - #strings] = str
|
||||
end
|
||||
return i
|
||||
end
|
||||
local function serialize_index(index, element_serializer)
|
||||
local sizes = {1}
|
||||
local length = 1
|
||||
local data = {}
|
||||
for i=1,#index do
|
||||
data[i] = element_serializer(index[i])
|
||||
length = length + #data[i]
|
||||
sizes[#sizes+1] = length
|
||||
end
|
||||
data = table.concat(data)
|
||||
if data == "" then return "\0\0" end
|
||||
local offSize = length < 2^8 and 1 or length < 2^16 and 2 or length < 2^24 and 3 or 4
|
||||
local offsetfmt = string.format(">I%i", offSize)
|
||||
local offsets = ""
|
||||
for i = #sizes,1,-1 do
|
||||
sizes[i+1] = pack(offsetfmt, sizes[i])
|
||||
end
|
||||
sizes[1] = pack(">I2B", #index, offSize)
|
||||
sizes[#sizes+1] = data
|
||||
return table.concat(sizes)
|
||||
end
|
||||
local function ident(...)
|
||||
return ...
|
||||
end
|
||||
local real_lookup = {
|
||||
['0'] = 0, ['1'] = 1, ['2'] = 2, ['3'] = 3, ['4'] = 4, ['5'] = 5, ['6'] = 6, ['7'] = 7, ['8'] = 8, ['9'] = 9,
|
||||
['.'] = 0xa, ['-'] = 0xe
|
||||
}
|
||||
local function dictInt(n)
|
||||
local num = math.floor(n)
|
||||
if num ~= n then
|
||||
num = tostring(n)
|
||||
local i, result, tmp = 1, string.char(0x1e)
|
||||
while i <= #num do
|
||||
local c = real_lookup[num:sub(i, i)]
|
||||
if not c then -- We got an 'e'
|
||||
c = num:sub(i+1, i+1) == '+' and 0xb or 0xc
|
||||
repeat
|
||||
i = i + 1
|
||||
until num:sub(i+1, i+1) ~= '0'
|
||||
end
|
||||
if tmp then
|
||||
result = result .. string.char(tmp * 16 + c)
|
||||
tmp = nil
|
||||
else
|
||||
tmp = c
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
return result .. string.char((tmp or 0xf) * 16 + 0xf)
|
||||
elseif num >= -107 and num <= 107 then
|
||||
return string.char(num + 139)
|
||||
elseif num >= 108 and num <= 1131 then
|
||||
num = num - 108
|
||||
return string.char(247 + ((num >> 8) & 0xFF), num & 0xFF)
|
||||
elseif num >= -1131 and num <= -108 then
|
||||
num = -num - 108
|
||||
return string.char(251 + ((num >> 8) & 0xFF), num & 0xFF)
|
||||
elseif num >= -32768 and num <= 32767 then
|
||||
return string.char(28, (num >> 8) & 0xFF, num & 0xFF)
|
||||
else
|
||||
return string.char(29, (num >> 24) & 0xFF, (num >> 16) & 0xFF,
|
||||
(num >> 8) & 0xFF, num & 0xFF)
|
||||
end
|
||||
end
|
||||
local function serialize_top(cff)
|
||||
local data = dictInt(getstring(cff, cff.registry or 'Adobe'))
|
||||
.. dictInt(getstring(cff, cff.ordering or 'Identity'))
|
||||
.. dictInt( cff.supplement or 0)
|
||||
.. string.char(12, 30)
|
||||
if cff.version then
|
||||
data = data .. dictInt(getstring(cff, cff.version)) .. string.char(0)
|
||||
end
|
||||
if cff.Notice then
|
||||
data = data .. dictInt(getstring(cff, cff.Notice)) .. string.char(1)
|
||||
end
|
||||
if cff.FullName then
|
||||
data = data .. dictInt(getstring(cff, cff.FullName)) .. string.char(2)
|
||||
end
|
||||
if cff.FamilyName then
|
||||
data = data .. dictInt(getstring(cff, cff.FamilyName)) .. string.char(3)
|
||||
end
|
||||
if cff.Weight then
|
||||
data = data .. dictInt(getstring(cff, cff.Weight)) .. string.char(4)
|
||||
end
|
||||
if cff.isFixedPitch then
|
||||
data = data .. dictInt(1) .. string.char(12, 1)
|
||||
end
|
||||
if cff.ItalicAngle and cff.ItalicAngle ~= 0 then
|
||||
data = data .. dictInt(cff.ItalicAngle) .. string.char(12, 2)
|
||||
end
|
||||
if cff.UnderlinePosition then
|
||||
data = data .. dictInt(cff.UnderlinePosition) .. string.char(12, 3)
|
||||
end
|
||||
if cff.UnderlineThickness then
|
||||
data = data .. dictInt(cff.UnderlineThickness) .. string.char(12, 4)
|
||||
end
|
||||
if cff.FontMatrix then
|
||||
data = data .. dictInt(cff.FontMatrix[1]) .. dictInt(cff.FontMatrix[2])
|
||||
.. dictInt(cff.FontMatrix[3]) .. dictInt(cff.FontMatrix[4])
|
||||
.. dictInt(cff.FontMatrix[5]) .. dictInt(cff.FontMatrix[6])
|
||||
.. string.char(12, 7)
|
||||
end
|
||||
if cff.FontBBox then
|
||||
data = data .. dictInt(cff.FontBBox[1]) .. dictInt(cff.FontBBox[2])
|
||||
.. dictInt(cff.FontBBox[3]) .. dictInt(cff.FontBBox[4])
|
||||
.. string.char(5)
|
||||
end
|
||||
if cff.PostScript then
|
||||
data = data .. dictInt(getstring(cff, cff.PostScript)) .. string.char(12, 21)
|
||||
end
|
||||
data = data .. dictInt(cff.charset_offset) .. string.char(15)
|
||||
data = data .. dictInt(cff.charstrings_offset) .. string.char(17)
|
||||
data = data .. dictInt(cff.fdarray_offset) .. string.char(12, 36)
|
||||
data = data .. dictInt(cff.fdselect_offset) .. string.char(12, 37)
|
||||
-- data = data .. dictInt(cff.private_size) .. dictInt(cff.private_offset) .. string.char(18)
|
||||
return data
|
||||
end
|
||||
local function serialize_font(cff, offset0) return function(private)
|
||||
local data = dictInt(private[3]) .. string.char(12, 38)
|
||||
data = data .. dictInt(private[2]) .. dictInt(offset0 + private[1]) .. string.char(18)
|
||||
return data
|
||||
end end
|
||||
local function va_minone(...)
|
||||
if select('#', ...) ~= 0 then
|
||||
return (...+1), select(2, ...)
|
||||
end
|
||||
end
|
||||
-- local function serialize_fdselect(cff)
|
||||
-- local fdselect = cff.FDSelect or {format=3, {0,1}}
|
||||
-- if not fdselect then
|
||||
-- return '\3\0\1\0\0\0' .. string.pack('>I2', #cff.glyphs)
|
||||
-- end
|
||||
-- if fdselect.format == 0 then
|
||||
-- return string.char(0, va_minone(table.unpack(fdselect)))
|
||||
-- elseif fdselect.format == 3 then
|
||||
-- local fdparts = {string.pack(">BI2", 3, #fdselect)}
|
||||
-- for i=1,#fdselect do
|
||||
-- fdparts[i+1] = string.pack(">I2B", fdselect[i][1], fdselect[i][2]-1)
|
||||
-- end
|
||||
-- fdparts[#fdselect+2] = string.pack(">I2", #cff.glyphs)
|
||||
-- return table.concat(fdparts)
|
||||
-- else
|
||||
-- error[[Confusion]]
|
||||
-- end
|
||||
-- end
|
||||
local function serialize_fdselect(cff)
|
||||
local fdselect = {""}
|
||||
local lastfont = -1
|
||||
for i, g in ipairs(cff.glyphs) do
|
||||
local font = g.cidfont or 1
|
||||
if font ~= lastfont then
|
||||
fdselect[#fdselect+1] = string.pack(">I2B", i-1, font-1)
|
||||
lastfont = font
|
||||
end
|
||||
end
|
||||
if #fdselect*3+2 > #cff.glyphs+1 then
|
||||
fdselect[1] = string.pack("B", 0)
|
||||
for i, g in ipairs(cff.glyphs) do
|
||||
local font = g.cidfont or 1
|
||||
fdselect[i+1] = string.pack("B", font-1)
|
||||
end
|
||||
else
|
||||
fdselect[1] = string.pack(">BI2", 3, #fdselect-1)
|
||||
fdselect[#fdselect+1] = string.pack(">I2", #cff.glyphs)
|
||||
end
|
||||
return table.concat(fdselect)
|
||||
end
|
||||
local function serialize_private(private, subrsoffset)
|
||||
local data = ""
|
||||
if not private.BlueValues then
|
||||
private.BlueValues = { }
|
||||
end
|
||||
local last = 0
|
||||
for _, v in ipairs(private.BlueValues) do
|
||||
data = data .. dictInt(v - last)
|
||||
last = v
|
||||
end
|
||||
data = data .. '\6'
|
||||
if private.OtherBlues and #private.OtherBlues ~= 0 then
|
||||
last = 0
|
||||
for _, v in ipairs(private.OtherBlues) do
|
||||
data = data .. dictInt(v - last)
|
||||
last = v
|
||||
end
|
||||
data = data .. '\7'
|
||||
end
|
||||
if private.BlueScale then
|
||||
data = data .. dictInt(private.BlueScale) .. '\12\9'
|
||||
end
|
||||
if private.BlueShift then
|
||||
data = data .. dictInt(private.BlueShift) .. '\12\10'
|
||||
end
|
||||
if private.BlueFuzz then
|
||||
data = data .. dictInt(private.BlueFuzz) .. '\12\11'
|
||||
end
|
||||
if private.ForceBold then
|
||||
data = data .. dictInt(1) .. '\12\14'
|
||||
end
|
||||
if private.StdHW then
|
||||
data = data .. dictInt(private.StdHW) .. '\10'
|
||||
end
|
||||
if private.StdVW then
|
||||
data = data .. dictInt(private.StdVW) .. '\11'
|
||||
end
|
||||
if private.StemSnapH and #private.StemSnapH ~= 0 then
|
||||
last = 0
|
||||
for _, v in ipairs(private.StemSnapH) do
|
||||
data = data .. dictInt(v - last)
|
||||
last = v
|
||||
end
|
||||
data = data .. '\12\12'
|
||||
end
|
||||
if private.StemSnapV and #private.StemSnapV ~= 0 then
|
||||
last = 0
|
||||
for _, v in ipairs(private.StemSnapV) do
|
||||
data = data .. dictInt(v - last)
|
||||
last = v
|
||||
end
|
||||
data = data .. '\12\13'
|
||||
end
|
||||
if subrsoffset and subrsoffset ~= 0 then
|
||||
data = data .. dictInt(subrsoffset) .. '\19'
|
||||
end
|
||||
if private.defaultWidthX then
|
||||
data = data .. dictInt(private.defaultWidthX) .. '\20'
|
||||
end
|
||||
if private.nominalWidthX then
|
||||
data = data .. dictInt(private.nominalWidthX) .. '\21'
|
||||
end
|
||||
return data
|
||||
end
|
||||
local function serialize_charset(cff)
|
||||
local data = string.char(2)
|
||||
local last, count = -42, -1
|
||||
for _, glyph in ipairs(cff.glyphs) do
|
||||
if not glyph.cid then glyph.cid = glyph.index end
|
||||
if glyph.cid ~= 0 then
|
||||
if glyph.cid ~= last + 1 or count == 0xFFFF then
|
||||
if count >= 0 then
|
||||
data = data .. string.pack(">I2", count)
|
||||
count = -1
|
||||
end
|
||||
data = data .. string.pack(">I2", glyph.cid)
|
||||
end
|
||||
last = glyph.cid
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
if count >= 0 then
|
||||
data = data .. string.pack(">I2", count)
|
||||
count = -1
|
||||
end
|
||||
return data
|
||||
end
|
||||
return function(cff)
|
||||
cff.strings = {}
|
||||
cff.private_offset = 0
|
||||
cff.charstrings_offset = 0
|
||||
cff.fdarray_offset = 0
|
||||
cff.fdselect_offset = 0
|
||||
cff.charset_offset = 0
|
||||
local top = serialize_index({serialize_top(cff)}, ident)
|
||||
local data = string.char(1, 0, 4, 2) -- Assuming 16Bit offsets (Where are they used?)
|
||||
local name = serialize_index({cff.FontName}, ident)
|
||||
local globalsubrs = serialize_index(cff.GlobalSubrs or {}, ident)
|
||||
local private_offsets, privates = {}, {}
|
||||
local privates_size = 0 -- These include the localsubrs sizes
|
||||
for i, p in ipairs(cff.Privates or {cff}) do
|
||||
local subrs = p.Subrs
|
||||
if not subrs or subrs and #subrs == 0 then
|
||||
subrs = ""
|
||||
else
|
||||
subrs = serialize_index(subrs, ident)
|
||||
end
|
||||
local serialized = ""
|
||||
if subrs ~= "" then
|
||||
local last_size = 0
|
||||
repeat
|
||||
last_size = #serialized
|
||||
serialized = serialize_private(p, last_size)
|
||||
until last_size == #serialized
|
||||
else
|
||||
serialized = serialize_private(p)
|
||||
end
|
||||
-- serialized = serialize_private(p, -#subrs)
|
||||
privates[i] = serialized .. subrs
|
||||
private_offsets[i] = {privates_size + 0*#subrs, #serialized, getstring(cff, p.FontName or (cff.FontName .. '-' .. i))}
|
||||
privates_size = privates_size + #subrs + #serialized
|
||||
end
|
||||
if cff.glyphs[1].index ~= 0 then
|
||||
table.insert(cff.glyphs, 1, {index = 0, cs = string.char(14), cidfont = cff.glyphs[1].cidfont})
|
||||
end
|
||||
local strings = serialize_index(cff.strings, ident)
|
||||
local charset = serialize_charset(cff)
|
||||
local fdselect = serialize_fdselect(cff)
|
||||
local charstrings = serialize_index(cff.glyphs, function(g) return g.cs end)
|
||||
local pre_private, top_size = #data + #name + #strings + #globalsubrs
|
||||
repeat
|
||||
top_size = #top
|
||||
cff.charstrings_offset = pre_private + top_size + privates_size
|
||||
cff.charset_offset = cff.charstrings_offset + #charstrings
|
||||
cff.fdselect_offset = cff.charset_offset + #charset
|
||||
cff.fdarray_offset = cff.fdselect_offset + #fdselect
|
||||
top = serialize_index({serialize_top(cff)}, ident)
|
||||
until top_size == #top
|
||||
local fdarray = serialize_index(private_offsets, serialize_font(cff, top_size + pre_private), ident)
|
||||
return data .. name .. top .. strings .. globalsubrs .. table.concat(privates) .. charstrings .. charset .. fdselect .. fdarray
|
||||
end
|
99
luametalatex-font-sfnt.lua
Normal file
99
luametalatex-font-sfnt.lua
Normal file
@ -0,0 +1,99 @@
|
||||
local function check(buf, i, afterI)
|
||||
local checksum = 0
|
||||
while i < afterI do
|
||||
if i+60 < afterI then
|
||||
local num1, num2, num3, num4, num5, num6, num7, num8, num9, num10, num11, num12, num13, num14, num15, num16, newI = string.unpack(">I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4", buf, i)
|
||||
i = newI
|
||||
checksum = checksum + num1 + num2 + num3 + num4 + num5 + num6 + num7 + num8 + num9 + num10 + num11 + num12 + num13 + num14 + num15 + num16
|
||||
elseif i+28 < afterI then
|
||||
local num1, num2, num3, num4, num5, num6, num7, num8, newI = string.unpack(">I4I4I4I4I4I4I4I4", buf, i)
|
||||
i = newI
|
||||
checksum = checksum + num1 + num2 + num3 + num4 + num5 + num6 + num7 + num8
|
||||
elseif i+12 < afterI then
|
||||
local num1, num2, num3, num4, newI = string.unpack(">I4I4I4I4", buf, i)
|
||||
i = newI
|
||||
checksum = checksum + num1 + num2 + num3 + num4
|
||||
else
|
||||
local num
|
||||
num, i = string.unpack(">I4", buf, i)
|
||||
checksum = checksum + num
|
||||
end
|
||||
end
|
||||
return checksum & 0xFFFFFFFF
|
||||
end
|
||||
local function log2floor(i)
|
||||
local j = 0
|
||||
if i>>8 ~= 0 then
|
||||
j = j + 8
|
||||
end
|
||||
if i>>(j+4) ~= 0 then
|
||||
j = j + 4
|
||||
end
|
||||
if i>>(j+2) ~= 0 then
|
||||
j = j + 2
|
||||
end
|
||||
if i>>(j+1) ~= 0 then
|
||||
j = j + 1
|
||||
end
|
||||
return j
|
||||
end
|
||||
return {
|
||||
write = function(magic, tables)
|
||||
local tabdata = {}
|
||||
for t, val in next, tables do
|
||||
tabdata[#tabdata+1] = {t, val .. string.rep("\0", (#val+3&~3)-#val), #val}
|
||||
end
|
||||
table.sort(tabdata, function(a,b)return a[1]<b[1]end)
|
||||
local logtabs = log2floor(#tabdata)
|
||||
local tabs = {string.pack(">c4I2I2I2I2", magic, #tabdata, 1<<logtabs+4, logtabs, #tabdata-(1<<logtabs)<<4)}
|
||||
local offset = #tabs[1]+#tabdata*16
|
||||
local checksum, headindex = check(tabs[1], 1, 1+#tabs[1])
|
||||
for i=1,#tabdata do
|
||||
local tab = tabdata[i]
|
||||
local data = tab[2]
|
||||
if tab[1] == "head" then
|
||||
headindex = i+1+#tabdata
|
||||
data = data:sub(1,8) .. '\0\0\0\0' .. data:sub(13) -- Benchmarking suggests that this is faster than a LPEG version
|
||||
end
|
||||
local thischeck = check(data, 1, 1+tab[3])
|
||||
tabs[i+1] = string.pack(">c4I4I4I4", tab[1], thischeck, offset, tab[3])
|
||||
checksum = checksum + check(tabs[i+1], 1, 17) + thischeck
|
||||
offset = offset + #data
|
||||
tabs[i+1+#tabdata] = data
|
||||
end
|
||||
if headindex then
|
||||
local data = tabs[headindex]
|
||||
data = data:sub(1,8) .. string.pack(">I4", 0xB1B0AFBA-checksum&0xFFFFFFFF) .. data:sub(13) -- Benchmarking suggests that this is faster than a LPEG version
|
||||
tabs[headindex] = data
|
||||
end
|
||||
return table.concat(tabs)
|
||||
end,
|
||||
parse = function(buf, off, fontid)
|
||||
off = off or 1
|
||||
local headMagic, numTables
|
||||
headMagic, numTables, off = string.unpack(">c4I2", buf, off)
|
||||
if headMagic == "ttcf" then
|
||||
if numTables > 2 then -- numTables is actually the major version here, 1&2 are basically equal
|
||||
error[[Unsupported TTC header version]]
|
||||
end
|
||||
headMagic, numTables, off = string.unpack(">I2I4", buf, off)
|
||||
fontid = fontid or 1
|
||||
if numTables < fontid then
|
||||
error[[There aren't that many fonts in this file]]
|
||||
end
|
||||
off = string.unpack(">I4", buf, off + (fontid-1)*4)+1
|
||||
headMagic, numTables, off = string.unpack(">c4I2", buf, off)
|
||||
end
|
||||
off = off+6
|
||||
local tables = {}
|
||||
for i=1,numTables do
|
||||
local tag, check, offset, len, newOff = string.unpack(">c4I4I4I4", buf, off)
|
||||
off = newOff
|
||||
tables[tag] = {offset+1, len}
|
||||
if offset+len > #buf then
|
||||
error[[Font file too small]]
|
||||
end
|
||||
end
|
||||
return headMagic, tables
|
||||
end,
|
||||
}
|
139
luametalatex-font-t1.lua
Normal file
139
luametalatex-font-t1.lua
Normal file
@ -0,0 +1,139 @@
|
||||
local white = (lpeg.S'\0\9\10\12\13\32' + '%' * (1 - lpeg.S'\r\n')^0)^1
|
||||
local regular = 1-lpeg.S'()<>[]{}/%\0\9\10\12\13\32'
|
||||
local lastbase = '123456789abcdefghiklmnopqrstuvwxyz'
|
||||
local number = lpeg.Cmt(lpeg.R'09'^1/tonumber * '#', function(s, p, base)
|
||||
if base < 2 then return end
|
||||
local pattern
|
||||
if base <= 10 then
|
||||
pattern = lpeg.R('0' .. lastbase:sub(base-1, base-1))
|
||||
else
|
||||
pattern = lpeg.R'09' + lpeg.R('a' .. lastbase:sub(base-1, base-1)) + lpeg.R('A' .. lastbase:sub(base-1, base-1):upper())
|
||||
end
|
||||
local num, p = (lpeg.C(pattern^1) * lpeg.Cp()):match(s, p)
|
||||
return p, num and tonumber(num, base)
|
||||
end)
|
||||
+ (lpeg.S'+-'^-1 * ('.' * lpeg.R'09'^1 + lpeg.R'09'^1 * lpeg.P'.'^-1 * lpeg.R'09'^0) * (lpeg.S'eE' * lpeg.S'+-'^-1 * lpeg.R'09'^1)^-1)/tonumber
|
||||
local literalstring = lpeg.P{'(' * lpeg.Cs((
|
||||
lpeg.P'\\n'/'\n'+lpeg.P'\\r'/'\r'+lpeg.P'\\t'/'\t'+lpeg.P'\\b'/'\b'+lpeg.P'\\f'/'\f'
|
||||
+'\\'*lpeg.C(lpeg.R'07'*lpeg.R'07'^-2)/function(n)return string.char(tonumber(n, 8))end
|
||||
+'\\'*('\n' + ('\r' * lpeg.P'\n'^-1))/''
|
||||
+'\\'*lpeg.C(1)/1
|
||||
+('\n' + ('\r' * lpeg.P'\n'^-1))/'\n'
|
||||
+(1-lpeg.S'()\\')+lpeg.V(1))^0) * ')'}
|
||||
local hexstring = '<' * lpeg.Cs((
|
||||
lpeg.C(lpeg.R'09'+lpeg.R'af'+lpeg.R'AF')*(lpeg.C(lpeg.R'09'+lpeg.R'af'+lpeg.R'AF')+lpeg.Cc'0')/function(a,b)return string.char(tonumber(a..b, 16))end)^0) * '>'
|
||||
local name = lpeg.C(regular^1)
|
||||
local lname = '/' * name / 1
|
||||
local function decrypt(key, n, cipher)
|
||||
-- Generally you should never implement your own crypto. So we call a well known, peer reviewed,
|
||||
-- high-quality cryptographic library. --- Ha-Ha, of course we are implementing by ourselves.
|
||||
-- That might be completely unsecure, but given that the encryption keys are well known constants
|
||||
-- documented in the T1 Spec, there is no need to worry about it.
|
||||
-- Also I do not think any cryptorgraphic library would implement this anyway, it doesn't even
|
||||
-- really deserve the term encryption.
|
||||
local decoded = {string.byte(cipher, 1,-1)}
|
||||
for i=1,#decoded do
|
||||
local c = decoded[i]
|
||||
decoded[i] = c ~ (key>>8)
|
||||
key = (((c+key)&0xFFFF)*52845+22719)&0xFFFF
|
||||
end
|
||||
return string.char(table.unpack(decoded, n+1))
|
||||
end
|
||||
|
||||
-- io.stdout:write(decrypt(55665, 4, string.sub(io.stdin:read'a', 7)))
|
||||
local boolean = (lpeg.P'true' + 'false')/{["true"] = true, ["false"] = false}
|
||||
local anytype = {hexstring + literalstring + number + lname + boolean + lpeg.V(2) + name, lpeg.Ct('[' * (white^-1 * lpeg.V(1))^0 * white^-1 * ']' + '{' * (white^-1 * lpeg.V(1))^0 * white^-1 * '}' * white^-1 * lpeg.P"executeonly"^-1)}
|
||||
local dict = lpeg.Cf(lpeg.Carg(1) * lpeg.Cg(white^-1*lname*white^-1*(anytype)*white^-1*lpeg.P"readonly"^-1*white^-1*lpeg.P"noaccess"^-1*white^-1*(lpeg.P"def"+"ND"+"|-"))^0, rawset)
|
||||
local encoding = (white+anytype-("for"*white))^0*"for"*white/0
|
||||
* lpeg.Cf(lpeg.Ct''
|
||||
* lpeg.Cg("dup"*white*number*white^-1*lname*white^-1*"put"*white)^0
|
||||
, rawset)
|
||||
* lpeg.P"readonly"^-1*white*"def"
|
||||
local function parse_encoding(offset, str)
|
||||
local found
|
||||
found, offset = (encoding*lpeg.Cp()):match(str, offset)
|
||||
return found, offset
|
||||
end
|
||||
local function parse_fontinfo(offset, str)
|
||||
local found
|
||||
repeat
|
||||
found, offset = ((white+(anytype-name))^0/0*name*lpeg.Cp()):match(str, offset)
|
||||
until found == 'begin'
|
||||
found, offset = (dict*lpeg.Cp()):match(str, offset, {})
|
||||
offset = (white^-1*"end"*white^-1*lpeg.P"readonly"^-1*white^-1*"def"):match(str, offset)
|
||||
return found, offset
|
||||
end
|
||||
local binary_bytes = lpeg.Cmt(number*white^-1*(lpeg.P'-| ' + 'RD '), function(s, p, l)return p+l, s:sub(p, p+l-1) end)*white*(lpeg.P"|-"+"|"+"ND"+"NP")
|
||||
local charstr = white^-1*lname*(white^-1*(anytype-lname))^0/0*white^-1
|
||||
* lpeg.Cf(lpeg.Ct''
|
||||
* lpeg.Cg(lname*white^-1*binary_bytes*white)^0
|
||||
, rawset)
|
||||
* lpeg.P"end"*white
|
||||
local subrs = (white^-1*(anytype-("dup"*white)))^0/0*white^-1
|
||||
* lpeg.Cf(lpeg.Ct''
|
||||
* lpeg.Cg("dup"*white^-1*number*white^-1*binary_bytes*white)^0
|
||||
, rawset)
|
||||
* (lpeg.P"readonly"*white)^-1 * (lpeg.P"noaccess"*white)^-1*(lpeg.P"def"+"ND"+"|-")
|
||||
local function parse_private(offset, str)
|
||||
local mydict, found
|
||||
repeat
|
||||
found, offset = ((white+(anytype-name))^0/0*name*lpeg.Cp()):match(str, offset)
|
||||
until found == 'begin'
|
||||
mydict, offset = (dict*lpeg.Cp()):match(str, offset, {})
|
||||
found = (white^-1*lname):match(str, offset)
|
||||
if found == "Subrs" then
|
||||
mydict.Subrs, offset = (subrs*lpeg.Cp()):match(str, offset)
|
||||
end
|
||||
return mydict, offset
|
||||
end
|
||||
local function continue_maintable(offset, str, mydict)
|
||||
mydict, offset = (dict*lpeg.Cp()):match(str, offset, mydict)
|
||||
local found = (white^-1*lname):match(str, offset)
|
||||
if found == "FontInfo" then
|
||||
mydict.FontInfo, offset = parse_fontinfo(offset, str)
|
||||
return continue_maintable(offset, str, mydict)
|
||||
elseif found == "Encoding" then
|
||||
mydict.Encoding, offset = parse_encoding(offset, str)
|
||||
return continue_maintable(offset, str, mydict)
|
||||
elseif found == "Private" then
|
||||
mydict.Private, offset = parse_private(offset, str)
|
||||
return continue_maintable(offset, str, mydict)
|
||||
elseif found == "CharStrings" then
|
||||
mydict.CharStrings, offset = (charstr*lpeg.Cp()):match(str, offset)
|
||||
return mydict
|
||||
else
|
||||
local newoffset = ((white+name)^1/0*lpeg.Cp()):match(str, offset)
|
||||
if newoffset and offset <= #str then
|
||||
return continue_maintable(newoffset, str, mydict)
|
||||
end
|
||||
end
|
||||
print(str:sub(offset))
|
||||
error[[Unable to read Type 1 font]]
|
||||
end
|
||||
local function parse_maintable(offset, str)
|
||||
local found
|
||||
repeat
|
||||
found, offset = ((white+(anytype-name))^0/0*name*lpeg.Cp()):match(str, offset)
|
||||
until found == 'begin'
|
||||
return continue_maintable(offset, str, {})
|
||||
end
|
||||
|
||||
return function(filename)
|
||||
local file = io.open(filename)
|
||||
local _, length = string.unpack("<I2I4", file:read(6))
|
||||
local preface = file:read(length)
|
||||
_, length = string.unpack("<I2I4", file:read(6))
|
||||
local private = decrypt(55665, 4, file:read(length))
|
||||
file:close()
|
||||
local after = parse_maintable(1, preface .. private)
|
||||
local lenIV = after.Private.lenIV or 4
|
||||
local chars = after.CharStrings
|
||||
for k, v in pairs(chars) do
|
||||
chars[k] = decrypt(4330, lenIV, v)
|
||||
end
|
||||
local subrs = after.Private.Subrs
|
||||
for k, v in pairs(subrs) do
|
||||
subrs[k] = decrypt(4330, lenIV, v)
|
||||
end
|
||||
return after
|
||||
end
|
237
luametalatex-font-t1tot2.lua
Normal file
237
luametalatex-font-t1tot2.lua
Normal file
@ -0,0 +1,237 @@
|
||||
local function parse_charstring(cs, subrs, result)
|
||||
result = result or {{false}}
|
||||
local lastresult = result[#result]
|
||||
local i = 1
|
||||
while i~=#cs+1 do
|
||||
local cmd = cs:byte(i)
|
||||
if cmd == 255 then
|
||||
lastresult[#lastresult+1] = string.unpack(">i4", cs:sub(i+1, i+4))
|
||||
i = i+4
|
||||
elseif cmd >= 251 then
|
||||
lastresult[#lastresult+1] = -((cmd-251)*256)-string.byte(cs, i+1)-108
|
||||
i = i+1
|
||||
elseif cmd >= 247 then
|
||||
lastresult[#lastresult+1] = (cmd-247)*256+string.byte(cs, i+1)+108
|
||||
i = i+1
|
||||
elseif cmd >= 32 then
|
||||
lastresult[#lastresult+1] = cmd-139
|
||||
elseif cmd == 9 then -- closepath, implicit in Type2
|
||||
elseif cmd == 10 then
|
||||
local subr = subrs[lastresult[#lastresult]]
|
||||
lastresult[#lastresult] = nil
|
||||
parse_charstring(subr, subrs, result)
|
||||
lastresult = result[#result]
|
||||
elseif cmd == 11 then
|
||||
break -- We do not keep subroutines, so drop returns and continue with the outer commands
|
||||
elseif cmd == 12 then
|
||||
i = i+1
|
||||
cmd = cs:byte(i)
|
||||
if cmd == 12 then -- div, we might have huge parameters, so execute directly
|
||||
lastresult[#lastresult-1] = lastresult[#lastresult-1]/lastresult[#lastresult]
|
||||
lastresult[#lastresult] = nil
|
||||
elseif cmd == 16 then -- othersubr...
|
||||
cmd = lastresult[#lastresult]
|
||||
lastresult[#lastresult] = nil
|
||||
local numargs = lastresult[#lastresult]
|
||||
lastresult[#lastresult] = nil
|
||||
if cmd == 3 then -- Hint replacement. This is easy, we support hint replacement, so we
|
||||
-- keep the original subr number
|
||||
assert(numargs == 1)
|
||||
elseif cmd == 1 then -- Flex initialization
|
||||
elseif cmd == 2 then -- Flex parameter
|
||||
if result[#result-1].flex then
|
||||
result[#result] = nil -- TODO: Warn if there were values.
|
||||
lastresult = result[#result] -- We keep collecting arguments
|
||||
end
|
||||
lastresult.flex = true
|
||||
elseif cmd == 0 then -- Flex
|
||||
local flexinit = result[#result-1]
|
||||
lastresult[2] = lastresult[2] + flexinit[2]
|
||||
lastresult[3] = lastresult[3] + flexinit[3]
|
||||
lastresult.flex = nil
|
||||
result[#result-1] = lastresult
|
||||
result[#result] = nil
|
||||
lastresult[#lastresult] = nil
|
||||
lastresult[#lastresult] = nil
|
||||
lastresult[1] = -36
|
||||
lastresult = {false}
|
||||
result[#result+1] = lastresult
|
||||
lastresult[#lastresult+1] = "setcurrentpointmark"
|
||||
elseif cmd == 12 or cmd == 13 then
|
||||
local pending = {}
|
||||
local results = #lastresult
|
||||
for i = 1,numargs do
|
||||
pending[i] = lastresult[results-numargs+i]
|
||||
lastresult[results-numargs+i] = nil
|
||||
end
|
||||
for i = 1,#lastresult.pendingargs do
|
||||
pending[numargs+i] = lastresult.pendingargs[i]
|
||||
end
|
||||
if cmd == 12 then
|
||||
lastresult.pendingargs = pending
|
||||
else
|
||||
lastresult.pendingargs = nil
|
||||
-- TODO Translate pending to counter mask
|
||||
end
|
||||
else
|
||||
error[[UNSUPPORTED Othersubr]]
|
||||
end
|
||||
elseif cmd == 17 then -- pop... Ignore them, they should already be handled by othersubr.
|
||||
-- Compatibility with unknown othersubrs is futile, because
|
||||
-- we can't interpret PostScript
|
||||
elseif cmd == 33 then -- setcurrentpoint... If we expected this, it is already handled.
|
||||
-- Otherwise fail, according to the spec it should
|
||||
-- only be used with othersubrs.
|
||||
assert(lastresult[#lastresult] == "setcurrentpointmark")
|
||||
lastresult[#lastresult] = nil
|
||||
else
|
||||
lastresult[1] = -cmd-1
|
||||
lastresult = {false}
|
||||
result[#result+1] = lastresult
|
||||
end
|
||||
else
|
||||
lastresult[1] = cmd
|
||||
lastresult = {false}
|
||||
result[#result+1] = lastresult
|
||||
end
|
||||
i = i+1
|
||||
end
|
||||
return result
|
||||
end
|
||||
local function adjust_charstring(cs) -- Here we get a not yet optimized but parsed Type1 charstring and
|
||||
-- do some adjustments to make them more "Type2-like".
|
||||
cs[#cs] = nil -- parse_charstring adds a `{false}` for internal reasons. Just drop it here. FIXME: Check that #cs[#cs]==1, otherwise there were values left on the charstring stack
|
||||
if cs[1][1] ~= 13 then
|
||||
error[[Unsupported first Type1 operator]] -- probably cs[1][1] == sbw
|
||||
-- If you find a font using this, I'm sorry for you.
|
||||
end
|
||||
local hoffset = cs[1][2]
|
||||
if hoffset ~= 0 then
|
||||
-- non-zero sidebearings :-(
|
||||
for i, cmd in ipairs(cs) do
|
||||
if cmd[1] == 21 or cmd[1] == 22 then
|
||||
cmd[2] = cmd[2] + cs[1][2]
|
||||
break
|
||||
elseif cmd[1] == 4 then
|
||||
cmd[3] = cmd[2]
|
||||
cmd[2] = cs[1][2]
|
||||
cmd[1] = 21
|
||||
break
|
||||
end
|
||||
-- Here I rely on the fact that the first relative command is always [hvr]moveto.
|
||||
-- This is based on "Use rmoveto for the first point in the path." in the T1 spec
|
||||
-- for hsbw. I am not entirely sure if this is a strict requirement or if there could
|
||||
-- be weird charstrings where this fails (esp. since [hv]moveto are also used in the example),
|
||||
-- but I decided to take the risk.
|
||||
-- hints are affected too. They do not use relative coordinates in T1, so we store the offset
|
||||
-- and handle hints later
|
||||
end
|
||||
end
|
||||
cs[1][2] = cs[1][3]
|
||||
cs[1][3] = nil
|
||||
cs[1][1] = nil
|
||||
-- That's it for the width, now we need some hinting stuff. This would be easy, if hint replacement
|
||||
-- wouldn't require hint masks in Type2. And because we really enjoy this BS, we get counter
|
||||
-- hinting as an additional treat... Oh, if you actually you counter hinting: Please test this
|
||||
-- and report back if it works, because this is pretty much untested.
|
||||
-- TODO: Even more than that, counters are not implemented at all right now, except for [hv]stem3
|
||||
local stems = {}
|
||||
local stem3 = {}
|
||||
-- First iterate over the charstring, recording all hints and collecting them in stems/stem3
|
||||
for i, cmd in ipairs(cs) do
|
||||
if cmd[1] == 1 or cmd[1] == 3 then
|
||||
stems[#stems + 1] = cmd
|
||||
elseif cmd[1] == -2 or cmd[1] == -3 then
|
||||
local c = cmd[1] == -2 and 3 or 1
|
||||
stems[#stems + 1] = {c, cmd[2], cmd[3]}
|
||||
stems[#stems + 1] = {c, cmd[4], cmd[5]}
|
||||
stems[#stems + 1] = {c, cmd[6], cmd[7]}
|
||||
table.move(stems, #stems-2, #stems, #stem3+1, stem3)
|
||||
cs[i] = false
|
||||
end
|
||||
end
|
||||
table.sort(stems, function(first, second)
|
||||
if first[1] ~= second[1] then return first[1] < second[1] end
|
||||
if first[2] ~= second[2] then return first[2] < second[2] end
|
||||
return first[3] < second[3]
|
||||
end)
|
||||
-- Now store the index of every stem in the idx member of the hint command
|
||||
-- After that `j` stores the number of stems
|
||||
local j,k = 1,1
|
||||
if stems[1] then stems[1].idx = 1 end
|
||||
for i = 2,#stems do
|
||||
if stems[i][2] == stems[k][2] and stems[i][3] == stems[k][3] then
|
||||
stems[i].idx = j
|
||||
stems[i] = false
|
||||
else
|
||||
j, k = j+1, i
|
||||
stems[i].idx = j
|
||||
end
|
||||
end
|
||||
-- Now the indices are known, so the cntrmask can be written, if stem3 occured.
|
||||
-- This is done before writing the stem list to make the thable.insert parameters easier.
|
||||
local bytes = {}
|
||||
if stem3[1] then
|
||||
for l = 1, math.floor((j + 7)/8) do
|
||||
bytes[l] = 0
|
||||
end
|
||||
for l = 1, #stem3 do
|
||||
local idx = stem3[l].idx-1
|
||||
bytes[math.floor(idx/8) + 1] = bytes[math.floor(idx/8) + 1] | (1<<(7-idx%8))
|
||||
end
|
||||
table.insert(cs, 2, {20, string.char(table.unpack(bytes))})
|
||||
end
|
||||
local current = 1
|
||||
-- Then list the collected stems at the beginning of the charstring
|
||||
if stems[current] and stems[current][1] == 1 then
|
||||
local stem_tbl, last = {18}, 0
|
||||
while stems[current] ~= nil and (not stems[current] or stems[current][1] == 1) do
|
||||
if stems[current] then
|
||||
stem_tbl[#stem_tbl + 1] = stems[current][2] - last
|
||||
last = stems[current][2] + stems[current][3]
|
||||
stem_tbl[#stem_tbl + 1] = stems[current][3]
|
||||
end
|
||||
current = current + 1
|
||||
end
|
||||
table.insert(cs, 2, stem_tbl)
|
||||
end
|
||||
if stems[current] and stems[current][1] == 3 then
|
||||
local stem_tbl, last = {false}, -hoffset
|
||||
while stems[current] ~= nil and (not stems[current] or stems[current][1] == 3) do
|
||||
if stems[current] then
|
||||
stem_tbl[#stem_tbl + 1] = stems[current][2] - last
|
||||
last = stems[current][2] + stems[current][3]
|
||||
stem_tbl[#stem_tbl + 1] = stems[current][3]
|
||||
end
|
||||
current = current + 1
|
||||
end
|
||||
table.insert(cs, stems[1][1] == 1 and 3 or 2, stem_tbl)
|
||||
end
|
||||
-- Finally, replace every run of hint commands, corresponding to a hint replacement, by a single hintmask
|
||||
local i = 1
|
||||
while cs[i] ~= nil do
|
||||
if cs[i] and cs[i].idx then
|
||||
for l = 1, math.floor((j + 7)/8) do
|
||||
bytes[l] = 0
|
||||
end
|
||||
while (cs[i] or {}).idx do
|
||||
local idx = cs[i].idx-1
|
||||
bytes[math.floor(idx/8) + 1] = bytes[math.floor(idx/8) + 1] | (1<<(7-idx%8))
|
||||
cs[i] = false
|
||||
i = i+1
|
||||
end
|
||||
for l = 1, #stem3 do
|
||||
local idx = stem3[l].idx-1
|
||||
bytes[math.floor(idx/8) + 1] = bytes[math.floor(idx/8) + 1] | (1<<(7-idx%8))
|
||||
end
|
||||
i = i-1
|
||||
cs[i] = {19, string.char(table.unpack(bytes))}
|
||||
end
|
||||
i = i+1
|
||||
end
|
||||
end
|
||||
return function(cs, subrs)
|
||||
local parsed = parse_charstring(cs, subrs)
|
||||
adjust_charstring(parsed)
|
||||
return parsed
|
||||
end
|
168
luametalatex-font-t2-opt.lua
Normal file
168
luametalatex-font-t2-opt.lua
Normal file
@ -0,0 +1,168 @@
|
||||
-- This is optimizet2.lua.
|
||||
-- Copyright 2019 Marcel Krüger <tex@2krueger.de>
|
||||
--
|
||||
-- This work may be distributed and/or modified under the
|
||||
-- conditions of the LaTeX Project Public License version 1.3c.
|
||||
-- The full text of this version can be found at
|
||||
-- http://www.latex-project.org/lppl/lppl-1-3c.txt
|
||||
--
|
||||
-- This work has the LPPL maintenance status `maintained'
|
||||
--
|
||||
-- The Current Maintainer of this work is Marcel Krüger.
|
||||
--
|
||||
-- This work consists of the files buildcffwrapper.lua, buildotfwrapper.lua,
|
||||
-- finish_pdffont.lua, finisht3.lua, glyph2char.lua, libSfnt.lua,
|
||||
-- luaglyphlist.lua, luaotfprovider.lua, make_extensible_per_char.lua,
|
||||
-- mpfont.lua, mplibtolist.lua, mplibtot2.lua, mpnodelib.lua,
|
||||
-- mt1_fontloader.lua, nodebuilder.lua, optimizet2.lua, serializet2.lua.
|
||||
|
||||
return function(cs)
|
||||
-- cs might contain some false entries, delete them
|
||||
do
|
||||
local j=1
|
||||
for i = 1,#cs do
|
||||
if cs[i] then
|
||||
if i ~= j then
|
||||
cs[j] = cs[i]
|
||||
end
|
||||
j = j+1
|
||||
end
|
||||
end
|
||||
for i = j,#cs do
|
||||
cs[i] = nil
|
||||
end
|
||||
end
|
||||
-- First some easy replacements:
|
||||
local use_hintmask
|
||||
for i, v in ipairs(cs) do
|
||||
if v[1] == 19 then
|
||||
-- If this happens only one time, we do not want masks
|
||||
use_hintmask = use_hintmask ~= nil
|
||||
elseif v[1] == 21 then -- rmoveto
|
||||
if v[2] == 0 then
|
||||
v[1] = 4
|
||||
v[2] = v[3]
|
||||
v[3] = nil
|
||||
elseif v[3] == 0 then
|
||||
v[1] = 22
|
||||
v[3] = nil
|
||||
end
|
||||
elseif v[1] == 5 then -- rlineto
|
||||
if v[2] == 0 then
|
||||
v[1] = 7
|
||||
v[2] = v[3]
|
||||
v[3] = nil
|
||||
elseif v[3] == 0 then
|
||||
v[1] = 6
|
||||
v[3] = nil
|
||||
end
|
||||
elseif v[1] == 8 then -- rrcurveto
|
||||
if v[2] == 0 then
|
||||
if v[6] == 0 then
|
||||
v[1] = 26 -- vvcurveto (even argument case)
|
||||
table.remove(v, 6)
|
||||
table.remove(v, 2)
|
||||
else
|
||||
v[1] = 30 -- vhcurveto
|
||||
table.remove(v, 2)
|
||||
if v[6] == 0 then table.remove(v, 6) end
|
||||
end
|
||||
elseif v[3] == 0 then
|
||||
if v[7] == 0 then
|
||||
v[1] = 27 -- hhcurveto (even argument case)
|
||||
table.remove(v, 7)
|
||||
table.remove(v, 3)
|
||||
else
|
||||
v[1] = 31 -- hvcurveto
|
||||
table.remove(v, 3)
|
||||
local t = v[5]
|
||||
table.remove(v, 5)
|
||||
if t ~= 0 then table.insert(v, t) end
|
||||
end
|
||||
elseif v[6] == 0 then
|
||||
v[1] = 26 -- vvcurveto (odd argument case)
|
||||
table.remove(v, 6)
|
||||
elseif v[7] == 0 then
|
||||
v[1] = 27 -- hhcurveto (odd argument case)
|
||||
table.remove(v, 7)
|
||||
local t = v[2]
|
||||
v[2] = v[3]
|
||||
v[3] = t
|
||||
end
|
||||
end
|
||||
end
|
||||
if not use_hintmask then
|
||||
for i, v in ipairs(cs) do
|
||||
if v[1] == 18 then
|
||||
v[1] = 1
|
||||
elseif not v[1] and cs[i+1] and cs[i+1][1] == 19 then
|
||||
v[1] = 3
|
||||
elseif v[1] == 19 then
|
||||
table.remove(cs, i)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Try combining lineto segments. We could try harder, but this should
|
||||
-- never be triggered anyway.
|
||||
for i, v in ipairs(cs) do
|
||||
if v[1] == 6 or v[1] == 7 then
|
||||
while cs[i+1] and v[1] == cs[i+1][1] do
|
||||
v[2] = v[2] + cs[i+1][2]
|
||||
table.remove(cs, i+1)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Now use the variable argument versions of most commands
|
||||
for i, v in ipairs(cs) do
|
||||
if v[1] == 5 then -- rlineto
|
||||
while cs[i+1] and 5 == cs[i+1][1] do
|
||||
table.insert(v, cs[i+1][2])
|
||||
table.insert(v, cs[i+1][3])
|
||||
table.remove(cs, i+1)
|
||||
end
|
||||
if cs[i+1] and 8 == cs[i+1][1] then -- rrcurveto
|
||||
v[1] = 25 -- rlinecurveto
|
||||
for j=2,7 do table.insert(v, cs[i+1][j]) end
|
||||
table.remove(cs, i+1)
|
||||
end
|
||||
elseif v[1] == 6 or v[1] == 7 then
|
||||
local next_cmd = (v[1]-5)%2+6
|
||||
while cs[i+1][1] == next_cmd do
|
||||
next_cmd = (cs[i+1][1]-5)%2+6
|
||||
table.insert(v, cs[i+1][2])
|
||||
table.remove(cs, i+1)
|
||||
end
|
||||
elseif v[1] == 8 then
|
||||
while cs[i+1] and 8 == cs[i+1][1] do -- rrcurveto
|
||||
for j=2,7 do table.insert(v, cs[i+1][j]) end
|
||||
table.remove(cs, i+1)
|
||||
end
|
||||
if cs[i+1] and 5 == cs[i+1][1] then -- rlineto
|
||||
v[1] = 24 -- rcurveline
|
||||
table.insert(v, cs[i+1][2])
|
||||
table.insert(v, cs[i+1][3])
|
||||
table.remove(cs, i+1)
|
||||
end
|
||||
elseif v[1] == 27 then
|
||||
while cs[i+1] and 27 == cs[i+1][1] and #cs[i+1] == 5 do -- hhcurveto
|
||||
for j=2,5 do table.insert(v, cs[i+1][j]) end
|
||||
table.remove(cs, i+1)
|
||||
end
|
||||
elseif v[1] == 26 then
|
||||
while cs[i+1] and 26 == cs[i+1][1] and #cs[i+1] == 5 do -- vvcurveto
|
||||
for j=2,5 do table.insert(v, cs[i+1][j]) end
|
||||
table.remove(cs, i+1)
|
||||
end
|
||||
elseif v[1] == 30 or v[1] == 31 then
|
||||
local next_cmd = (v[1]-29)%2+30
|
||||
while #v % 2 == 1 and cs[i+1] and next_cmd == cs[i+1][1] do -- [vh|hv]curveto
|
||||
local next_cmd = (cs[i+1][1]-29)%2+30
|
||||
for j=2,#cs[i+1] do table.insert(v, cs[i+1][j]) end
|
||||