summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/tex/context/base/lpdf-xmp.lua
blob: b1a795c4b778a1cddd3116c013282d8c2482eada (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
if not modules then modules = { } end modules ['lpdf-xmp'] = {
    version   = 1.001,
    comment   = "companion to lpdf-ini.mkiv",
    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
    copyright = "PRAGMA ADE / ConTeXt Development Team",
    license   = "see context related readme files",
    comment   = "with help from Peter Rolf",
}

local tostring = tostring
local format, random, char, gsub, concat = string.format, math.random, string.char, string.gsub, table.concat
local xmlfillin = xml.fillin

local trace_xmp  = false  trackers.register("backend.xmp",  function(v) trace_xmp  = v end)
local trace_info = false  trackers.register("backend.info", function(v) trace_info = v end)

local report_xmp  = logs.reporter("backend","xmp")
local report_info = logs.reporter("backend","info")

local backends, lpdf = backends, lpdf

local codeinjections       = backends.pdf.codeinjections -- normally it is registered

local pdfdictionary        = lpdf.dictionary
local pdfconstant          = lpdf.constant
local pdfreference         = lpdf.reference
local pdfflushstreamobject = lpdf.flushstreamobject

-- I wonder why this begin end is empty / w (no time now to look into it) / begin can also be "?"

local xpacket = [[
<?xpacket begin="" id="%s"?>

%s

<?xpacket end="w"?>]]

local mapping = {
    -- user defined keys (pdfx:)
    ["ConTeXt.Jobname"] = "rdf:Description/pdfx:ConTeXt.Jobname",
    ["ConTeXt.Time"]    = "rdf:Description/pdfx:ConTeXt.Time",
    ["ConTeXt.Url"]     = "rdf:Description/pdfx:ConTeXt.Url",
    ["ConTeXt.Version"] = "rdf:Description/pdfx:ConTeXt.Version",
    ["ID"]              = "rdf:Description/pdfx:ID",
    ["PTEX.Fullbanner"] = "rdf:Description/pdfx:PTEX.Fullbanner",
    -- Adobe PDF schema
    ["Keywords"]        = "rdf:Description/pdf:Keywords",
    ["Producer"]        = "rdf:Description/pdf:Producer",
 -- ["Trapped"]         = "rdf:Description/pdf:Trapped", -- '/False' in /Info, but 'False' in XMP
    -- Dublin Core schema
    ["Author"]          = "rdf:Description/dc:creator/rdf:Seq/rdf:li",
    ["Format"]          = "rdf:Description/dc:format", -- optional, but nice to have
    ["Subject"]         = "rdf:Description/dc:description/rdf:Alt/rdf:li",
    ["Title"]           = "rdf:Description/dc:title/rdf:Alt/rdf:li",
    -- XMP Basic schema
    ["CreateDate"]      = "rdf:Description/xmp:CreateDate",
    ["Creator"]         = "rdf:Description/xmp:CreatorTool",
    ["MetadataDate"]    = "rdf:Description/xmp:MetadataDate",
    ["ModifyDate"]      = "rdf:Description/xmp:ModifyDate",
    -- XMP Media Management schema
    ["DocumentID"]      = "rdf:Description/xmpMM:DocumentID",
    ["InstanceID"]      = "rdf:Description/xmpMM:InstanceID",
    ["RenditionClass"]  = "rdf:Description/xmpMM:RenditionClass", -- PDF/X-4
    ["VersionID"]       = "rdf:Description/xmpMM:VersionID", -- PDF/X-4
    -- additional entries
    -- PDF/X
    ["GTS_PDFXVersion"] = "rdf:Description/pdfxid:GTS_PDFXVersion",
    -- optional entries
    -- all what is visible in the 'document properties --> additional metadata' window
    -- XMP Rights Management schema (optional)
    ["Marked"]          = "rdf:Description/xmpRights:Marked",
 -- ["Owner"]           = "rdf:Description/xmpRights:Owner/rdf:Bag/rdf:li", -- maybe useful (not visible)
 -- ["UsageTerms"]      = "rdf:Description/xmpRights:UsageTerms", -- maybe useful (not visible)
    ["WebStatement"]    = "rdf:Description/xmpRights:WebStatement",
    -- Photoshop PDF schema (optional)
    ["AuthorsPosition"] = "rdf:Description/photoshop:AuthorsPosition",
    ["Copyright"]       = "rdf:Description/photoshop:Copyright",
    ["CaptionWriter"]   = "rdf:Description/photoshop:CaptionWriter",
}

-- maybe some day we will load the xmp file at runtime

local xmp, xmpfile, xmpname = nil, nil, "lpdf-pdx.xml"

local function setxmpfile(name)
    if xmp then
        report_xmp("discarding loaded file %a",xmpfile)
        xmp = nil
    end
    xmpfile = name ~= "" and name
end

codeinjections.setxmpfile = setxmpfile

interfaces.implement {
    name      = "setxmpfile",
    arguments = "string",
    actions   = setxmpfile
}

local function valid_xmp()
    if not xmp then
     -- local xmpfile = xmpfile or resolvers.findfile(xmpname) or ""
        if xmpfile and xmpfile ~= "" then
            xmpfile = resolvers.findfile(xmpfile) or ""
        end
        if not xmpfile or xmpfile == "" then
            xmpfile = resolvers.findfile(xmpname) or ""
        end
        if xmpfile ~= "" then
            report_xmp("using file %a",xmpfile)
        end
        local xmpdata = xmpfile ~= "" and io.loaddata(xmpfile) or ""
        xmp = xml.convert(xmpdata)
    end
    return xmp
end

function lpdf.addxmpinfo(tag,value,check)
    local pattern = mapping[tag]
    if pattern then
        xmlfillin(xmp or valid_xmp(),pattern,value,check)
    end
end

-- redefined

local pdfaddtoinfo  = lpdf.addtoinfo
local pdfaddxmpinfo = lpdf.addxmpinfo

function lpdf.addtoinfo(tag,pdfvalue,strvalue)
    pdfaddtoinfo(tag,pdfvalue)
    local value = strvalue or gsub(tostring(pdfvalue),"^%((.*)%)$","%1") -- hack
    if trace_info then
        report_info("set %a to %a",tag,value)
    end
    pdfaddxmpinfo(tag,value)
end

-- for the do-it-yourselvers

function lpdf.insertxmpinfo(pattern,whatever,prepend)
    xml.insert(xmp or valid_xmp(),pattern,whatever,prepend)
end

function lpdf.injectxmpinfo(pattern,whatever,prepend)
    xml.inject(xmp or valid_xmp(),pattern,whatever,prepend)
end

-- flushing

local t = { } for i=1,24 do t[i] = random() end

local function flushxmpinfo()
    commands.pushrandomseed()
    commands.setrandomseed(os.time())

    local t = { } for i=1,24 do t[i] = char(96 + random(26)) end
    local packetid = concat(t)

    local documentid = format("uuid:%s",os.uuid())
    local instanceid = format("uuid:%s",os.uuid())
    local producer   = format("LuaTeX-%0.2f.%s",tex.luatexversion/100,tex.luatexrevision)
    local creator    = "LuaTeX + ConTeXt MkIV"
    local time       = lpdf.timestamp()
    local fullbanner = status.banner

    pdfaddxmpinfo("DocumentID",      documentid)
    pdfaddxmpinfo("InstanceID",      instanceid)
    pdfaddxmpinfo("Producer",        producer)
    pdfaddxmpinfo("CreatorTool",     creator)
    pdfaddxmpinfo("CreateDate",      time)
    pdfaddxmpinfo("ModifyDate",      time)
    pdfaddxmpinfo("MetadataDate",    time)
    pdfaddxmpinfo("PTEX.Fullbanner", fullbanner)

    pdfaddtoinfo("Producer",         producer)
    pdfaddtoinfo("Creator",          creator)
    pdfaddtoinfo("CreationDate",     time)
    pdfaddtoinfo("ModDate",          time)
 -- pdfaddtoinfo("PTEX.Fullbanner",  fullbanner) -- no checking done on existence

    local blob = xml.tostring(xml.first(xmp or valid_xmp(),"/x:xmpmeta"))
    local md = pdfdictionary {
        Subtype = pdfconstant("XML"),
        Type    = pdfconstant("Metadata"),
    }
    if trace_xmp then
        report_xmp("data flushed, see log file")
        logs.pushtarget("logfile")
        report_xmp("start xmp blob")
        logs.newline()
        logs.writer(blob)
        logs.newline()
        report_xmp("stop xmp blob")
        logs.poptarget()
    end
    blob = format(xpacket,packetid,blob)
    if not verbose and tex.pdfcompresslevel > 0 then
        blob = gsub(blob,">%s+<","><")
    end
    local r = pdfflushstreamobject(blob,md,false) -- uncompressed
    lpdf.addtocatalog("Metadata",pdfreference(r))

    commands.poprandomseed() -- hack
end

--  his will be enabled when we can inhibit compression for a stream at the lua end

lpdf.registerdocumentfinalizer(flushxmpinfo,1,"metadata")

directives.register("backend.verbosexmp", function(v)
    verbose = v
end)