diff --git a/luametalatex-back-pdf.lua b/luametalatex-back-pdf.lua index 0d9cf25..6d7d04c 100644 --- a/luametalatex-back-pdf.lua +++ b/luametalatex-back-pdf.lua @@ -1,8 +1,15 @@ +local pdf = pdf 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 = {} +local colorstacks = {{ + page = true, + mode = "direct", + default = "0 g 0 G", + page_stack = {"0 g 0 G"}, + }} token.luacmd("shipout", function() local voff = node.new'kern' voff.kern = tex.voffset + pdf.variable.vorigin @@ -11,7 +18,7 @@ token.luacmd("shipout", function() local list = node.vpack(voff) list.height = tex.pageheight list.width = tex.pagewidth - local out, resources, annots = writer(pfile, list, fontdirs, usedglyphs) + local out, resources, annots = writer(pfile, list, fontdirs, usedglyphs, colorstacks) local page, parent = pfile:newpage() local content = pfile:stream(nil, '', out) pfile:indirect(page, string.format([[<>]], parent, content, -math.ceil(list.depth/65781.76), math.ceil(list.width/65781.76), math.ceil(list.height/65781.76), resources, annots)) @@ -42,5 +49,89 @@ token.luacmd("pdfvariable", function() end -- The following error message gobbles the next word as a side effect. -- This is intentional to make error-recovery easier. - error(string.format("Unknown PDF variable %s", token.scan_string())) + error(string.format("Unknown PDF variable %s", token.scan_word())) +end) +local whatsit_id = node.id'whatsit' +local whatsits = node.whatsits() +function pdf.newcolorstack(default, mode, page) + local idx = #colorstacks + colorstacks[idx + 1] = { + page = page, + mode = mode or "origin", + default = default, + page_stack = {default}, + } + return idx +end +local function do_colorstack(prop, p, n, x, y) + local colorstack = prop.colorstack + local stack + if p.is_page then + stack = colorstack.page_stack + elseif prop.last_form == resources then + stack = colorstack.form_stack + else + stack = {prop.default} + colorstack.form_stack = stack + end + if prop.action == "push" then + stack[#stack+1] = prop.data + elseif prop.action == "pop" then + assert(#stack > 1) + stack[#stack] = nil + elseif prop.action == "set" then + stack[#stack] = prop.data + end + pdf.write(colorstack.mode, stack[#stack], x, y, p) +end +local function write_colorstack() + local idx = token.scan_int() + local colorstack = colorstacks[idx + 1] + if not colorstack then + error[[Undefined colorstack]] + 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]] + 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) + node.setproperty(whatsit, { + handle = do_colorstack, + colorstack = colorstack, + action = action, + data = text, + }) + node.write(whatsit) +end +token.luacmd("pdffeedback", function() + if token.scan_keyword"colorstackinit" then + local page = token.scan_keyword'page' + or (token.scan_keyword'nopage' and false) -- If you want to pass "page" as mode + local mode = token.scan_keyword'direct' and 'direct' + or token.scan_keyword'page' and 'page' + or 'origin' + local default = token.scan_string() + tex.sprint(tostring(pdf.newcolorstack(default, mode, page))) + else + -- The following error message gobbles the next word as a side effect. + -- This is intentional to make error-recovery easier. + error(string.format("Unknown PDF feedback %s", token.scan_word())) + end +end) +token.luacmd("pdfextension", function() + if token.scan_keyword"colorstack" then + write_colorstack() + else + -- The following error message gobbles the next word as a side effect. + -- This is intentional to make error-recovery easier. + error(string.format("Unknown PDF extension %s", token.scan_word())) + end end) diff --git a/luametalatex-init.lua b/luametalatex-init.lua index afc8445..5d8e847 100644 --- a/luametalatex-init.lua +++ b/luametalatex-init.lua @@ -314,7 +314,13 @@ end -- -- unicode = {utf8 = utf8} -- utf8.byte = utf8.codepoint -kpse.set_program_name() +do + local progname + for _, a in ipairs(arg) do if a:sub(1,11) == "--progname=" then + progname = a:sub(12) + end end + kpse.set_program_name(nil, progname) +end package.searchers[2] = function(modname) local filename = kpse.find_file(modname, "kpse_lua_format", true) if not filename then diff --git a/luametalatex-nodewriter.lua b/luametalatex-nodewriter.lua index 562c010..8cb11ee 100644 --- a/luametalatex-nodewriter.lua +++ b/luametalatex-nodewriter.lua @@ -26,7 +26,7 @@ end)() local whatsithandler = (function() local whatsits = node.whatsits() local function unknown_handler(p, n, x, y, ...) - local prop = properties[n] + local prop = properties[n] or node.getproperty(n) if prop and prop.handle then prop:handle(p, n, x, y, ...) else @@ -398,7 +398,8 @@ function nodehandler.glyph(p, n, x, y, ...) local f, fid = p.vfont.font, p.vfont.fid local c = f.characters[n.char] if not c then - error[[Invalid characters]] + texio.write_nl("Missing character") + return 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) @@ -473,6 +474,25 @@ function whatsithandler.pdf_end_link(p, n, x, y, outer, _, level) if link.level ~= level then error[[Wrong link level]] end addlinkpoint(p, link, x, y, outer, true) end +local global_p, global_x, global_y +function pdf._latelua(p, x, y, func, ...) + global_p, global_x, global_y = p, x, y + return func(...) +end +function pdf.write(mode, text, x, y, p) + x, y, p = x or global_x, y or global_y, p or global_p + if mode == "direct" then + topage(p) + p.strings[#p.strings + 1] = text + elseif mode == "origin" then + write_matrix(p, 1, 0, 0, 1, x, y) + topage(p) + p.strings[#p.strings + 1] = text + write_matrix(p, 1, 0, 0, 1, -x, -y) + else + write(format('Literal type %s unsupported', mode)) + end +end local ondemandmeta = { __index = function(t, k) t[k] = {} @@ -494,7 +514,7 @@ local function writeresources(p) 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) +return function(file, n, fontdirs, usedglyphs, colorstacks) setmetatable(usedglyphs, ondemandmeta) local linkcontext = file.linkcontext if not linkcontext then @@ -502,6 +522,7 @@ return function(file, n, fontdirs, usedglyphs) file.linkcontext = linkcontext end local p = { + is_page = not not colorstacks, file = file, mode = 3, strings = {}, @@ -521,6 +542,17 @@ return function(file, n, fontdirs, usedglyphs) annots = {}, linkcontext = file.linkcontext, } + if colorstacks then + for i=1, #colorstacks do + local colorstack = colorstacks[i] + if colorstack.page then + local stack = colorstack.page_stack + if colorstack.default ~= stack[#stack] then + pdf.write(colorstack.mode, stack[#stack], 0, 0, p) + end + end + end + end nodehandler[n.id](p, n, 0, 0, n, nil, 0) -- nodehandler[n.id](p, n, 0, n.depth, n) topage(p)