Better errors and new whatsit handling

The whatsit handling is actually much more similar to the old whatsit
handling then the previous new one, so that part could be seen as a
partial revert of the new whatsit handling... Anyway, whatsits work
different than in the previous commit now.
This commit is contained in:
Marcel Krüger 2020-07-01 19:47:25 +02:00
parent d13bbbc5eb
commit 94ba4a2557
5 changed files with 199 additions and 105 deletions

View File

@ -8,6 +8,7 @@ local fontdirs = setmetatable({}, {__index=function(t, k)t[k] = pfile:getobj() r
local usedglyphs = {} local usedglyphs = {}
local dests = {} local dests = {}
local cur_page local cur_page
local declare_whatsit = require'luametalatex-whatsits'.new
local whatsit_id = node.id'whatsit' local whatsit_id = node.id'whatsit'
local whatsits = node.whatsits() local whatsits = node.whatsits()
local colorstacks = {{ local colorstacks = {{
@ -264,9 +265,16 @@ local function linkcontext_set(linkcontext, p, x, y, list, level, kind)
end end end end
end end
function do_start_link(prop, p, n, x, y, outer, _, level) local start_link_whatsit = declare_whatsit('pdf_start_link', function(prop, p, n, x, y, outer, _, level)
if not prop then
tex.error('Invalid pdf_start_link whatsit', {"A pdf_start_link whatsit did not contain all necessary \z
parameters. Maybe your code hasn't been adapted to LuaMetaLaTeX yet?"})
return
end
if not p.is_page then if not p.is_page then
error[[No link allowed here]] tex.error('pdf_start_link outside of page', {"PDF links are not allowed in Type3 charstrings or Form XObjects. \z
The link will be ignored"})
return
end end
local links = p.linkcontext local links = p.linkcontext
if not links then if not links then
@ -276,29 +284,45 @@ function do_start_link(prop, p, n, x, y, outer, _, level)
local link = {quads = {}, attr = prop.link_attr, action = prop.action, level = level, force_separate = false} -- force_separate should become an option local link = {quads = {}, attr = prop.link_attr, action = prop.action, level = level, force_separate = false} -- force_separate should become an option
links[#links+1] = link links[#links+1] = link
addlinkpoint(p, link, x, y, outer, 'start') addlinkpoint(p, link, x, y, outer, 'start')
end end)
function do_end_link(prop, p, n, x, y, outer, _, level) local end_link_whatsit = declare_whatsit('pdf_end_link', function(prop, p, n, x, y, outer, _, level)
if not p.is_page then if not p.is_page then
error[[No link allowed here]] tex.error('pdf_start_link outside of page', {"PDF links are not allowed in Type3 charstrings or Form XObjects. \z
The link will be ignored"})
return
end end
local links = p.linkcontext local links = p.linkcontext
if not links then error"No link here to end" end if not links then
tex.error('No link here to end', {"You asked me to end a link, but currently there is no link active. \z
Maybe you forgot to run \\pdfextension startlink first?"})
return
end
local link = links[#links] local link = links[#links]
if link.level ~= level then
tex.error('Inconsistent link level', {"You asked me to end a link, but the most recent link had been started at another level. \z
I will continue with the link for now."})
return
end
links[#links] = nil links[#links] = nil
if not links[1] then p.linkcontext = nil end if not links[1] then p.linkcontext = nil end
if link.level ~= level then error"Wrong link level" end
addlinkpoint(p, link, x, y, outer, 'final') addlinkpoint(p, link, x, y, outer, 'final')
end end)
local do_setmatrix do local setmatrix_whatsit do
local numberpattern = (lpeg.P'-'^-1 * lpeg.R'09'^0 * ('.' * lpeg.R'09'^0)^-1)/tonumber local numberpattern = (lpeg.P'-'^-1 * lpeg.R'09'^0 * ('.' * lpeg.R'09'^0)^-1)/tonumber
local matrixpattern = numberpattern * ' ' * numberpattern * ' ' * numberpattern * ' ' * numberpattern local matrixpattern = numberpattern * ' ' * numberpattern * ' ' * numberpattern * ' ' * numberpattern
function do_setmatrix(prop, p, n, x, y, outer) setmatrix_whatsit = declare_whatsit('pdf_setmatrix', function(prop, p, n, x, y, outer)
if not prop then
tex.error('Invalid pdf_setmatrix whatsit', {"A pdf_setmatrix whatsit did not contain a matrix value. \z
Maybe your code hasn't been adapted to LuaMetaLaTeX yet?"})
return
end
local m = p.matrix local m = p.matrix
local a, b, c, d = matrixpattern:match(prop.data) local a, b, c, d = matrixpattern:match(prop.data)
if not a then if not a then
print(prop.data) tex.error('Invalid matrix', {"The matrix in this pdf_setmatrix whatsit does not have the expected structure and could not be parsed. \z
error[[No valid matrix found]] Did you provide enough parameters? The matrix needs exactly four decimal entries."})
return
end end
local e, f = (1-a)*x-c*y, (1-d)*y-b*x -- Emulate that the origin is at x, y for this transformation local e, f = (1-a)*x-c*y, (1-d)*y-b*x -- Emulate that the origin is at x, y for this transformation
-- (We could also first translate by (-x, -y), then apply the matrix -- (We could also first translate by (-x, -y), then apply the matrix
@ -308,19 +332,23 @@ local do_setmatrix do
c, d = projected(m, c, d, 0) c, d = projected(m, c, d, 0)
e, f = projected(m, e, f, 1) e, f = projected(m, e, f, 1)
m[1], m[2], m[3], m[4], m[5], m[6] = a, b, c, d, e, f m[1], m[2], m[3], m[4], m[5], m[6] = a, b, c, d, e, f
end end)
end end
local function do_save(prop, p, n, x, y, outer) local save_whatsit = declare_whatsit('pdf_save', function(prop, p, n, x, y, outer)
pdf.write('page', 'q', x, y, p) pdf.write('page', 'q', x, y, p)
local lastmatrix = p.matrix local lastmatrix = p.matrix
p.matrix = {[0] = lastmatrix, table.unpack(lastmatrix)} p.matrix = {[0] = lastmatrix, table.unpack(lastmatrix)}
end end)
local function do_restore(prop, p, n, x, y, outer) local restore_whatsit = declare_whatsit('pdf_restore', function(prop, p, n, x, y, outer)
-- TODO: Check x, y -- TODO: Check x, y
pdf.write('page', 'Q', x, y, p) pdf.write('page', 'Q', x, y, p)
p.matrix = p.matrix[0] p.matrix = p.matrix[0]
end end)
local function do_dest(prop, p, n, x, y) local dest_whatsit = declare_whatsit('pdf_dest', function(prop, p, n, x, y)
if not prop then
tex.error('Invalid pdf_dest whatsit', {"A pdf_dest whatsit did not contain all necessary \z
parameters. Maybe your code hasn't been adapted to LuaMetaLaTeX yet?"})
end
assert(cur_page, "Destinations can not appear outside of a page") assert(cur_page, "Destinations can not appear outside of a page")
local id = prop.dest_id local id = prop.dest_id
local dest_type = prop.dest_type local dest_type = prop.dest_type
@ -365,14 +393,29 @@ local function do_dest(prop, p, n, x, y)
else else
dests[id] = pfile:indirect(dests[id], data) dests[id] = pfile:indirect(dests[id], data)
end end
end end)
local function do_refobj(prop, p, n, x, y) local refobj_whatsit = declare_whatsit('pdf_refobj', function(prop, p, n, x, y)
if not prop then
tex.error('Invalid pdf_refobj whatsit', {"A pdf_refobj whatsit did not reference any object. \z
Maybe your code hasn't been adapted to LuaMetaLaTeX yet?"})
return
end
pfile:reference(prop.obj) pfile:reference(prop.obj)
end end)
local function do_literal(prop, p, n, x, y) local literal_whatsit = declare_whatsit('pdf_literal', function(prop, p, n, x, y)
if not prop then
tex.error('Invalid pdf_literal whatsit', {"A pdf_literal whatsit did not contain a literal to be inserted. \z
Maybe your code hasn't been adapted to LuaMetaLaTeX yet?"})
return
end
pdf.write(prop.mode, prop.data, x, y, p) pdf.write(prop.mode, prop.data, x, y, p)
end end)
local function do_colorstack(prop, p, n, x, y) local colorstack_whatsit = declare_whatsit('pdf_colorstack', function(prop, p, n, x, y)
if not prop then
tex.error('Invalid pdf_colorstack whatsit', {"A pdf_colorstack whatsit did not contain all necessary \z
parameters. Maybe your code hasn't been adapted to LuaMetaLaTeX yet?"})
return
end
local colorstack = prop.colorstack local colorstack = prop.colorstack
local stack local stack
if p.is_page then if p.is_page then
@ -393,28 +436,33 @@ local function do_colorstack(prop, p, n, x, y)
stack[#stack] = prop.data stack[#stack] = prop.data
end end
pdf.write(colorstack.mode, stack[#stack], x, y, p) pdf.write(colorstack.mode, stack[#stack], x, y, p)
end end)
local function write_colorstack() local function write_colorstack()
local idx = token.scan_int() local idx = token.scan_int()
local colorstack = colorstacks[idx + 1] local colorstack = colorstacks[idx + 1]
if not colorstack then if not colorstack then
error[[Undefined colorstack]] tex.error('Undefined colorstack', {"The requested colorstack is not initialized. \z
This probably means that you forgot to run \\pdffeedback colorstackinit or \z
that you specified the wrong index. I will continue with colorstack 0."})
colorstack = colorstacks[1]
end end
local action = token.scan_keyword'pop' and 'pop' local action = token.scan_keyword'pop' and 'pop'
or token.scan_keyword'set' and 'set' or token.scan_keyword'set' and 'set'
or token.scan_keyword'current' and 'current' or token.scan_keyword'current' and 'current'
or token.scan_keyword'push' and 'push' or token.scan_keyword'push' and 'push'
if not action then if not action then
error[[Missing action specifier for colorstack command]] tex.error('Missing action specifier for colorstack', {
"I don't know what you want to do with this colorstack. I would have expected pop/set/current or push here. \z
I will ignore this command."})
return
end end
local text local text
if action == "push" or "set" then if action == "push" or "set" then
text = token.scan_string() text = token.scan_string()
-- text = token.to_string(token.scan_tokenlist()) -- Attention! This should never be executed in an expand-only context -- text = token.to_string(token.scan_tokenlist()) -- Attention! This should never be executed in an expand-only context
end end
local whatsit = node.new(whatsit_id, whatsits.pdf_colorstack) local whatsit = node.new(whatsit_id, colorstack_whatsit)
node.setproperty(whatsit, { node.setproperty(whatsit, {
handle = do_colorstack,
colorstack = colorstack, colorstack = colorstack,
action = action, action = action,
data = text, data = text,
@ -498,52 +546,40 @@ token.luacmd("pdfextension", function(_, imm)
elseif token.scan_keyword"literal" then elseif token.scan_keyword"literal" then
local mode = scan_literal_mode() local mode = scan_literal_mode()
local literal = token.scan_string() local literal = token.scan_string()
local whatsit = node.new(whatsit_id, whatsits.pdf_literal) local whatsit = node.new(whatsit_id, literal_whatsit)
node.setproperty(whatsit, { node.setproperty(whatsit, {
handle = do_literal,
mode = mode, mode = mode,
data = literal, data = literal,
}) })
node.write(whatsit) node.write(whatsit)
elseif token.scan_keyword"startlink" then elseif token.scan_keyword"startlink" then
local pfile = get_pfile() local pfile = get_pfile()
local whatsit = node.new(whatsit_id, whatsits.pdf_start_link) local whatsit = node.new(whatsit_id, start_link_whatsit)
local attr = token.scan_keyword'attr' and token.scan_string() or '' local attr = token.scan_keyword'attr' and token.scan_string() or ''
local action = scan_action() local action = scan_action()
local objnum = pfile:getobj() local objnum = pfile:getobj()
lastannot = num lastannot = num
node.setproperty(whatsit, { node.setproperty(whatsit, {
handle = do_start_link,
link_attr = attr, link_attr = attr,
action = action, action = action,
objnum = objnum, objnum = objnum,
}) })
node.write(whatsit) node.write(whatsit)
elseif token.scan_keyword"endlink" then elseif token.scan_keyword"endlink" then
local whatsit = node.new(whatsit_id, whatsits.pdf_end_link) local whatsit = node.new(whatsit_id, end_link_whatsit)
node.setproperty(whatsit, {
handle = do_end_link,
})
node.write(whatsit) node.write(whatsit)
elseif token.scan_keyword"save" then elseif token.scan_keyword"save" then
local whatsit = node.new(whatsit_id, whatsits.pdf_save) local whatsit = node.new(whatsit_id, save_whatsit)
node.setproperty(whatsit, {
handle = do_save,
})
node.write(whatsit) node.write(whatsit)
elseif token.scan_keyword"setmatrix" then elseif token.scan_keyword"setmatrix" then
local matrix = token.scan_string() local matrix = token.scan_string()
local whatsit = node.new(whatsit_id, whatsits.pdf_setmatrix) local whatsit = node.new(whatsit_id, setmatrix_whatsit)
node.setproperty(whatsit, { node.setproperty(whatsit, {
handle = do_setmatrix,
data = matrix, data = matrix,
}) })
node.write(whatsit) node.write(whatsit)
elseif token.scan_keyword"restore" then elseif token.scan_keyword"restore" then
local whatsit = node.new(whatsit_id, whatsits.pdf_restore) local whatsit = node.new(whatsit_id, restore_whatsit)
node.setproperty(whatsit, {
handle = do_restore,
})
node.write(whatsit) node.write(whatsit)
elseif token.scan_keyword"info" then elseif token.scan_keyword"info" then
infodir = infodir .. token.scan_string() infodir = infodir .. token.scan_string()
@ -577,10 +613,9 @@ token.luacmd("pdfextension", function(_, imm)
end end
elseif token.scan_keyword"refobj" then elseif token.scan_keyword"refobj" then
local num = token.scan_int() local num = token.scan_int()
local whatsit = node.new(whatsit_id, whatsits.pdf_refobj) local whatsit = node.new(whatsit_id, refobj_whatsit)
node.setproperty(whatsit, { node.setproperty(whatsit, {
obj = num, obj = num,
handle = do_refobj,
}) })
node.write(whatsit) node.write(whatsit)
elseif token.scan_keyword"outline" then elseif token.scan_keyword"outline" then
@ -616,10 +651,9 @@ token.luacmd("pdfextension", function(_, imm)
else else
error[[Unsupported id type]] error[[Unsupported id type]]
end end
local whatsit = node.new(whatsit_id, whatsits.pdf_dest) local whatsit = node.new(whatsit_id, dest_whatsit)
local prop = { local prop = {
dest_id = id, dest_id = id,
handle = do_dest,
} }
node.setproperty(whatsit, prop) node.setproperty(whatsit, prop)
if token.scan_keyword'xyz' then if token.scan_keyword'xyz' then

View File

@ -63,50 +63,8 @@ end
-- end -- end
-- }) -- })
local new_whatsit = require'luametalatex-whatsits'.new
local whatsit_id = node.id'whatsit' local whatsit_id = node.id'whatsit'
local whatsits = {
[0] = "open",
"write",
"close",
"special",
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 spacer_cmd, relax_cmd = token.command_id'spacer', token.command_id'relax'
local function scan_filename() local function scan_filename()
local name = {} local name = {}
@ -141,15 +99,16 @@ local function do_openout(p)
end end
end end
end end
local open_whatsit = new_whatsit('open', do_openout)
token.luacmd("openout", function(_, immediate) -- \openout token.luacmd("openout", function(_, immediate) -- \openout
local file = token.scan_int() local file = token.scan_int()
token.scan_keyword'=' token.scan_keyword'='
local name = scan_filename() local name = scan_filename()
local props = {file = file, name = name, handle = do_openout} local props = {file = file, name = name}
if immediate == "immediate" then if immediate == "immediate" then
do_openout(props) do_openout(props)
else else
local whatsit = node.direct.new(whatsit_id, whatsits.open) local whatsit = node.direct.new(whatsit_id, open_whatsit)
properties[whatsit] = props properties[whatsit] = props
node.direct.write(whatsit) node.direct.write(whatsit)
end end
@ -160,13 +119,14 @@ local function do_closeout(p)
ofiles[p.file] = nil ofiles[p.file] = nil
end end
end end
local close_whatsit = new_whatsit('close', do_closeout)
token.luacmd("closeout", function(_, immediate) -- \closeout token.luacmd("closeout", function(_, immediate) -- \closeout
local file = token.scan_int() local file = token.scan_int()
local props = {file = file, handle = do_closeout} local props = {file = file}
if immediate == "immediate" then if immediate == "immediate" then
do_closeout(props) do_closeout(props)
else else
local whatsit = node.direct.new(whatsit_id, whatsits.close) local whatsit = node.direct.new(whatsit_id, close_whatsit)
properties[whatsit] = props properties[whatsit] = props
node.direct.write(whatsit) node.direct.write(whatsit)
end end
@ -180,14 +140,15 @@ local function do_write(p)
texio.write_nl(p.file < 0 and "log" or "term and log", content) texio.write_nl(p.file < 0 and "log" or "term and log", content)
end end
end end
local write_whatsit = new_whatsit('write', do_write)
token.luacmd("write", function(_, immediate) -- \write token.luacmd("write", function(_, immediate) -- \write
local file = token.scan_int() local file = token.scan_int()
local content = token.scan_tokenlist() local content = token.scan_tokenlist()
local props = {file = file, data = content, handle = do_write} local props = {file = file, data = content}
if immediate == "immediate" then if immediate == "immediate" then
do_write(props) do_write(props)
else else
local whatsit = node.direct.new(whatsit_id, whatsits.write) local whatsit = node.direct.new(whatsit_id, write_whatsit)
properties[whatsit] = props properties[whatsit] = props
node.direct.write(whatsit) node.direct.write(whatsit)
end end
@ -199,7 +160,7 @@ token.luacmd("immediate", function() -- \immediate
return token.put_next(next_tok) return token.put_next(next_tok)
end end
local function_id = next_tok.mode local function_id = next_tok.mode
functions[function_id](function_id, 'immediate') return functions[function_id](function_id, 'immediate')
end, "protected") end, "protected")
-- functions[43] = function() -- \pdfvariable -- functions[43] = function() -- \pdfvariable
-- local name = token.scan_string() -- local name = token.scan_string()

View File

@ -98,7 +98,8 @@ callback_register('handle_error_hook', function()
if line == "" then return 3 end if line == "" then return 3 end
local first = line:sub(1,1):upper() local first = line:sub(1,1):upper()
if first == 'H' then if first == 'H' then
texio.write(tex.gethelptext()) texio.write(tex.gethelptext() or "Sorry, I don't know how to help in this situation.\n\z
Maybe you should try asking a human?")
elseif first == 'I' then elseif first == 'I' then
line = line:sub(2) line = line:sub(2)
tex.runtoks(function() tex.runtoks(function()

View File

@ -31,6 +31,8 @@ local rangedimensions = direct.rangedimensions
local traverse_id = direct.traverse_id local traverse_id = direct.traverse_id
local getdata = direct.getdata local getdata = direct.getdata
local get_whatsit_handler = require'luametalatex-whatsits'.handler
local dir_id = node.id'dir' local dir_id = node.id'dir'
local function doublekeyed(t, id2name, name2id, index) local function doublekeyed(t, id2name, name2id, index)
@ -470,9 +472,9 @@ function nodehandler.glyph(p, n, x, y, ...)
p.pos.x = p.pos.x + math.floor(getwidth(n)*(1+getexpansion(n)/1000000)+.5) p.pos.x = p.pos.x + math.floor(getwidth(n)*(1+getexpansion(n)/1000000)+.5)
end end
function nodehandler.whatsit(p, n, ...) -- Whatsit? function nodehandler.whatsit(p, n, ...) -- Whatsit?
local prop = properties[n]-- or node.getproperty(n) local handler, prop = get_whatsit_handler(n)
if prop and prop.handle then if handler then
prop:handle(p, n, ...) handler(prop, p, n, ...)
else else
write("Invalid whatsit found (missing handler).") write("Invalid whatsit found (missing handler).")
end end

96
luametalatex-whatsits.lua Normal file
View File

@ -0,0 +1,96 @@
local whatsit_id = node.id'whatsit'
local whatsits = {
[0] = "open",
"write",
"close",
"special",
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",
}
local whatsithandler = {}
-- for i = 0,#whatsits do -- #whatsits isn't guaranteed to work because of the nil entries
for i = 0,31 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 direct = node.direct
local getsubtype = direct.getsubtype
local properties = direct.get_properties_table()
local tonode, todirect = direct.tonode, direct.todirect
local function get_handler(n, subtype)
local props = properties[n]
return props and props.handle or whatsithandler[subtype or getsubtype(n)], props
end
local function new(name, handler)
assert(type(name) == 'string')
local subtype = whatsits[name]
if subtype then
if whatsithandler[subtype] then
texio.write_nl'WARNING: Overwriting default whatsit handler'
end
else
subtype = #whatsits + 1
whatsits[subtype] = name
whatsits[name] = subtype
end
whatsithandler[subtype] = handler
return subtype
end
-- TODO: Some fields might expect different values
local function setwhatsitfield(n, name, value)
local props = properties[n]
if not props then
props = {}
properties[n] = props
end
props[name] = value
end
direct.setwhatsitfield = setwhatsitfield
local function getwhatsitfield(n, name)
local props = properties[n]
return props and props[name]
end
direct.getwhatsitfield = getwhatsitfield
-- TODO: Some fields might be nodes and therefore have to be converted
function node.setwhatsitfield(n, ...) return setwhatsitfield(todirect(n), ...) end
function node.getwhatsitfield(n, ...) return getwhatsitfield(todirect(n), ...) end
return {
handler = get_handler,
new = new,
}