From efedcba3e143da3ba1b53554d0cfd88dcf4fd956 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20Fabian=20Kr=C3=BCger?= Date: Mon, 29 Jun 2020 19:04:09 +0200 Subject: [PATCH] [use/save]boxresource, the Lua part --- luametalatex-back-pdf.lua | 31 ++++++++++- luametalatex-nodewriter.lua | 39 +++++++------- luametalatex-pdf-savedbox.lua | 99 +++++++++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+), 21 deletions(-) create mode 100644 luametalatex-pdf-savedbox.lua diff --git a/luametalatex-back-pdf.lua b/luametalatex-back-pdf.lua index 5b7bde8..5a5c4fd 100644 --- a/luametalatex-back-pdf.lua +++ b/luametalatex-back-pdf.lua @@ -47,11 +47,29 @@ token.luacmd("shipout", function() local out, resources, annots = writer(pfile, list, fontdirs, usedglyphs, colorstacks) cur_page = nil 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)) + pfile:indirect(page, string.format([[<>%s>>]], parent, content, -math.ceil(list.depth/65781.76), math.ceil(list.width/65781.76), math.ceil(list.height/65781.76), resources, annots)) node.flush_list(list) token.put_next(token.create'immediateassignment', token.create'global', token.create'deadcycles', token.create(0x30), token.create'relax') token.scan_token() end, 'force', 'protected') + +local savedbox = require'luametalatex-pdf-savedbox' +local savedbox_save = savedbox.save +function tex.saveboxresource(n, attr, resources, immediate, type, margin, pfile) + if not node.type(n) then + n = tonumber(n) + if not n then + error[[Invalid argument to saveboxresource]] + end + token.put_next(token.create'box', token.new(n, token.command_id'char_given')) + n = token.scan_list() + end + margin = margin or tex.sp'1bp' -- FIXME: default margin variable + return savedbox_save(pfile or get_pfile(), n, attr, resources, immediate, type, margin, fontdirs, usedglyphs) +end +tex.useboxresource = savedbox.use +-- TODO: savedbox TeX interface + local infodir = "" local namesdir = "" local catalogdir = "" @@ -257,12 +275,16 @@ local function addlinkpoint(p, link, x, y, list, kind) end end local function linkcontext_set(linkcontext, p, x, y, list, level, kind) + if not p.is_page then return end for _,l in ipairs(linkcontext) do if l.level == level then addlinkpoint(p, l, x, y, list, level, kind) end end end function do_start_link(prop, p, n, x, y, outer, _, level) + if not p.is_page then + error[[No link allowed here]] + end local links = p.linkcontext if not links then links = {set = linkcontext_set} @@ -273,10 +295,14 @@ function do_start_link(prop, p, n, x, y, outer, _, level) addlinkpoint(p, link, x, y, outer, 'start') end function do_end_link(prop, p, n, x, y, outer, _, level) + if not p.is_page then + error[[No link allowed here]] + end local links = p.linkcontext if not links then error"No link here to end" end local link = links[#links] 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 @@ -367,9 +393,10 @@ local function do_colorstack(prop, p, n, x, y) local stack if p.is_page then stack = colorstack.page_stack - elseif prop.last_form == resources then + elseif colorstack.last_form == p.resources then stack = colorstack.form_stack else + colorstack.last_form = p.resources stack = {prop.default} colorstack.form_stack = stack end diff --git a/luametalatex-nodewriter.lua b/luametalatex-nodewriter.lua index 147ba06..b6ce05d 100644 --- a/luametalatex-nodewriter.lua +++ b/luametalatex-nodewriter.lua @@ -77,6 +77,7 @@ end local function sp2bp(sp) return sp/65781.76 end +local fontnames = setmetatable({}, {__index = function(t, k) local res = format("F%i", k) t[k] = res return res end}) local topage local function totext(p, fid) local last = p.mode @@ -91,8 +92,12 @@ local function totext(p, fid) 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, p.font.extend, p.font.squeeze, p.font.slant = 0, 0, 0, 0, 0, 1, 1, 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. + + if not f.parent then f.parent = pdf.getfontname(fid) end + p.resources.Font[fontnames[f.parent]] = p.fontdirs[f.parent] + p.strings[#p.strings+1] = format("/F%i %f Tf 0 Tr", f.parent, sp2bp(f.size)) -- TODO: Setting the mode, width, etc. + p.font.usedglyphs = p.usedglyphs[f.parent] + p.font.fid = fid p.font.font = f local need_tm = false @@ -257,6 +262,7 @@ local user_rule = rulesubtypes.user local empty_rule = rulesubtypes.empty local outline_rule = rulesubtypes.outline local ship_img = require'luametalatex-pdf-image'.ship +local ship_box = require'luametalatex-pdf-savedbox'.ship -- print(require'inspect'(node.subtypes('glue'))) -- print(require'inspect'(node.fields('glue'))) -- print(require'inspect'(node.fields('rule'))) @@ -266,10 +272,10 @@ function nodehandler.rule(p, n, x, y, outer) if getheight(n) == -1073741824 then setheight(n, getheight(outer)) end if getdepth(n) == -1073741824 then setdepth(n, getdepth(outer)) end local sub = getsubtype(n) + if getwidth(n) <= 0 or getdepth(n) + getheight(n) <= 0 then return end if sub == box_rule then - error[[We can't handle boxes yet]] + ship_box(getdata(n), p, n, x, y) elseif sub == image_rule then - if getwidth(n) <= 0 or getdepth(n) + getheight(n) <= 0 then return end ship_img(getdata(n), p, n, x, y) elseif sub == empty_rule then elseif sub == user_rule then @@ -277,7 +283,6 @@ function nodehandler.rule(p, n, x, y, outer) elseif sub == outline_rule then error[[We can't handle outline rules yet]] else - if getwidth(n) <= 0 or getdepth(n) + getheight(n) <= 0 then return end topage(p) p.strings[#p.strings+1] = gsub(format("%f %f %f %f re f", sp2bp(x), sp2bp(y - getdepth(n)), sp2bp(getwidth(n)), sp2bp(getdepth(n) + getheight(n))), '%.?0+ ', ' ') end @@ -453,13 +458,13 @@ function nodehandler.glyph(p, n, x, y, ...) else p.pending[#p.pending+1] = pdf_escape(string.pack('>H', index)) end - if not p.usedglyphs[index] then - p.usedglyphs[index] = {index, math.floor(c.width * 1000 / f.size / p.font.extend + .5), c.tounicode} + if not p.font.usedglyphs[index] then + p.font.usedglyphs[index] = {index, math.floor(c.width * 1000 / f.size / p.font.extend + .5), c.tounicode} end else p.pending[#p.pending+1] = pdf_escape(string.char(getchar(n))) - if not p.usedglyphs[getchar(n)] then - p.usedglyphs[getchar(n)] = {getchar(n), math.floor(c.width * 1000 / f.size / p.font.extend + .5), c.tounicode} + if not p.font.usedglyphs[getchar(n)] then + p.font.usedglyphs[getchar(n)] = {getchar(n), math.floor(c.width * 1000 / f.size / p.font.extend + .5), c.tounicode} end end p.pos.x = p.pos.x + math.floor(getwidth(n)*(1+getexpansion(n)/1000000)+.5) @@ -521,7 +526,7 @@ local ondemandmeta = { } local function writeresources(p) local resources = p.resources - local result = {"<<"} + 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 @@ -530,11 +535,9 @@ local function writeresources(p) 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, colorstacks) +local function nodewriter(file, n, fontdirs, usedglyphs, colorstacks) n = todirect(n) setmetatable(usedglyphs, ondemandmeta) local p = { @@ -544,12 +547,6 @@ return function(file, n, fontdirs, usedglyphs, colorstacks) 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}, @@ -557,6 +554,8 @@ return function(file, n, fontdirs, usedglyphs, colorstacks) resources = setmetatable({}, ondemandmeta), annots = {}, linkcontext = file.linkcontext, + fontdirs = fontdirs, + usedglyphs = usedglyphs, } if colorstacks then for i=1, #colorstacks do @@ -574,3 +573,5 @@ return function(file, n, fontdirs, usedglyphs, colorstacks) topage(p) return concat(p.strings, '\n'), writeresources(p), (p.annots[1] and string.format("/Annots[%s]", table.concat(p.annots, ' ')) or "") end +require'luametalatex-pdf-savedbox':init_nodewriter(nodewriter) +return nodewriter diff --git a/luametalatex-pdf-savedbox.lua b/luametalatex-pdf-savedbox.lua new file mode 100644 index 0000000..dabcffe --- /dev/null +++ b/luametalatex-pdf-savedbox.lua @@ -0,0 +1,99 @@ +local writer -- = require'luametalatex-nodewriter' -- This would introduce some cyclic dependency + +-- XForms currently have the form {width, height, depth, objnum, attributes, list, margin} +local xforms = {} + +local function to_bp(sp) return sp/65781.76 end + +local function shipout(pfile, xform, fontdirs, usedglyphs) + local list, margin = xform.list, xform.margin + if not list then return xform.objnum end -- Already shipped out + local last_page = cur_page cur_page = nil + local out, resources, annots = writer(pfile, list, fontdirs, usedglyphs) + cur_page = last_page + assert(annots == '') + local dict = string.format('/Subtype/Form/BBox[%f %f %f %f]/Resources<<%s%s>>%s', -to_bp(margin), -to_bp(list.depth+margin), to_bp(list.width+margin), to_bp(list.height+margin), resources, xform.resources or '', xform.attributes or '') + node.flush_list(list) + xform.list = nil + local objnum = pfile:stream(xform.objnum, dict, out) + xform.objnum = objnum + return objnum +end + +local function save(pfile, n, attr, resources, immediate, type, margin, fontdirs, usedglyphs) + local index = #xforms+1 + local xform = { + list = assert(n, 'List required for saveboxresource'), + width = n.width, + height = n.height, + depth = n.depth, + attributes = attr, + resources = resources, + margin = margin, + -- type = type, -- TODO: Not yet used. Do we need this at all? + } + xforms[index] = xform + if immediate then + shipout(pfile, xform, fontdirs, usedglyphs) + end + return index +end + +local function adjust_sizes(width, height, depth, real_width, real_height, real_depth) + if not depth then + if height then + local scale = height/real_height + depth = (real_depth*scale + .5)//1 + width = width or (real_width*scale + .5)//1 + elseif width then + local scale = width/real_width + depth = (real_depth*scale + .5)//1 + height = (real_height*scale + .5)//1 + else + width, height, depth = real_width, real_height, real_depth + end + elseif height then + width = width or (real_width*(height+depth)/(real_height+real_depth) + .5)//1 + else + width = width or real_width + local scale = width/real_width + height = ((real_depth+real_height)*scale + .5)//1 - depth + end + return width, height, depth +end + +local ruleid = node.id'rule' +local ruletypes = node.subtypes'rule' +local boxrule +for n, name in next, ruletypes do + if name == 'box' then boxrule = n break end +end + +local function use(index, width, height, depth) + local xform = xforms[index] + if not xform then return nil, nil, nil, nil end + width, height, depth = adjust_sizes(width, height, depth, xform.width, xform.height, xform.depth) + local n = node.direct.new(ruleid, boxrule) + node.direct.setdata(n, index) + node.direct.setwhd(n, width, height, depth) + return node.direct.tonode(n), width, height, depth +end + +local function do_box(data, p, n, x, y) + local xform = assert(xforms[data], 'Invalid XForm') + local objnum = shipout(p.file, xform, p.fontdirs, p.usedglyphs) + local width, height, depth = node.direct.getwhd(n) + local xscale, yscale = width / xform.width, (height+depth) / (xform.height+xform.depth) + p.resources.XObject['Fm' .. tostring(data)] = objnum + pdf.write('page', string.format('q %f 0 0 %f %f %f cm /Fm%i Do Q', + xscale, yscale, + to_bp(x), to_bp(y-depth+yscale*xform.depth), + data), nil, nil, p) +end + +return { + save = save, + use = use, + ship = do_box, + init_nodewriter = function(t, nodewriter) writer, t.init_nodewriter = nodewriter, nil end, +}