1 -- A library for writing PDF metadata
3 luatexbase.provides_module {
6 author = 'Martin Mareš',
11 -- Debugging function for dumping a table
13 local function do_dump_table(t, key, depth)
14 local tmp = string.rep(" ", depth)
16 tmp = tmp .. key .. ": "
18 if type(t) == "table" then
19 texio.write(tmp .. "{\n")
20 for k, v in pairs(t) do
21 do_dump_table(v, k, depth+1)
23 tmp = string.rep(" ", depth) .. "}"
24 elseif type(t) == "number" then
25 tmp = tmp .. tostring(t)
26 elseif type(t) == "string" then
27 tmp = tmp .. string.format("%q", t)
31 texio.write(tmp .. "\n")
34 local function dump_table(t)
35 do_dump_table(t, nil, 0)
38 -- PDF metadata can be encoded either in an ancient PDFEncoding, which covers
39 -- only a small subset of Unicode characters, or in UTF-16-BE. We use the latter
40 -- for anything outside basic ASCII. It is tempting to use "lualibs-unicode"
41 -- for the conversion, but alas, it is too broken.
43 local function pdf_string(s)
47 table.insert(u16, "<feff")
48 for x in string.utfvalues(s) do
49 if x >= 0x20 and x < 0x7f and x ~= 0x28 and x ~= 0x29 and x ~= 0x5c then
50 if x == 0x28 or x == 0x29 or x == 0x5c then
51 table.insert(str, "\\" .. string.char(x))
53 table.insert(str, string.char(x))
59 table.insert(u16, string.format("%04x", x))
62 local h = y/1024 + 0xd800
63 local l = y%1024 + 0xdc00
64 table.insert(u16, string.format("%04x%04x", h, l))
69 return "(" .. table.concat(str) .. ")"
71 table.insert(u16, ">")
72 return table.concat(u16)
76 local function pdf_encode_dict_body(d)
78 for k, v in pairs(d) do
79 table.insert(t, "/" .. k .. " " .. v)
81 return table.concat(t, "\n")
84 local function pdf_encode_dict(d)
85 return '<< ' .. pdf_encode_dict_body(d) .. ' >>'
88 -- Metadata dictionary
93 function pdfmeta.set_info(key, val)
94 pdfmeta.info[key] = pdf_string(val)
97 local function write_metas()
98 pdfmeta.make_outline()
99 pdf.setinfo(pdf_encode_dict_body(pdfmeta.info))
100 pdf.setcatalog(pdf_encode_dict_body(pdfmeta.catalog))
103 luatexbase.add_to_callback('finish_pdffile', write_metas, 'Write all PDF metadata')
107 pdfmeta.outline = { children = {} }
108 pdfmeta.outline_stack = { pdfmeta.outline }
110 function pdfmeta.add_outline(key, dest, text)
111 local stk = pdfmeta.outline_stack
115 for k in string.gmatch(key, '%.*(%d+)') do
118 while #stk >= level do
121 while #stk < level do
123 r = { children = {} }
124 table.insert(p.children, r)
128 text = string.gsub(text, '~', unicode.utf8.char(160))
129 text = string.gsub(text, '%-%-%-', unicode.utf8.char(8212))
130 text = string.gsub(text, '%-%-', unicode.utf8.char(8211))
134 local function reserve_objects(r)
135 r.obj = pdf.reserveobj()
136 r.num_descendants = 0
137 for i, child in pairs(r.children) do
138 reserve_objects(child)
139 r.num_descendants = r.num_descendants + child.num_descendants + 1
143 local function outline_ref(r)
145 return r.obj .. ' 0 R'
147 tex.error('Asked to format null reference')
152 local function gen_outline(r, parent)
158 d.Count = r.num_descendants
160 d.Parent = outline_ref(parent)
161 d.Count = r.num_descendants
165 d.Title = pdf_string(r.text)
166 d.A = '<< /D (' .. r.dest .. ') /S /GoTo >>'
169 if r.children[1] then
170 d.First = outline_ref(r.children[1])
171 d.Last = outline_ref(r.children[#r.children])
172 for i, child in pairs(r.children) do
173 gen_outline(child, r)
175 child.dict.Prev = outline_ref(r.children[i-1])
177 if i<#r.children then
178 child.dict.Next = outline_ref(r.children[i+1])
184 local function write_outline(r)
189 string = pdf_encode_dict(r.dict),
191 for i, child in pairs(r.children) do
196 function pdfmeta.make_outline()
197 local o = pdfmeta.outline
202 pdfmeta.catalog.Outlines = outline_ref(o)