Initial commit

This commit is contained in:
Marcel Krüger 2019-07-17 21:14:34 +02:00
commit 3815ab3963
28 changed files with 4239 additions and 0 deletions

36
luametalatex-back-pdf.lua Normal file
View 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")

View 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
View 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
View 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'

View 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
View 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

View 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
View 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

View 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

View 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