From 94ba4a25574bc639ecc7556aac6820cb157f71df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20Fabian=20Kr=C3=BCger?= Date: Wed, 1 Jul 2020 19:47:25 +0200 Subject: [PATCH] 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. --- luametalatex-back-pdf.lua | 136 ++++++++++++++++++++++-------------- luametalatex-firstcode.lua | 61 +++------------- luametalatex-init.lua | 3 +- luametalatex-nodewriter.lua | 8 ++- luametalatex-whatsits.lua | 96 +++++++++++++++++++++++++ 5 files changed, 199 insertions(+), 105 deletions(-) create mode 100644 luametalatex-whatsits.lua diff --git a/luametalatex-back-pdf.lua b/luametalatex-back-pdf.lua index d80264e..74afa30 100644 --- a/luametalatex-back-pdf.lua +++ b/luametalatex-back-pdf.lua @@ -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 diff --git a/luametalatex-firstcode.lua b/luametalatex-firstcode.lua index 33a97d5..15318b8 100644 --- a/luametalatex-firstcode.lua +++ b/luametalatex-firstcode.lua @@ -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() diff --git a/luametalatex-init.lua b/luametalatex-init.lua index 6e140a8..7711f0c 100644 --- a/luametalatex-init.lua +++ b/luametalatex-init.lua @@ -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() diff --git a/luametalatex-nodewriter.lua b/luametalatex-nodewriter.lua index b6ce05d..07fd02d 100644 --- a/luametalatex-nodewriter.lua +++ b/luametalatex-nodewriter.lua @@ -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 diff --git a/luametalatex-whatsits.lua b/luametalatex-whatsits.lua new file mode 100644 index 0000000..2bed3de --- /dev/null +++ b/luametalatex-whatsits.lua @@ -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, +}