]> mj.ucw.cz Git - ucwmac.git/blob - ucw-pdfmeta.lua
ucw-ref: Fixed bug in \pageid
[ucwmac.git] / ucw-pdfmeta.lua
1 -- A library for writing PDF metadata
2
3 luatexbase.provides_module {
4         name    = 'pdfmeta',
5         version = '0.0',
6         author  = 'Martin Mareš',
7 }
8
9 pdfmeta = { }
10
11 -- Debugging function for dumping a table
12
13 local function do_dump_table(t, key, depth)
14         local tmp = string.rep("  ", depth)
15         if key then
16                 tmp = tmp .. key .. ": "
17         end
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)
22                 end
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)
28         else
29                 tmp = tmp .. "???"
30         end
31         texio.write(tmp .. "\n")
32 end
33
34 local function dump_table(t)
35         do_dump_table(t, nil, 0)
36 end
37
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.
42
43 local function pdf_string(s)
44         local u16 = {}
45         local str = {}
46         local strok = true
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))
52                         else
53                                 table.insert(str, string.char(x))
54                         end
55                 else
56                         strok = false
57                 end
58                 if x < 0x10000 then
59                         table.insert(u16, string.format("%04x", x))
60                 else
61                         local y = x - 0x10000
62                         local h = y/1024 + 0xd800
63                         local l = y%1024 + 0xdc00
64                         table.insert(u16, string.format("%04x%04x", h, l))
65                 end
66         end
67
68         if strok then
69                 return "(" .. table.concat(str) .. ")"
70         else
71                 table.insert(u16, ">")
72                 return table.concat(u16)
73         end
74 end
75
76 local function pdf_encode_dict_body(d)
77         local t = {}
78         for k, v in pairs(d) do
79                 table.insert(t, "/" .. k .. " " .. v)
80         end
81         return table.concat(t, "\n")
82 end
83
84 local function pdf_encode_dict(d)
85         return '<< ' .. pdf_encode_dict_body(d) .. ' >>'
86 end
87
88 -- Metadata dictionary
89
90 pdfmeta.info = { }
91 pdfmeta.catalog = { }
92
93 function pdfmeta.set_info(key, val)
94         pdfmeta.info[key] = pdf_string(val)
95 end
96
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))
101 end
102
103 luatexbase.add_to_callback('finish_pdffile', write_metas, 'Write all PDF metadata')
104
105 -- Document outline
106
107 pdfmeta.outline = { children = {} }
108 pdfmeta.outline_stack = { pdfmeta.outline }
109
110 function pdfmeta.add_outline(key, dest, text)
111         local stk = pdfmeta.outline_stack
112         local level = 1
113         local p, r
114
115         for k in string.gmatch(key, '%.*(%d+)') do
116                 level = level+1
117         end
118         while #stk >= level do
119                 table.remove(stk)
120         end
121         while #stk < level do
122                 p = stk[#stk]
123                 r = { children = {} }
124                 table.insert(p.children, r)
125                 table.insert(stk, r)
126         end
127         r.dest = dest
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))
131         r.text = text;
132 end
133
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
140         end
141 end
142
143 local function outline_ref(r)
144         if r then
145                 return r.obj .. ' 0 R'
146         else
147                 tex.error('Asked to format null reference')
148                 return 'nil'
149         end
150 end
151
152 local function gen_outline(r, parent)
153         d = {}
154         r.dict = d
155
156         if not parent then
157                 d.Type = '/Outlines'
158                 d.Count = r.num_descendants
159         else
160                 d.Parent = outline_ref(parent)
161                 d.Count = r.num_descendants
162         end
163
164         if r.text then
165                 d.Title = pdf_string(r.text)
166                 d.A = '<< /D (' .. r.dest .. ') /S /GoTo >>'
167         end
168
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)
174                         if i>1 then
175                                 child.dict.Prev = outline_ref(r.children[i-1])
176                         end
177                         if i<#r.children then
178                                 child.dict.Next = outline_ref(r.children[i+1])
179                         end
180                 end
181         end
182 end
183
184 local function write_outline(r)
185         pdf.obj {
186                 type = 'raw',
187                 immediate = true,
188                 objnum = r.obj,
189                 string = pdf_encode_dict(r.dict),
190         }
191         for i, child in pairs(r.children) do
192                 write_outline(child)
193         end
194 end
195
196 function pdfmeta.make_outline()
197         local o = pdfmeta.outline
198         reserve_objects(o)
199         gen_outline(o, nil)
200         -- dump_table(o)
201         write_outline(o)
202         pdfmeta.catalog.Outlines = outline_ref(o)
203 end