1if not modules then modules = { } end modules ['lpdf-xmp'] = {
2 version = 1.001,
3 comment = "companion to lpdf-ini.mkiv",
4 author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5 copyright = "PRAGMA ADE / ConTeXt Development Team",
6 license = "see context related readme files",
7 comment = "with help from Peter Rolf",
8}
9
10local tostring, type = tostring, type
11local format, gsub = string.format, string.gsub
12local utfchar = utf.char
13local xmlfillin = xml.fillin
14local md5HEX = md5.HEX
15
16local trace_xmp = false trackers.register("backend.xmp", function(v) trace_xmp = v end)
17local trace_info = false trackers.register("backend.info", function(v) trace_info = v end)
18
19local report_xmp = logs.reporter("backend","xmp")
20local report_info = logs.reporter("backend","info")
21
22local backends, lpdf = backends, lpdf
23
24local codeinjections = backends.pdf.codeinjections
25
26local pdfdictionary = lpdf.dictionary
27local pdfconstant = lpdf.constant
28local pdfreference = lpdf.reference
29local pdfflushstreamobject = lpdf.flushstreamobject
30
31local pdfgetmetadata = lpdf.getmetadata
32
33
34
35
36local xpacket = format ( [[
37<?xpacket begin="%s" id="W5M0MpCehiHzreSzNTczkc9d"?>
38
39%%s
40
41<?xpacket end="w"?>]], utfchar(0xFEFF) )
42
43local mapping = {
44
45 ["ConTeXt.Jobname"] = { "context", "rdf:Description/pdfx:ConTeXt.Jobname" },
46 ["ConTeXt.Time"] = { "date", "rdf:Description/pdfx:ConTeXt.Time" },
47 ["ConTeXt.Url"] = { "context", "rdf:Description/pdfx:ConTeXt.Url" },
48 ["ConTeXt.Support"] = { "context", "rdf:Description/pdfx:ConTeXt.Support" },
49 ["ConTeXt.Version"] = { "context", "rdf:Description/pdfx:ConTeXt.Version" },
50 ["ConTeXt.LMTX"] = { "context", "rdf:Description/pdfx:ConTeXt.LMTX" },
51 ["TeX.Support"] = { "metadata","rdf:Description/pdfx:TeX.Support" },
52 ["LuaTeX.Version"] = { "metadata","rdf:Description/pdfx:LuaTeX.Version" },
53 ["LuaTeX.Functionality"] = { "metadata","rdf:Description/pdfx:LuaTeX.Functionality" },
54 ["LuaTeX.LuaVersion"] = { "metadata","rdf:Description/pdfx:LuaTeX.LuaVersion" },
55 ["LuaTeX.Platform"] = { "metadata","rdf:Description/pdfx:LuaTeX.Platform" },
56 ["ID"] = { "id", "rdf:Description/pdfx:ID" },
57
58 ["Keywords"] = { "metadata","rdf:Description/pdf:Keywords" },
59 ["Producer"] = { "metadata","rdf:Description/pdf:Producer" },
60
61
62 ["Author"] = { "metadata","rdf:Description/dc:creator/rdf:Seq/rdf:li" },
63 ["Format"] = { "metadata","rdf:Description/dc:format" },
64 ["Subject"] = { "metadata","rdf:Description/dc:description/rdf:Alt/rdf:li" },
65 ["Title"] = { "metadata","rdf:Description/dc:title/rdf:Alt/rdf:li" },
66
67 ["CreateDate"] = { "date", "rdf:Description/xmp:CreateDate" },
68 ["CreationDate"] = { "date", "rdf:Description/xmp:CreationDate" },
69 ["Creator"] = { "metadata","rdf:Description/xmp:CreatorTool" },
70 ["MetadataDate"] = { "date", "rdf:Description/xmp:MetadataDate" },
71 ["ModDate"] = { "date", "rdf:Description/xmp:ModDate" },
72 ["ModifyDate"] = { "date", "rdf:Description/xmp:ModifyDate" },
73
74 ["DocumentID"] = { "id", "rdf:Description/xmpMM:DocumentID" },
75 ["InstanceID"] = { "id", "rdf:Description/xmpMM:InstanceID" },
76 ["RenditionClass"] = { "pdf", "rdf:Description/xmpMM:RenditionClass" },
77 ["VersionID"] = { "pdf", "rdf:Description/xmpMM:VersionID" },
78
79
80 ["GTS_PDFXVersion"] = { "pdf", "rdf:Description/pdfxid:GTS_PDFXVersion" },
81
82
83
84 ["Marked"] = { "pdf", "rdf:Description/xmpRights:Marked" },
85
86
87 ["WebStatement"] = { "metadata", "rdf:Description/xmpRights:WebStatement" },
88
89 ["AuthorsPosition"] = { "metadata", "rdf:Description/photoshop:AuthorsPosition" },
90 ["Copyright"] = { "metadata", "rdf:Description/photoshop:Copyright" },
91 ["CaptionWriter"] = { "metadata", "rdf:Description/photoshop:CaptionWriter" },
92}
93
94lpdf.setsuppressoptionalinfo (
95 0
96 + 1
97 + 2
98 + 4
99 + 8
100 + 16
101 + 32
102 + 64
103 + 128
104 + 256
105
106)
107
108local included = backends.included
109local lpdfid = lpdf.id
110
111function lpdf.id()
112 return lpdfid(included.date)
113end
114
115local settrailerid = lpdf.settrailerid
116
117local trailerid = nil
118local dates = nil
119
120local function update()
121 if trailer_id then
122 local b = toboolean(trailer_id) or trailer_id == ""
123 if b then
124 trailer_id = "This file is processed by ConTeXt and LuaTeX."
125 else
126 trailer_id = tostring(trailer_id)
127 end
128 local h = md5HEX(trailer_id)
129 if b then
130 report_info("using frozen trailer id")
131 else
132 report_info("using hashed trailer id %a (%a)",trailer_id,h)
133 end
134 settrailerid(format("[<%s> <%s>]",h,h))
135 end
136
137 local t = type(dates)
138 if t == "number" or t == "string" then
139 local d = converters.totime(dates)
140 if d then
141 included.date = true
142 included.id = "fake"
143 report_info("forced date/time information %a will be used",lpdf.settime(d))
144 settrailerid(false)
145 return
146 end
147 if t == "string" then
148 dates = toboolean(dates)
149 included.date = dates
150 if dates ~= false then
151 included.id = true
152 else
153 report_info("no date/time but fake id information will be added")
154 settrailerid(true)
155 included.id = "fake"
156 end
157 end
158 end
159end
160
161function lpdf.settrailerid(v) trailerid = v end
162function lpdf.setdates (v) dates = v end
163
164lpdf.registerdocumentfinalizer(update,"trailer id and dates",1)
165
166directives.register("backend.trailerid", lpdf.settrailerid)
167directives.register("backend.date", lpdf.setdates)
168
169local function permitdetail(what)
170 local m = mapping[what]
171 if m then
172 return included[m[1]] and m[2]
173 else
174 return included[what] and true or false
175 end
176end
177
178lpdf.permitdetail = permitdetail
179
180
181
182local xmp, xmpfile, xmpname = nil, nil, "lpdf-pdx.xml"
183
184local function setxmpfile(name)
185 if xmp then
186 report_xmp("discarding loaded file %a",xmpfile)
187 xmp = nil
188 end
189 xmpfile = name ~= "" and name
190end
191
192codeinjections.setxmpfile = setxmpfile
193
194interfaces.implement {
195 name = "setxmpfile",
196 arguments = "string",
197 actions = setxmpfile
198}
199
200local function valid_xmp()
201 if not xmp then
202
203 if xmpfile and xmpfile ~= "" then
204 xmpfile = resolvers.findfile(xmpfile) or ""
205 end
206 if not xmpfile or xmpfile == "" then
207 xmpfile = resolvers.findfile(xmpname) or ""
208 end
209 if xmpfile ~= "" then
210 report_xmp("using file %a",xmpfile)
211 end
212 local xmpdata = xmpfile ~= "" and io.loaddata(xmpfile) or ""
213 xmp = xml.convert(xmpdata)
214 end
215 return xmp
216end
217
218function lpdf.addxmpinfo(tag,value,check)
219 local pattern = permitdetail(tag)
220 if type(pattern) == "string" then
221 xmlfillin(xmp or valid_xmp(),pattern,value,check)
222 end
223end
224
225
226
227local pdfaddtoinfo = lpdf.addtoinfo
228local pdfaddxmpinfo = lpdf.addxmpinfo
229
230function lpdf.addtoinfo(tag,pdfvalue,strvalue)
231 local pattern = permitdetail(tag)
232 if pattern then
233 pdfaddtoinfo(tag,pdfvalue)
234 end
235 if type(pattern) == "string" then
236 local value = strvalue or gsub(tostring(pdfvalue),"^%((.*)%)$","%1")
237 if trace_info then
238 report_info("set %a to %a",tag,value)
239 end
240 xmlfillin(xmp or valid_xmp(),pattern,value,check)
241 end
242end
243
244local pdfaddtoinfo = lpdf.addtoinfo
245
246
247
248function lpdf.insertxmpinfo(pattern,whatever,prepend)
249 xml.insert(xmp or valid_xmp(),pattern,whatever,prepend)
250end
251
252function lpdf.injectxmpinfo(pattern,whatever,prepend)
253 xml.inject(xmp or valid_xmp(),pattern,whatever,prepend)
254end
255
256
257
258local add_xmp_blob = true directives.register("backend.xmp",function(v) add_xmp_blob = v end)
259
260local function flushxmpinfo()
261 commands.pushrandomseed()
262 commands.setrandomseed(os.time())
263
264 local documentid = "no unique document id here"
265 local instanceid = "no unique instance id here"
266 local metadata = pdfgetmetadata()
267 local time = metadata.time
268 local producer = metadata.producer
269 local creator = metadata.creator
270
271 if included.id ~= "fake" then
272 documentid = "uuid:" .. os.uuid()
273 instanceid = "uuid:" .. os.uuid()
274 end
275
276 pdfaddtoinfo("Producer",producer)
277 pdfaddtoinfo("Creator",creator)
278 pdfaddtoinfo("CreationDate",time)
279 pdfaddtoinfo("ModDate",time)
280
281 if add_xmp_blob then
282
283 pdfaddxmpinfo("DocumentID",documentid)
284 pdfaddxmpinfo("InstanceID",instanceid)
285 pdfaddxmpinfo("Producer",producer)
286 pdfaddxmpinfo("CreatorTool",creator)
287 pdfaddxmpinfo("CreateDate",time)
288 pdfaddxmpinfo("ModifyDate",time)
289 pdfaddxmpinfo("MetadataDate",time)
290 pdfaddxmpinfo("LuaTeX.Version",metadata.luatexversion)
291 pdfaddxmpinfo("LuaTeX.Functionality",metadata.luatexfunctionality)
292 pdfaddxmpinfo("LuaTeX.LuaVersion",metadata.luaversion)
293 pdfaddxmpinfo("LuaTeX.Platform",metadata.platform)
294
295 local blob = xml.tostring(xml.first(xmp or valid_xmp(),"/x:xmpmeta"))
296 local md = pdfdictionary {
297 Subtype = pdfconstant("XML"),
298 Type = pdfconstant("Metadata"),
299 }
300 if trace_xmp then
301 report_xmp("data flushed, see log file")
302 logs.pushtarget("logfile")
303 report_xmp("start xmp blob")
304 logs.newline()
305 logs.writer(blob)
306 logs.newline()
307 report_xmp("stop xmp blob")
308 logs.poptarget()
309 end
310 blob = format(xpacket,blob)
311 if not verbose and lpdf.compresslevel() > 0 then
312 blob = gsub(blob,">%s+<","><")
313 end
314 local r = pdfflushstreamobject(blob,md,false)
315 lpdf.addtocatalog("Metadata",pdfreference(r))
316 end
317
318 commands.poprandomseed()
319end
320
321
322
323lpdf.registerdocumentfinalizer(flushxmpinfo,1,"metadata")
324
325directives.register("backend.verbosexmp", function(v)
326 verbose = v
327end)
328 |