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