Named destinations and improved outline interface
There is no support for PDFTeX's outline command yet, but the new one is much easier to use.
This commit is contained in:
parent
c83b7ffc5e
commit
7e3a4e3c74
@ -1,6 +1,7 @@
|
||||
local pdf = pdf
|
||||
local writer = require'luametalatex-nodewriter'
|
||||
local newpdf = require'luametalatex-pdf'
|
||||
local nametree = require'luametalatex-pdf-nametree'
|
||||
local pdfname, pfile
|
||||
local fontdirs = setmetatable({}, {__index=function(t, k)t[k] = pfile:getobj() return t[k] end})
|
||||
local usedglyphs = {}
|
||||
@ -23,6 +24,13 @@ local function get_pfile()
|
||||
end
|
||||
return pfile
|
||||
end
|
||||
local outline
|
||||
local function get_outline()
|
||||
if not outline then
|
||||
outline = require'luametalatex-pdf-outline'()
|
||||
end
|
||||
return outline
|
||||
end
|
||||
local properties = node.direct.properties
|
||||
token.luacmd("shipout", function()
|
||||
local pfile = get_pfile()
|
||||
@ -44,6 +52,7 @@ token.luacmd("shipout", function()
|
||||
token.scan_token()
|
||||
end, 'force', 'protected')
|
||||
local infodir = ""
|
||||
local namesdir = ""
|
||||
local catalogdir = ""
|
||||
local creationdate = os.date("D:%Y%m%d%H%M%S%z"):gsub("+0000$", "Z"):gsub("%d%d$", "'%0")
|
||||
local function write_infodir(p)
|
||||
@ -87,7 +96,26 @@ callback.register("stop_run", function()
|
||||
end
|
||||
pfile.root = pfile:getobj()
|
||||
pfile.version = string.format("%i.%i", pdf.variable.majorversion, pdf.variable.minorversion)
|
||||
local destnames = {}
|
||||
for k,obj in next, dests do
|
||||
if pfile:written(obj) then
|
||||
if type(k) == 'string' then
|
||||
destnames[k] = obj .. ' 0 R'
|
||||
end
|
||||
else
|
||||
texio.write_nl("Warning: Undefined destination %q", tostring(k))
|
||||
end
|
||||
end
|
||||
if next(destnames) then
|
||||
namesdir = string.format("/Dests %i 0 R%s", nametree(destnames, pfile), namesdir or '')
|
||||
end
|
||||
if namesdir then
|
||||
catalogdir = string.format("/Names<<%s>>%s", namesdir, catalogdir)
|
||||
end
|
||||
local pages = #pfile.pages
|
||||
if outline then
|
||||
catalogdir = string.format("/Outlines %i 0 R%s", outline:write(pfile), catalogdir)
|
||||
end
|
||||
pfile:indirect(pfile.root, string.format([[<</Type/Catalog/Version/%s/Pages %i 0 R%s>>]], pfile.version, pfile:writepages(), catalogdir))
|
||||
pfile.info = write_infodir(pfile)
|
||||
local size = pfile:close()
|
||||
@ -151,10 +179,10 @@ local function projected(m, x, y, w)
|
||||
return x*m[1] + y*m[3] + w*m[5], x*m[2] + y*m[4] + w*m[6]
|
||||
end
|
||||
|
||||
local function get_action_attr(p, action)
|
||||
local function get_action_attr(p, action, is_link)
|
||||
local action_type = action.action_type
|
||||
if action_type == 3 then return action.data end
|
||||
local action_attr = "/Subtype/Link/A<<"
|
||||
local action_attr = is_link and "/Subtype/Link/A<<" or "<<"
|
||||
local file = action.file
|
||||
if file then
|
||||
action_attr = action_attr .. '/F' .. pdf_string(file)
|
||||
@ -190,7 +218,7 @@ end
|
||||
local function write_link(p, link)
|
||||
local quads = link.quads
|
||||
local minX, maxX, minY, maxY = math.huge, -math.huge, math.huge, -math.huge
|
||||
local attr = link.attr .. get_action_attr(p, link.action)
|
||||
local attr = link.attr .. get_action_attr(p, link.action, true)
|
||||
assert(#quads%8==0)
|
||||
local quadStr = {}
|
||||
for i=1,#quads,8 do
|
||||
@ -421,7 +449,7 @@ end
|
||||
local function maybe_gobble_cmd(cmd)
|
||||
local t = token.scan_token()
|
||||
if t.command ~= cmd then
|
||||
token.put_next(cmd)
|
||||
token.put_next(t)
|
||||
end
|
||||
end
|
||||
token.luacmd("pdffeedback", function()
|
||||
@ -500,6 +528,8 @@ token.luacmd("pdfextension", function(_, imm)
|
||||
infodir = infodir .. token.scan_string()
|
||||
elseif token.scan_keyword"catalog" then
|
||||
catalogdir = catalogdir .. ' ' .. token.scan_string()
|
||||
elseif token.scan_keyword"names" then
|
||||
namesdir = namesdir .. ' ' .. token.scan_string()
|
||||
elseif token.scan_keyword"obj" then
|
||||
local pfile = get_pfile()
|
||||
if token.scan_keyword"reserveobjnum" then
|
||||
@ -532,6 +562,25 @@ token.luacmd("pdfextension", function(_, imm)
|
||||
handle = do_refobj,
|
||||
})
|
||||
node.write(whatsit)
|
||||
elseif token.scan_keyword"outline" then
|
||||
local pfile = get_pfile()
|
||||
local attr = token.scan_keyword'attr' and token.scan_string() or ''
|
||||
local action
|
||||
if token.scan_keyword"useobjnum" then
|
||||
action = token.scan_int()
|
||||
else
|
||||
local actionobj = scan_action()
|
||||
action = pfile:indirect(nil, get_action_attr(pfile, actionobj))
|
||||
end
|
||||
if token.scan_keyword'level' then
|
||||
local level = token.scan_int()
|
||||
local open = token.scan_keyword'open'
|
||||
local outline = get_outline()
|
||||
local title = token.scan_string()
|
||||
outline:add(pdf_string(title), action, level, open, attr)
|
||||
else
|
||||
error[[Legacy outline not yet supported]]
|
||||
end
|
||||
elseif token.scan_keyword"dest" then
|
||||
local id
|
||||
if token.scan_keyword'num' then
|
||||
|
53
luametalatex-pdf-nametree.lua
Normal file
53
luametalatex-pdf-nametree.lua
Normal file
@ -0,0 +1,53 @@
|
||||
local min = math.min
|
||||
local format = string.format
|
||||
local concat = table.concat
|
||||
local move = table.move
|
||||
local function write(pdf, tree, escaped, step)
|
||||
local nextcount = (#tree-1)//6+1
|
||||
for i=1, nextcount do
|
||||
if #tree > 6 then
|
||||
tree[i] = pdf:indirect(nil, format('<</Limits[%s %s]/Kids[%s 0 R]>>', escaped[step*(i-1)+1], escaped[step*i] or escaped[#escaped], concat(tree, ' 0 R ', 6*i-5, min(#tree, 6*i))))
|
||||
else
|
||||
return pdf:indirect(nil, format('<</Kids[%s 0 R]>>', concat(tree, ' 0 R ', 6*i-5, #tree)))
|
||||
end
|
||||
end
|
||||
move(tree, #tree+1, 2*#tree-nextcount, nextcount+1)
|
||||
return write(pdf, tree, escaped, step*6)
|
||||
end
|
||||
local function pdf_string(s)
|
||||
-- Emulate other engines here: If looks like an escaped string, treat it as such. Otherwise, add parenthesis.
|
||||
return s:match("^%(.*%)$") or s:match("^<.*>$") or '(' .. s .. ')'
|
||||
end
|
||||
local serialized = {}
|
||||
return function(values, pdf)
|
||||
local tree = {}
|
||||
for k in next, values do
|
||||
if type(k) ~= "string" then
|
||||
error[[Invalid entry in nametree]] -- Might get ignored in a later version
|
||||
end
|
||||
tree[#tree+1] = k
|
||||
end
|
||||
table.sort(tree)
|
||||
local total = #tree
|
||||
local newtree = {}
|
||||
for i=0,(total-1)//6 do
|
||||
for j=1, 6 do
|
||||
local key = tree[6*i+j]
|
||||
if key then
|
||||
local value = values[key]
|
||||
key = pdf_string(key)
|
||||
tree[6*i+j] = key
|
||||
serialized[2*j-1] = key
|
||||
serialized[2*j] = value
|
||||
else
|
||||
serialized[2*j-1], serialized[2*j] = nil, nil
|
||||
end
|
||||
end
|
||||
if total > 6 then
|
||||
newtree[i+1] = pdf:indirect(nil, format('<</Limits[%s %s]/Names[%s]>>', tree[6*i+1], tree[6*i+6] or tree[total], concat(serialized, ' ')))
|
||||
else
|
||||
return pdf:indirect(nil, format('<</Names[%s]>>', concat(serialized, ' ')))
|
||||
end
|
||||
end
|
||||
return write(pdf, newtree, tree, 36)
|
||||
end
|
100
luametalatex-pdf-outline.lua
Normal file
100
luametalatex-pdf-outline.lua
Normal file
@ -0,0 +1,100 @@
|
||||
local outline = {
|
||||
{ title = "title 1", attr = "", open = true, level = 5,
|
||||
{ ... },
|
||||
}
|
||||
}
|
||||
local function add_outline(outline, title, action, level, open, attr)
|
||||
local entry = {title = title, action = action, attr = attr, open = open, level = level}
|
||||
-- Now find the right nesting level. We have to deal with non-continuous
|
||||
-- levels, so we search the last entry which still had a smaller level
|
||||
-- and append under that
|
||||
local parent
|
||||
repeat
|
||||
parent = outline
|
||||
outline = outline[#outline]
|
||||
until not outline or outline.level >= level
|
||||
parent[#parent + 1] = entry
|
||||
end
|
||||
local function assign_objnum(pdf, outline)
|
||||
local objnum = pdf:getobj()
|
||||
outline.objnum = objnum
|
||||
local cur
|
||||
for i=1,#outline do
|
||||
local prev = cur
|
||||
cur = outline[i]
|
||||
cur.parent = objnum
|
||||
assign_objnum(pdf, cur)
|
||||
if prev then
|
||||
cur.prev = prev.objnum
|
||||
prev.next = cur.objnum
|
||||
end
|
||||
end
|
||||
if outline[1] then
|
||||
outline.first, outline.last = outline[1].objnum, outline[#outline].objnum
|
||||
end
|
||||
end
|
||||
local function get_count(pdf, outline)
|
||||
local count = 0
|
||||
for i=1,#outline do
|
||||
local child = outline[i]
|
||||
local sub = get_count(pdf, child)
|
||||
local open = child.open
|
||||
child.count = sub ~= 0 and (open and sub or -sub) or nil
|
||||
count = count + 1 + (open and sub or 0)
|
||||
end
|
||||
return count
|
||||
end
|
||||
local function write_objects(pdf, outline)
|
||||
local content = "<<"
|
||||
local title = outline.title
|
||||
if title then
|
||||
content = string.format("%s/Title%s", content, title)
|
||||
end
|
||||
local parent = outline.parent
|
||||
if parent then
|
||||
content = string.format("%s/Parent %i 0 R", content, parent)
|
||||
end
|
||||
local prev = outline.prev
|
||||
if prev then
|
||||
content = string.format("%s/Prev %i 0 R", content, prev)
|
||||
end
|
||||
local next = outline.next
|
||||
if next then
|
||||
content = string.format("%s/Next %i 0 R", content, next)
|
||||
end
|
||||
local first = outline.first
|
||||
if first then
|
||||
content = string.format("%s/First %i 0 R", content, first)
|
||||
end
|
||||
local last = outline.last
|
||||
if last then
|
||||
content = string.format("%s/Last %i 0 R", content, last)
|
||||
end
|
||||
local action = outline.action
|
||||
if action then
|
||||
content = string.format("%s/A %i 0 R", content, action)
|
||||
end
|
||||
local count = outline.count
|
||||
if count then
|
||||
content = string.format("%s/Count %i", content, count)
|
||||
end
|
||||
content = content .. (outline.attr or '') .. ">>"
|
||||
pdf:indirect(outline.objnum, content)
|
||||
for i=1,#outline do
|
||||
write_objects(pdf, outline[i])
|
||||
end
|
||||
end
|
||||
local function write_outline(outline, pdf)
|
||||
assign_objnum(pdf, outline)
|
||||
local count = get_count(pdf, outline)
|
||||
outline.count = count == #outline and count or nil
|
||||
write_objects(pdf, outline)
|
||||
return outline.objnum
|
||||
end
|
||||
local meta = {__index = {
|
||||
write = write_outline,
|
||||
add = add_outline,
|
||||
}}
|
||||
return function()
|
||||
return setmetatable({}, meta)
|
||||
end
|
Loading…
Reference in New Issue
Block a user