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 dests = {}
local cur_page
local declare_whatsit = require'luametalatex-whatsits'.new
local whatsit_id = node.id'whatsit'
local whatsits = node.whatsits()
local colorstacks = {{
@ -264,9 +265,16 @@ local function linkcontext_set(linkcontext, p, x, y, list, level, kind)
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
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
local links = p.linkcontext
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
links[#links+1] = link
addlinkpoint(p, link, x, y, outer, 'start')
end
function do_end_link(prop, p, n, x, y, outer, _, level)
end)
local end_link_whatsit = declare_whatsit('pdf_end_link', function(prop, p, n, x, y, outer, _, level)
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
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]
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
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')
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 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 a, b, c, d = matrixpattern:match(prop.data)
if not a then
print(prop.data)
error[[No valid matrix found]]
tex.error('Invalid matrix', {"The matrix in this pdf_setmatrix whatsit does not have the expected structure and could not be parsed. \z
Did you provide enough parameters? The matrix needs exactly four decimal entries."})
return
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
-- (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)
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
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)
local lastmatrix = p.matrix
p.matrix = {[0] = lastmatrix, table.unpack(lastmatrix)}
end
local function do_restore(prop, p, n, x, y, outer)
end)
local restore_whatsit = declare_whatsit('pdf_restore', function(prop, p, n, x, y, outer)
-- TODO: Check x, y
pdf.write('page', 'Q', x, y, p)
p.matrix = p.matrix[0]
end
local function do_dest(prop, p, n, x, y)
end)
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")
local id = prop.dest_id
local dest_type = prop.dest_type
@ -365,14 +393,29 @@ local function do_dest(prop, p, n, x, y)
else
dests[id] = pfile:indirect(dests[id], data)
end
end
local function do_refobj(prop, p, n, x, y)
end)
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)
end
local function do_literal(prop, p, n, x, y)
end)
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)
end
local function do_colorstack(prop, p, n, x, y)
end)
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 stack
if p.is_page then
@ -393,28 +436,33 @@ local function do_colorstack(prop, p, n, x, y)
stack[#stack] = prop.data
end
pdf.write(colorstack.mode, stack[#stack], x, y, p)
end
end)
local function write_colorstack()
local idx = token.scan_int()
local colorstack = colorstacks[idx + 1]
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
local action = token.scan_keyword'pop' and 'pop'
or token.scan_keyword'set' and 'set'
or token.scan_keyword'current' and 'current'
or token.scan_keyword'push' and 'push'
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
local text
if action == "push" or "set" then
text = token.scan_string()
-- text = token.to_string(token.scan_tokenlist()) -- Attention! This should never be executed in an expand-only context
end
local whatsit = node.new(whatsit_id, whatsits.pdf_colorstack)
local whatsit = node.new(whatsit_id, colorstack_whatsit)
node.setproperty(whatsit, {
handle = do_colorstack,
colorstack = colorstack,
action = action,
data = text,
@ -498,52 +546,40 @@ token.luacmd("pdfextension", function(_, imm)
elseif token.scan_keyword"literal" then
local mode = scan_literal_mode()
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, {
handle = do_literal,
mode = mode,
data = literal,
})
node.write(whatsit)
elseif token.scan_keyword"startlink" then
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 action = scan_action()
local objnum = pfile:getobj()
lastannot = num
node.setproperty(whatsit, {
handle = do_start_link,
link_attr = attr,
action = action,
objnum = objnum,
})
node.write(whatsit)
elseif token.scan_keyword"endlink" then
local whatsit = node.new(whatsit_id, whatsits.pdf_end_link)
node.setproperty(whatsit, {
handle = do_end_link,
})
local whatsit = node.new(whatsit_id, end_link_whatsit)
node.write(whatsit)
elseif token.scan_keyword"save" then
local whatsit = node.new(whatsit_id, whatsits.pdf_save)
node.setproperty(whatsit, {
handle = do_save,
})
local whatsit = node.new(whatsit_id, save_whatsit)
node.write(whatsit)
elseif token.scan_keyword"setmatrix" then
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, {
handle = do_setmatrix,
data = matrix,
})
node.write(whatsit)
elseif token.scan_keyword"restore" then
local whatsit = node.new(whatsit_id, whatsits.pdf_restore)
node.setproperty(whatsit, {
handle = do_restore,
})
local whatsit = node.new(whatsit_id, restore_whatsit)
node.write(whatsit)
elseif token.scan_keyword"info" then
infodir = infodir .. token.scan_string()
@ -577,10 +613,9 @@ token.luacmd("pdfextension", function(_, imm)
end
elseif token.scan_keyword"refobj" then
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, {
obj = num,
handle = do_refobj,
})
node.write(whatsit)
elseif token.scan_keyword"outline" then
@ -616,10 +651,9 @@ token.luacmd("pdfextension", function(_, imm)
else
error[[Unsupported id type]]
end
local whatsit = node.new(whatsit_id, whatsits.pdf_dest)
local whatsit = node.new(whatsit_id, dest_whatsit)
local prop = {
dest_id = id,
handle = do_dest,
}
node.setproperty(whatsit, prop)
if token.scan_keyword'xyz' then

View File

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

View File

@ -98,7 +98,8 @@ callback_register('handle_error_hook', function()
if line == "" then return 3 end
local first = line:sub(1,1):upper()
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
line = line:sub(2)
tex.runtoks(function()

View File

@ -31,6 +31,8 @@ local rangedimensions = direct.rangedimensions
local traverse_id = direct.traverse_id
local getdata = direct.getdata
local get_whatsit_handler = require'luametalatex-whatsits'.handler
local dir_id = node.id'dir'
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)
end
function nodehandler.whatsit(p, n, ...) -- Whatsit?
local prop = properties[n]-- or node.getproperty(n)
if prop and prop.handle then
prop:handle(p, n, ...)
local handler, prop = get_whatsit_handler(n)
if handler then
handler(prop, p, n, ...)
else
write("Invalid whatsit found (missing handler).")
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,
}