lpdf-fmt.lua /size: 44 Kb    last modification: 2021-10-28 13:50
1if not modules then modules = { } end modules ['lpdf-fmt'] = {
2    version   = 1.001,
3    comment   = "companion to lpdf-ini.mkiv",
4    author    = "Peter Rolf and Hans Hagen",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files",
7}
8
9-- Thanks to Luigi and Steffen for testing.
10
11-- context --directives="backend.format=PDF/X-1a:2001" --trackers=backend.format yourfile
12
13local tonumber = tonumber
14local lower, gmatch, format, find = string.lower, string.gmatch, string.format, string.find
15local concat, serialize, sortedhash = table.concat, table.serialize, table.sortedhash
16
17local trace_format    = false  trackers.register("backend.format",    function(v) trace_format    = v end)
18local trace_variables = false  trackers.register("backend.variables", function(v) trace_variables = v end)
19
20local report_backend = logs.reporter("backend","profiles")
21
22local backends, lpdf = backends, lpdf
23
24local codeinjections           = backends.pdf.codeinjections
25
26local variables                = interfaces.variables
27local viewerlayers             = attributes.viewerlayers
28local colors                   = attributes.colors
29local transparencies           = attributes.transparencies
30
31local pdfdictionary            = lpdf.dictionary
32local pdfarray                 = lpdf.array
33local pdfconstant              = lpdf.constant
34local pdfreference             = lpdf.reference
35local pdfflushobject           = lpdf.flushobject
36local pdfstring                = lpdf.string
37local pdfverbose               = lpdf.verbose
38local pdfflushstreamfileobject = lpdf.flushstreamfileobject
39
40local addtoinfo                = lpdf.addtoinfo
41local injectxmpinfo            = lpdf.injectxmpinfo
42local insertxmpinfo            = lpdf.insertxmpinfo
43
44local settings_to_array        = utilities.parsers.settings_to_array
45local settings_to_hash         = utilities.parsers.settings_to_hash
46
47--[[
48    Comments by Peter:
49
50    output intent       : only one profile per color space (and device class)
51    default color space : (theoretically) several profiles per color space possible
52
53    The default color space profiles define the current gamuts (part of/all the
54    colors we have in the document), while the output intent profile declares the
55    gamut of the output devices (the colors that we get normally a printer or
56    monitor).
57
58    Example:
59
60    I have two RGB pictures (both 'painted' in /DeviceRGB) and I declare sRGB as
61    default color space for one picture and AdobeRGB for the other. As output
62    intent I use ISO_coated_v2_eci.icc.
63
64    If I had more than one output intent profile for the combination CMYK/printer I
65    can't decide which one to use. But it is no problem to use several default color
66    space profiles for the same color space as it's just a different color
67    transformation. The relation between picture and profile is clear.
68]]--
69
70local channels = {
71    gray = 1,
72    grey = 1,
73    rgb  = 3,
74    cmyk = 4,
75}
76
77local prefixes = {
78    gray = "DefaultGray",
79    grey = "DefaultGray",
80    rgb  = "DefaultRGB",
81    cmyk = "DefaultCMYK",
82}
83
84local formatspecification = nil
85local formatname          = nil
86
87-- * correspondent document wide flags (write once) needed for permission tests
88
89-- defaults as mt
90
91local formats = utilities.storage.allocate {
92    version = {
93        external_icc_profiles   = 1.4, -- 'p' in name; URL reference of output intent
94        jbig2_compression       = 1.4,
95        jpeg2000_compression    = 1.5, -- not supported yet
96        nchannel_colorspace     = 1.6, -- 'n' in name; n-channel colorspace support
97        open_prepress_interface = 1.3, -- 'g' in name; reference to external graphics
98        optional_content        = 1.5,
99        transparency            = 1.4,
100        object_compression      = 1.5,
101        attachments             = 1.7,
102    },
103    default = {
104        pdf_version             = 1.7,  -- todo: block tex primitive
105        format_name             = "default",
106        xmp_file                = "lpdf-pdx.xml",
107        gray_scale              = true,
108        cmyk_colors             = true,
109        rgb_colors              = true,
110        spot_colors             = true,
111        calibrated_rgb_colors   = true, -- unknown
112        cielab_colors           = true, -- unknown
113        nchannel_colorspace     = true, -- unknown
114        internal_icc_profiles   = true, -- controls profile inclusion
115        external_icc_profiles   = true, -- controls profile inclusion
116        include_intents         = true,
117        open_prepress_interface = true, -- unknown
118        optional_content        = true, -- todo: block at lua level
119        transparency            = true, -- todo: block at lua level
120        jbig2_compression       = true, -- todo: block at lua level (dropped anyway)
121        jpeg2000_compression    = true, -- todo: block at lua level (dropped anyway)
122        include_cidsets         = true,
123        include_charsets        = true,
124        attachments             = true,
125        inject_metadata         = function()
126            -- nothing
127        end
128    },
129    data = {
130        ["pdf/x-1a:2001"] = {
131            pdf_version             = 1.3,
132            format_name             = "PDF/X-1a:2001",
133            xmp_file                = "lpdf-pdx.xml",
134            gts_flag                = "GTS_PDFX",
135            gray_scale              = true,
136            cmyk_colors             = true,
137            spot_colors             = true,
138            internal_icc_profiles   = true,
139            include_cidsets         = true,
140            include_charsets        = true,
141            attachments             = false,
142            inject_metadata         = function()
143                addtoinfo("GTS_PDFXVersion","PDF/X-1a:2001")
144                injectxmpinfo("xml://rdf:RDF","<rdf:Description rdf:about='' xmlns:pdfxid='http://www.npes.org/pdfx/ns/id/'><pdfxid:GTS_PDFXVersion>PDF/X-1a:2001</pdfxid:GTS_PDFXVersion></rdf:Description>",false)
145            end
146        },
147        ["pdf/x-1a:2003"] = {
148            pdf_version             = 1.4,
149            format_name             = "PDF/X-1a:2003",
150            xmp_file                = "lpdf-pdx.xml",
151            gts_flag                = "GTS_PDFX",
152            gray_scale              = true,
153            cmyk_colors             = true,
154            spot_colors             = true,
155            internal_icc_profiles   = true,
156            include_cidsets         = true,
157            include_charsets        = true,
158            attachments             = false,
159            inject_metadata         = function()
160                addtoinfo("GTS_PDFXVersion","PDF/X-1a:2003")
161                injectxmpinfo("xml://rdf:RDF","<rdf:Description rdf:about='' xmlns:pdfxid='http://www.npes.org/pdfx/ns/id/'><pdfxid:GTS_PDFXVersion>PDF/X-1a:2003</pdfxid:GTS_PDFXVersion></rdf:Description>",false)
162            end
163        },
164        ["pdf/x-3:2002"] = {
165            pdf_version             = 1.3,
166            format_name             = "PDF/X-3:2002",
167            xmp_file                = "lpdf-pdx.xml",
168            gts_flag                = "GTS_PDFX",
169            gray_scale              = true,
170            cmyk_colors             = true,
171            rgb_colors              = true,
172            calibrated_rgb_colors   = true,
173            spot_colors             = true,
174            cielab_colors           = true,
175            internal_icc_profiles   = true,
176            include_intents         = true,
177            include_cidsets         = true,
178            include_charsets        = true,
179            attachments             = false,
180            inject_metadata         = function()
181                addtoinfo("GTS_PDFXVersion","PDF/X-3:2002")
182            end
183        },
184        ["pdf/x-3:2003"] = {
185            pdf_version             = 1.4,
186            format_name             = "PDF/X-3:2003",
187            xmp_file                = "lpdf-pdx.xml",
188            gts_flag                = "GTS_PDFX",
189            gray_scale              = true,
190            cmyk_colors             = true,
191            rgb_colors              = true,
192            calibrated_rgb_colors   = true,
193            spot_colors             = true,
194            cielab_colors           = true,
195            internal_icc_profiles   = true,
196            include_intents         = true,
197            jbig2_compression       = true,
198            include_cidsets         = true,
199            include_charsets        = true,
200            attachments             = false,
201            inject_metadata         = function()
202                addtoinfo("GTS_PDFXVersion","PDF/X-3:2003")
203            end
204        },
205        ["pdf/x-4"] = {
206            pdf_version             = 1.6,
207            format_name             = "PDF/X-4",
208            xmp_file                = "lpdf-pdx.xml",
209            gts_flag                = "GTS_PDFX",
210            gray_scale              = true,
211            cmyk_colors             = true,
212            rgb_colors              = true,
213            calibrated_rgb_colors   = true,
214            spot_colors             = true,
215            cielab_colors           = true,
216            internal_icc_profiles   = true,
217            include_intents         = true,
218            optional_content        = true,
219            transparency            = true,
220            jbig2_compression       = true,
221            jpeg2000_compression    = true,
222            object_compression      = true,
223            include_cidsets         = true,
224            include_charsets        = true,
225            attachments             = false,
226            inject_metadata         = function()
227                injectxmpinfo("xml://rdf:RDF","<rdf:Description rdf:about='' xmlns:pdfxid='http://www.npes.org/pdfx/ns/id/'><pdfxid:GTS_PDFXVersion>PDF/X-4</pdfxid:GTS_PDFXVersion></rdf:Description>",false)
228                insertxmpinfo("xml://rdf:Description/xmpMM:InstanceID","<xmpMM:VersionID>1</xmpMM:VersionID>",false)
229                insertxmpinfo("xml://rdf:Description/xmpMM:InstanceID","<xmpMM:RenditionClass>default</xmpMM:RenditionClass>",false)
230            end
231        },
232        ["pdf/x-4p"] = {
233            pdf_version             = 1.6,
234            format_name             = "PDF/X-4p",
235            xmp_file                = "lpdf-pdx.xml",
236            gts_flag                = "GTS_PDFX",
237            gray_scale              = true,
238            cmyk_colors             = true,
239            rgb_colors              = true,
240            calibrated_rgb_colors   = true,
241            spot_colors             = true,
242            cielab_colors           = true,
243            internal_icc_profiles   = true,
244            external_icc_profiles   = true,
245            include_intents         = true,
246            optional_content        = true,
247            transparency            = true,
248            jbig2_compression       = true,
249            jpeg2000_compression    = true,
250            object_compression      = true,
251            include_cidsets         = true,
252            include_charsets        = true,
253            attachments             = false,
254            inject_metadata         = function()
255                injectxmpinfo("xml://rdf:RDF","<rdf:Description rdf:about='' xmlns:pdfxid='http://www.npes.org/pdfx/ns/id/'><pdfxid:GTS_PDFXVersion>PDF/X-4p</pdfxid:GTS_PDFXVersion></rdf:Description>",false)
256                insertxmpinfo("xml://rdf:Description/xmpMM:InstanceID","<xmpMM:VersionID>1</xmpMM:VersionID>",false)
257                insertxmpinfo("xml://rdf:Description/xmpMM:InstanceID","<xmpMM:RenditionClass>default</xmpMM:RenditionClass>",false)
258            end
259        },
260        ["pdf/x-5g"] = {
261            pdf_version             = 1.6,
262            format_name             = "PDF/X-5g",
263            xmp_file                = "lpdf-pdx.xml",
264            gts_flag                = "GTS_PDFX",
265            gray_scale              = true,
266            cmyk_colors             = true,
267            rgb_colors              = true,
268            calibrated_rgb_colors   = true,
269            spot_colors             = true,
270            cielab_colors           = true,
271            internal_icc_profiles   = true,
272            include_intents         = true,
273            open_prepress_interface = true,
274            optional_content        = true,
275            transparency            = true,
276            jbig2_compression       = true,
277            jpeg2000_compression    = true,
278            object_compression      = true,
279            include_cidsets         = true,
280            include_charsets        = true,
281            attachments             = false,
282            inject_metadata         = function()
283                -- todo
284            end
285        },
286        ["pdf/x-5pg"] = {
287            pdf_version             = 1.6,
288            format_name             = "PDF/X-5pg",
289            xmp_file                = "lpdf-pdx.xml",
290            gts_flag                = "GTS_PDFX",
291            gray_scale              = true,
292            cmyk_colors             = true,
293            rgb_colors              = true,
294            calibrated_rgb_colors   = true,
295            spot_colors             = true,
296            cielab_colors           = true,
297            internal_icc_profiles   = true,
298            external_icc_profiles   = true,
299            include_intents         = true,
300            open_prepress_interface = true,
301            optional_content        = true,
302            transparency            = true,
303            jbig2_compression       = true,
304            jpeg2000_compression    = true,
305            object_compression      = true,
306            include_cidsets         = true,
307            include_charsets        = true,
308            attachments             = false,
309            inject_metadata         = function()
310                -- todo
311            end
312        },
313        ["pdf/x-5n"] = {
314            pdf_version             = 1.6,
315            format_name             = "PDF/X-5n",
316            xmp_file                = "lpdf-pdx.xml",
317            gts_flag                = "GTS_PDFX",
318            gray_scale              = true,
319            cmyk_colors             = true,
320            rgb_colors              = true,
321            calibrated_rgb_colors   = true,
322            spot_colors             = true,
323            cielab_colors           = true,
324            internal_icc_profiles   = true,
325            include_intents         = true,
326            optional_content        = true,
327            transparency            = true,
328            jbig2_compression       = true,
329            jpeg2000_compression    = true,
330            nchannel_colorspace     = true,
331            object_compression      = true,
332            include_cidsets         = true,
333            include_charsets        = true,
334            attachments             = false,
335            inject_metadata         = function()
336                -- todo
337            end
338        },
339        ["pdf/a-1a:2005"] = {
340            pdf_version             = 1.4,
341            format_name             = "pdf/a-1a:2005",
342            xmp_file                = "lpdf-pda.xml",
343            gts_flag                = "GTS_PDFA1",
344            gray_scale              = true,
345            cmyk_colors             = true,
346            rgb_colors              = true,
347            spot_colors             = true,
348            calibrated_rgb_colors   = true, -- unknown
349            cielab_colors           = true, -- unknown
350            include_intents         = true,
351            forms                   = true, -- new: forms are allowed (with limitations); no JS,  other restrictions are unknown (TODO)
352            tagging                 = true, -- new: the only difference to PDF/A-1b
353            internal_icc_profiles   = true,
354            include_cidsets         = true,
355            include_charsets        = true,
356            attachments             = false,
357            inject_metadata         = function()
358                injectxmpinfo("xml://rdf:RDF","<rdf:Description rdf:about='' xmlns:pdfaid='http://www.aiim.org/pdfa/ns/id/'><pdfaid:part>1</pdfaid:part><pdfaid:conformance>A</pdfaid:conformance></rdf:Description>",false)
359            end
360        },
361        ["pdf/a-1b:2005"] = {
362            pdf_version             = 1.4,
363            format_name             = "pdf/a-1b:2005",
364            xmp_file                = "lpdf-pda.xml",
365            gts_flag                = "GTS_PDFA1",
366            gray_scale              = true,
367            cmyk_colors             = true,
368            rgb_colors              = true,
369            spot_colors             = true,
370            calibrated_rgb_colors   = true, -- unknown
371            cielab_colors           = true, -- unknown
372            include_intents         = true,
373            forms                   = true,
374            internal_icc_profiles   = true,
375            include_cidsets         = true,
376            include_charsets        = true,
377            attachments             = false,
378            inject_metadata         = function()
379                injectxmpinfo("xml://rdf:RDF","<rdf:Description rdf:about='' xmlns:pdfaid='http://www.aiim.org/pdfa/ns/id/'><pdfaid:part>1</pdfaid:part><pdfaid:conformance>B</pdfaid:conformance></rdf:Description>",false)
380            end
381        },
382        -- Only PDF/A Attachments are allowed but we don't check the attachments
383        -- for any quality: they are just blobs.
384        ["pdf/a-2a"] = {
385            pdf_version             = 1.7,
386            format_name             = "pdf/a-2a",
387            xmp_file                = "lpdf-pda.xml",
388            gts_flag                = "GTS_PDFA1",
389            gray_scale              = true,
390            cmyk_colors             = true,
391            rgb_colors              = true,
392            spot_colors             = true,
393            calibrated_rgb_colors   = true, -- unknown
394            cielab_colors           = true, -- unknown
395            include_intents         = true,
396            forms                   = true,
397            tagging                 = true,
398            internal_icc_profiles   = true,
399            transparency            = true, -- new
400            jbig2_compression       = true,
401            jpeg2000_compression    = true, -- new
402            object_compression      = true, -- new
403            include_cidsets         = false,
404            include_charsets        = false,
405            attachments             = true, -- new
406            inject_metadata         = function()
407                injectxmpinfo("xml://rdf:RDF","<rdf:Description rdf:about='' xmlns:pdfaid='http://www.aiim.org/pdfa/ns/id/'><pdfaid:part>2</pdfaid:part><pdfaid:conformance>A</pdfaid:conformance></rdf:Description>",false)
408            end
409        },
410		["pdf/a-2b"] = {
411            pdf_version             = 1.7,
412            format_name             = "pdf/a-2b",
413            xmp_file                = "lpdf-pda.xml",
414            gts_flag                = "GTS_PDFA1",
415            gray_scale              = true,
416            cmyk_colors             = true,
417            rgb_colors              = true,
418            spot_colors             = true,
419            calibrated_rgb_colors   = true, -- unknown
420            cielab_colors           = true, -- unknown
421            include_intents         = true,
422            forms                   = true,
423            tagging                 = false,
424            internal_icc_profiles   = true,
425            transparency            = true,
426            jbig2_compression       = true,
427            jpeg2000_compression    = true,
428            object_compression      = true,
429            include_cidsets         = false,
430            include_charsets        = false,
431            attachments             = true,
432            inject_metadata         = function()
433                injectxmpinfo("xml://rdf:RDF","<rdf:Description rdf:about='' xmlns:pdfaid='http://www.aiim.org/pdfa/ns/id/'><pdfaid:part>2</pdfaid:part><pdfaid:conformance>B</pdfaid:conformance></rdf:Description>",false)
434            end
435        },
436        -- This is like the b variant, but it requires Unicode mapping of fonts
437        -- which we do anyway.
438		["pdf/a-2u"] = {
439            pdf_version             = 1.7,
440            format_name             = "pdf/a-2u",
441            xmp_file                = "lpdf-pda.xml",
442            gts_flag                = "GTS_PDFA1",
443            gray_scale              = true,
444            cmyk_colors             = true,
445            rgb_colors              = true,
446            spot_colors             = true,
447            calibrated_rgb_colors   = true, -- unknown
448            cielab_colors           = true, -- unknown
449            include_intents         = true,
450            forms                   = true,
451            tagging                 = false,
452            internal_icc_profiles   = true,
453            transparency            = true,
454            jbig2_compression       = true,
455            jpeg2000_compression    = true,
456            object_compression      = true,
457            include_cidsets         = false,
458            include_charsets        = false,
459            attachments             = true,
460            inject_metadata         = function()
461                injectxmpinfo("xml://rdf:RDF","<rdf:Description rdf:about='' xmlns:pdfaid='http://www.aiim.org/pdfa/ns/id/'><pdfaid:part>2</pdfaid:part><pdfaid:conformance>U</pdfaid:conformance></rdf:Description>",false)
462            end
463        },
464        -- Any type of attachment is allowed but we don't check the quality
465        -- of them.
466        ["pdf/a-3a"] = {
467            pdf_version             = 1.7,
468            format_name             = "pdf/a-3a",
469            xmp_file                = "lpdf-pda.xml",
470            gts_flag                = "GTS_PDFA1",
471            gray_scale              = true,
472            cmyk_colors             = true,
473            rgb_colors              = true,
474            spot_colors             = true,
475            calibrated_rgb_colors   = true, -- unknown
476            cielab_colors           = true, -- unknown
477            include_intents         = true,
478            forms                   = true,
479            tagging                 = true,
480            internal_icc_profiles   = true,
481            transparency            = true,
482            jbig2_compression       = true,
483            jpeg2000_compression    = true,
484            object_compression      = true,
485            include_cidsets         = false,
486            include_charsets        = false,
487            attachments             = true,
488            inject_metadata         = function()
489                injectxmpinfo("xml://rdf:RDF","<rdf:Description rdf:about='' xmlns:pdfaid='http://www.aiim.org/pdfa/ns/id/'><pdfaid:part>3</pdfaid:part><pdfaid:conformance>A</pdfaid:conformance></rdf:Description>",false)
490            end
491        },
492      ["pdf/a-3b"] = {
493            pdf_version             = 1.7,
494            format_name             = "pdf/a-3b",
495            xmp_file                = "lpdf-pda.xml",
496            gts_flag                = "GTS_PDFA1",
497            gray_scale              = true,
498            cmyk_colors             = true,
499            rgb_colors              = true,
500            spot_colors             = true,
501            calibrated_rgb_colors   = true, -- unknown
502            cielab_colors           = true, -- unknown
503            include_intents         = true,
504            forms                   = true,
505            tagging                 = false,
506            internal_icc_profiles   = true,
507            transparency            = true,
508            jbig2_compression       = true,
509            jpeg2000_compression    = true,
510            object_compression      = true,
511            include_cidsets         = false,
512            include_charsets        = false,
513            attachments             = true,
514            inject_metadata         = function()
515                injectxmpinfo("xml://rdf:RDF","<rdf:Description rdf:about='' xmlns:pdfaid='http://www.aiim.org/pdfa/ns/id/'><pdfaid:part>3</pdfaid:part><pdfaid:conformance>B</pdfaid:conformance></rdf:Description>",false)
516            end
517        },
518      ["pdf/a-3u"] = {
519            pdf_version             = 1.7,
520            format_name             = "pdf/a-3u",
521            xmp_file                = "lpdf-pda.xml",
522            gts_flag                = "GTS_PDFA1",
523            gray_scale              = true,
524            cmyk_colors             = true,
525            rgb_colors              = true,
526            spot_colors             = true,
527            calibrated_rgb_colors   = true, -- unknown
528            cielab_colors           = true, -- unknown
529            include_intents         = true,
530            forms                   = true,
531            tagging                 = false,
532            internal_icc_profiles   = true,
533            transparency            = true,
534            jbig2_compression       = true,
535            jpeg2000_compression    = true,
536            object_compression      = true,
537            include_cidsets         = false,
538            include_charsets        = false,
539            attachments             = true,
540            inject_metadata         = function()
541                injectxmpinfo("xml://rdf:RDF","<rdf:Description rdf:about='' xmlns:pdfaid='http://www.aiim.org/pdfa/ns/id/'><pdfaid:part>3</pdfaid:part><pdfaid:conformance>U</pdfaid:conformance></rdf:Description>",false)
542            end
543        },
544        ["pdf/ua-1"] = { -- based on PDF/A-3a, but no 'gts_flag'
545            pdf_version             = 1.7,
546            format_name             = "pdf/ua-1",
547            xmp_file                = "lpdf-pua.xml",
548            gray_scale              = true,
549            cmyk_colors             = true,
550            rgb_colors              = true,
551            spot_colors             = true,
552            calibrated_rgb_colors   = true, -- unknown
553            cielab_colors           = true, -- unknown
554            include_intents         = true,
555            forms                   = true,
556            tagging                 = true,
557            internal_icc_profiles   = true,
558            transparency            = true,
559            jbig2_compression       = true,
560            jpeg2000_compression    = true,
561            object_compression      = true,
562            include_cidsets         = true,
563            include_charsets        = true, --- really ?
564            attachments             = true,
565            inject_metadata         = function()
566                injectxmpinfo("xml://rdf:RDF","<rdf:Description rdf:about='' xmlns:pdfaid='http://www.aiim.org/pdfa/ns/id/'><pdfaid:part>3</pdfaid:part><pdfaid:conformance>A</pdfaid:conformance></rdf:Description>",false)
567                injectxmpinfo("xml://rdf:RDF","<rdf:Description rdf:about='' xmlns:pdfuaid='http://www.aiim.org/pdfua/ns/id/'><pdfuaid:part>1</pdfuaid:part></rdf:Description>",false)
568            end
569        },
570    }
571}
572
573lpdf.formats = formats -- it does not hurt to have this one visible
574
575local filenames = {
576    "colorprofiles.xml",
577    "colorprofiles.lua",
578}
579
580local function locatefile(filename)
581    local fullname = resolvers.findfile(filename,"icc",1,true)
582    if not fullname or fullname == "" then
583        fullname = resolvers.finders.byscheme("loc",filename) -- could be specific to the project
584    end
585    return fullname or ""
586end
587
588local function loadprofile(name,filename)
589    local profile = false
590    local databases = filename and filename ~= "" and settings_to_array(filename) or filenames
591    for i=1,#databases do
592        local filename = locatefile(databases[i])
593        if filename and filename ~= "" then
594            local suffix = file.suffix(filename)
595            local lname = lower(name)
596            if suffix == "xml" then
597                local xmldata = xml.load(filename) -- no need for caching it
598                if xmldata then
599                    profile = xml.filter(xmldata,format('xml://profiles/profile/(info|filename)[lower(text())=="%s"]/../table()',lname))
600                end
601            elseif suffix == "lua" then
602                local luadata = loadfile(filename)
603                luadata = ludata and luadata()
604                if luadata then
605                    profile = luadata[name] or luadata[lname] -- hashed
606                    if not profile then
607                        for i=1,#luadata do
608                            local li = luadata[i]
609                            if lower(li.info) == lname then -- indexed
610                                profile = li
611                                break
612                            end
613                        end
614                    end
615                end
616            end
617            if profile then
618                if next(profile) then
619                    report_backend("profile specification %a loaded from %a",name,filename)
620                    return profile
621                elseif trace_format then
622                    report_backend("profile specification %a loaded from %a but empty",name,filename)
623                end
624                return false
625            end
626        end
627    end
628    report_backend("profile specification %a not found in %a",name,concat(filenames, ", "))
629end
630
631local function urls(url)
632    if not url or url == "" then
633        return nil
634    else
635        local u = pdfarray()
636        for url in gmatch(url,"([^, ]+)") do
637            if find(url,"^http") then
638                u[#u+1] = pdfdictionary {
639                    FS = pdfconstant("URL"),
640                    F  = pdfstring(url),
641                }
642            end
643        end
644        return u
645    end
646end
647
648local function profilename(filename)
649    return lower(file.basename(filename))
650end
651
652local internalprofiles = { }
653
654local function handleinternalprofile(s,include)
655    local filename, colorspace = s.filename or "", s.colorspace or ""
656    if filename == "" or colorspace == "" then
657        report_backend("error in internal profile specification: %s",serialize(s,false))
658    else
659        local tag = profilename(filename)
660        local profile = internalprofiles[tag]
661        if not profile then
662            local colorspace = lower(colorspace)
663            if include then
664             -- local fullname = resolvers.findctxfile(filename) or ""
665                local fullname = locatefile(filename)
666                local channel = channels[colorspace] or nil
667                if fullname == "" then
668                    report_backend("error, couldn't locate profile %a",filename)
669                elseif not channel then
670                    report_backend("error, couldn't resolve channel entry for colorspace %a",colorspace)
671                else
672                    profile = pdfflushstreamfileobject(fullname,pdfdictionary{ N = channel },false) -- uncompressed
673                    internalprofiles[tag] = profile
674                    if trace_format then
675                        report_backend("including %a color profile from %a",colorspace,fullname)
676                    end
677                end
678            else
679                internalprofiles[tag] = true
680                if trace_format then
681                    report_backend("not including %a color profile %a",colorspace,filename)
682                end
683            end
684        end
685        return profile
686    end
687end
688
689local externalprofiles = { }
690
691local function handleexternalprofile(s,include) -- specification (include ignored here)
692    local name, url, filename, checksum, version, colorspace =
693        s.info or s.filename or "", s.url or "", s.filename or "", s.checksum or "", s.version or "", s.colorspace or ""
694    if false then -- somehow leads to invalid pdf
695        local iccprofile = colors.iccprofile(filename)
696        if iccprofile then
697            name       = name       ~= "" and name       or iccprofile.tags.desc.cleaned       or ""
698            url        = url        ~= "" and url        or iccprofile.tags.dmnd.cleaned       or ""
699            checksum   = checksum   ~= "" and checksum   or file.checksum(iccprofile.fullname) or ""
700            version    = version    ~= "" and version    or iccprofile.header.version          or ""
701            colorspace = colorspace ~= "" and colorspace or iccprofile.header.colorspace       or ""
702        end
703     -- table.print(iccprofile)
704    end
705    if name == "" or url == "" or checksum == "" or version == "" or colorspace == "" or filename == "" then
706        local profile = handleinternalprofile(s)
707        if profile then
708            report_backend("incomplete external profile specification, falling back to internal")
709        else
710            report_backend("error in external profile specification: %s",serialize(s,false))
711        end
712    else
713        local tag = profilename(filename)
714        local profile = externalprofiles[tag]
715        if not profile then
716            local d = pdfdictionary {
717                ProfileName = name,                                     -- not file name!
718                ProfileCS   = colorspace,
719                URLs        = urls(url),                                -- array containing at least one URL
720                CheckSum    = pdfverbose { "<", lower(checksum), ">" }, -- 16byte MD5 hash
721                ICCVersion  = pdfverbose { "<", version, ">"  },        -- bytes 8..11 from the header of the ICC profile, as a hex string
722            }
723            profile = pdfflushobject(d)
724            externalprofiles[tag] = profile
725        end
726        return profile
727    end
728end
729
730local loadeddefaults = { }
731
732local function handledefaultprofile(s,spec) -- specification
733    local filename, colorspace = s.filename or "", lower(s.colorspace or "")
734    if filename == "" or colorspace == "" then
735        report_backend("error in default profile specification: %s",serialize(s,false))
736    elseif not loadeddefaults[colorspace] then
737        local tag = profilename(filename)
738        local n = internalprofiles[tag] -- or externalprofiles[tag]
739        if n == true then -- not internalized
740            report_backend("no default profile %a for colorspace %a",filename,colorspace)
741        elseif n then
742            local a = pdfarray {
743                pdfconstant("ICCBased"),
744                pdfreference(n),
745            }
746             -- used in page /Resources, so this must be inserted at runtime
747            lpdf.adddocumentcolorspace(prefixes[colorspace],pdfreference(pdfflushobject(a)))
748            loadeddefaults[colorspace] = true
749            report_backend("setting %a as default %a color space",filename,colorspace)
750        else
751            report_backend("no default profile %a for colorspace %a",filename,colorspace)
752        end
753    elseif trace_format then
754        report_backend("a default %a colorspace is already in use",colorspace)
755    end
756end
757
758local loadedintents = { }
759local intents       = pdfarray()
760
761local function handleoutputintent(s,spec)
762    local url             = s.url or ""
763    local filename        = s.filename or ""
764    local name            = s.info or filename
765    local id              = s.id or ""
766    local outputcondition = s.outputcondition or ""
767    local info            = s.info or ""
768    if name == "" or id == "" then
769        report_backend("error in output intent specification: %s",serialize(s,false))
770    elseif not loadedintents[name] then
771        local tag = profilename(filename)
772        local internal, external = internalprofiles[tag], externalprofiles[tag]
773        if internal or external then
774            local d = {
775                  Type                      = pdfconstant("OutputIntent"),
776                  S                         = pdfconstant(spec.gts_flag or "GTS_PDFX"),
777                  OutputConditionIdentifier = id,
778                  RegistryName              = url,
779                  OutputCondition           = outputcondition,
780                  Info                      = info,
781            }
782            if internal and internal ~= true then
783                d.DestOutputProfile    = pdfreference(internal)
784            elseif external and external ~= true then
785                d.DestOutputProfileRef = pdfreference(external)
786            else
787                report_backend("omitting reference to profile for intent %a",name)
788            end
789            intents[#intents+1] = pdfreference(pdfflushobject(pdfdictionary(d)))
790            if trace_format then
791                report_backend("setting output intent to %a with id %a for entry %a",name,id,#intents)
792            end
793        else
794            report_backend("invalid output intent %a",name)
795        end
796        loadedintents[name] = true
797    elseif trace_format then
798        report_backend("an output intent with name %a is already in use",name)
799    end
800end
801
802local function handleiccprofile(message,spec,name,filename,how,options,alwaysinclude,gts_flag)
803    if name and name ~= "" then
804        local list = settings_to_array(name)
805        for i=1,#list do
806            local name = list[i]
807            local profile = loadprofile(name,filename)
808            if trace_format then
809                report_backend("handling %s %a",message,name)
810            end
811            if profile then
812                if formatspecification.cmyk_colors then
813                    profile.colorspace = profile.colorspace or "CMYK"
814                else
815                    profile.colorspace = profile.colorspace or "RGB"
816                end
817                local external = formatspecification.external_icc_profiles
818                local internal = formatspecification.internal_icc_profiles
819                local include  = formatspecification.include_intents
820                local always, never = options[variables.always], options[variables.never]
821                if always or alwaysinclude then
822                    if trace_format then
823                        report_backend("forcing internal profiles") -- can make preflight unhappy
824                    end
825                 -- internal, external = true, false
826                    internal, external = not never, false
827                elseif never then
828                    if trace_format then
829                        report_backend("forcing external profiles") -- can make preflight unhappy
830                    end
831                    internal, external = false, true
832                end
833                if external then
834                    if trace_format then
835                        report_backend("handling external profiles cf. %a",name)
836                    end
837                    handleexternalprofile(profile,false)
838                else
839                    if trace_format then
840                        report_backend("handling internal profiles cf. %a",name)
841                    end
842                    if internal then
843                        handleinternalprofile(profile,always or include)
844                    else
845                        report_backend("no profile inclusion for %a",formatname)
846                    end
847                end
848                how(profile,spec)
849            elseif trace_format then
850                report_backend("unknown profile %a",name)
851            end
852        end
853    end
854end
855
856local function flushoutputintents()
857    if #intents > 0 then
858        lpdf.addtocatalog("OutputIntents",pdfreference(pdfflushobject(intents)))
859    end
860end
861
862lpdf.registerdocumentfinalizer(flushoutputintents,2,"output intents")
863
864function codeinjections.setformat(s)
865    local format   = s.format or ""
866    local level    = tonumber(s.level)
867    local intent   = s.intent or ""
868    local profile  = s.profile or ""
869    local option   = s.option or ""
870    local filename = s.file or ""
871    if format ~= "" then
872        local spec = formats.data[lower(format)]
873        if spec then
874            formatspecification = spec
875            formatname = spec.format_name
876            report_backend("setting format to %a",formatname)
877            local xmp_file = formatspecification.xmp_file or ""
878            if xmp_file == "" then
879                -- weird error
880            else
881                codeinjections.setxmpfile(xmp_file)
882            end
883            if not level then
884                level = 3 -- good compromise, default anyway
885            end
886            local pdf_version         = spec.pdf_version * 10
887            local inject_metadata     = spec.inject_metadata
888            local majorversion        = math.floor(math.div(pdf_version,10))
889            local minorversion        = math.floor(math.mod(pdf_version,10))
890            local objectcompression   = spec.object_compression and pdf_version >= 15
891            local compresslevel       = level or lpdf.compresslevel() -- keep default
892            local objectcompresslevel = (objectcompression and (level or lpdf.objectcompresslevel())) or 0
893            lpdf.setcompression(compresslevel,objectcompresslevel)
894            lpdf.setversion(majorversion,minorversion)
895            if objectcompression then
896                report_backend("forcing pdf version %s.%s, compression level %s, object compression level %s",
897                    majorversion,minorversion,compresslevel,objectcompresslevel)
898            elseif compresslevel > 0 then
899                report_backend("forcing pdf version %s.%s, compression level %s, object compression disabled",
900                    majorversion,minorversion,compresslevel)
901            else
902                report_backend("forcing pdf version %s.%s, compression disabled",
903                    majorversion,minorversion)
904            end
905            --
906            -- cid sets can always omitted now, but those validators still complain so let's
907            -- for a while keep it (for luigi):
908            --
909            lpdf.setomitcidset (formatspecification.include_cidsets  == false and 1 or 0) -- why a number
910            lpdf.setomitcharset(formatspecification.include_charsets == false and 1 or 0) -- why a number
911            --
912            -- maybe block by pdf version
913            --
914            codeinjections.settaggingsupport(formatspecification.tagging)
915            codeinjections.setattachmentsupport(formatspecification.attachments)
916            --
917            -- context.setupcolors { -- not this way
918            --     cmyk = spec.cmyk_colors and variables.yes or variables.no,
919            --     rgb  = spec.rgb_colors  and variables.yes or variables.no,
920            -- }
921            --
922            colors.forcesupport(
923                spec.gray_scale          or false,
924                spec.rgb_colors          or false,
925                spec.cmyk_colors         or false,
926                spec.spot_colors         or false,
927                spec.nchannel_colorspace or false
928            )
929            transparencies.forcesupport(
930                spec.transparency        or false
931            )
932            viewerlayers.forcesupport(
933                spec.optional_content    or false
934            )
935            viewerlayers.setfeatures(
936                spec.has_order           or false -- new
937            )
938            --
939            -- spec.jbig2_compression    : todo, block in image inclusion
940            -- spec.jpeg2000_compression : todo, block in image inclusion
941            --
942            if type(inject_metadata) == "function" then
943                inject_metadata()
944            end
945            local options = settings_to_hash(option)
946            handleiccprofile("color profile",spec,profile,filename,handledefaultprofile,options,true)
947            handleiccprofile("output intent",spec,intent,filename,handleoutputintent,options,false)
948            if trace_variables then
949                for k, v in sortedhash(formats.default) do
950                    local v = formatspecification[k]
951                    if type(v) ~= "function" then
952                        report_backend("%a = %a",k,v or false)
953                    end
954                end
955            end
956            function codeinjections.setformat(noname)
957                if trace_format then
958                    report_backend("error, format is already set to %a, ignoring %a",formatname,noname.format)
959                end
960            end
961        else
962            report_backend("error, format %a is not supported",format)
963        end
964    elseif level then
965        lpdf.setcompression(level,level)
966    else
967        -- we ignore this as we hook it in \everysetupbackend
968    end
969end
970
971directives.register("backend.format", function(v) -- table !
972    local tv = type(v)
973    if tv == "table" then
974        codeinjections.setformat(v)
975    elseif tv == "string" then
976        codeinjections.setformat { format = v }
977    end
978end)
979
980interfaces.implement {
981    name      = "setformat",
982    actions   = codeinjections.setformat,
983    arguments = { { "*" } }
984}
985
986function codeinjections.getformatoption(key)
987    return formatspecification and formatspecification[key]
988end
989
990-- function codeinjections.getformatspecification()
991--     return formatspecification
992-- end
993
994function codeinjections.supportedformats()
995    local t = { }
996    for k, v in sortedhash(formats.data) do
997        t[#t+1] = k
998    end
999    return t
1000end
1001
1002-- The following is somewhat cleaner but then we need to flag that there are
1003-- color spaces set so that the page flusher does not optimize the (at that
1004-- moment) still empty array away. So, next(d_colorspaces) should then become
1005-- a different test, i.e. also on flag. I'll add that when we need more forward
1006-- referencing.
1007--
1008-- local function embedprofile = handledefaultprofile
1009--
1010-- local function flushembeddedprofiles()
1011--     for colorspace, filename in next, defaults do
1012--         embedprofile(colorspace,filename)
1013--     end
1014-- end
1015--
1016-- local function handledefaultprofile(s)
1017--     defaults[lower(s.colorspace)] = s.filename
1018-- end
1019--
1020-- lpdf.registerdocumentfinalizer(flushembeddedprofiles,1,"embedded color profiles")
1021