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
|
||||
table.remove(cs, i+1)
|
||||
end
|
||||
elseif false then
|
||||
-- TODO: More commands
|
||||
end
|
||||
end
|
||||
end
|
66
luametalatex-font-t2.lua
Normal file
66
luametalatex-font-t2.lua
Normal file
@ -0,0 +1,66 @@
|
||||
-- This is serializet2.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.
|
||||
|
||||
local pack = string.pack
|
||||
local optimizet2 = require'luametalatex-font-t2-opt'
|
||||
local function numbertot2(n)
|
||||
if math.abs(n) > 2^15 then
|
||||
error[[Number too big]]
|
||||
end
|
||||
local num = math.floor(n + .5)
|
||||
if n ~= 0 and math.abs((num-n)/n) > 0.001 then
|
||||
num = math.floor(n * 2^16 + 0.5)
|
||||
return pack(">Bi4", 255, math.floor(n * 2^16 + 0.5))
|
||||
elseif num >= -107 and num <= 107 then
|
||||
return string.char(num + 139)
|
||||
elseif num >= 108 and num <= 1131 then
|
||||
return pack(">I2", num+0xF694) -- -108+(247*0x100)
|
||||
elseif num >= -1131 and num <= -108 then
|
||||
return pack(">I2", -num+0xFA94) -- -108+(251*0x100)
|
||||
else
|
||||
return pack(">Bi2", 28, num)
|
||||
end
|
||||
end
|
||||
local function convert_cs(cs, upem)
|
||||
local cs_parts = {}
|
||||
local function add(cmd, first, ...)
|
||||
if cmd == 19 or cmd == 20 then
|
||||
cs_parts[#cs_parts+1] = string.char(cmd)
|
||||
cs_parts[#cs_parts+1] = first
|
||||
return
|
||||
end
|
||||
if first then
|
||||
cs_parts[#cs_parts+1] = numbertot2(first*upem/1000)
|
||||
return add(cmd, ...)
|
||||
end
|
||||
if cmd then
|
||||
if cmd < 0 then
|
||||
cs_parts[#cs_parts+1] = string.char(12, -cmd-1)
|
||||
else
|
||||
cs_parts[#cs_parts+1] = string.char(cmd)
|
||||
end
|
||||
end
|
||||
end
|
||||
for _, args in ipairs(cs) do if args then add(table.unpack(args)) end end
|
||||
return table.concat(cs_parts)
|
||||
end
|
||||
|
||||
return function(cs, upem, raw)
|
||||
if not raw then optimizet2(cs) end
|
||||
return convert_cs(cs, upem or 1000)
|
||||
end
|
146
luametalatex-font-tfm.lua
Normal file
146
luametalatex-font-tfm.lua
Normal file
@ -0,0 +1,146 @@
|
||||
local upper_mask = (1<<20)-1<<44
|
||||
local shifted_sign = 1<<43
|
||||
local function scale(factor1, factor2)
|
||||
local result = factor1*factor2 >> 20
|
||||
if result & shifted_sign == shifted_sign then
|
||||
return result | upper_mask
|
||||
else
|
||||
return result
|
||||
end
|
||||
end
|
||||
local function read_scaled(buf, i, count, factor)
|
||||
local result = {}
|
||||
for j = 1, count do
|
||||
result[j] = scale(factor, string.unpack(">i4", buf, i + (j-1)*4))
|
||||
end
|
||||
return result, i + count * 4
|
||||
end
|
||||
local function parse_tfm(buf, i, size)
|
||||
local lf, lh, bc, ec, nw, nh, nd, ni, nl, nk, ne, np
|
||||
lf, lh, bc, ec, nw, nh, nd, ni, nl, nk, ne, np, i =
|
||||
string.unpack(">HHHHHHHHHHHH", buf, i)
|
||||
assert(bc-1 <= ec and ec <= 255)
|
||||
assert(lf == 6 + lh + (ec - bc + 1) + nw + nh + nd + ni + nl + nk + ne + np)
|
||||
assert(lh >= 2)
|
||||
local checksum, designsize
|
||||
checksum, designsize = string.unpack(">I4i4", buf, i)
|
||||
i = i + 4*lh
|
||||
designsize = designsize>>4 -- Adjust TFM sizes to sp
|
||||
if size < 0 then
|
||||
size = math.floor(-size*designsize/1000+.5)
|
||||
end
|
||||
-- In contrast to TeX, we will assume that multiplication of two 32 bit
|
||||
-- integers never overflows. This is safe if Lua integers have 64 bit,
|
||||
-- which is the default.
|
||||
local ligatureoffset, r_boundary
|
||||
local widths, heights, depths, italics, kerns, parameters
|
||||
local extensibles = {}
|
||||
do
|
||||
local i = i + (ec - bc + 1) * 4
|
||||
widths, i = read_scaled(buf, i, nw, size)
|
||||
heights, i = read_scaled(buf, i, nh, size)
|
||||
depths, i = read_scaled(buf, i, nd, size)
|
||||
italics, i = read_scaled(buf, i, ni, size)
|
||||
for k,v in ipairs(italics) do if v == 0 then italics[k] = nil end end
|
||||
ligatureoffset = i
|
||||
if string.byte(buf, i, i) > 128 then
|
||||
r_boundary = string.byte(buf, i+1, i+1)
|
||||
end
|
||||
i = i + nl * 4
|
||||
kerns, i = read_scaled(buf, i, nk, size)
|
||||
for j = 1, ne do
|
||||
local ext = {}
|
||||
ext.top, ext.mid, ext.bot, ext.rep, i = string.unpack("BBBB", buf, i)
|
||||
for k,v in pairs(ext) do
|
||||
if v == 0 then ext[k] = nil end
|
||||
end
|
||||
extensibles[j] = ext
|
||||
end
|
||||
local slant = string.unpack(">i4", buf, i) >> 4
|
||||
parameters = read_scaled(buf, i, np, size)
|
||||
parameters[1] = slant
|
||||
end
|
||||
local characters = {}
|
||||
for cc = bc,ec do
|
||||
local charinfo
|
||||
charinfo, i = string.unpack(">I4", buf, i)
|
||||
if (charinfo >> 24) & 0xFF ~= 0 then
|
||||
local char = {
|
||||
width = widths[((charinfo >> 24) & 0xFF) + 1],
|
||||
height = heights[((charinfo >> 20) & 0xF) + 1],
|
||||
depth = depths[((charinfo >> 16) & 0xF) + 1],
|
||||
italic = italics[((charinfo >> 10) & 0xF) + 1],
|
||||
}
|
||||
local tag = (charinfo >> 8) & 0x3
|
||||
if tag == 0 then
|
||||
elseif tag == 1 then
|
||||
local offset = (charinfo & 0xFF) * 4 + ligatureoffset
|
||||
if string.byte(buf, offset, offset) > 128 then
|
||||
offset = string.unpack(">H", buf, offset + 2)
|
||||
end
|
||||
char.kerns, char.ligatures = {}, {}
|
||||
local done = {}
|
||||
repeat
|
||||
local skip, next, op, rem
|
||||
skip, next, op, rem, offset = string.unpack("BBBB", buf, offset)
|
||||
if skip > 128 then break end
|
||||
if next == r_boundary then next = "right_boundary" end
|
||||
if not done[next] then
|
||||
done[next] = true
|
||||
if op >= 128 then
|
||||
char.kerns[next] = kerns[(op - 128 << 8) + rem + 1]
|
||||
else
|
||||
char.ligatures[next] = {
|
||||
type = op,
|
||||
char = rem,
|
||||
}
|
||||
end
|
||||
end
|
||||
offset = offset + 4*skip
|
||||
until skip == 128
|
||||
if not next(char.kerns) then char.kerns = nil end
|
||||
if not next(char.ligatures) then char.ligatures = nil end
|
||||
elseif tag == 2 then
|
||||
char.next = charinfo & 0xFF
|
||||
elseif tag == 3 then
|
||||
char.extensible = extensibles[(charinfo & 0xFF) + 1]
|
||||
end
|
||||
characters[cc] = char
|
||||
end
|
||||
end
|
||||
return {
|
||||
checksum = checksum,
|
||||
direction = 0,
|
||||
embedding = "unknown",
|
||||
-- encodingbytes = 0,
|
||||
extend = 1000,
|
||||
format = "unknown",
|
||||
identity = "unknown",
|
||||
mode = 0,
|
||||
slant = 0,
|
||||
squeeze = 1000,
|
||||
oldmath = false,
|
||||
streamprovider = 0,
|
||||
tounicode = 0,
|
||||
type = "unknown",
|
||||
units_per_em = 0,
|
||||
used = false,
|
||||
width = 0,
|
||||
writingmode = "unknown",
|
||||
size = size,
|
||||
designsize = designsize,
|
||||
parameters = parameters,
|
||||
characters = characters,
|
||||
}
|
||||
end
|
||||
local basename = ((1-lpeg.S'\\/')^0*lpeg.S'\\/')^0*lpeg.C((1-lpeg.P'.tfm'*-1)^0)
|
||||
return function(name, size)
|
||||
local filename = kpse.find_file(name, 'tfm', true)
|
||||
local f = io.open(filename)
|
||||
if not f then return end
|
||||
local buf = f:read'*a'
|
||||
f:close()
|
||||
local result = parse_tfm(buf, 1, size)
|
||||
result.name = basename:match(name)
|
||||
return result
|
||||
end
|
176
luametalatex-font-vf.lua
Normal file
176
luametalatex-font-vf.lua
Normal file
@ -0,0 +1,176 @@
|
||||
local fontcmds = {
|
||||
[243] = ">I1I4I4I4BB",
|
||||
[244] = ">I2I4I4I4BB",
|
||||
[245] = ">I3I4I4I4BB",
|
||||
[246] = ">I4I4I4I4BB",
|
||||
}
|
||||
local function read_fonts(buf, i, fonts, size)
|
||||
local cmd = fontcmds[string.byte(buf, i)]
|
||||
if not cmd then return i end
|
||||
local fid, check, scale, designsize, arealen, namelen, i =
|
||||
string.unpack(cmd, buf, i + 1)
|
||||
local fsize = size * scale >> 20
|
||||
if fonts[fid] then error[[font number reused in VF file]] end
|
||||
fonts[fid] = {
|
||||
area = string.sub(buf, i, i+arealen-1),
|
||||
name = string.sub(buf, i+arealen, i+arealen+namelen-1),
|
||||
size = fsize,
|
||||
designsize = designsize >> 4,
|
||||
checksum = check,
|
||||
}
|
||||
return read_fonts(buf, i+arealen+namelen, fonts, size)
|
||||
end
|
||||
local Cmds = {
|
||||
[1] = ">I1",
|
||||
[2] = ">I2",
|
||||
[3] = ">I3",
|
||||
[4] = ">I4",
|
||||
}
|
||||
local cmds = {
|
||||
[1] = ">i1",
|
||||
[2] = ">i2",
|
||||
[3] = ">i3",
|
||||
[4] = ">i4",
|
||||
}
|
||||
local xxx = {
|
||||
[239] = ">s1",
|
||||
[240] = ">s2",
|
||||
[241] = ">s3",
|
||||
[242] = ">s4",
|
||||
}
|
||||
local function read_chars(buf, i, characters, size)
|
||||
local cmd = string.byte(buf, i)
|
||||
if cmd > 242 then return i end
|
||||
local code, tfmwidth
|
||||
if cmd == 242 then
|
||||
cmd, code, tfmwidth, i = string.unpack(">I4I4I4", buf, i + 1)
|
||||
else
|
||||
code, tfmwidth, i = string.unpack(">BI3", buf, i + 1)
|
||||
end
|
||||
local commands = {}
|
||||
local character = {
|
||||
width = tfmwidth, -- Unscaled for compatibility with LuaTeX
|
||||
commands = commands,
|
||||
}
|
||||
characters[code] = character
|
||||
local after = i + cmd
|
||||
local w, x, y, z, stack = 0, 0, 0, 0, {}
|
||||
while i < after do
|
||||
local cmd = string.byte(buf, i)
|
||||
if cmd <= 131 then
|
||||
if cmd >= 128 then
|
||||
cmd, i = string.unpack(Cmds[cmd-127], buf, i + 1)
|
||||
else
|
||||
i = i + 1
|
||||
end
|
||||
commands[#commands + 1] = { "char", cmd }
|
||||
elseif cmd == 132 then
|
||||
local height, width
|
||||
height, width, i = string.unpack(">I4I4", buf, i + 1)
|
||||
commands[#commands + 1] =
|
||||
{ "rule", height * size >> 20, width * size >> 20 }
|
||||
elseif cmd <= 136 then
|
||||
cmd, i = string.unpack(Cmds[cmd-132], buf, i + 1)
|
||||
commands[#commands + 1] = { "push" }
|
||||
commands[#commands + 1] = { "char", cmd }
|
||||
commands[#commands + 1] = { "pop" }
|
||||
elseif cmd == 137 then
|
||||
local height, width
|
||||
height, width, i = string.unpack(">I4I4", buf, i + 1)
|
||||
commands[#commands + 1] = { "push" }
|
||||
commands[#commands + 1] =
|
||||
{ "rule", height * size >> 20, width * size >> 20 }
|
||||
commands[#commands + 1] = { "pop" }
|
||||
elseif cmd == 138 then -- NOP
|
||||
i = i + 1
|
||||
elseif cmd <= 140 then
|
||||
error[[Invalid command in packet]]
|
||||
elseif cmd == 141 then
|
||||
stack[#stack+1] = {w, x, y, z}
|
||||
commands[#commands + 1] = { "push" }
|
||||
i = i + 1
|
||||
elseif cmd == 142 then
|
||||
local top = stack[#stack]
|
||||
if not top then error[[Attempt to pop with empty stack]] end
|
||||
stack[#stack] = nil
|
||||
w, x, y, z = top[1], top[2], top[3], top[4]
|
||||
commands[#commands + 1] = { "pop" }
|
||||
i = i + 1
|
||||
elseif cmd <= 146 then
|
||||
cmd, i = string.unpack(cmds[cmd-142], buf, i + 1)
|
||||
commands[#commands + 1] = { "right", (cmd * size >> 20) | (cmd < 0 and 0xFFFFFFFF00000000 or 0) }
|
||||
elseif cmd == 147 then
|
||||
commands[#commands + 1] = { "right", w }
|
||||
i = i + 1
|
||||
elseif cmd <= 151 then
|
||||
cmd, i = string.unpack(cmds[cmd-147], buf, i + 1)
|
||||
w = (cmd * size >> 20) | (cmd < 0 and 0xFFFFFFFF00000000 or 0)
|
||||
commands[#commands + 1] = { "right", w }
|
||||
elseif cmd == 152 then
|
||||
commands[#commands + 1] = { "right", x }
|
||||
i = i + 1
|
||||
elseif cmd <= 156 then
|
||||
cmd, i = string.unpack(cmds[cmd-152], buf, i + 1)
|
||||
x = (cmd * size >> 20) | (cmd < 0 and 0xFFFFFFFF00000000 or 0)
|
||||
commands[#commands + 1] = { "right", x }
|
||||
elseif cmd <= 160 then
|
||||
cmd, i = string.unpack(cmds[cmd-156], buf, i + 1)
|
||||
commands[#commands + 1] = { "down", (cmd * size >> 20) | (cmd < 0 and 0xFFFFFFFF00000000 or 0) }
|
||||
elseif cmd == 161 then
|
||||
commands[#commands + 1] = { "down", y }
|
||||
i = i + 1
|
||||
elseif cmd <= 165 then
|
||||
cmd, i = string.unpack(cmds[cmd-161], buf, i + 1)
|
||||
y = (cmd * size >> 20) | (cmd < 0 and 0xFFFFFFFF00000000 or 0)
|
||||
commands[#commands + 1] = { "down", y }
|
||||
elseif cmd == 166 then
|
||||
commands[#commands + 1] = { "down", z }
|
||||
i = i + 1
|
||||
elseif cmd <= 170 then
|
||||
cmd, i = string.unpack(cmds[cmd-166], buf, i + 1)
|
||||
z = (cmd * size >> 20) | (cmd < 0 and 0xFFFFFFFF00000000 or 0)
|
||||
commands[#commands + 1] = { "down", z }
|
||||
elseif cmd <= 238 then
|
||||
if cmd >= 235 then
|
||||
cmd, i = string.unpack(Cmds[cmd-234], buf, i + 1)
|
||||
else
|
||||
i = i + 1
|
||||
end
|
||||
commands[#commands + 1] = { "font", cmd }
|
||||
elseif xxx[cmd] then
|
||||
cmd, i = string.unpack(xxx[cmd], buf, i + 1)
|
||||
commands[#commands + 1] = { "special", cmd }
|
||||
else
|
||||
error[[Invalid command in packet]]
|
||||
end
|
||||
end
|
||||
if i > after then error[[Ill-formed packet]] end
|
||||
return read_chars(buf, after, characters, size)
|
||||
end
|
||||
local function parse_vf(buf, i, size)
|
||||
local font = {}
|
||||
local magic, designsize
|
||||
magic, font.header, font.checksum, designsize, i =
|
||||
string.unpack(">Hs1I4I4", buf, i)
|
||||
if magic ~= 247*256+202 then error[[Not a VF file]] end
|
||||
font.designsize = designsize >> 4
|
||||
|
||||
local fonts, characters = {}, {}
|
||||
font.fonts, font.characters = fonts, characters
|
||||
|
||||
i = read_fonts(buf, i, fonts, size)
|
||||
i = read_chars(buf, i, characters, size)
|
||||
|
||||
print(require'inspect'(font))
|
||||
end
|
||||
local basename = ((1-lpeg.S'\\/')^0*lpeg.S'\\/')^0*lpeg.C((1-lpeg.P'.tfm'*-1)^0)
|
||||
return function(name, size, must_exist)
|
||||
local filename = kpse.find_file(name, 'vf', must_exist)
|
||||
local f = io.open(filename)
|
||||
if not f then return end
|
||||
local buf = f:read'*a'
|
||||
f:close()
|
||||
local result = parse_tfm(buf, 1, size)
|
||||
result.name = basename:match(name)
|
||||
return result
|
||||
end
|
399
luametalatex-init.lua
Normal file
399
luametalatex-init.lua
Normal file
@ -0,0 +1,399 @@
|
||||
do
|
||||
ffi.cdef[[
|
||||
typedef enum
|
||||
{
|
||||
kpse_gf_format,
|
||||
kpse_pk_format,
|
||||
kpse_any_glyph_format,
|
||||
kpse_tfm_format,
|
||||
kpse_afm_format,
|
||||
kpse_base_format,
|
||||
kpse_bib_format,
|
||||
kpse_bst_format,
|
||||
kpse_cnf_format,
|
||||
kpse_db_format,
|
||||
kpse_fmt_format,
|
||||
kpse_fontmap_format,
|
||||
kpse_mem_format,
|
||||
kpse_mf_format,
|
||||
kpse_mfpool_format,
|
||||
kpse_mft_format,
|
||||
kpse_mp_format,
|
||||
kpse_mppool_format,
|
||||
kpse_mpsupport_format,
|
||||
kpse_ocp_format,
|
||||
kpse_ofm_format,
|
||||
kpse_opl_format,
|
||||
kpse_otp_format,
|
||||
kpse_ovf_format,
|
||||
kpse_ovp_format,
|
||||
kpse_pict_format,
|
||||
kpse_tex_format,
|
||||
kpse_texdoc_format,
|
||||
kpse_texpool_format,
|
||||
kpse_texsource_format,
|
||||
kpse_tex_ps_header_format,
|
||||
kpse_troff_font_format,
|
||||
kpse_type1_format,
|
||||
kpse_vf_format,
|
||||
kpse_dvips_config_format,
|
||||
kpse_ist_format,
|
||||
kpse_truetype_format,
|
||||
kpse_type42_format,
|
||||
kpse_web2c_format,
|
||||
kpse_program_text_format,
|
||||
kpse_program_binary_format,
|
||||
kpse_miscfonts_format,
|
||||
kpse_web_format,
|
||||
kpse_cweb_format,
|
||||
kpse_enc_format,
|
||||
kpse_cmap_format,
|
||||
kpse_sfd_format,
|
||||
kpse_opentype_format,
|
||||
kpse_pdftex_config_format,
|
||||
kpse_lig_format,
|
||||
kpse_texmfscripts_format,
|
||||
kpse_lua_format,
|
||||
kpse_fea_format,
|
||||
kpse_cid_format,
|
||||
kpse_mlbib_format,
|
||||
kpse_mlbst_format,
|
||||
kpse_clua_format,
|
||||
kpse_ris_format,
|
||||
kpse_bltxml_format,
|
||||
kpse_last_format /* one past last index */
|
||||
} kpse_file_format_type;
|
||||
|
||||
typedef enum {
|
||||
kpse_glyph_source_normal,
|
||||
kpse_glyph_source_alias,
|
||||
kpse_glyph_source_maketex,
|
||||
kpse_glyph_source_fallback_res,
|
||||
kpse_glyph_source_fallback
|
||||
} kpse_glyph_source_type;
|
||||
|
||||
typedef enum {
|
||||
kpse_src_implicit,
|
||||
kpse_src_compile,
|
||||
kpse_src_texmf_cnf,
|
||||
kpse_src_client_cnf,
|
||||
kpse_src_env,
|
||||
kpse_src_x,
|
||||
kpse_src_cmdline
|
||||
} kpse_src_type;
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
unsigned dpi;
|
||||
kpse_file_format_type format;
|
||||
kpse_glyph_source_type source;
|
||||
} kpse_glyph_file_type;
|
||||
|
||||
void *kpathsea_new(void);
|
||||
void kpathsea_set_program_name(void*, const char*, const char*);
|
||||
void kpathsea_init_prog(void*, const char*, unsigned, const char *, const char *);
|
||||
const char *kpathsea_find_file(void*, const char *, kpse_file_format_type, int);
|
||||
const char *kpathsea_find_glyph(void*, const char *, unsigned, kpse_file_format_type, kpse_glyph_file_type*);
|
||||
const char *kpathsea_brace_expand(void*, const char *);
|
||||
const char *kpathsea_path_expand(void*, const char *);
|
||||
const char *kpathsea_var_expand(void*, const char *);
|
||||
const char *kpathsea_var_value(void*, const char *);
|
||||
void kpathsea_set_program_enabled (void *, kpse_file_format_type, int, kpse_src_type);
|
||||
int kpathsea_in_name_ok(void*, const char *);
|
||||
int kpathsea_out_name_ok(void*, const char *);
|
||||
void kpathsea_finish(void*);
|
||||
const char *kpathsea_version_string;
|
||||
]]
|
||||
local kpse_glyph_file_type = ffi.typeof'kpse_glyph_file_type'
|
||||
local type_remap = {
|
||||
-- These are the command line/LuaTeX names
|
||||
gf = "kpse_gf_format",
|
||||
pk = "kpse_pk_format",
|
||||
["bitmap font"] = "kpse_any_glyph_format",
|
||||
tfm = "kpse_tfm_format",
|
||||
afm = "kpse_afm_format",
|
||||
base = "kpse_base_format",
|
||||
bib = "kpse_bib_format",
|
||||
bst = "kpse_bst_format",
|
||||
cnf = "kpse_cnf_format",
|
||||
["ls-R"] = "kpse_db_format",
|
||||
fmt = "kpse_fmt_format",
|
||||
map = "kpse_fontmap_format",
|
||||
mem = "kpse_mem_format",
|
||||
mf = "kpse_mf_format",
|
||||
mfpool = "kpse_mfpool_format",
|
||||
mft = "kpse_mft_format",
|
||||
mp = "kpse_mp_format",
|
||||
mppool = "kpse_mppool_format",
|
||||
["MetaPost support"] = "kpse_mpsupport_format",
|
||||
ocp = "kpse_ocp_format",
|
||||
ofm = "kpse_ofm_format",
|
||||
opl = "kpse_opl_format",
|
||||
otp = "kpse_otp_format",
|
||||
ovf = "kpse_ovf_format",
|
||||
ovp = "kpse_ovp_format",
|
||||
["graphic/figure"] = "kpse_pict_format",
|
||||
tex = "kpse_tex_format",
|
||||
["TeX system documentation"] = "kpse_texdoc_format",
|
||||
texpool = "kpse_texpool_format",
|
||||
["TeX system sources"] = "kpse_texsource_format",
|
||||
["PostScript header"] = "kpse_tex_ps_header_format",
|
||||
["Troff fonts"] = "kpse_troff_font_format",
|
||||
["type1 fonts"] = "kpse_type1_format",
|
||||
vf = "kpse_vf_format",
|
||||
["dvips config"] = "kpse_dvips_config_format",
|
||||
ist = "kpse_ist_format",
|
||||
["truetype fonts"] = "kpse_truetype_format",
|
||||
["type42 fonts"] = "kpse_type42_format",
|
||||
["web2c files"] = "kpse_web2c_format",
|
||||
["other text files"] = "kpse_program_text_format",
|
||||
["other binary files"] = "kpse_program_binary_format",
|
||||
["mics fonts"] = "kpse_miscfonts_format",
|
||||
web = "kpse_web_format",
|
||||
cweb = "kpse_cweb_format",
|
||||
["enc files"] = "kpse_enc_format",
|
||||
["cmap files"] = "kpse_cmap_format",
|
||||
["subfont definition files"] = "kpse_sfd_format",
|
||||
["opentype fonts"] = "kpse_opentype_format",
|
||||
["pdftex config"] = "kpse_pdftex_config_format",
|
||||
["lig files"] = "kpse_lig_format",
|
||||
texmfscripts = "kpse_texmfscripts_format",
|
||||
lua = "kpse_lua_format",
|
||||
["font feature files"] = "kpse_fea_format",
|
||||
["cid maps"] = "kpse_cid_format",
|
||||
mlbib = "kpse_mlbib_format",
|
||||
mlbst = "kpse_mlbst_format",
|
||||
clua = "kpse_clua_format",
|
||||
ris = "kpse_ris_format",
|
||||
bltxml = "kpse_bltxml_format",
|
||||
-- Some additional aliases to make naming more consistant
|
||||
any_glyph = "kpse_any_glyph_format",
|
||||
db = "kpse_db_format",
|
||||
fontmap = "kpse_fontmap_format",
|
||||
mpsupport = "kpse_mpsupport_format",
|
||||
pict = "kpse_pict_format",
|
||||
texdoc = "kpse_texdoc_format",
|
||||
texsource = "kpse_texsource_format",
|
||||
tex_ps_header = "kpse_tex_ps_header_format",
|
||||
troff_font = "kpse_troff_font_format",
|
||||
type1 = "kpse_type1_format",
|
||||
dvips_config = "kpse_dvips_config_format",
|
||||
truetype = "kpse_truetype_format",
|
||||
type42 = "kpse_type42_format",
|
||||
web2c = "kpse_web2c_format",
|
||||
program_text = "kpse_program_text_format",
|
||||
program_binary = "kpse_program_binary_format",
|
||||
miscfonts = "kpse_miscfonts_format",
|
||||
enc = "kpse_enc_format",
|
||||
cmap = "kpse_cmap_format",
|
||||
sfd = "kpse_sfd_format",
|
||||
opentype = "kpse_opentype_format",
|
||||
pdftex_config = "kpse_pdftex_config_format",
|
||||
lig = "kpse_lig_format",
|
||||
fea = "kpse_fea_format",
|
||||
cid = "kpse_cid_format",
|
||||
-- And some other aliases
|
||||
eps = "kpse_pict_format",
|
||||
pfb = "kpse_type1_format",
|
||||
ttc = "kpse_truetype_format",
|
||||
ttf = "kpse_truetype_format",
|
||||
otf = "kpse_opentype_format",
|
||||
text = "kpse_program_text_format",
|
||||
binary = "kpse_program_binary_format",
|
||||
}
|
||||
local kpselib = ffi.load("kpathsea")
|
||||
local realarg0
|
||||
if arg[1]:sub(1,7) == "--arg0=" then
|
||||
realarg0 = arg[1]:sub(8)
|
||||
else
|
||||
local i = 0
|
||||
while arg[i] do
|
||||
realarg0 = arg[i]
|
||||
i = i-1
|
||||
end
|
||||
end
|
||||
local file_format = ffi.typeof'kpse_file_format_type'
|
||||
local NULL = ffi.new("const char*", nil)
|
||||
local function get_string(s) return s ~= NULL and ffi.string(s) end
|
||||
local function set_program_name (t, arg0, progname)
|
||||
kpselib.kpathsea_set_program_name(t.cdata, arg0 or realarg0, progname)
|
||||
return t
|
||||
end
|
||||
local methods = {
|
||||
init_prog = function(t, prefix, dpi, mode, fallback)
|
||||
kpselib.kpathsea_init_prog(t.cdata, prefix, dpi, mode, fallback)
|
||||
end,
|
||||
find_file = function(t, name, ...)
|
||||
local ftype, must_exist = "kpse_tex_format", 0
|
||||
for i=select('#', ...),1,-1 do
|
||||
local arg = select(i, ...)
|
||||
local argtype = type(arg)
|
||||
if argtype == "string" then
|
||||
ftype = arg
|
||||
elseif argtype == "number" then
|
||||
must_exist = arg
|
||||
elseif argtype == "boolean" then
|
||||
must_exist = arg and 1 or 0
|
||||
end
|
||||
end
|
||||
ftype = type_remap[ftype] or ftype
|
||||
if ftype == "kpse_gf_format" or ftype == "kpse_pk_format"
|
||||
or ftype == "kpse_any_glyph_format" then
|
||||
local glyph_file = kpse_glyph_file_type()
|
||||
local res = kpselib.kpathsea_find_glyph(t.cdata, name, must_exist, ftype, glyph_file)
|
||||
if res ~= NULL then
|
||||
return ffi.string(res), ffi.string(glyph_file.name), glyph_file.dpi, glyph_file.format, glyph_file.source
|
||||
end
|
||||
else
|
||||
return get_string(kpselib.kpathsea_find_file(t.cdata, name, ftype, must_exist > 0 and 1 or 0))
|
||||
end
|
||||
end,
|
||||
-- show_path = function(t, ftype)
|
||||
-- error [[Not yet implemented]]
|
||||
-- end,
|
||||
expand_braces = function(t, path)
|
||||
return get_string(kpselib.kpathsea_brace_expand(t.cdata, path))
|
||||
end,
|
||||
expand_path = function(t, path)
|
||||
return get_string(kpselib.kpathsea_path_expand(t.cdata, path))
|
||||
end,
|
||||
expand_var = function(t, var)
|
||||
return get_string(kpselib.kpathsea_var_expand(t.cdata, var))
|
||||
end,
|
||||
var_value = function(t, var)
|
||||
return get_string(kpselib.kpathsea_var_value(t.cdata, var))
|
||||
end,
|
||||
set_maketex = function(t, ftype, value, src)
|
||||
ftype = type_remap[ftype] or ftype
|
||||
kpselib.kpathsea_set_program_enabled(t.cdata, ftype, value and 1 or 0, src or "kpse_src_cmdline")
|
||||
end,
|
||||
finish = function(t)
|
||||
if t.cdata then
|
||||
kpselib.kpathsea_finish(t.cdata)
|
||||
end
|
||||
end,
|
||||
}
|
||||
local meta = {
|
||||
__index = methods,
|
||||
__gc = methods.finish,
|
||||
}
|
||||
local global_kpse = setmetatable({cdata = kpselib.kpathsea_new()}, meta)
|
||||
kpse = {
|
||||
set_program_name = function(...)
|
||||
set_program_name(global_kpse, ...)
|
||||
end,
|
||||
set_maketex = function(...)
|
||||
return global_kpse:set_maketex(...)
|
||||
end,
|
||||
init_prog = function(...)
|
||||
return global_kpse:init_prog(...)
|
||||
end,
|
||||
new = function(...)
|
||||
return set_program_name(setmetatable({cdata = kpselib.kpathsea_new()}, meta), ...)
|
||||
end,
|
||||
expand_braces = function(...)
|
||||
return global_kpse:expand_braces(...)
|
||||
end,
|
||||
expand_path = function(...)
|
||||
return global_kpse:expand_path(...)
|
||||
end,
|
||||
expand_var = function(...)
|
||||
return global_kpse:expand_var(...)
|
||||
end,
|
||||
var_value = function(...)
|
||||
return global_kpse:var_value(...)
|
||||
end,
|
||||
find_file = function(...)
|
||||
return global_kpse:find_file(...)
|
||||
end,
|
||||
version = function()
|
||||
return ffi.string(kpselib.kpathsea_version_string)
|
||||
end,
|
||||
}
|
||||
end
|
||||
--
|
||||
-- unicode = {utf8 = utf8}
|
||||
-- utf8.byte = utf8.codepoint
|
||||
kpse.set_program_name()
|
||||
package.searchers[2] = function(modname)
|
||||
local filename = kpse.find_file(modname, "kpse_lua_format", true)
|
||||
if not filename then
|
||||
return string.format("\n\tno file located through kpse for %s", modname)
|
||||
end
|
||||
local mod, msg = loadfile(filename)
|
||||
if msg then
|
||||
error(string.format("error loading module '%s' from file '%s':\n\t%s", modname, filename, msg))
|
||||
end
|
||||
return mod, filename
|
||||
end
|
||||
kpse.set_maketex("kpse_fmt_format", true)
|
||||
bit32 = require'luametalatex-bit32'
|
||||
kpse.init_prog("LUATEX", 400, "nexthi", nil)
|
||||
status.init_kpse = 1
|
||||
status.safer_option = 0
|
||||
local read_tfm = require'luametalatex-font-tfm'
|
||||
read_vf = require'luametalatex-font-vf'
|
||||
font.read_tfm = read_tfm
|
||||
font.read_vf = read_vf
|
||||
local reserved_ids = -1
|
||||
font.fonts = {}
|
||||
function font.getfont(id)
|
||||
return font.fonts[id]
|
||||
end
|
||||
pdf = {
|
||||
getfontname = function(id) -- No font sharing
|
||||
return id
|
||||
end,
|
||||
}
|
||||
callback.register('define_font', function(name, size)
|
||||
if status.ini_version then
|
||||
reserved_ids = font.nextid()-1
|
||||
lua.prepared_code[#lua.prepared_code+1] = string.format("font.define(%i, font.read_tfm(%q, %i))", reserved_ids, name, size)
|
||||
end
|
||||
local f = read_tfm(name, size)
|
||||
font.fonts[font.nextid()-1] = f
|
||||
return f
|
||||
end)
|
||||
local olddefinefont = font.define
|
||||
function font.define(i, f)
|
||||
if not f then
|
||||
f = i
|
||||
i = font.nextid(true)
|
||||
end
|
||||
font.fonts[i] = f
|
||||
return olddefinefont(i, f)
|
||||
end
|
||||
-- do
|
||||
-- local register = callback.register
|
||||
-- function callback.register(...)
|
||||
-- print('callback.register', ...)
|
||||
-- return register(...)
|
||||
-- end
|
||||
-- end
|
||||
callback.register('find_log_file', function(name) return name end)
|
||||
callback.register('find_data_file', function(name) return kpse.find_file(name, 'kpse_tex_format', true) end)
|
||||
callback.register('find_format_file', function(name) return kpse.find_file(name, 'kpse_fmt_format', true) end)
|
||||
callback.register('show_warning_message', function()
|
||||
texio.write_nl('WARNING Tag: ' .. status.lastwarningtag)
|
||||
texio.write_nl(status.lastwarningstring)
|
||||
end)
|
||||
callback.register('show_error_message', function()
|
||||
if status.lasterrorcontext then
|
||||
texio.write_nl('ERROR Context: ' .. status.lasterrorcontext)
|
||||
end
|
||||
texio.write_nl(status.lasterrorstring)
|
||||
end)
|
||||
callback.register('pre_dump', function()
|
||||
-- for k,v in pairs(callback.list()) do print('CB', k,v) end
|
||||
lua.bytecode[1], msg = load("do local id "
|
||||
.. "repeat id = font.nextid(true) "
|
||||
.. "until id == " .. reserved_ids
|
||||
.. " end "
|
||||
.. table.concat(lua.prepared_code, ' '))
|
||||
end)
|
||||
if status.ini_version then
|
||||
lua.prepared_code = {}
|
||||
local code = package.searchers[2]('luametalatex-firstcode')
|
||||
if type(code) == "string" then error(string.format("Initialization code not found %s", code)) end
|
||||
lua.bytecode[2] = code
|
||||
end
|
528
luametalatex-nodewriter.lua
Normal file
528
luametalatex-nodewriter.lua
Normal file
@ -0,0 +1,528 @@
|
||||
local format = string.format
|
||||
local concat = table.concat
|
||||
local write = texio.write_nl
|
||||
local properties = node.get_properties_table()
|
||||
local function doublekeyed(t, id2name, name2id, index)
|
||||
return setmetatable(t, {
|
||||
__index = index,
|
||||
__newindex = function(t, k, v)
|
||||
rawset(t, k, v)
|
||||
if type(k) == 'string' then
|
||||
rawset(t, name2id(k), v)
|
||||
else
|
||||
rawset(t, id2name(k), v)
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
local nodehandler = (function()
|
||||
local function unknown_handler(_, n, x, y)
|
||||
write(format("Sorry, but the PDF backend does not support %q (id = %i) nodes right now. The supplied node will be dropped at coordinates (%i, %i).", node.type(n.id), n.id, x, y))
|
||||
end
|
||||
return doublekeyed({}, node.type, node.id, function()
|
||||
return unknown_handler
|
||||
end)
|
||||
end)()
|
||||
local whatsithandler = (function()
|
||||
local whatsits = node.whatsits()
|
||||
local function unknown_handler(p, n, x, y, ...)
|
||||
local prop = properties[n]
|
||||
if prop and prop.handle then
|
||||
prop:handle(p, n, x, y, ...)
|
||||
else
|
||||
write(format("Sorry, but the PDF backend does not support %q (id = %i) whatsits right now. The supplied node will be dropped at coordinates (%i, %i).", whatsits[n.subtype], n.subtype, x, y))
|
||||
end
|
||||
end
|
||||
return doublekeyed({}, function(n)return whatsits[n]end, function(n)return whatsits[n]end, function()
|
||||
return unknown_handler
|
||||
end)
|
||||
end)()
|
||||
local glyph, text, page, cm_pending = 1, 2, 3, 4
|
||||
local gsub = string.gsub
|
||||
local function projected_point(m, x, y, w)
|
||||
w = w or 1
|
||||
return x*m[1] + y*m[3] + w*m[5], x*m[2] + y*m[4] + w*m[6]
|
||||
end
|
||||
local function pdfsave(p, x, y)
|
||||
local lastmatrix = p.matrix
|
||||
p.matrix = {[0] = lastmatrix, table.unpack(lastmatrix)}
|
||||
end
|
||||
local function pdfmatrix(p, a, b, c, d, e, f)
|
||||
local m = p.matrix
|
||||
a, b = projected_point(m, a, b, 0)
|
||||
c, d = projected_point(m, c, d, 0)
|
||||
e, f = projected_point(m, e, f, 1)
|
||||
m[1], m[2], m[3], m[4], m[5], m[6] = a, b, c, d, e, f
|
||||
end
|
||||
local function pdfrestore(p, x, y)
|
||||
-- TODO: Check x, y
|
||||
p.matrix = p.matrix[0]
|
||||
end
|
||||
local function sp2bp(sp)
|
||||
return sp/65781.76
|
||||
end
|
||||
local topage
|
||||
local function totext(p, fid)
|
||||
local last = p.mode
|
||||
if last == glyph then
|
||||
p.pending[#p.pending+1] = ")]TJ"
|
||||
p.strings[#p.strings+1] = concat(p.pending)
|
||||
for i=1,#p.pending do p.pending[i] = nil end
|
||||
last = text
|
||||
end
|
||||
if last == cm_pending then topage(p) end
|
||||
p.mode = text
|
||||
if last == text and p.font.fid == fid then return end
|
||||
local f = font.getfont(fid) or font.fonts[fid]
|
||||
if last ~= text then p.strings[#p.strings+1] = "BT" p.pos.lx, p.pos.ly, p.pos.x, p.pos.y, p.font.exfactor = 0, 0, 0, 0, 0 end
|
||||
p:fontprovider(f, fid)
|
||||
-- p.strings[#p.strings+1] = format("/F%i %f Tf 0 Tr", f.parent, sp2bp(f.size)) -- TODO: Setting the mode, expansion, etc.
|
||||
p.font.fid = fid
|
||||
p.font.font = f
|
||||
return false -- Return true if we need a new textmatrix
|
||||
end
|
||||
local inspect = require'inspect'
|
||||
local function show(t) print(inspect(t)) end
|
||||
local function topage(p)
|
||||
local last = p.mode
|
||||
if last == page then return end
|
||||
if last <= text then
|
||||
totext(p, p.font.fid) -- First make sure we are really in text mode
|
||||
p.strings[#p.strings+1] = "ET"
|
||||
elseif last == cm_pending then
|
||||
local pending = p.pending_matrix
|
||||
if pending[1] ~= 1 or pending[2] ~= 0 or pending[3] ~= 0 or pending[4] ~= 1 or pending[5] ~= 0 or pending[6] ~= 0 then
|
||||
p.strings[#p.strings+1] = format("%f %f %f %f %f %f cm", pending[1], pending[2], pending[3], pending[4], sp2bp(pending[5]), sp2bp(pending[6]))
|
||||
end
|
||||
else
|
||||
error[[Unknown mode]]
|
||||
end
|
||||
p.mode = page
|
||||
end
|
||||
local function toglyph(p, fid, x, y, exfactor)
|
||||
local last = p.mode
|
||||
if last == glyph and p.font.fid == fid and p.pos.y == y and p.font.exfactor == exfactor then
|
||||
if x == p.pos.x then return end
|
||||
local xoffset = (x - p.pos.x)/p.font.font.size * 1000 / (1+exfactor/1000000)
|
||||
if math.abs(xoffset) < 1000000 then -- 1000000 is arbitrary
|
||||
p.pending[#p.pending+1] = format(")%i(", math.floor(-xoffset))
|
||||
p.pos.x = x
|
||||
return
|
||||
end
|
||||
end
|
||||
if totext(p, fid) or exfactor ~= p.font.exfactor then
|
||||
p.font.exfactor = exfactor
|
||||
p.strings[#p.strings+1] = gsub(format("%f 0.0 %f %f %f %f Tm", 1+exfactor/1000000, 0, 1, sp2bp(x), sp2bp(y)), '%.?0+ ', ' ')
|
||||
else
|
||||
p.strings[#p.strings+1] = gsub(format("%f %f Td", sp2bp((x - p.pos.lx)/(1+exfactor/1000000)), sp2bp(y - p.pos.ly)), '%.?0+ ', ' ')
|
||||
end
|
||||
p.pos.lx, p.pos.ly, p.pos.x, p.pos.y = x, y, x, y
|
||||
p.mode = glyph
|
||||
p.pending[1] = "[("
|
||||
end
|
||||
local function write_matrix(p, a, b, c, d, e, f)
|
||||
local pending = p.pending_matrix
|
||||
if p.mode ~= cm_pending then
|
||||
topage(p)
|
||||
p.mode = cm_pending
|
||||
pending[1], pending[2], pending[3], pending[4], pending[5], pending[6] = a, b, c, d, e, f
|
||||
return
|
||||
else
|
||||
a, b = projected_point(pending, a, b, 0)
|
||||
c, d = projected_point(pending, c, d, 0)
|
||||
e, f = projected_point(pending, e, f, 1)
|
||||
pending[1], pending[2], pending[3], pending[4], pending[5], pending[6] = a, b, c, d, e, f
|
||||
return
|
||||
end
|
||||
end
|
||||
local linkcontext = {}
|
||||
-- local function get_action_attr(p, action)
|
||||
-- if action.action_type == 3 then
|
||||
-- return action.data
|
||||
-- elseif action.action_type == 2 then
|
||||
local function write_link(p, link)
|
||||
local quads = link.quads
|
||||
local minX, maxX, minY, maxY = math.huge, -math.huge, math.huge, -math.huge
|
||||
assert(link.action.action_type == 3) -- TODO: Other types
|
||||
local attr = link.attr .. link.action.data
|
||||
assert(#quads%8==0)
|
||||
local quadStr = {}
|
||||
for i=1,#quads,8 do
|
||||
local x1, y1, x4, y4, x2, y2, x3, y3 = table.unpack(quads, i, i+7)
|
||||
x1, y1, x2, y2, x3, y3, x4, y4 = sp2bp(x1), sp2bp(y1), sp2bp(x2), sp2bp(y2), sp2bp(x3), sp2bp(y3), sp2bp(x4), sp2bp(y4)
|
||||
quadStr[i//8+1] = string.format("%f %f %f %f %f %f %f %f", x1, y1, x2, y2, x3, y3, x4, y4)
|
||||
minX = math.min(minX, x1, x2, x3, x4)
|
||||
minY = math.min(minY, y1, y2, y3, y4)
|
||||
maxX = math.max(maxX, x1, x2, x3, x4)
|
||||
maxY = math.max(maxY, y1, y2, y3, y4)
|
||||
end
|
||||
p.file:indirect(link.objnum, string.format("<</Rect[%f %f %f %f]/QuadPoints[%s]%s>>", minX-.2, minY-.2, maxX+.2, maxY+.2, table.concat(quadStr, ' '), attr))
|
||||
for i=1,#quads do quads[i] = nil end
|
||||
link.objnum = nil
|
||||
end
|
||||
local function addlinkpoint(p, link, x, y, list, final)
|
||||
local quads = link.quads
|
||||
print'addlink'
|
||||
if link.annots and link.annots ~= p.annots then -- We started on another page, let's finish that before starting the new page
|
||||
write_link(p, link)
|
||||
link.annots = nil
|
||||
end
|
||||
if not link.annots then
|
||||
link.annots = p.annots -- Just a marker to indicate the page
|
||||
link.objnum = link.objnum or p.file:getobj()
|
||||
p.annots[#p.annots+1] = link.objnum .. " 0 R"
|
||||
end
|
||||
local m = p.matrix
|
||||
local lx, ly = projected_point(m, x, y-(link.depth or list.depth))
|
||||
local ux, uy = projected_point(m, x, y+(link.height or list.height))
|
||||
local n = #quads
|
||||
quads[n+1], quads[n+2], quads[n+3], quads[n+4] = lx, ly, ux, uy
|
||||
if final or (link.force_separate and (n+4)%8 == 0) then
|
||||
print(final, n, link.force_separate)
|
||||
write_link(p, link)
|
||||
link.annots = nil
|
||||
end
|
||||
end
|
||||
function nodehandler.hlist(p, list, x0, y, outerlist, origin, level)
|
||||
if outerlist then
|
||||
if outerlist.id == 0 then
|
||||
y = y - list.shift
|
||||
else
|
||||
x0 = x0 + list.shift
|
||||
end
|
||||
end
|
||||
local x = x0
|
||||
for _,l in ipairs(p.linkcontext) do if l.level == level+1 then
|
||||
addlinkpoint(p, l, x, y, list)
|
||||
end end
|
||||
for n in node.traverse(list.head) do
|
||||
local next = n.next
|
||||
local w = next and node.rangedimensions(list, n, next) or node.rangedimensions(list, n)
|
||||
nodehandler[n.id](p, n, x, y, list, x0, level+1)
|
||||
x = w + x
|
||||
end
|
||||
for _,l in ipairs(p.linkcontext) do if l.level == level+1 then
|
||||
addlinkpoint(p, l, x, y, list)
|
||||
end end
|
||||
end
|
||||
function nodehandler.vlist(p, list, x, y0, outerlist, origin, level)
|
||||
if outerlist then
|
||||
if outerlist.id == 0 then
|
||||
y0 = y0 - list.shift
|
||||
else
|
||||
x = x + list.shift
|
||||
end
|
||||
end
|
||||
y0 = y0 + list.height
|
||||
local y = y0
|
||||
for n in node.traverse(list.head) do
|
||||
local d, h, _ = 0, node.effective_glue(n, list) or math.tointeger(n.kern)
|
||||
if not h then
|
||||
_, h, d = node.direct.getwhd(node.direct.todirect(n))
|
||||
end
|
||||
y = y - (h or 0)
|
||||
nodehandler[n.id](p, n, x, y, list, y0, level+1)
|
||||
y = y - (d or 0)
|
||||
end
|
||||
end
|
||||
function nodehandler.rule(p, n, x, y, outer)
|
||||
if n.width == -1073741824 then n.width = outer.width end
|
||||
if n.height == -1073741824 then n.height = outer.height end
|
||||
if n.depth == -1073741824 then n.depth = outer.depth end
|
||||
local sub = n.subtype
|
||||
if sub == 1 then
|
||||
error[[We can't handle boxes yet]]
|
||||
elseif sub == 2 then
|
||||
error[[We can't handle images yet]]
|
||||
elseif sub == 3 then
|
||||
elseif sub == 4 then
|
||||
error[[We can't handle user rules yet]]
|
||||
elseif sub == 9 then
|
||||
error[[We can't handle outline rules yet]]
|
||||
else
|
||||
if n.width <= 0 or n.depth + n.height <= 0 then return end
|
||||
topage(p)
|
||||
p.strings[#p.strings+1] = gsub(format("%f %f %f %f re f", sp2bp(x), sp2bp(y - n.depth), sp2bp(n.width), sp2bp(n.depth + n.height)), '%.?0+ ', ' ')
|
||||
end
|
||||
end
|
||||
function nodehandler.boundary() end
|
||||
function nodehandler.disc(p, n, x, y, list, ...) -- FIXME: I am not sure why this can happen, let's assume we can use .replace
|
||||
for n in node.traverse(n.replace) do
|
||||
local next = n.next
|
||||
local w = next and node.rangedimensions(list, n, next) or node.rangedimensions(list, n)
|
||||
nodehandler[n.id](p, n, x, y, list, ...)
|
||||
x = w + x
|
||||
end
|
||||
end
|
||||
function nodehandler.local_par() end
|
||||
function nodehandler.math() end
|
||||
function nodehandler.glue(p, n, x, y, outer, origin, level) -- Naturally this is an interesting one.
|
||||
local subtype = n.subtype
|
||||
if subtype < 100 then return end -- We only really care about leaders
|
||||
local leader = n.leader
|
||||
local w = node.effective_glue(n, outer)
|
||||
if leader.id == 2 then -- We got a rule, this should be easy
|
||||
if outer.id == 0 then
|
||||
leader.width = w
|
||||
else
|
||||
leader.height = w
|
||||
leader.depth = 0
|
||||
end
|
||||
return nodehandler.rule(p, leader, x, y, outer)
|
||||
end
|
||||
local lwidth = outer.id == 0 and leader.width or leader.height + leader.depth
|
||||
if outer.id ~= 0 then
|
||||
y = y + w
|
||||
end
|
||||
if subtype == 100 then
|
||||
if outer.id == 0 then
|
||||
local newx = ((x-origin - 1)//lwidth + 1) * lwidth + origin
|
||||
-- local newx = -(origin-x)//lwidth * lwidth + origin
|
||||
w = w + x - newx
|
||||
x = newx
|
||||
else
|
||||
-- local newy = -(origin-y)//lwidth * lwidth + origin
|
||||
local newy = (y-origin)//lwidth * lwidth + origin
|
||||
w = w + newy - y
|
||||
y = newy
|
||||
end
|
||||
elseif subtype == 101 then
|
||||
local inner = w - (w // lwidth) * lwidth
|
||||
if outer.id == 0 then
|
||||
x = x + inner/2
|
||||
else
|
||||
y = y - inner/2
|
||||
end
|
||||
elseif subtype == 102 then
|
||||
local count = w // lwidth
|
||||
local skip = (w - count * lwidth) / (count + 1)
|
||||
if outer.id == 0 then
|
||||
x = x + skip
|
||||
else
|
||||
y = y - skip
|
||||
end
|
||||
lwidth = lwidth + skip
|
||||
elseif subtype == 103 then
|
||||
if outer.id == 0 then
|
||||
local newx = ((x - 1)//lwidth + 1) * lwidth
|
||||
w = w + x - newx
|
||||
x = newx
|
||||
else
|
||||
local newy = y//lwidth * lwidth
|
||||
w = w + newy - y
|
||||
y = newy
|
||||
end
|
||||
end
|
||||
local handler = nodehandler[leader.id]
|
||||
if outer.id == 0 then
|
||||
while w >= lwidth do
|
||||
handler(p, leader, x, y, outer, origin, level+1)
|
||||
w = w - lwidth
|
||||
x = x + lwidth
|
||||
end
|
||||
else
|
||||
y = y - leader.height
|
||||
while w >= lwidth do
|
||||
handler(p, leader, x, y, outer, origin, level+1)
|
||||
w = w - lwidth
|
||||
y = y - lwidth
|
||||
end
|
||||
end
|
||||
end
|
||||
function nodehandler.kern() end
|
||||
function nodehandler.penalty() end
|
||||
local literalescape = lpeg.Cs((lpeg.S'\\()\r'/{['\\'] = '\\\\', ['('] = '\\(', [')'] = '\\)', ['\r'] = '\\r'}+1)^0)
|
||||
local match = lpeg.match
|
||||
local function do_commands(p, c, f, fid, x, y, outer, ...)
|
||||
local fonts = f.fonts
|
||||
local stack, current_font = {}, fonts[1]
|
||||
for _, cmd in ipairs(c.commands) do
|
||||
if cmd[1] == "node" then
|
||||
local cmd = cmd[2]
|
||||
nodehandler[cmd.id](p, cmd, x, y, nil, ...)
|
||||
x = x + cmd.width
|
||||
elseif cmd[1] == "font" then
|
||||
current_font = fonts[cmd[2]]
|
||||
elseif cmd[1] == "char" then
|
||||
local n = node.new'glyph'
|
||||
n.subtype, n.font, n.char = 256, current_font.id, cmd[2]
|
||||
nodehandler.glyph(p, n, x, y, outer, ...)
|
||||
node.free(n)
|
||||
x = x + n.width
|
||||
elseif cmd[1] == "slot" then
|
||||
local n = node.new'glyph'
|
||||
n.subtype, n.font, n.char = 256, cmd[2], cmd[3]
|
||||
nodehandler.glyph(p, n, x, y, outer, ...)
|
||||
node.free(n)
|
||||
x = x + n.width
|
||||
elseif cmd[1] == "rule" then
|
||||
local n = node.new'rule'
|
||||
n.height, n.width = cmd[2], cmd[3]
|
||||
nodehandler.rule(p, n, x, y, outer, ...)
|
||||
node.free(n)
|
||||
x = x + n.width
|
||||
elseif cmd[1] == "left" then
|
||||
x = x + cmd[2]
|
||||
elseif cmd[1] == "down" then
|
||||
y = y + cmd[2]
|
||||
elseif cmd[1] == "push" then
|
||||
stack[#stack + 1] = {x, y}
|
||||
elseif cmd[1] == "pop" then
|
||||
local top = stack[#stack]
|
||||
stack[#stack] = nil
|
||||
x, y = top[1], top[2]
|
||||
elseif cmd[1] == "special" then
|
||||
-- ???
|
||||
elseif cmd[1] == "pdf" then
|
||||
-- ???
|
||||
elseif cmd[1] == "lua" then
|
||||
cmd = cmd[2]
|
||||
if type(cmd) == "string" then cmd = load(cmd) end
|
||||
assert(type(cmd) == "function")
|
||||
elseif cmd[1] == "image" then
|
||||
-- ???
|
||||
-- else
|
||||
-- NOP, comment and invalid commands ignored
|
||||
end
|
||||
if #commands ~= 1 then error[[Unsupported command number]] end
|
||||
if commands[1][1] ~= "node" then error[[Unsupported command name]] end
|
||||
commands = commands[1][2]
|
||||
nodehandler[commands.id](p, commands, x, y, nil, ...)
|
||||
end
|
||||
end
|
||||
function nodehandler.glyph(p, n, x, y, ...)
|
||||
if n.font ~= p.vfont.fid then
|
||||
p.vfont.fid = n.font
|
||||
p.vfont.font = font.getfont(n.font) or font.fonts[n.font]
|
||||
end
|
||||
local f, fid = p.vfont.font, p.vfont.fid
|
||||
local c = f.characters[n.char]
|
||||
if not c then
|
||||
error[[Invalid characters]]
|
||||
end
|
||||
if c.commands then return do_commands(p, c, f, fid, x, y, ...) end
|
||||
toglyph(p, n.font, x + n.xoffset, y + n.yoffset, n.expansion_factor)
|
||||
local index = c.index
|
||||
if index then
|
||||
-- if f.encodingbytes == -3 then
|
||||
if true then
|
||||
if index < 0x80 then
|
||||
p.pending[#p.pending+1] = match(literalescape, string.pack('>B', index))
|
||||
elseif index < 0x7F80 then
|
||||
p.pending[#p.pending+1] = match(literalescape, string.pack('>H', index+0x7F80))
|
||||
else
|
||||
p.pending[#p.pending+1] = match(literalescape, string.pack('>BH', 0xFF, index-0x7F80))
|
||||
end
|
||||
else
|
||||
p.pending[#p.pending+1] = match(literalescape, string.pack('>H', index))
|
||||
end
|
||||
if not p.usedglyphs[index] then
|
||||
p.usedglyphs[index] = {index, math.floor(c.width * 1000 / f.size + .5), c.tounicode}
|
||||
end
|
||||
else
|
||||
p.pending[#p.pending+1] = match(literalescape, string.char(n.char))
|
||||
if not p.usedglyphs[n.char] then
|
||||
p.usedglyphs[n.char] = {n.char, math.floor(c.width * 1000 / f.size + .5), c.tounicode}
|
||||
end
|
||||
end
|
||||
p.pos.x = p.pos.x + math.floor(n.width*(1+n.expansion_factor/1000000)+.5)
|
||||
end
|
||||
function nodehandler.whatsit(p, n, ...) -- Whatsit?
|
||||
return whatsithandler[n.subtype](p, n, ...)
|
||||
end
|
||||
function whatsithandler.pdf_literal(p, n, x, y)
|
||||
if n.mode == 2 then
|
||||
topage(p)
|
||||
p.strings[#p.strings + 1] = n.data
|
||||
elseif n.mode == 0 then
|
||||
write_matrix(p, 1, 0, 0, 1, x, y)
|
||||
topage(p)
|
||||
p.strings[#p.strings + 1] = n.data
|
||||
write_matrix(p, 1, 0, 0, 1, -x, -y)
|
||||
else
|
||||
write(format('Literal type %i', n.mode))
|
||||
end
|
||||
end
|
||||
function whatsithandler.pdf_save(p, n, x, y, outer)
|
||||
topage(p)
|
||||
p.strings[#p.strings + 1] = 'q'
|
||||
pdfsave(p, x, y)
|
||||
end
|
||||
function whatsithandler.pdf_restore(p, n, x, y, outer)
|
||||
topage(p)
|
||||
p.strings[#p.strings + 1] = 'Q'
|
||||
pdfrestore(p, x, y)
|
||||
end
|
||||
local numberpattern = (lpeg.R'09'^0 * ('.' * lpeg.R'09'^0)^-1)/tonumber
|
||||
local matrixpattern = numberpattern * ' ' * numberpattern * ' ' * numberpattern * ' ' * numberpattern
|
||||
function whatsithandler.pdf_setmatrix(p, n, x, y, outer)
|
||||
local a, b, c, d = matrixpattern:match(n.data)
|
||||
local e, f = (1-a)*x-c*y, (1-d)*y-b*x
|
||||
write_matrix(p, a, b, c, d, e, f)
|
||||
end
|
||||
function whatsithandler.pdf_start_link(p, n, x, y, outer, _, level)
|
||||
local links = p.linkcontext
|
||||
local link = {quads = {}, attr = n.link_attr, action = n.action, level = level, force_separate = false} -- force_separate should become an option
|
||||
links[#links+1] = link
|
||||
addlinkpoint(p, link, x, y, outer)
|
||||
end
|
||||
function whatsithandler.pdf_end_link(p, n, x, y, outer, _, level)
|
||||
local links = p.linkcontext
|
||||
local link = links[#links]
|
||||
links[#links] = nil
|
||||
if link.level ~= level then error[[Wrong link level]] end
|
||||
addlinkpoint(p, link, x, y, outer, true)
|
||||
end
|
||||
local ondemandmeta = {
|
||||
__index = function(t, k)
|
||||
t[k] = {}
|
||||
return t[k]
|
||||
end
|
||||
}
|
||||
local function writeresources(p)
|
||||
local resources = p.resources
|
||||
local result = {"<<"}
|
||||
for kind, t in pairs(resources) do if next(t) then
|
||||
result[#result+1] = format("/%s<<", kind)
|
||||
for name, value in pairs(t) do
|
||||
result[#result+1] = format("/%s %i 0 R", name, value)
|
||||
t[name] = nil
|
||||
end
|
||||
result[#result+1] = ">>"
|
||||
end end
|
||||
result[#result+1] = ">>"
|
||||
return concat(result)
|
||||
end
|
||||
local fontnames = setmetatable({}, {__index = function(t, k) local res = format("F%i", k) t[k] = res return res end})
|
||||
return function(file, n, fontdirs, usedglyphs)
|
||||
setmetatable(usedglyphs, ondemandmeta)
|
||||
local linkcontext = file.linkcontext
|
||||
if not linkcontext then
|
||||
linkcontext = {}
|
||||
file.linkcontext = linkcontext
|
||||
end
|
||||
local p = {
|
||||
file = file,
|
||||
mode = 3,
|
||||
strings = {},
|
||||
pending = {},
|
||||
pos = {},
|
||||
fontprovider = function(p, f, fid)
|
||||
if not f.parent then f.parent = pdf.getfontname(fid) end
|
||||
p.resources.Font[fontnames[f.parent]] = fontdirs[f.parent]
|
||||
p.strings[#p.strings+1] = format("/F%i %f Tf 0 Tr", f.parent, sp2bp(f.size)) -- TODO: Setting the mode, expansion, etc.
|
||||
p.usedglyphs = usedglyphs[f.parent]
|
||||
end,
|
||||
font = {},
|
||||
vfont = {},
|
||||
matrix = {1, 0, 0, 1, 0, 0},
|
||||
pending_matrix = {},
|
||||
resources = setmetatable({}, ondemandmeta),
|
||||
annots = {},
|
||||
linkcontext = file.linkcontext,
|
||||
}
|
||||
nodehandler[n.id](p, n, 0, 0, n, nil, 0)
|
||||
-- nodehandler[n.id](p, n, 0, n.depth, n)
|
||||
topage(p)
|
||||
return concat(p.strings, '\n'), writeresources(p), (p.annots[1] and string.format("/Annots[%s]", table.concat(p.annots, ' ')) or "")
|
||||
end
|
601
luametalatex-pdf-font-cff.lua
Normal file
601
luametalatex-pdf-font-cff.lua
Normal file
@ -0,0 +1,601 @@
|
||||
local sfnt = require'luametalatex-font-sfnt'
|
||||
local stdStrings = require'luametalatex-font-cff-data'
|
||||
local offsetfmt = ">I%i"
|
||||
local function parse_index(buf, i)
|
||||
local count, offsize
|
||||
count, offsize, i = string.unpack(">I2B", buf, i)
|
||||
if count == 0 then return {}, i-1 end
|
||||
local fmt = offsetfmt:format(offsize)
|
||||
local offsets = {}
|
||||
local dataoffset = i + offsize*count - 1
|
||||
for j=1,count+1 do
|
||||
offsets[j], i = string.unpack(fmt, buf, i)
|
||||
end
|
||||
for j=1,count+1 do
|
||||
offsets[j] = offsets[j] + i - 1
|
||||
end
|
||||
return offsets, offsets[#offsets]
|
||||
end
|
||||
local real_mapping = { [0] = '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||
'.', 'E', 'E-', nil, '-', nil}
|
||||
local function parse_real(cs, offset)
|
||||
local c = cs:byte(offset)
|
||||
if not c then return offset end
|
||||
local c1, c2 = real_mapping[c>>4], real_mapping[c&0xF]
|
||||
if not c1 or not c2 then
|
||||
return c1 or offset, c1 and offset
|
||||
else
|
||||
return c1, c2, parse_real(cs, offset+1) --Warning: This is not a tail-call,
|
||||
-- so we are affected by the stack limit. On the other hand, as long as
|
||||
-- there are less than ~50 bytes we should be safe.
|
||||
end
|
||||
end
|
||||
local function get_number(result)
|
||||
if #result ~= 1 then
|
||||
print(require'inspect'(result))
|
||||
end
|
||||
assert(#result == 1)
|
||||
local num = result[1]
|
||||
result[1] = nil
|
||||
return num
|
||||
end
|
||||
local function get_bool(result)
|
||||
return get_number(result) == 1
|
||||
end
|
||||
local function get_string(result, strings)
|
||||
local sid = get_number(result)
|
||||
return stdStrings[sid] or strings[sid-#stdStrings]
|
||||
end
|
||||
local function get_array(result)
|
||||
local arr = table.move(result, 1, #result, 1, {})
|
||||
for i=1,#result do result[i] = nil end
|
||||
return arr
|
||||
end
|
||||
local function get_delta(result)
|
||||
local arr = get_array(result)
|
||||
local last = 0
|
||||
for i=1,#arr do
|
||||
arr[i] = arr[i]+last
|
||||
last = arr[i]
|
||||
end
|
||||
return arr
|
||||
end
|
||||
local function get_private(result)
|
||||
local arr = get_array(result)
|
||||
assert(#arr == 2)
|
||||
return arr
|
||||
end
|
||||
local function get_ros(result, strings)
|
||||
local arr = get_array(result)
|
||||
assert(#arr == 3)
|
||||
result[1] = arr[1] arr[1] = get_string(result, strings)
|
||||
result[1] = arr[2] arr[2] = get_string(result, strings)
|
||||
return arr
|
||||
end
|
||||
local function apply_matrix(m, x, y)
|
||||
return (m[1] * x + m[3] * y + m[5])*1000, (m[2] * x + m[4] * y + m[6])*1000
|
||||
end
|
||||
local operators = {
|
||||
[0] = {'version', get_string},
|
||||
{'Notice', get_string},
|
||||
{'FullName', get_string},
|
||||
{'FamilyName', get_string},
|
||||
{'Weight', get_string},
|
||||
{'FontBBox', get_array},
|
||||
{'BlueValues', get_delta},
|
||||
{'OtherBlues', get_delta},
|
||||
{'FamilyBlues', get_delta},
|
||||
{'FamilyOtherBlues', get_delta},
|
||||
{'StdHW', get_number},
|
||||
{'StdVW', get_number},
|
||||
nil, -- 12, escape
|
||||
{'UniqueID', get_number},
|
||||
{'XUID', get_array},
|
||||
{'charset', get_number},
|
||||
{'Encoding', get_number},
|
||||
{'CharStrings', get_number},
|
||||
{'Private', get_private},
|
||||
{'Subrs', get_number},
|
||||
{'defaultWidthX', get_number},
|
||||
{'nominalWidthX', get_number},
|
||||
[-1] = {'Copyright', get_string},
|
||||
[-2] = {'isFixedPitch', get_bool},
|
||||
[-3] = {'ItalicAngle', get_number},
|
||||
[-4] = {'UnderlinePosition', get_number},
|
||||
[-5] = {'UnderlineThickness', get_number},
|
||||
[-6] = {'PaintType', get_number},
|
||||
[-7] = {'CharstringType', get_number},
|
||||
[-8] = {'FontMatrix', get_array},
|
||||
[-9] = {'StrokeWidth', get_number},
|
||||
[-10] = {'BlueScale', get_number},
|
||||
[-11] = {'BlueShift', get_number},
|
||||
[-12] = {'BlueFuzz', get_number},
|
||||
[-13] = {'StemSnapH', get_delta},
|
||||
[-14] = {'StemSnapV', get_delta},
|
||||
[-15] = {'ForceBold', get_bool},
|
||||
[-18] = {'LanguageGroup', get_number},
|
||||
[-19] = {'ExpansionFactor', get_number},
|
||||
[-20] = {'initialRandomSeed', get_number},
|
||||
[-21] = {'SyntheticBase', get_number},
|
||||
[-22] = {'PostScript', get_string},
|
||||
[-23] = {'BaseFontName', get_string},
|
||||
[-24] = {'BaseFontBlend', get_delta},
|
||||
[-31] = {'ROS', get_ros},
|
||||
[-32] = {'CIDFontVersion', get_number},
|
||||
[-33] = {'CIDFontRevision', get_number},
|
||||
[-34] = {'CIDFontType', get_number},
|
||||
[-35] = {'CIDCount', get_number},
|
||||
[-36] = {'UIDBase', get_number},
|
||||
[-37] = {'FDArray', get_number},
|
||||
[-38] = {'FDSelect', get_number},
|
||||
[-39] = {'FontName', get_string},
|
||||
}
|
||||
local function parse_dict(buf, i, j, strings)
|
||||
result = {}
|
||||
while i<=j do
|
||||
local cmd = buf:byte(i)
|
||||
if cmd == 29 then
|
||||
result[#result+1] = string.unpack(">i4", buf:sub(i+1, i+4))
|
||||
i = i+4
|
||||
elseif cmd == 28 then
|
||||
result[#result+1] = string.unpack(">i2", buf:sub(i+1, i+2))
|
||||
i = i+2
|
||||
elseif cmd >= 251 then -- Actually "and cmd ~= 255", but 255 is reserved
|
||||
result[#result+1] = -((cmd-251)*256)-string.byte(buf, i+1)-108
|
||||
i = i+1
|
||||
elseif cmd >= 247 then
|
||||
result[#result+1] = (cmd-247)*256+string.byte(buf, i+1)+108
|
||||
i = i+1
|
||||
elseif cmd >= 32 then
|
||||
result[#result+1] = cmd-139
|
||||
elseif cmd == 30 then -- 31 is reserved again
|
||||
local real = {parse_real(buf, i+1)}
|
||||
i = real[#real]
|
||||
real[#real] = nil
|
||||
result[#result+1] = tonumber(table.concat(real))
|
||||
else
|
||||
if cmd == 12 then
|
||||
i = i+1
|
||||
cmd = -buf:byte(i)-1
|
||||
end
|
||||
local op = operators[cmd]
|
||||
if not op then error[[Unknown CFF operator]] end
|
||||
result[op[1]] = op[2](result, strings)
|
||||
end
|
||||
i = i+1
|
||||
end
|
||||
return result
|
||||
end
|
||||
local function parse_charstring(cs, globalsubrs, subrs, result)
|
||||
result = result or {{false}, stemcount = 0}
|
||||
local lastresult = result[#result]
|
||||
local i = 1
|
||||
while i~=#cs+1 do
|
||||
local cmd = cs:byte(i)
|
||||
if cmd == 28 then
|
||||
lastresult[#lastresult+1] = string.unpack(">i2", cs:sub(i+1, i+2))
|
||||
i = i+2
|
||||
elseif cmd == 255 then
|
||||
lastresult[#lastresult+1] = string.unpack(">i4", cs:sub(i+1, i+4))/0x10000
|
||||
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 == 10 then
|
||||
local idx = lastresult[#lastresult]+subrs.bias
|
||||
local subr = subrs[idx]
|
||||
subrs.used[idx] = true
|
||||
lastresult[#lastresult] = nil
|
||||
parse_charstring(subr, globalsubrs, subrs, result)
|
||||
lastresult = result[#result]
|
||||
elseif cmd == 29 then
|
||||
local idx = lastresult[#lastresult]+globalsubrs.bias
|
||||
local subr = globalsubrs[idx]
|
||||
globalsubrs.used[idx] = true
|
||||
lastresult[#lastresult] = nil
|
||||
parse_charstring(subr, globalsubrs, 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)
|
||||
lastresult[1] = -cmd-1
|
||||
lastresult = {false}
|
||||
result[#result+1] = lastresult
|
||||
elseif cmd == 19 or cmd == 20 then
|
||||
if #result == 1 then
|
||||
lastresult = {}
|
||||
result[#result+1] = lastresult
|
||||
end
|
||||
lastresult[1] = cmd
|
||||
local newi = i+(result.stemcount+7)//8
|
||||
lastresult[2] = cs:sub(i+1, newi)
|
||||
i = newi
|
||||
else
|
||||
if cmd == 21 and #result == 1 then
|
||||
table.insert(result, 1, {false})
|
||||
if #lastresult == 4 then
|
||||
result[1][2] = lastresult[2]
|
||||
table.remove(lastresult, 2)
|
||||
end
|
||||
elseif (cmd == 4 or cmd == 22) and #result == 1 then
|
||||
table.insert(result, 1, {false})
|
||||
if #lastresult == 3 then
|
||||
result[1][2] = lastresult[2]
|
||||
table.remove(lastresult, 2)
|
||||
end
|
||||
elseif cmd == 14 and #result == 1 then
|
||||
table.insert(result, 1, {false})
|
||||
if #lastresult == 2 or #lastresult == 6 then
|
||||
result[1][2] = lastresult[2]
|
||||
table.remove(lastresult, 2)
|
||||
end
|
||||
elseif cmd == 1 or cmd == 3 or cmd == 18 or cmd == 23 then
|
||||
if #result == 1 then
|
||||
table.insert(result, 1, {false})
|
||||
if #lastresult % 2 == 0 then
|
||||
result[1][2] = lastresult[2]
|
||||
table.remove(lastresult, 2)
|
||||
end
|
||||
end
|
||||
result.stemcount = result.stemcount + #lastresult//2
|
||||
end
|
||||
lastresult[1] = cmd
|
||||
lastresult = {false}
|
||||
result[#result+1] = lastresult
|
||||
end
|
||||
i = i+1
|
||||
end
|
||||
return result
|
||||
end
|
||||
local function parse_charset(buf, i0, offset, strings, num)
|
||||
if not offset then offset = 0 end
|
||||
if offset == 0 then
|
||||
return ISOAdobe
|
||||
elseif offset == 1 then
|
||||
return Expert
|
||||
elseif offset == 2 then
|
||||
return ExpertSubset
|
||||
else offset = i0+offset end
|
||||
local format
|
||||
format, offset = string.unpack(">B", buf, offset)
|
||||
local charset = {[0] = 0}
|
||||
if format == 0 then
|
||||
for i=1,num-1 do
|
||||
charset[i], offset = string.unpack(">I2", buf, offset)
|
||||
end
|
||||
elseif format == 1 then
|
||||
local i = 1
|
||||
while i < num do
|
||||
local first, nLeft
|
||||
first, nLeft, offset = string.unpack(">I2I1", buf, offset)
|
||||
for j=0,nLeft do
|
||||
charset[i+j] = first+j
|
||||
end
|
||||
i = i+1+nLeft
|
||||
end
|
||||
elseif format == 2 then
|
||||
local i = 1
|
||||
while i < num do
|
||||
local first, nLeft
|
||||
first, nLeft, offset = string.unpack(">I2I2", buf, offset)
|
||||
for j=0,nLeft do
|
||||
charset[i+j] = first+j
|
||||
end
|
||||
i = i+1+nLeft
|
||||
end
|
||||
else
|
||||
error[[Invalid Charset format]]
|
||||
end
|
||||
if strings then -- We are not CID-keyed, so we should use strings instead of numbers
|
||||
local string_charset = {}
|
||||
for i=#charset,0,-1 do
|
||||
local sid = charset[i]
|
||||
charset[i] = nil
|
||||
string_charset[i] = stdStrings[sid] or strings[sid-#stdStrings]
|
||||
end
|
||||
charset = string_charset
|
||||
end
|
||||
return charset
|
||||
end
|
||||
local function parse_encoding(buf, i0, offset, CharStrings)
|
||||
if not offset then offset = 0 end
|
||||
if offset == 0 then
|
||||
error[[TODO]]
|
||||
return "StandardEncoding"
|
||||
elseif offset == 1 then
|
||||
error[[TODO]]
|
||||
return "ExpertEncoding"
|
||||
else offset = i0+offset end
|
||||
local format, num
|
||||
format, num, offset = string.unpack(">BB", buf, offset)
|
||||
local encoding = {}
|
||||
if format == 0 then
|
||||
for i=1,num do
|
||||
local code
|
||||
code, offset = string.unpack(">B", buf, offset)
|
||||
encoding[code] = CharStrings[i]
|
||||
end
|
||||
elseif format == 1 then
|
||||
local i = 1
|
||||
while i <= num do
|
||||
local first, nLeft
|
||||
first, nLeft, offset = string.unpack(">BB", buf, offset)
|
||||
for j=0,nLeft do
|
||||
encoding[first + j] = CharStrings[i + j]
|
||||
end
|
||||
i = i+1+nLeft
|
||||
end
|
||||
else
|
||||
error[[Invalid Encoding format]]
|
||||
end
|
||||
return encoding
|
||||
end
|
||||
local function parse_fdselect(buf, offset, CharStrings)
|
||||
local format
|
||||
format, offset = string.unpack(">B", buf, offset)
|
||||
if format == 0 then
|
||||
for i=1,#CharStrings-1 do
|
||||
local code
|
||||
code, offset = string.unpack(">B", buf, offset)
|
||||
CharStrings[i][3] = code + 1
|
||||
end -- Reimplement with string.byte
|
||||
elseif format == 3 then
|
||||
local count, last
|
||||
count, offset = string.unpack(">I2", buf, offset)
|
||||
for i=1,count do
|
||||
local first, code, after = string.unpack(">I2BI2", buf, offset)
|
||||
for j=first, after-1 do
|
||||
CharStrings[j][3] = code + 1
|
||||
end
|
||||
offset = offset + 3
|
||||
end
|
||||
else
|
||||
error[[Invalid FDSelect format]]
|
||||
end
|
||||
end
|
||||
local function applyencoding(buf, i, usedcids, encoding)
|
||||
local usednames = {}
|
||||
local numglyphs
|
||||
numglyphs, i = string.unpack(">I2", buf, i)
|
||||
local stroffset = 2*numglyphs + i
|
||||
local names = setmetatable({}, {__index = function(t, i)
|
||||
for j=#t+1,i do
|
||||
t[j], stroffset = string.unpack("s1", buf, stroffset)
|
||||
end
|
||||
return t[i]
|
||||
end})
|
||||
local newusedcids = {}
|
||||
for j=1,#usedcids do
|
||||
local name = encoding[usedcids[j][1]]
|
||||
if name then
|
||||
local new = {old = usedcids[j]}
|
||||
usednames[name], newusedcids[j] = new, new
|
||||
else
|
||||
newusedcids[j] = {j} -- FIXME: Someone used a character which does not exists in the encoding.
|
||||
-- This should probably at least trigger a warning.
|
||||
end
|
||||
end
|
||||
for j=1,numglyphs do
|
||||
local name
|
||||
name, i = string.unpack(">I2", buf, i)
|
||||
if name < 258 then
|
||||
name = stdnames[name]
|
||||
else
|
||||
name = names[name-257]
|
||||
end
|
||||
if usednames[name] then
|
||||
usednames[name][1] = j-1
|
||||
usednames[name] = nil
|
||||
end
|
||||
end
|
||||
if next(usednames) then
|
||||
error[[Missing character]]
|
||||
end
|
||||
return newusedcids
|
||||
end
|
||||
-- The encoding parameter might be:
|
||||
-- an encoding dictionary - Use the supplied encoding
|
||||
-- true - Use the build-in encoding
|
||||
-- false - Use GIDs
|
||||
-- nil - Use CIDs, falling back to GIDs in name.based fonts
|
||||
function myfunc(buf, i0, fontid, usedcids, encoding, trust_widths)
|
||||
-- return function(filename, fontid)
|
||||
fontid = fontid or 1
|
||||
local major, minor, hdrSize, offSize = string.unpack(">BBBB", buf, i0)
|
||||
if major ~= 1 then error[[Unsupported CFF version]] end
|
||||
-- local offfmt = offsetfmt:format(offSize)
|
||||
local nameoffsets, topoffsets, stringoffsets, globalsubrs
|
||||
local i = i0+hdrSize
|
||||
nameoffsets, i = parse_index(buf, i)
|
||||
topoffsets, i = parse_index(buf, i)
|
||||
stringoffsets, i = parse_index(buf, i)
|
||||
globalsubrs, i = parse_index(buf, i)
|
||||
local strings = {}
|
||||
for j=1,#stringoffsets-1 do
|
||||
strings[j] = buf:sub(stringoffsets[j], stringoffsets[j+1]-1)
|
||||
end
|
||||
if #nameoffsets ~= #topoffsets then error[[Inconsistant size of FontSet]] end
|
||||
if fontid >= #nameoffsets then error[[Invalid font id]] end
|
||||
local top = parse_dict(buf, topoffsets[fontid], topoffsets[fontid+1]-1, strings)
|
||||
top.FontName = buf:sub(nameoffsets[fontid], nameoffsets[fontid+1]-1)
|
||||
local gsubrsdict = {}
|
||||
for i=1,#globalsubrs-1 do
|
||||
gsubrsdict[i] = buf:sub(globalsubrs[i], globalsubrs[i+1]-1)
|
||||
end
|
||||
gsubrsdict.used = {}
|
||||
gsubrsdict.bias = #gsubrsdict < 1240 and 108 or #gsubrsdict < 33900 and 1132 or 32769
|
||||
top.GlobalSubrs = gsubrsdict
|
||||
local CharStrings = parse_index(buf, i0+top.CharStrings)
|
||||
if not not encoding ~= encoding and (encoding or top.ROS) then -- If we use the build-in encoding *or* GIDs, we do not need to waste our time making sense of the charset
|
||||
local charset = parse_charset(buf, i0, top.charset, not top.ROS and strings, #CharStrings-1)
|
||||
named_charstrings = {}
|
||||
for i=1,#CharStrings-1 do
|
||||
named_charstrings[charset[i-1]] = {CharStrings[i], CharStrings[i+1]-1}
|
||||
end
|
||||
CharStrings = named_charstrings
|
||||
else
|
||||
for i=1,#CharStrings-1 do
|
||||
CharStrings[i-1] = {CharStrings[i], CharStrings[i+1]-1}
|
||||
end
|
||||
CharStrings[#CharStrings] = nil
|
||||
CharStrings[#CharStrings] = nil
|
||||
end
|
||||
-- top.CharStrings = named_charstrings
|
||||
if not top.ROS then
|
||||
-- if encoding == true and top.Encoding < 3 then
|
||||
-- if not reencode and parsed_t1.Encoding == "StandardEncoding" then
|
||||
-- reencode = kpse.find_file("8a.enc", "enc files")
|
||||
-- end
|
||||
-- end
|
||||
if encoding == true then -- Use the built-in encoding
|
||||
CharStrings = parse_encoding(buf, i0, top.Encoding, CharStrings)
|
||||
elseif encoding then
|
||||
encoding = require'parseEnc'(encoding)
|
||||
local encoded = {}
|
||||
for i, n in pairs(encoding) do
|
||||
encoded[i] = CharStrings[n]
|
||||
end
|
||||
CharStrings = encoded
|
||||
end -- else: Use GIDs
|
||||
top.Privates = {parse_dict(buf, i0+top.Private[2], i0+top.Private[2]+top.Private[1]-1, strings)}
|
||||
local subrs = top.Privates[1].Subrs
|
||||
if subrs then
|
||||
subrs = parse_index(buf, i0+top.Private[2]+subrs)
|
||||
local subrsdict ={}
|
||||
for j=1,#subrs-1 do
|
||||
subrsdict[j] = buf:sub(subrs[j], subrs[j+1]-1)
|
||||
end
|
||||
subrsdict.used = {}
|
||||
subrsdict.bias = #subrsdict < 1240 and 108 or #subrsdict < 33900 and 1132 or 32769
|
||||
top.Privates[1].Subrs = subrsdict
|
||||
end
|
||||
top.Private = nil
|
||||
else
|
||||
assert(not encoding) -- FIXME: If we actually get these from OpenType, the glyph names might be hidden there...
|
||||
-- Would that even be allowed?
|
||||
local fonts = parse_index(buf, i0+top.FDArray)
|
||||
local privates = {}
|
||||
top.Privates = privates
|
||||
for i=1,#fonts-1 do
|
||||
local font = fonts[i]
|
||||
local fontdir = parse_dict(buf, fonts[i], fonts[i+1]-1, strings)
|
||||
privates[i] = parse_dict(buf, i0+fontdir.Private[2], i0+fontdir.Private[2]+fontdir.Private[1]-1, strings)
|
||||
privates[i].FontName = fontdir.FontName
|
||||
local subrs = privates[i].Subrs
|
||||
if subrs then
|
||||
subrs = parse_index(buf, i0+fontdir.Private[2]+subrs)
|
||||
local subrsdict ={}
|
||||
for j=1,#subrs-1 do
|
||||
subrsdict[j] = buf:sub(subrs[j], subrs[j+1]-1)
|
||||
end
|
||||
subrsdict.used = {}
|
||||
subrsdict.bias = #subrsdict < 1240 and 108 or #subrsdict < 33900 and 1132 or 32769
|
||||
privates[i].Subrs = subrsdict
|
||||
end
|
||||
end
|
||||
top.FDArray = nil
|
||||
parse_fdselect(buf, i0+top.FDSelect, CharStrings)
|
||||
end
|
||||
local glyphs = {}
|
||||
-- if false and usedcids then -- Subsetting FIXME: Disabled, because other tables have to be fixed up first
|
||||
if usedcids then -- Subsetting FIXME: Should be Disabled, because other tables have to be fixed up first -- Actually seems to work now, let's test it a bit more
|
||||
local usedfonts = {}
|
||||
for i=1,#usedcids do
|
||||
local cid = usedcids[i][1]
|
||||
local cs = CharStrings[cid]
|
||||
glyphs[i] = {cs = buf:sub(cs[1], cs[2]), index = cid, cidfont = cs[3], usedcid = usedcids[i]}
|
||||
usedfonts[CharStrings[cid][3] or 1] = true
|
||||
end
|
||||
local lastfont = 0
|
||||
for i=1,#top.Privates do
|
||||
if usedfonts[i] then
|
||||
lastfont = lastfont + 1
|
||||
usedfonts[i] = lastfont
|
||||
top.Privates[lastfont] = top.Privates[i]
|
||||
end
|
||||
end
|
||||
for i=lastfont+1,#top.Privates do
|
||||
top.Privates[i] = nil
|
||||
end
|
||||
for i=1,#glyphs do
|
||||
glyphs[i].cidfont = usedfonts[glyphs[i].cidfont]
|
||||
end
|
||||
-- TODO: CIDFont / Privates subsetting... DONE(?)
|
||||
-- TODO: Subrs subsetting... Instead of deleting unused SubRs, we only make them empty.
|
||||
-- This avoids problems with renumberings whiuch would have to be consitant across
|
||||
-- Fonts in some odd way, because they might be used by globalsubrs.
|
||||
for i=1,#glyphs do
|
||||
local g = glyphs[i]
|
||||
local private = top.Privates[g.cidfont or 1]
|
||||
local parsed = parse_charstring(g.cs, top.GlobalSubrs, private.Subrs) -- TODO: Implement
|
||||
local width = parsed[1][2]
|
||||
if width then
|
||||
width = width + (private.nominalWidthX or 0)
|
||||
else
|
||||
width = private.defaultWidthX or 0
|
||||
end
|
||||
local m = top.FontMatrix or {.001, 0, 0, .001, 0, 0}
|
||||
width = width * m[1] + m[3] -- I really have no idea why m[3] /= 0 might happen, but why not?
|
||||
width = math.floor(width*1000+.5) -- Thats rescale into "PDF glyph space"
|
||||
if g.usedcid[2] ~= width then print("MISMATCH:", g.usedcid[1], g.usedcid[2], width) end
|
||||
g.usedcid[2] = width
|
||||
end
|
||||
for i=1,#top.GlobalSubrs do
|
||||
if not top.GlobalSubrs.used[i] then
|
||||
top.GlobalSubrs[i] = ""
|
||||
end
|
||||
end
|
||||
for _, priv in ipairs(top.Privates) do if priv.Subrs then
|
||||
for i=1,#priv.Subrs do
|
||||
if not priv.Subrs.used[i] then
|
||||
priv.Subrs[i] = ""
|
||||
end
|
||||
end
|
||||
end end
|
||||
else
|
||||
for i, cs in pairs(CharStrings) do -- Not subsetting
|
||||
glyphs[#glyphs+1] = {cs = buf:sub(cs[1], cs[2]), index = i, cidfont = cs.font}
|
||||
end
|
||||
end
|
||||
top.glyphs = glyphs
|
||||
table.sort(glyphs, function(a,b)return a.index<b.index end)
|
||||
local bbox
|
||||
if top.FontMatrix then
|
||||
local x0, y0 = apply_matrix(top.FontMatrix, top.FontBBox[1], top.FontBBox[2])
|
||||
local x1, y1 = apply_matrix(top.FontMatrix, top.FontBBox[3], top.FontBBox[4])
|
||||
bbox = {x0, y0, x1, y1}
|
||||
else
|
||||
bbox = top.FontBBox
|
||||
end
|
||||
return require'luametalatex-font-cff'(top), bbox
|
||||
end
|
||||
-- local file = io.open(arg[1])
|
||||
-- local buf = file:read'a'
|
||||
-- file:close()
|
||||
-- io.open(arg[3], 'w'):write(myfunc(buf, 1, 1, nil, {{3}, {200}, {1000}, {1329}, {1330}, {1331}})):close()
|
||||
return function(filename, encoding) return function(fontdir, usedcids)
|
||||
local file = io.open(filename)
|
||||
local buf = file:read'a'
|
||||
local i = 1
|
||||
file:close()
|
||||
local magic = buf:sub(1, 4)
|
||||
if magic == "ttcf" or magic == "OTTO" then
|
||||
-- assert(not encoding) -- nil or false
|
||||
encoding = encoding or false
|
||||
local magic, tables = sfnt.parse(buf, 1) -- TODO: Interpret widths etc, they might differ from the CFF ones.
|
||||
assert(magic == "OTTO")
|
||||
-- Also CFF2 would be nice to have
|
||||
i = tables['CFF '][1]
|
||||
end
|
||||
local content, bbox = myfunc(buf, i, 1, usedcids, encoding)
|
||||
fontdir.bbox = bbox
|
||||
return content
|
||||
end end
|
||||
-- io.open(arg[3], 'w'):write(myfunc(buf, 1, 1, require'parseEnc'(arg[2]), {{string.byte'a'}, {string.byte'b'}, {string.byte'-'}})):close()
|
54
luametalatex-pdf-font-cmap1.lua
Normal file
54
luametalatex-pdf-font-cmap1.lua
Normal file
@ -0,0 +1,54 @@
|
||||
local template = [[%%!PS-Adobe-3.0 Resource-CMap
|
||||
%%%%DocumentNeededResources: ProcSet (CIDInit)
|
||||
%%%%IncludeResource: ProcSet (CIDInit)
|
||||
%%%%BeginResource: CMap (TeX-%s-0)
|
||||
%%%%Title: (TeX-%s-0 TeX %s 0)
|
||||
%%%%Version: 1.000
|
||||
%%%%EndComments
|
||||
/CIDInit /ProcSet findresource begin
|
||||
12 dict begin
|
||||
begincmap
|
||||
/CIDSystemInfo
|
||||
<< /Registry (TeX)
|
||||
/Ordering (%s)
|
||||
/Supplement 0
|
||||
>> def
|
||||
/CMapName /TeX-%s-0 def
|
||||
/CMapType 2 def
|
||||
1 begincodespacerange
|
||||
<00> <FF>
|
||||
endcodespacerange
|
||||
]]
|
||||
local separator = [[endbfrange
|
||||
%i beginbfchar
|
||||
]]
|
||||
local betweenchars = [[endbfchar
|
||||
%i beginbfchar
|
||||
]]
|
||||
local trailer = [[endbfchar
|
||||
endcmap
|
||||
CMapName currentdict /CMap defineresource pop
|
||||
end
|
||||
end
|
||||
%%EndResource
|
||||
%%EOF
|
||||
]]
|
||||
return function(f)
|
||||
local name = 'XXXstuffXXX-' .. f.name
|
||||
local text = template:format(name, name, name, name, name)
|
||||
text = text .. "0 beginbfrange\n"
|
||||
local count, chars = 0, ""
|
||||
local next_head = separator
|
||||
for u, char in pairs(f.characters) do
|
||||
if char.used and char.tounicode then
|
||||
count = count + 1
|
||||
chars = chars .. ("<%02X> <%s>\n"):format(u, char.tounicode)
|
||||
if count == 100 then
|
||||
text = text .. next_head:format(100) .. chars
|
||||
next_head = betweenchars
|
||||
end
|
||||
end
|
||||
end
|
||||
text = text .. next_head:format(count) .. chars .. trailer
|
||||
return text
|
||||
end
|
54
luametalatex-pdf-font-cmap2.lua
Normal file
54
luametalatex-pdf-font-cmap2.lua
Normal file
@ -0,0 +1,54 @@
|
||||
local template = [[%%!PS-Adobe-3.0 Resource-CMap
|
||||
%%%%DocumentNeededResources: ProcSet (CIDInit)
|
||||
%%%%IncludeResource: ProcSet (CIDInit)
|
||||
%%%%BeginResource: CMap (TeX-%s-0)
|
||||
%%%%Title: (TeX-%s-0 TeX %s 0)
|
||||
%%%%Version: 1.000
|
||||
%%%%EndComments
|
||||
/CIDInit /ProcSet findresource begin
|
||||
12 dict begin
|
||||
begincmap
|
||||
/CIDSystemInfo
|
||||
<< /Registry (TeX)
|
||||
/Ordering (%s)
|
||||
/Supplement 0
|
||||
>> def
|
||||
/CMapName /TeX-%s-0 def
|
||||
/CMapType 2 def
|
||||
1 begincodespacerange
|
||||
<0000> <FFFF>
|
||||
endcodespacerange
|
||||
]]
|
||||
local separator = [[endbfrange
|
||||
%i beginbfchar
|
||||
]]
|
||||
local betweenchars = [[endbfchar
|
||||
%i beginbfchar
|
||||
]]
|
||||
local trailer = [[endbfchar
|
||||
endcmap
|
||||
CMapName currentdict /CMap defineresource pop
|
||||
end
|
||||
end
|
||||
%%EndResource
|
||||
%%EOF
|
||||
]]
|
||||
return function(f, usedcids)
|
||||
local name = 'XXXstuffXXX-' .. f.name
|
||||
local text = template:format(name, name, name, name, name)
|
||||
text = text .. "0 beginbfrange\n"
|
||||
local count, chars = 0, ""
|
||||
local next_head = separator
|
||||
for _, cid in ipairs(usedcids) do
|
||||
if cid[3] then
|
||||
count = count + 1
|
||||
chars = chars .. (type(cid[3]) == 'string' and "<%04X> <%s>\n" or "<%04X> <%04X>\n"):format(cid[1], cid[3])
|
||||
if count == 100 then
|
||||
text = text .. next_head:format(100) .. chars
|
||||
next_head = betweenchars
|
||||
end
|
||||
end
|
||||
end
|
||||
text = text .. next_head:format(count) .. chars .. trailer
|
||||
return text
|
||||
end
|
62
luametalatex-pdf-font-cmap3.lua
Normal file
62
luametalatex-pdf-font-cmap3.lua
Normal file
@ -0,0 +1,62 @@
|
||||
local template = [[%%!PS-Adobe-3.0 Resource-CMap
|
||||
%%%%DocumentNeededResources: ProcSet (CIDInit)
|
||||
%%%%IncludeResource: ProcSet (CIDInit)
|
||||
%%%%BeginResource: CMap (TeX-%s-0)
|
||||
%%%%Title: (TeX-%s-0 TeX %s 0)
|
||||
%%%%Version: 1.000
|
||||
%%%%EndComments
|
||||
/CIDInit /ProcSet findresource begin
|
||||
12 dict begin
|
||||
begincmap
|
||||
/CIDSystemInfo
|
||||
<< /Registry (TeX)
|
||||
/Ordering (%s)
|
||||
/Supplement 0
|
||||
>> def
|
||||
/CMapName /TeX-%s-0 def
|
||||
/CMapType 2 def
|
||||
3 begincodespacerange
|
||||
<00> <7F>
|
||||
<8000> <FEFF>
|
||||
<FF0000> <FF807F>
|
||||
endcodespacerange
|
||||
]]
|
||||
local separator = [[endbfrange
|
||||
%i beginbfchar
|
||||
]]
|
||||
local betweenchars = [[endbfchar
|
||||
%i beginbfchar
|
||||
]]
|
||||
local trailer = [[endbfchar
|
||||
endcmap
|
||||
CMapName currentdict /CMap defineresource pop
|
||||
end
|
||||
end
|
||||
%%EndResource
|
||||
%%EOF
|
||||
]]
|
||||
return function(f, usedcids)
|
||||
local name = 'XXXstuffXXX-' .. f.name
|
||||
local text = template:format(name, name, name, name, name)
|
||||
text = text .. "0 beginbfrange\n"
|
||||
local count, chars = 0, ""
|
||||
local next_head = separator
|
||||
for _, cid in ipairs(usedcids) do
|
||||
if cid[3] then
|
||||
count = count + 1
|
||||
if cid[1] < 0x80 then
|
||||
chars = chars .. (type(cid[3]) == 'string' and "<%02X> <%s>\n" or "<%02X> <%04X>\n"):format(cid[1], cid[3])
|
||||
elseif cid[1] < 0x7F80 then
|
||||
chars = chars .. (type(cid[3]) == 'string' and "<%04X> <%s>\n" or "<%04X> <%04X>\n"):format(cid[1]+0x7F80, cid[3])
|
||||
else
|
||||
chars = chars .. (type(cid[3]) == 'string' and "<%06X> <%s>\n" or "<%06X> <%04X>\n"):format(cid[1]-0x7F80, cid[3])
|
||||
end
|
||||
if count == 100 then
|
||||
text = text .. next_head:format(100) .. chars
|
||||
next_head = betweenchars
|
||||
end
|
||||
end
|
||||
end
|
||||
text = text .. next_head:format(count) .. chars .. trailer
|
||||
return text
|
||||
end
|
12
luametalatex-pdf-font-enc.lua
Normal file
12
luametalatex-pdf-font-enc.lua
Normal file
@ -0,0 +1,12 @@
|
||||
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 name = lpeg.C(regular^1)
|
||||
local lname = '/' * name / 1
|
||||
local namearray = lpeg.Ct('['*white^0*lpeg.Cg(lname*white^0, 0)^-1*(lname*white^0)^0*']')
|
||||
local encfile = white^0*lname*white^0*namearray*white^0*'def'*white^0*-1
|
||||
return function(filename)
|
||||
local file = io.open(filename)
|
||||
local name, encoding = encfile:match(file:read'a')
|
||||
file:close()
|
||||
return encoding, name
|
||||
end
|
49
luametalatex-pdf-font-map.lua
Normal file
49
luametalatex-pdf-font-map.lua
Normal file
@ -0,0 +1,49 @@
|
||||
local purenumber = lpeg.R'09'^1
|
||||
local optoperator = lpeg.C(lpeg.S'+-='^-1)*lpeg.C(lpeg.P(1)^0)
|
||||
local commentchar = lpeg.S' %*;#'+-1
|
||||
local wordpatt = (lpeg.C('"') * lpeg.C((1-lpeg.P'"')^0) * lpeg.P'"'^-1 + lpeg.C(('<' * lpeg.S'<['^-1)^-1) * lpeg.S' \t'^0 * lpeg.C((1-lpeg.S' \t')^0)) * lpeg.S' \t'^0 * lpeg.Cp()
|
||||
local fontmap = {}
|
||||
local function mapline(line, operator)
|
||||
if not operator then
|
||||
operator, line = optoperator:match(line)
|
||||
end
|
||||
if commentchar:match(line) then return end
|
||||
local pos = 1
|
||||
local tfmname, psname, flags, special, enc, font, subset
|
||||
local kind, word
|
||||
while pos ~= #line+1 do
|
||||
kind, word, pos = wordpatt:match(line, pos)
|
||||
if kind == "" then
|
||||
if not tfmname then tfmname = word
|
||||
elseif not psname and not purenumber:match(word) then
|
||||
psname = word
|
||||
elseif purenumber:match(word) then flags = tonumber(word)
|
||||
else
|
||||
error[[Invalid map file line, excessive simple words]]
|
||||
end
|
||||
elseif kind == '"' then
|
||||
special = word
|
||||
else
|
||||
if kind == "<[" or (kind ~= "<<" and word:sub(-4) == ".enc") then
|
||||
enc = word
|
||||
else
|
||||
font = word
|
||||
subset = kind ~= "<<"
|
||||
end
|
||||
end
|
||||
end
|
||||
fontmap[tfmname] = {psname or tfmname, flags or (font and 4 or 0x22), font, enc, special, subset}
|
||||
end
|
||||
local function mapfile(filename, operator)
|
||||
if not operator then
|
||||
operator, filename = optoperator:match(filename)
|
||||
end
|
||||
local file = io.open(kpse.find_file(filename, 'map'))
|
||||
for line in file:lines() do mapline(line, operator) end
|
||||
file:close()
|
||||
end
|
||||
return {
|
||||
mapline = mapline,
|
||||
mapfile = mapfile,
|
||||
fontmap = fontmap
|
||||
}
|
56
luametalatex-pdf-font-t1.lua
Normal file
56
luametalatex-pdf-font-t1.lua
Normal file
@ -0,0 +1,56 @@
|
||||
-- nodefont = true
|
||||
-- Some helpers:
|
||||
-- A kpse wrapper
|
||||
local serialize_cff = require'luametalatex-font-cff'
|
||||
local serializet2 = require'luametalatex-font-t2'
|
||||
local parseT1 = require'luametalatex-font-t1'
|
||||
local t1tot2 = require'luametalatex-font-t1tot2'
|
||||
return function(filename, reencode)
|
||||
local parsed_t1 = parseT1(filename)
|
||||
return function(f, usedcids)
|
||||
f.bbox = parsed_t1.FontBBox
|
||||
local fonttable = {
|
||||
version = parsed_t1.FontInfo.version,
|
||||
Notice = parsed_t1.FontInfo.Notice,
|
||||
FullName = parsed_t1.FontInfo.FullName,
|
||||
FamilyName = parsed_t1.FontInfo.FamilyName,
|
||||
Weight = parsed_t1.FontInfo.Weight,
|
||||
ItalicAngle = parsed_t1.FontInfo.ItalicAngle,
|
||||
isFixedPitch = parsed_t1.FontInfo.isFixedPitch,
|
||||
UnderlinePosition = parsed_t1.FontInfo.UnderlinePosition,
|
||||
UnderlineThickness = parsed_t1.FontInfo.UnderlineThickness,
|
||||
FontName = parsed_t1.FontName,
|
||||
FontMatrix = parsed_t1.FontMatrix,
|
||||
FontBBox = parsed_t1.FontBBox,
|
||||
-- UniqueID = parsed_t1.UniqueID,
|
||||
-- ? = parsed_t1.Metrics,
|
||||
---- PRIVATE ----
|
||||
BlueValues = parsed_t1.Private.BlueValues,
|
||||
OtherBlues = parsed_t1.Private.OtherBlues,
|
||||
-- FamilyBlues?
|
||||
BlueScale = parsed_t1.Private.BlueScale,
|
||||
BlueShift = parsed_t1.Private.BlueShift,
|
||||
BlueFuzz = parsed_t1.Private.BlueFuzz,
|
||||
StdHW = (parsed_t1.Private.StdHW or {})[1], -- Why are these arrays in T1?
|
||||
StdVW = (parsed_t1.Private.StdVW or {})[1], -- They are also undocumented in the spec...
|
||||
StemSnapH = parsed_t1.Private.StemSnapH,
|
||||
StemSnapV = parsed_t1.Private.StemSnapV,
|
||||
ForceBold = parsed_t1.Private.ForceBold,
|
||||
-- LanguageGroup = parsed_t1.Private.LanguageGroup,
|
||||
}
|
||||
if not reencode and parsed_t1.Encoding == "StandardEncoding" then
|
||||
reencode = kpse.find_file("8a.enc", "enc files")
|
||||
end
|
||||
if reencode then
|
||||
parsed_t1.Encoding = require'parseEnc'(reencode)
|
||||
end
|
||||
-- parsed_t1.Encoding[0] = ".notdef"
|
||||
local glyphs = {}
|
||||
fonttable.glyphs = glyphs
|
||||
for i=1,#usedcids do
|
||||
local name = parsed_t1.Encoding[usedcids[i][1]] -- TODO: Reencoding and StandardEncoding
|
||||
glyphs[#glyphs + 1] = {index = usedcids[i][1], name = name, cs = serializet2(t1tot2(parsed_t1.CharStrings[name], parsed_t1.Private.Subrs))} -- TODO: Missing glyphs
|
||||
end
|
||||
return serialize_cff(fonttable)
|
||||
end
|
||||
end
|
155
luametalatex-pdf-font-ttf.lua
Normal file
155
luametalatex-pdf-font-ttf.lua
Normal file
@ -0,0 +1,155 @@
|
||||
local sfnt = require'libSfnt'
|
||||
local stdnames = require'ttfstaticstrings'
|
||||
local function round(x)
|
||||
local i, f = math.modf(x)
|
||||
if f < 0 then
|
||||
return i - (f<=-0.5 and 1 or 0)
|
||||
else
|
||||
return i + (f>=-0.5 and 1 or 0)
|
||||
end
|
||||
end
|
||||
local function addglyph(glyph, usedcids, cidtogid)
|
||||
-- FIXME: Pseudocode
|
||||
if string.unpack(">i2", glyph) < 0 then -- We have a composite glyph.
|
||||
-- This is an untested mess. Disaster will follow.
|
||||
local offset = 11
|
||||
while offset do
|
||||
local flags, component = string.unpack(">I2I2", glyph, offset)
|
||||
local gid = cidtogid[component]
|
||||
if not gid then
|
||||
gid = #usedcids+1
|
||||
usedcids[gid] = {component}
|
||||
cidtogid[component] = gid
|
||||
end
|
||||
glyph = glyph:sub(1, offset-1) .. string.pack(">I2", gid).. glyph:sub(offset+2)
|
||||
offset = flags&32==32 and offset + 4 + (flags&1==1 and 4 or 2) + (flags&8==8 and 2 or (flags&64==64 and 4 or (flags&128==128 and 8 or 0)))
|
||||
end
|
||||
end
|
||||
return glyph
|
||||
end
|
||||
local function readpostnames(buf, i, usedcids, encoding)
|
||||
local usednames = {}
|
||||
local numglyphs
|
||||
numglyphs, i = string.unpack(">I2", buf, i)
|
||||
local stroffset = 2*numglyphs + i
|
||||
local names = setmetatable({}, {__index = function(t, i)
|
||||
for j=#t+1,i do
|
||||
t[j], stroffset = string.unpack("s1", buf, stroffset)
|
||||
end
|
||||
return t[i]
|
||||
end})
|
||||
local newusedcids = {}
|
||||
for j=1,#usedcids do
|
||||
local name = encoding[usedcids[j][1]]
|
||||
if name then
|
||||
local new = {}
|
||||
usednames[name], newusedcids[j] = new, new
|
||||
else
|
||||
newusedcids[j] = {j} -- FIXME: Someone used a character which does not exists in the encoding.
|
||||
-- This should probably at least trigger a warning.
|
||||
end
|
||||
end
|
||||
for j=1,numglyphs do
|
||||
local name
|
||||
name, i = string.unpack(">I2", buf, i)
|
||||
if name < 258 then
|
||||
name = stdnames[name]
|
||||
else
|
||||
name = names[name-257]
|
||||
end
|
||||
if usednames[name] then
|
||||
usednames[name][1] = j-1
|
||||
usednames[name] = nil
|
||||
end
|
||||
end
|
||||
if next(usednames) then
|
||||
error[[Missing character]]
|
||||
end
|
||||
return newusedcids
|
||||
end
|
||||
return function(filename, fontid, reencode)
|
||||
local file = io.open(filename)
|
||||
local buf = file:read'a'
|
||||
file:close()
|
||||
local magic, tables = sfnt.parse(buf, 1, fontid)
|
||||
if magic ~= "\0\1\0\0" then error[[Invalid TTF font]] end
|
||||
-- TODO: Parse post table and add reencoding support
|
||||
-- if tables.post and string.unpack(">I4", buf, tables.post[1]) == 0x00020000 and reencode then
|
||||
-- local encoding = require'parseEnc'(reencode)
|
||||
-- if encoding then
|
||||
-- local names = {}
|
||||
-- local off = tables.post[1] + 4
|
||||
-- for i = 1,string.unpack(">I2", buf, tables.maxp[1] + 4) do
|
||||
|
||||
return function(fontdir, usedcids)
|
||||
if reencode and string.unpack(">I4", buf, tables.post[1]) == 0x00020000 then
|
||||
usedcids = readpostnames(buf, tables.post[1] + 32, usedcids, require'parseEnc'(reencode))
|
||||
else
|
||||
usedcids = table.move(usedcids, 1, #usedcids, 1, {})
|
||||
end
|
||||
table.insert(usedcids, 1, {0})
|
||||
local newtables = {}
|
||||
newtables.head = buf:sub(tables.head[1], tables.head[1]+tables.head[2]-1)
|
||||
local bbox1, bbox2, bbox3, bbox4, scale, _
|
||||
scale, _, _, bbox1, bbox2, bbox3, bbox4 = string.unpack(">I2I8I8i2i2i2i2", buf, tables.head[1]+18)
|
||||
scale = 1000/scale
|
||||
fontdir.bbox = {math.floor(bbox1*scale), math.floor(bbox2*scale), math.ceil(bbox3*scale), math.ceil(bbox4*scale)}
|
||||
local cidtogid = {}
|
||||
for i=1,#usedcids do
|
||||
cidtogid[usedcids[i][1]] = i
|
||||
end
|
||||
local loca, glyf, locaOff, glyfOff = {}, {}, tables.loca[1], tables.glyf[1]
|
||||
hmtx = nil
|
||||
if string.unpack(">i2", buf, tables.head[1]+50) == 0 then -- short offsets
|
||||
local s, i = 0, 1
|
||||
while i <= #usedcids do
|
||||
local from, til = string.unpack(">I2I2", buf, locaOff+2*usedcids[i][1])
|
||||
loca[i] = string.pack(">I2", s)
|
||||
s = s+til-from
|
||||
glyf[i] = from ~= til and addglyph(buf:sub(glyfOff+from*2, glyfOff+til*2-1), usedcids, cidtogid) or ""
|
||||
i = i+1
|
||||
end
|
||||
loca[#usedcids+1] = string.pack(">I2", s)
|
||||
else -- long offsets
|
||||
local s, i = 0, 1
|
||||
while i <= #usedcids do
|
||||
local from, til = string.unpack(">I4I4", buf, locaOff+4*usedcids[i][1])
|
||||
loca[i] = string.pack(">I4", s)
|
||||
s = s+til-from
|
||||
glyf[i] = til == from and "" or addglyph(buf:sub(glyfOff+from, glyfOff+til-1), usedcids, cidtogid)
|
||||
i = i+1
|
||||
end
|
||||
loca[#usedcids+1] = string.pack(">I4", s)
|
||||
end
|
||||
newtables.loca = table.concat(loca)
|
||||
loca = nil
|
||||
newtables.glyf = table.concat(glyf)
|
||||
local hmtx = glyf
|
||||
glyf = nil
|
||||
for i = 1,#hmtx do hmtx[i] = nil end
|
||||
assert(tables.hhea[2] == 36)
|
||||
local numhmetrics = string.unpack(">I2", buf, tables.hhea[1]+34)
|
||||
newtables.hhea = buf:sub(tables.hhea[1], tables.hhea[1]+33) .. string.pack(">I2", #usedcids)
|
||||
local off = tables.hmtx[1]
|
||||
local finaladv, off2 = buf:sub(off+(numhmetrics-1)*4, off+numhmetrics*4-3), off+2*numhmetrics
|
||||
for i=1,#usedcids do
|
||||
if usedcids[i][1] < numhmetrics then
|
||||
hmtx[i] = buf:sub(off+usedcids[i][1]*4, off+usedcids[i][1]*4+3)
|
||||
else
|
||||
hmtx[i] = finaladv .. buf:sub(off2+usedcids[i][1]*2, off2+usedcids[i][1]*2+1)
|
||||
end
|
||||
end
|
||||
newtables.hmtx = table.concat(hmtx)
|
||||
newtables.maxp = buf:sub(tables.maxp[1], tables.maxp[1]+3) .. string.pack(">I2", #usedcids) .. buf:sub(tables.maxp[1]+6, tables.maxp[1]+tables.maxp[2]-1)
|
||||
if tables.fpgm then
|
||||
newtables.fpgm = buf:sub(tables.fpgm[1], tables.fpgm[1]+tables.fpgm[2]-1)
|
||||
end
|
||||
if tables.prep then
|
||||
newtables.prep = buf:sub(tables.prep[1], tables.prep[1]+tables.prep[2]-1)
|
||||
end
|
||||
if tables['cvt '] then
|
||||
newtables['cvt '] = buf:sub(tables['cvt '][1], tables['cvt '][1]+tables['cvt '][2]-1)
|
||||
end
|
||||
return sfnt.write(magic, newtables)
|
||||
end
|
||||
end
|
274
luametalatex-pdf-font.lua
Normal file
274
luametalatex-pdf-font.lua
Normal file
@ -0,0 +1,274 @@
|
||||
local mapping = require'luametalatex-pdf-font-map'
|
||||
mapping.mapfile'/usr/local/texlive/2019/texmf-var/fonts/map/pdftex/updmap/pdftex.map'
|
||||
local tounicode = {
|
||||
[-3] = require'luametalatex-pdf-font-cmap3',
|
||||
require'luametalatex-pdf-font-cmap1',
|
||||
require'luametalatex-pdf-font-cmap2',
|
||||
}
|
||||
local function allcids(fontdir)
|
||||
local cids = {}
|
||||
local scale = 1000/fontdir.size
|
||||
for i,v in pairs(fontdir.characters) do
|
||||
v.used = true
|
||||
cids[#cids+1] = {v.index or i, math.floor(v.width*scale+.5), v.tounicode}
|
||||
end
|
||||
return cids
|
||||
end
|
||||
local function buildW(f, usedcids)
|
||||
local used = #usedcids
|
||||
if used == 0 then return "" end
|
||||
local result = {}
|
||||
local index = 1
|
||||
while index <= used do
|
||||
local width = usedcids[index][2]
|
||||
local last = index
|
||||
while last ~= used and usedcids[last+1][2] == width do
|
||||
last = last + 1
|
||||
end
|
||||
if index == last then
|
||||
local span = {width}
|
||||
width = (usedcids[last+1] or {})[2]
|
||||
while (last + 2 <= used
|
||||
and usedcids[last+2][2] ~= width
|
||||
or last + 1 == used)
|
||||
and usedcids[last+1][1]-usedcids[last][1] <= 2 do
|
||||
for i=usedcids[last][1]+1,usedcids[last+1][1]-1 do
|
||||
span[#span+1] = 0
|
||||
end
|
||||
last = last + 1
|
||||
span[#span+1] = width
|
||||
width = (usedcids[last + 1] or {})[2]
|
||||
end
|
||||
result[#result+1] = string.format("%i[%s]", usedcids[index][1], table.concat(span, ' '))
|
||||
else
|
||||
result[#result+1] = string.format("%i %i %i ", usedcids[index][1], usedcids[last][1], width)
|
||||
end
|
||||
index = last + 1
|
||||
end
|
||||
return table.concat(result)
|
||||
end
|
||||
local function fontdescriptor(pdf, basefont, fontdir, stream, kind)
|
||||
local scale = 1000/fontdir.size
|
||||
return string.format(
|
||||
"<</Type/FontDescriptor/FontName/%s/Flags %i/FontBBox[%i %i %i %i]/ItalicAngle %i/Ascent %i/Descent %i/CapHeight %i/StemV %i/FontFile%s %i 0 R>>",
|
||||
basefont,
|
||||
4,-- FIXME: Flags ??? (4 means "symbolic")
|
||||
fontdir.bbox[1], fontdir.bbox[2], fontdir.bbox[3], fontdir.bbox[4], -- FIXME: How to determine BBox?
|
||||
math.floor(math.atan(fontdir.parameters.slant or fontdir.parameters[1] or 0, 0x10000)+.5),
|
||||
fontdir.bbox[4], fontdir.bbox[2],
|
||||
fontdir.parameters[8] and math.floor(fontdir.parameters[8]*scale+0.5) or fontdir.bbox[4],
|
||||
fontdir.StemV or 100, -- FIXME: How to determine StemV?
|
||||
kind, stream)
|
||||
end
|
||||
local function cidmap1byte(pdf)
|
||||
if not pdf.cidmap1byte then
|
||||
pdf.cidmap1byte = string.format(" %i 0 R", pdf:stream(nil, [[/Type/CMap/CMapName/Identity-8-H/CIDSystemInfo<</Registry(Adobe)/Ordering(Identity)/Supplement 0>>]],
|
||||
[[%!PS-Adobe-3.0 Resource-CMap
|
||||
%%DocumentNeededResources : ProcSet (CIDInit)
|
||||
%%IncludeResource : ProcSet (CIDInit)
|
||||
%%BeginResource : CMap (Identity-8-H)
|
||||
%%Title: (Custom 8bit Identity CMap)
|
||||
%%Version: 1.000
|
||||
%%EndComments
|
||||
/CIDInit /ProcSet findresource begin
|
||||
12 dict begin
|
||||
begincmap
|
||||
/CIDSystemInfo
|
||||
3 dict dup begin
|
||||
/Registry (Adobe) def
|
||||
/Ordering (Identity) def
|
||||
/Supplement 0 def
|
||||
end def
|
||||
/CMapName /Identity-8-H def
|
||||
/CMapVersion 1.000 def
|
||||
/CMapType 1 def
|
||||
/WMode 0 def
|
||||
1 begincodespacerange
|
||||
<00> <FF>
|
||||
endcodespacerange
|
||||
1 begincidrange
|
||||
<00> <FF> 0
|
||||
endcidrange
|
||||
endcmap
|
||||
CMapName currentdict /CMap defineresource pop
|
||||
end
|
||||
end
|
||||
%%EndResource
|
||||
%%EOF]]))
|
||||
end
|
||||
return pdf.cidmap1byte
|
||||
end
|
||||
local function cidmap3byte(pdf)
|
||||
if not pdf.cidmap3byte then
|
||||
pdf.cidmap3byte = string.format(" %i 0 R", pdf:stream(nil, [[/Type/CMap/CMapName/Identity-Var-H/CIDSystemInfo<</Registry(Adobe)/Ordering(Identity)/Supplement 0>>]],
|
||||
[[%!PS-Adobe-3.0 Resource-CMap
|
||||
%%DocumentNeededResources : ProcSet (CIDInit)
|
||||
%%IncludeResource : ProcSet (CIDInit)
|
||||
%%BeginResource : CMap (Identity-Var-H)
|
||||
%%Title: (Custom 8-24bit variable size Identity CMap)
|
||||
%%Version: 1.000
|
||||
%%EndComments
|
||||
/CIDInit /ProcSet findresource begin
|
||||
12 dict begin
|
||||
begincmap
|
||||
/CIDSystemInfo
|
||||
3 dict dup begin
|
||||
/Registry (Adobe) def
|
||||
/Ordering (Identity) def
|
||||
/Supplement 0 def
|
||||
end def
|
||||
/CMapName /Identity-Var-H def
|
||||
/CMapVersion 1.000 def
|
||||
/CMapType 1 def
|
||||
/WMode 0 def
|
||||
3 begincodespacerange
|
||||
<FF0000> <FF807F>
|
||||
<00> <7F>
|
||||
<8000> <FEFF>
|
||||
endcodespacerange
|
||||
3 begincidrange
|
||||
<00> <7F> 0
|
||||
<8000> <FEFF> 128
|
||||
<FF0000> <FF807F> 32640
|
||||
endcidrange
|
||||
endcmap
|
||||
CMapName currentdict /CMap defineresource pop
|
||||
end
|
||||
end
|
||||
%%EndResource
|
||||
%%EOF]]))
|
||||
end
|
||||
return pdf.cidmap3byte
|
||||
end
|
||||
local capitals = {string.byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 1, -1)}
|
||||
local function gen_subsettagchar(i, left, ...)
|
||||
if left == 0 then return ... end
|
||||
return gen_subsettagchar(i//#capitals, left-1, capitals[i%#capitals+1], ...)
|
||||
end
|
||||
local function gen_subsettag(ident)
|
||||
local i = string.unpack("j", sha2.digest256(ident))
|
||||
return string.char(gen_subsettagchar(i, 7, 43))
|
||||
end
|
||||
local function buildfont0cff(pdf, fontdir, usedcids)
|
||||
local basefont = fontdir.psname or fontdir.fullname or fontdir.name -- FIXME: Subset-tag(?), Name-Escaping(?), fallback
|
||||
if fontdir.cff then
|
||||
cff = fontdir:cff(usedcids)
|
||||
else
|
||||
if fontdir.filename then
|
||||
if fontdir.format == "type1" then
|
||||
cff = require'luametalatex-pdf-font-t1'(fontdir.filename, fontdir.encoding)(fontdir, usedcids)
|
||||
elseif fontdir.format == "opentype" then
|
||||
cff = require'luametalatex-pdf-font-cff'(fontdir.filename, fontdir.encodingbytes == 1 and (fontdir.encoding or true))(fontdir, usedcids)
|
||||
else
|
||||
error[[Unsupported format]]
|
||||
end
|
||||
else
|
||||
return string.format("<</Type/Font/Subtype/Type1/BaseFont/%s/FontDescriptor %i 0 R/FirstChar %i/LastChar %i>>", basefont, -42, usedcids[1][1], usedcids[#usedcids][1])
|
||||
end
|
||||
end
|
||||
local widths = buildW(fontdir, usedcids) -- Do this after generating the CFF to allow for extracting the widths from the font file
|
||||
basefont = gen_subsettag(widths)..basefont
|
||||
local cidfont = pdf:indirect(nil, string.format(
|
||||
"<</Type/Font/Subtype/CIDFontType0/BaseFont/%s/CIDSystemInfo<</Registry(Adobe)/Ordering(Identity)/Supplement 0>>/FontDescriptor %i 0 R/W[%s]>>",
|
||||
basefont,
|
||||
pdf:indirect(nil, fontdescriptor(pdf, basefont, fontdir, pdf:stream(nil, '/Subtype/CIDFontType0C', cff), 3)),
|
||||
widths
|
||||
))
|
||||
return basefont, cidfont
|
||||
end
|
||||
local function buildfont0ttf(pdf, fontdir, usedcids)
|
||||
local basefont = fontdir.psname or fontdir.fullname or fontdir.name -- FIXME: Subset-tag(?), Name-Escaping(?), fallback
|
||||
local ttf
|
||||
if fontdir.ttf then
|
||||
ttf = fontdir:ttf(usedcids) -- WARNING: If you implement this: You always have to add a .notdef glyph at index 0. This one is *not* included in usedcids
|
||||
else
|
||||
ttf = require'luametalatex-pdf-font-ttf'(fontdir.filename, 1, fontdir.encoding)(fontdir, usedcids)
|
||||
end
|
||||
local lastcid = -1
|
||||
local cidtogid = {}
|
||||
for i=1,#usedcids do
|
||||
cidtogid[2*i-1] = string.rep("\0\0", usedcids[i][1]-lastcid-1)
|
||||
cidtogid[2*i] = string.pack(">I2", i)
|
||||
lastcid = usedcids[i][1]
|
||||
end
|
||||
cidtogid = pdf:stream(nil, "", table.concat(cidtogid))
|
||||
local widths = buildW(fontdir, usedcids)
|
||||
basefont = gen_subsettag(widths)..basefont
|
||||
local cidfont = pdf:indirect(nil, string.format(
|
||||
"<</Type/Font/Subtype/CIDFontType2/BaseFont/%s/CIDSystemInfo<</Registry(Adobe)/Ordering(Identity)/Supplement 0>>/FontDescriptor %i 0 R/W[%s]/CIDToGIDMap %i 0 R>>",
|
||||
basefont,
|
||||
pdf:indirect(nil, fontdescriptor(pdf, basefont, fontdir, pdf:stream(nil, string.format('/Length1 %i', #ttf), ttf), 2)),
|
||||
widths,
|
||||
cidtogid
|
||||
))
|
||||
return basefont, cidfont
|
||||
end
|
||||
local function buildfont0(pdf, fontdir, usedcids)
|
||||
usedcids = usedcids or allcids(fontdir)
|
||||
table.sort(usedcids, function(a,b) return a[1]<b[1] end)
|
||||
local enc
|
||||
if fontdir.encodingbytes == 1 then
|
||||
enc = cidmap1byte(pdf)
|
||||
elseif true then -- FIXME: This should only be used for encodingbyzes == -3 (variable, max 3)
|
||||
fontdir.encodingbytes = -3 -- FIXME
|
||||
enc = cidmap3byte(pdf)
|
||||
else
|
||||
enc = "/Identity-H"
|
||||
end
|
||||
local basefont, cidfont = (fontdir.format == "truetype" and buildfont0ttf or buildfont0cff)(pdf, fontdir, usedcids)
|
||||
local touni = pdf:stream(nil, "", tounicode[fontdir.encodingbytes](fontdir, usedcids)) -- Done late to allow for defaults set from the font file
|
||||
return string.format(
|
||||
"<</Type/Font/Subtype/Type0/BaseFont/%s/Encoding%s/ToUnicode %i 0 R/DescendantFonts[%i 0 R]>>",
|
||||
basefont,
|
||||
enc,
|
||||
touni,
|
||||
cidfont)
|
||||
end
|
||||
local fontextensions = {
|
||||
ttf = {"truetype", "truetype fonts",},
|
||||
otf = {"opentype", "opentype fonts",},
|
||||
pfb = {"type1", "type1 fonts",},
|
||||
}
|
||||
fontextensions.cff = fontextensions.otf
|
||||
local fontformats = {
|
||||
fontextensions.pfb, fontextensions.otf, fontextensions.ttf,
|
||||
}
|
||||
return function(pdf, fontdir, usedcids)
|
||||
if fontdir.encodingbytes == 0 then fontdir.encodingbytes = nil end
|
||||
if fontdir.format == "unknown" or not fontdir.format or fontdir.encodingbytes == 1 then -- TODO: How to check this?
|
||||
fontdir.encodingbytes = fontdir.encodingbytes or 1
|
||||
local mapentry = mapping.fontmap[fontdir.name]
|
||||
if mapentry then
|
||||
local format = mapentry[3] and mapentry[3]:sub(-4, -4) == '.' and fontextensions[mapentry[3]:sub(-3, -1)]
|
||||
if format then
|
||||
fontdir.format = format[1]
|
||||
fontdir.filename = kpse.find_file(mapentry[3], format[2])
|
||||
if mapentry[4] then
|
||||
fontdir.encoding = kpse.find_file(mapentry[4], 'enc files')
|
||||
end
|
||||
goto format_set
|
||||
else
|
||||
for _, format in ipairs(fontformats) do
|
||||
local font = kpse.find_file(mapentry[3],format[2])
|
||||
if font then
|
||||
fontdir.format = "type1"
|
||||
fontdir.filename = font
|
||||
if mapentry[4] then
|
||||
fontdir.encoding = kpse.find_file(mapentry[4], 'enc files')
|
||||
end
|
||||
goto format_set
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
fontdir.format = "type3"
|
||||
::format_set::
|
||||
else
|
||||
fontdir.encodingbytes = fontdir.encodingbytes or 2
|
||||
end
|
||||
if fontdir.format == "type3" then
|
||||
error[[Currently unsupported]] -- TODO
|
||||
else
|
||||
return buildfont0(pdf, fontdir, usedcids)
|
||||
end
|
||||
end
|
44
luametalatex-pdf-pagetree.lua
Normal file
44
luametalatex-pdf-pagetree.lua
Normal file
@ -0,0 +1,44 @@
|
||||
local min = math.min
|
||||
local format = string.format
|
||||
local concat = table.concat
|
||||
local _ENV = {}
|
||||
function write(pdf, tree, total, max)
|
||||
tree = tree or pdf.pages
|
||||
if #tree == 0 then
|
||||
local id = pdf:getobj()
|
||||
pdf:indirect(id, '<</Type/Pages/Kids[]/Count 0>>')
|
||||
return id
|
||||
end
|
||||
max = max or 6 -- These defaults only work on the lowest level
|
||||
total = total or #tree
|
||||
local remaining = total
|
||||
-- if #tree == 1 then
|
||||
-- retur
|
||||
local newtree = {}
|
||||
local parent = ""
|
||||
for i=0,(#tree-1)//6 do
|
||||
local id = tree[-i]
|
||||
newtree[i+1] = id
|
||||
if 0 == i % 6 and #tree > 6 then
|
||||
local parentid = pdf:getid()
|
||||
newtree[-(i//6)] = parentid
|
||||
parent = format("/Parent %i 0 R", parentid)
|
||||
end
|
||||
pdf:indirect(id, format('<</Type/Pages%s/Kids[%s 0 R]/Count %i>>', parent, concat(tree, ' 0 R ', 6*i+1, min(#tree, 6*i+6)), min(remaining, max)))
|
||||
remaining = remaining - max
|
||||
end
|
||||
if #parent > 0 then
|
||||
return writetree(pdf, newtree, total, max*6)
|
||||
end
|
||||
return newtree[1]
|
||||
end
|
||||
function newpage(pdf)
|
||||
local pageid = pdf:getobj()
|
||||
local pagenumber = #pdf.pages
|
||||
pdf.pages[pagenumber+1] = pageid
|
||||
if 0 == pagenumber % 6 then
|
||||
pdf.pages[-(pagenumber//6)] = pdf:getobj()
|
||||
end
|
||||
return pageid, pdf.pages[-(pagenumber//6)]
|
||||
end
|
||||
return _ENV
|
87
luametalatex-pdf.lua
Normal file
87
luametalatex-pdf.lua
Normal file
@ -0,0 +1,87 @@
|
||||
local format = string.format
|
||||
local gsub = string.gsub
|
||||
local byte = string.byte
|
||||
local pack = string.pack
|
||||
local error = error
|
||||
local pairs = pairs
|
||||
local setmetatable = setmetatable
|
||||
local assigned = {}
|
||||
local function stream(pdf, num, dict, content)
|
||||
if not num then num = pdf:getobj() end
|
||||
if pdf[num] ~= assigned then
|
||||
error[[Invalid object]]
|
||||
end
|
||||
pdf[num] = {offset = pdf.file:seek()}
|
||||
pdf.file:write(format('%i 0 obj\n<<%s/Length %i>>stream\n', num, dict, #content))
|
||||
pdf.file:write(content)
|
||||
pdf.file:write'\nendstream\nendobj\n'
|
||||
return num
|
||||
end
|
||||
local function indirect(pdf, num, content)
|
||||
if not num then num = pdf:getobj() end
|
||||
if pdf[num] ~= assigned then
|
||||
error[[Invalid object]]
|
||||
end
|
||||
pdf[num] = {offset = pdf.file:seek()}
|
||||
pdf.file:write(format('%i 0 obj\n', num))
|
||||
pdf.file:write(content)
|
||||
pdf.file:write'\nendobj\n'
|
||||
return num
|
||||
end
|
||||
local function getid(pdf)
|
||||
local id = pdf[0] + 1
|
||||
pdf[0] = id
|
||||
pdf[id] = assigned
|
||||
return id
|
||||
end
|
||||
local function trailer(pdf)
|
||||
local nextid = getid(pdf)
|
||||
local myoff = pdf.file:seek()
|
||||
pdf[nextid] = {offset = myoff}
|
||||
local linked = 0
|
||||
local offsets = {}
|
||||
for i=1,nextid do
|
||||
local off = pdf[i].offset
|
||||
if off then
|
||||
offsets[i+1] = pack(">I1I3I1", 1, off, 0)
|
||||
else
|
||||
offsets[linked+1] = pack(">I1I3I1", 0, i, 255)
|
||||
linked = i
|
||||
end
|
||||
end
|
||||
offsets[linked+1] = '\0\0\0\0\255'
|
||||
pdf[nextid] = assigned
|
||||
-- TODO: Add an /ID according to 14.4
|
||||
stream(pdf, nextid, format([[/Type/XRef/Size %i/W[1 3 1]/Root %i 0 R]], nextid+1, pdf.root), table.concat(offsets))
|
||||
pdf.file:write('startxref\n', myoff, '\n%%EOF')
|
||||
end
|
||||
local function close(pdf)
|
||||
trailer(pdf)
|
||||
if #pdf.version ~= 3 then
|
||||
error[[Invalid PDF version]]
|
||||
end
|
||||
pdf.file:seek('set', 5)
|
||||
pdf.file:write(pdf.version)
|
||||
pdf.file:close()
|
||||
end
|
||||
local pagetree = require'luametalatex-pdf-pagetree'
|
||||
local pdfmeta = {
|
||||
close = close,
|
||||
getobj = getid,
|
||||
indirect = indirect,
|
||||
stream = stream,
|
||||
newpage = pagetree.newpage,
|
||||
writepages = pagetree.write,
|
||||
-- delayed = delayed,
|
||||
-- delayedstream = delayedstream,
|
||||
-- reference
|
||||
}
|
||||
pdfmeta.__index = pdfmeta
|
||||
local function open(filename)
|
||||
local file = io.open(filename, 'w')
|
||||
file:write"%PDF-X.X\n%🖋\n"
|
||||
return setmetatable({file = file, version = '1.7', [0] = 0, pages = {}}, pdfmeta)
|
||||
end
|
||||
return {
|
||||
open = open,
|
||||
}
|
34
luametalatex.ini
Normal file
34
luametalatex.ini
Normal file
@ -0,0 +1,34 @@
|
||||
% Thomas Esser, 1998. public domain.
|
||||
%
|
||||
\ifx\pdfoutput\undefined \else
|
||||
\ifx\pdfoutput\relax \else
|
||||
%
|
||||
% We're building the latex format with the pdfetex engine (started 2004).
|
||||
\input pdftexconfig
|
||||
\pdfoutput=0
|
||||
%
|
||||
% pdfTeX related primitives are no longer hidden by default
|
||||
% (started 2005). Uncomment and recreate the format files by running
|
||||
% "fmtutil --all" resp. "fmtutil-sys --all" to revert to the old
|
||||
% (2004) behaviour.
|
||||
% \input pdftex-dvi.tex
|
||||
%
|
||||
\fi
|
||||
\fi
|
||||
%
|
||||
% the usual format initialization.
|
||||
\scrollmode
|
||||
\luabytecode2
|
||||
\begingroup
|
||||
\catcode`\{=1
|
||||
\catcode`\}=2
|
||||
\def\x{\everyjob{\luabytecode2}}
|
||||
\expandafter\endgroup\x
|
||||
\let\savedversionofdump\dump
|
||||
\let\dump\relax
|
||||
\input latex.ltx
|
||||
\input luametalatex-baseregisters
|
||||
\let\dump\savedversionofdump
|
||||
\let\savedversionofdump\undefined
|
||||
\dump
|
||||
\endinput
|
121
luametalatex.lua
Normal file
121
luametalatex.lua
Normal file
@ -0,0 +1,121 @@
|
||||
-- Some helpers based on Penlight
|
||||
local absdir, dirsep
|
||||
do
|
||||
local sep = package.config:sub(1,1)
|
||||
local is_windows = sep == "\\"
|
||||
dirsep = lpeg.S(is_windows and '\\/' or '/')
|
||||
local anchor_pattern = lpeg.Cs(is_windows
|
||||
and lpeg.P'\\\\' + dirsep/'\\' + 1*lpeg.P':'*dirsep^-1/'\\'
|
||||
or lpeg.P'//' + dirsep^1/'/')
|
||||
function isabs(P)
|
||||
return P:sub(1,1) == '/' or (is_windows and (P:sub(1,1)=='\\' or P:sub(2,2)==':'))
|
||||
end
|
||||
local insert, remove, concat = table.insert, table.remove, table.concat
|
||||
function normpath(P)
|
||||
-- Split path into anchor and relative path.
|
||||
local anchor, P = ((anchor_pattern + lpeg.Cc'') * lpeg.C(lpeg.P(1)^0)):match(P)
|
||||
if is_windows then
|
||||
P = P:gsub('/','\\')
|
||||
end
|
||||
local parts = {}
|
||||
for part in P:gmatch('[^'..sep..']+') do
|
||||
if part == '..' then
|
||||
if #parts ~= 0 and parts[#parts] ~= '..' then
|
||||
remove(parts)
|
||||
else
|
||||
insert(parts, part)
|
||||
end
|
||||
elseif part ~= '.' then
|
||||
insert(parts, part)
|
||||
end
|
||||
end
|
||||
P = anchor..concat(parts, sep)
|
||||
if P == '' then P = '.' end
|
||||
return P
|
||||
end
|
||||
function join(p1,p2,...)
|
||||
if select('#',...) > 0 then
|
||||
local p = join(p1,p2)
|
||||
return join(p, ...)
|
||||
end
|
||||
if isabs(p2) then return p2 end
|
||||
local endc = p1:sub(#p1,#p1)
|
||||
if endc ~= "/" and (not is_windows or endc ~= "\\") and endc ~= "" then
|
||||
p1 = p1..sep
|
||||
end
|
||||
return p1..p2
|
||||
end
|
||||
function absdir(P,pwd)
|
||||
local use_pwd = pwd ~= nil
|
||||
pwd = pwd or lfs.currentdir()
|
||||
if not isabs(P) then
|
||||
P = join(pwd,P)
|
||||
elseif is_windows and not use_pwd and P:sub(2,2) ~= ':' and P:sub(2,2) ~= '\\' then
|
||||
P = pwd:sub(1,2)..P -- attach current drive to path like '\\fred.txt'
|
||||
end
|
||||
return normpath(P) .. sep
|
||||
end
|
||||
end
|
||||
-- Who are we anyway?
|
||||
local format = os.selfname -- or os.selfcore, I couldn't find a difference yet
|
||||
local ourname = arg[0] -- This might not be os.selfarg[0]
|
||||
if os.selfarg[0] == ourname then
|
||||
ourname = nil
|
||||
end
|
||||
for i, a in ipairs(os.selfarg) do
|
||||
-- LuaMetaTeX needs -- to introduce parameters,
|
||||
-- but fmtutil uses just -. Let's rewrite this on the fly:
|
||||
if a == ourname then -- Avoid recursion
|
||||
table.remove(os.selfarg, i)
|
||||
ourname = nil
|
||||
a = os.selfarg[i]
|
||||
end
|
||||
if a == "--" then break end
|
||||
a = a:gsub("^%-%-?", "--")
|
||||
os.selfarg[i] = a
|
||||
if a:sub(1, 11) == "--progname=" then
|
||||
format = a:sub(12)
|
||||
elseif a == '--ini' then
|
||||
is_initex = true
|
||||
end
|
||||
end
|
||||
local dir = absdir(os.selfdir)
|
||||
local dirseparators = {((lpeg.S'\\/'^1 + 1 * lpeg.P':' * lpeg.S'\\/'^-1) * lpeg.Cp() * ((1-lpeg.S'\\/')^0*lpeg.S'\\/'*lpeg.Cp())^0):match(dir)}
|
||||
-- First step: Find our actual format.
|
||||
local init_script = format .. "-init.lua"
|
||||
local texmf_dir = "tex/lualatex/" .. format .. '/' .. init_script
|
||||
local paths = {
|
||||
init_script,
|
||||
"share/texmf-local/" .. texmf_dir,
|
||||
"share/texmf-dist/" .. texmf_dir,
|
||||
"share/texmf/" .. texmf_dir,
|
||||
"texmf-local/" .. texmf_dir,
|
||||
"texmf-dist/" .. texmf_dir,
|
||||
"texmf/" .. texmf_dir,
|
||||
}
|
||||
for i = #dirseparators, 1, -1 do
|
||||
dir = dir:sub(1, dirseparators[i] - 1)
|
||||
for _, subdir in ipairs(paths) do
|
||||
local full_path = dir .. subdir
|
||||
local attr = lfs.attributes(full_path)
|
||||
if attr then
|
||||
dir = full_path
|
||||
goto FOUND
|
||||
end
|
||||
end
|
||||
end
|
||||
error[[CRITICAL: Initialization script not found]]
|
||||
::FOUND::
|
||||
-- table.insert(arg, 1, "--lua=" .. dir)
|
||||
-- table.insert(arg, 1, "luametatex")
|
||||
-- arg[0] = nil
|
||||
-- local _, msg = os.exec(arg)
|
||||
-- error(msg)
|
||||
os.setenv("engine", status.luatex_engine)
|
||||
local ret_value
|
||||
if is_initex then
|
||||
ret_value = os.execute(string.format("luametatex \"--lua=%s\" --arg0=\"%s\" \"%s\"", dir, os.selfarg[0], table.concat(os.selfarg, "\" \"")))
|
||||
else
|
||||
ret_value = os.execute(string.format("luametatex \"--fmt=%s\" \"--lua=%s\" --arg0=\"%s\" \"%s\"", format, dir, os.selfarg[0], table.concat(os.selfarg, "\" \"")))
|
||||
end
|
||||
os.exit(x)
|
Loading…
Reference in New Issue
Block a user