lpdf-fmt.lua /size: 45 Kb    last modification: 2024-01-16 09:02
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
43local replacexmpinfo           = lpdf.replacexmpinfo
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                replacexmpinfo(
146                    "xml://rdf:RDF/pdfaid-placeholder",
147[[
148<rdf:Description rdf:about='' xmlns:pdfxid='http://www.npes.org/pdfx/ns/id/'>
149      <pdfxid:GTS_PDFXVersion>PDF/X-1a:2001</pdfxid:GTS_PDFXVersion>
150    </rdf:Description>
151]]
152                )
153            end
154        },
155        ["pdf/x-1a:2003"] = {
156            pdf_version             = 1.4,
157            format_name             = "PDF/X-1a:2003",
158            xmp_file                = "lpdf-pdx.xml",
159            gts_flag                = "GTS_PDFX",
160            gray_scale              = true,
161            cmyk_colors             = true,
162            spot_colors             = true,
163            internal_icc_profiles   = true,
164            include_cidsets         = true,
165            include_charsets        = true,
166            attachments             = false,
167            inject_metadata         = function()
168                addtoinfo("GTS_PDFXVersion","PDF/X-1a:2003")
169                replacexmpinfo(
170                    "xml://rdf:RDF/pdfaid-placeholder",
171[[
172<rdf:Description rdf:about='' xmlns:pdfxid='http://www.npes.org/pdfx/ns/id/'>
173      <pdfxid:GTS_PDFXVersion>PDF/X-1a:2003</pdfxid:GTS_PDFXVersion>
174    </rdf:Description>
175]]
176                )
177            end
178        },
179        ["pdf/x-3:2002"] = {
180            pdf_version             = 1.3,
181            format_name             = "PDF/X-3:2002",
182            xmp_file                = "lpdf-pdx.xml",
183            gts_flag                = "GTS_PDFX",
184            gray_scale              = true,
185            cmyk_colors             = true,
186            rgb_colors              = true,
187            calibrated_rgb_colors   = true,
188            spot_colors             = true,
189            cielab_colors           = true,
190            internal_icc_profiles   = true,
191            include_intents         = true,
192            include_cidsets         = true,
193            include_charsets        = true,
194            attachments             = false,
195            inject_metadata         = function()
196                addtoinfo("GTS_PDFXVersion","PDF/X-3:2002")
197            end
198        },
199        ["pdf/x-3:2003"] = {
200            pdf_version             = 1.4,
201            format_name             = "PDF/X-3:2003",
202            xmp_file                = "lpdf-pdx.xml",
203            gts_flag                = "GTS_PDFX",
204            gray_scale              = true,
205            cmyk_colors             = true,
206            rgb_colors              = true,
207            calibrated_rgb_colors   = true,
208            spot_colors             = true,
209            cielab_colors           = true,
210            internal_icc_profiles   = true,
211            include_intents         = true,
212            jbig2_compression       = true,
213            include_cidsets         = true,
214            include_charsets        = true,
215            attachments             = false,
216            inject_metadata         = function()
217                addtoinfo("GTS_PDFXVersion","PDF/X-3:2003")
218            end
219        },
220        ["pdf/x-4"] = {
221            pdf_version             = 1.6,
222            format_name             = "PDF/X-4",
223            xmp_file                = "lpdf-pdx.xml",
224            gts_flag                = "GTS_PDFX",
225            gray_scale              = true,
226            cmyk_colors             = true,
227            rgb_colors              = true,
228            calibrated_rgb_colors   = true,
229            spot_colors             = true,
230            cielab_colors           = true,
231            internal_icc_profiles   = true,
232            include_intents         = true,
233            optional_content        = true,
234            transparency            = true,
235            jbig2_compression       = true,
236            jpeg2000_compression    = true,
237            object_compression      = true,
238            include_cidsets         = true,
239            include_charsets        = true,
240            attachments             = false,
241            inject_metadata         = function()
242                replacexmpinfo(
243                    "xml://rdf:RDF/pdfaid-placeholder",
244[[
245<rdf:Description rdf:about='' xmlns:pdfxid='http://www.npes.org/pdfx/ns/id/'>
246      <pdfxid:GTS_PDFXVersion>PDF/X-4</pdfxid:GTS_PDFXVersion>
247    </rdf:Description>
248]]
249                )
250                insertxmpinfo(
251                    "xml://rdf:Description/xmpMM:InstanceID",
252                    [[<xmpMM:VersionID>1</xmpMM:VersionID>]],
253                    false
254                )
255                insertxmpinfo(
256                    "xml://rdf:Description/xmpMM:InstanceID",
257                    [[<xmpMM:RenditionClass>default</xmpMM:RenditionClass>]],
258                    false
259                )
260            end
261        },
262        ["pdf/x-4p"] = {
263            pdf_version             = 1.6,
264            format_name             = "PDF/X-4p",
265            xmp_file                = "lpdf-pdx.xml",
266            gts_flag                = "GTS_PDFX",
267            gray_scale              = true,
268            cmyk_colors             = true,
269            rgb_colors              = true,
270            calibrated_rgb_colors   = true,
271            spot_colors             = true,
272            cielab_colors           = true,
273            internal_icc_profiles   = true,
274            external_icc_profiles   = true,
275            include_intents         = true,
276            optional_content        = true,
277            transparency            = true,
278            jbig2_compression       = true,
279            jpeg2000_compression    = true,
280            object_compression      = true,
281            include_cidsets         = true,
282            include_charsets        = true,
283            attachments             = false,
284            inject_metadata         = function()
285                replacexmpinfo(
286                    "xml://rdf:RDF/pdfaid-placeholder",
287[[
288<rdf:Description rdf:about='' xmlns:pdfxid='http://www.npes.org/pdfx/ns/id/'>
289      <pdfxid:GTS_PDFXVersion>PDF/X-4p</pdfxid:GTS_PDFXVersion>
290    </rdf:Description>
291]]
292                )
293                insertxmpinfo(
294                    "xml://rdf:Description/xmpMM:InstanceID",
295                    [[<xmpMM:VersionID>1</xmpMM:VersionID>]],
296                    false
297                )
298                insertxmpinfo(
299                    "xml://rdf:Description/xmpMM:InstanceID",
300                    [[<xmpMM:RenditionClass>default</xmpMM:RenditionClass>]],
301                    false
302                )
303            end
304        },
305        ["pdf/x-5g"] = {
306            pdf_version             = 1.6,
307            format_name             = "PDF/X-5g",
308            xmp_file                = "lpdf-pdx.xml",
309            gts_flag                = "GTS_PDFX",
310            gray_scale              = true,
311            cmyk_colors             = true,
312            rgb_colors              = true,
313            calibrated_rgb_colors   = true,
314            spot_colors             = true,
315            cielab_colors           = true,
316            internal_icc_profiles   = true,
317            include_intents         = true,
318            open_prepress_interface = true,
319            optional_content        = true,
320            transparency            = true,
321            jbig2_compression       = true,
322            jpeg2000_compression    = true,
323            object_compression      = true,
324            include_cidsets         = true,
325            include_charsets        = true,
326            attachments             = false,
327            inject_metadata         = function()
328                -- todo
329            end
330        },
331        ["pdf/x-5pg"] = {
332            pdf_version             = 1.6,
333            format_name             = "PDF/X-5pg",
334            xmp_file                = "lpdf-pdx.xml",
335            gts_flag                = "GTS_PDFX",
336            gray_scale              = true,
337            cmyk_colors             = true,
338            rgb_colors              = true,
339            calibrated_rgb_colors   = true,
340            spot_colors             = true,
341            cielab_colors           = true,
342            internal_icc_profiles   = true,
343            external_icc_profiles   = true,
344            include_intents         = true,
345            open_prepress_interface = true,
346            optional_content        = true,
347            transparency            = true,
348            jbig2_compression       = true,
349            jpeg2000_compression    = true,
350            object_compression      = true,
351            include_cidsets         = true,
352            include_charsets        = true,
353            attachments             = false,
354            inject_metadata         = function()
355                -- todo
356            end
357        },
358        ["pdf/x-5n"] = {
359            pdf_version             = 1.6,
360            format_name             = "PDF/X-5n",
361            xmp_file                = "lpdf-pdx.xml",
362            gts_flag                = "GTS_PDFX",
363            gray_scale              = true,
364            cmyk_colors             = true,
365            rgb_colors              = true,
366            calibrated_rgb_colors   = true,
367            spot_colors             = true,
368            cielab_colors           = true,
369            internal_icc_profiles   = true,
370            include_intents         = true,
371            optional_content        = true,
372            transparency            = true,
373            jbig2_compression       = true,
374            jpeg2000_compression    = true,
375            nchannel_colorspace     = true,
376            object_compression      = true,
377            include_cidsets         = true,
378            include_charsets        = true,
379            attachments             = false,
380            inject_metadata         = function()
381                -- todo
382            end
383        },
384        ["pdf/a-1a:2005"] = {
385            pdf_version             = 1.4,
386            format_name             = "pdf/a-1a:2005",
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, -- new: forms are allowed (with limitations); no JS,  other restrictions are unknown (TODO)
397            tagging                 = true, -- new: the only difference to PDF/A-1b
398            internal_icc_profiles   = true,
399            include_cidsets         = true,
400            include_charsets        = true,
401            attachments             = false,
402            inject_metadata         = function()
403                replacexmpinfo(
404                    "xml://rdf:RDF/pdfaid-placeholder",
405[[
406<rdf:Description rdf:about='' xmlns:pdfaid='http://www.aiim.org/pdfa/ns/id/'>
407      <pdfaid:part>1</pdfaid:part>
408      <pdfaid:conformance>A</pdfaid:conformance>
409    </rdf:Description>
410]]
411                )
412            end
413        },
414        ["pdf/a-1b:2005"] = {
415            pdf_version             = 1.4,
416            format_name             = "pdf/a-1b:2005",
417            xmp_file                = "lpdf-pda.xml",
418            gts_flag                = "GTS_PDFA1",
419            gray_scale              = true,
420            cmyk_colors             = true,
421            rgb_colors              = true,
422            spot_colors             = true,
423            calibrated_rgb_colors   = true, -- unknown
424            cielab_colors           = true, -- unknown
425            include_intents         = true,
426            forms                   = true,
427            internal_icc_profiles   = true,
428            include_cidsets         = true,
429            include_charsets        = true,
430            attachments             = false,
431            inject_metadata         = function()
432                replacexmpinfo(
433                    "xml://rdf:RDF/pdfaid-placeholder",
434[[
435<rdf:Description rdf:about='' xmlns:pdfaid='http://www.aiim.org/pdfa/ns/id/'>
436      <pdfaid:part>1</pdfaid:part>
437      <pdfaid:conformance>B</pdfaid:conformance>
438    </rdf:Description>
439]]
440                )
441            end
442        },
443        -- Only PDF/A Attachments are allowed but we don't check the attachments
444        -- for any quality: they are just blobs.
445        ["pdf/a-2a"] = {
446            pdf_version             = 1.7,
447            format_name             = "pdf/a-2a",
448            xmp_file                = "lpdf-pda.xml",
449            gts_flag                = "GTS_PDFA1",
450            gray_scale              = true,
451            cmyk_colors             = true,
452            rgb_colors              = true,
453            spot_colors             = true,
454            calibrated_rgb_colors   = true, -- unknown
455            cielab_colors           = true, -- unknown
456            include_intents         = true,
457            forms                   = true,
458            tagging                 = true,
459            internal_icc_profiles   = true,
460            transparency            = true, -- new
461            jbig2_compression       = true,
462            jpeg2000_compression    = true, -- new
463            object_compression      = true, -- new
464            include_cidsets         = false,
465            include_charsets        = false,
466            attachments             = true, -- new
467            inject_metadata         = function()
468                replacexmpinfo(
469                    "xml://rdf:RDF/pdfaid-placeholder",
470[[
471<rdf:Description rdf:about='' xmlns:pdfaid='http://www.aiim.org/pdfa/ns/id/'>
472      <pdfaid:part>2</pdfaid:part>
473      <pdfaid:conformance>A</pdfaid:conformance>
474    </rdf:Description>
475]]
476                )
477            end
478        },
479		["pdf/a-2b"] = {
480            pdf_version             = 1.7,
481            format_name             = "pdf/a-2b",
482            xmp_file                = "lpdf-pda.xml",
483            gts_flag                = "GTS_PDFA1",
484            gray_scale              = true,
485            cmyk_colors             = true,
486            rgb_colors              = true,
487            spot_colors             = true,
488            calibrated_rgb_colors   = true, -- unknown
489            cielab_colors           = true, -- unknown
490            include_intents         = true,
491            forms                   = true,
492            tagging                 = false,
493            internal_icc_profiles   = true,
494            transparency            = true,
495            jbig2_compression       = true,
496            jpeg2000_compression    = true,
497            object_compression      = true,
498            include_cidsets         = false,
499            include_charsets        = false,
500            attachments             = true,
501            inject_metadata         = function()
502                replacexmpinfo(
503                    "xml://rdf:RDF/pdfaid-placeholder",
504[[
505<rdf:Description rdf:about='' xmlns:pdfaid='http://www.aiim.org/pdfa/ns/id/'>
506      <pdfaid:part>2</pdfaid:part>
507      <pdfaid:conformance>B</pdfaid:conformance>
508    </rdf:Description>
509]]
510                )
511            end
512        },
513        -- This is like the b variant, but it requires Unicode mapping of fonts
514        -- which we do anyway.
515		["pdf/a-2u"] = {
516            pdf_version             = 1.7,
517            format_name             = "pdf/a-2u",
518            xmp_file                = "lpdf-pda.xml",
519            gts_flag                = "GTS_PDFA1",
520            gray_scale              = true,
521            cmyk_colors             = true,
522            rgb_colors              = true,
523            spot_colors             = true,
524            calibrated_rgb_colors   = true, -- unknown
525            cielab_colors           = true, -- unknown
526            include_intents         = true,
527            forms                   = true,
528            tagging                 = false,
529            internal_icc_profiles   = true,
530            transparency            = true,
531            jbig2_compression       = true,
532            jpeg2000_compression    = true,
533            object_compression      = true,
534            include_cidsets         = false,
535            include_charsets        = false,
536            attachments             = true,
537            inject_metadata         = function()
538                replacexmpinfo(
539                    "xml://rdf:RDF/pdfaid-placeholder",
540[[
541<rdf:Description rdf:about='' xmlns:pdfaid='http://www.aiim.org/pdfa/ns/id/'>
542      <pdfaid:part>2</pdfaid:part>
543      <pdfaid:conformance>U</pdfaid:conformance>
544    </rdf:Description>
545]]
546                )
547            end
548        },
549        -- Any type of attachment is allowed but we don't check the quality
550        -- of them.
551        ["pdf/a-3a"] = {
552            pdf_version             = 1.7,
553            format_name             = "pdf/a-3a",
554            xmp_file                = "lpdf-pda.xml",
555            gts_flag                = "GTS_PDFA1",
556            gray_scale              = true,
557            cmyk_colors             = true,
558            rgb_colors              = true,
559            spot_colors             = true,
560            calibrated_rgb_colors   = true, -- unknown
561            cielab_colors           = true, -- unknown
562            include_intents         = true,
563            forms                   = true,
564            tagging                 = true,
565            internal_icc_profiles   = true,
566            transparency            = true,
567            jbig2_compression       = true,
568            jpeg2000_compression    = true,
569            object_compression      = true,
570            include_cidsets         = false,
571            include_charsets        = false,
572            attachments             = true,
573            inject_metadata         = function()
574                replacexmpinfo(
575                    "xml://rdf:RDF/pdfaid-placeholder",
576[[
577<rdf:Description rdf:about='' xmlns:pdfaid='http://www.aiim.org/pdfa/ns/id/'>
578      <pdfaid:part>3</pdfaid:part>
579      <pdfaid:conformance>A</pdfaid:conformance>
580    </rdf:Description>
581]]
582                )
583            end
584        },
585      ["pdf/a-3b"] = {
586            pdf_version             = 1.7,
587            format_name             = "pdf/a-3b",
588            xmp_file                = "lpdf-pda.xml",
589            gts_flag                = "GTS_PDFA1",
590            gray_scale              = true,
591            cmyk_colors             = true,
592            rgb_colors              = true,
593            spot_colors             = true,
594            calibrated_rgb_colors   = true, -- unknown
595            cielab_colors           = true, -- unknown
596            include_intents         = true,
597            forms                   = true,
598            tagging                 = false,
599            internal_icc_profiles   = true,
600            transparency            = true,
601            jbig2_compression       = true,
602            jpeg2000_compression    = true,
603            object_compression      = true,
604            include_cidsets         = false,
605            include_charsets        = false,
606            attachments             = true,
607            inject_metadata         = function()
608                replacexmpinfo(
609                    "xml://rdf:RDF/pdfaid-placeholder",
610[[
611<rdf:Description rdf:about='' xmlns:pdfaid='http://www.aiim.org/pdfa/ns/id/'>
612      <pdfaid:part>3</pdfaid:part>
613      <pdfaid:conformance>B</pdfaid:conformance>
614    </rdf:Description>
615]]
616                )
617            end
618        },
619      ["pdf/a-3u"] = {
620            pdf_version             = 1.7,
621            format_name             = "pdf/a-3u",
622            xmp_file                = "lpdf-pda.xml",
623            gts_flag                = "GTS_PDFA1",
624            gray_scale              = true,
625            cmyk_colors             = true,
626            rgb_colors              = true,
627            spot_colors             = true,
628            calibrated_rgb_colors   = true, -- unknown
629            cielab_colors           = true, -- unknown
630            include_intents         = true,
631            forms                   = true,
632            tagging                 = false,
633            internal_icc_profiles   = true,
634            transparency            = true,
635            jbig2_compression       = true,
636            jpeg2000_compression    = true,
637            object_compression      = true,
638            include_cidsets         = false,
639            include_charsets        = false,
640            attachments             = true,
641            inject_metadata         = function()
642                replacexmpinfo(
643                    "xml://rdf:RDF/pdfaid-placeholder",
644[[
645<rdf:Description rdf:about='' xmlns:pdfaid='http://www.aiim.org/pdfa/ns/id/'>
646      <pdfaid:part>3</pdfaid:part>
647      <pdfaid:conformance>U</pdfaid:conformance>
648    </rdf:Description>
649]]
650            )
651            end
652        },
653        ["pdf/ua-1"] = { -- based on PDF/A-3a, but no 'gts_flag'
654            pdf_version             = 1.7,
655            format_name             = "pdf/ua-1",
656            xmp_file                = "lpdf-pua.xml",
657            gray_scale              = true,
658            cmyk_colors             = true,
659            rgb_colors              = true,
660            spot_colors             = true,
661            calibrated_rgb_colors   = true, -- unknown
662            cielab_colors           = true, -- unknown
663            include_intents         = true,
664            forms                   = true,
665            tagging                 = true,
666            internal_icc_profiles   = true,
667            transparency            = true,
668            jbig2_compression       = true,
669            jpeg2000_compression    = true,
670            object_compression      = true,
671            include_cidsets         = true,
672            include_charsets        = true, --- really ?
673            attachments             = true,
674            inject_metadata         = function()
675                replacexmpinfo(
676                    "xml://rdf:RDF/pdfaid-placeholder",
677[[
678<rdf:Description rdf:about='' xmlns:pdfaid='http://www.aiim.org/pdfa/ns/id/'>
679      <pdfaid:part>3</pdfaid:part>
680      <pdfaid:conformance>A</pdfaid:conformance>
681    </rdf:Description>
682    <rdf:Description rdf:about='' xmlns:pdfuaid='http://www.aiim.org/pdfua/ns/id/'>
683      <pdfuaid:part>1</pdfuaid:part>
684    </rdf:Description>
685]]
686                )
687            end
688        },
689    }
690}
691
692lpdf.formats = formats -- it does not hurt to have this one visible
693
694local filenames = {
695    "colorprofiles.xml",
696    "colorprofiles.lua",
697}
698
699local function locatefile(filename)
700    local fullname = resolvers.findfile(filename,"icc",1,true)
701    if not fullname or fullname == "" then
702        fullname = resolvers.finders.byscheme("loc",filename) -- could be specific to the project
703    end
704    return fullname or ""
705end
706
707local function loadprofile(name,filename)
708    local profile = false
709    local databases = filename and filename ~= "" and settings_to_array(filename) or filenames
710    for i=1,#databases do
711        local filename = locatefile(databases[i])
712        if filename and filename ~= "" then
713            local suffix = file.suffix(filename)
714            local lname = lower(name)
715            if suffix == "xml" then
716                local xmldata = xml.load(filename) -- no need for caching it
717                if xmldata then
718                    profile = xml.filter(xmldata,format('xml://profiles/profile/(info|filename)[lower(text())=="%s"]/../table()',lname))
719                end
720            elseif suffix == "lua" then
721                local luadata = loadfile(filename)
722                luadata = ludata and luadata()
723                if luadata then
724                    profile = luadata[name] or luadata[lname] -- hashed
725                    if not profile then
726                        for i=1,#luadata do
727                            local li = luadata[i]
728                            if lower(li.info) == lname then -- indexed
729                                profile = li
730                                break
731                            end
732                        end
733                    end
734                end
735            end
736            if profile then
737                if next(profile) then
738                    report_backend("profile specification %a loaded from %a",name,filename)
739                    return profile
740                elseif trace_format then
741                    report_backend("profile specification %a loaded from %a but empty",name,filename)
742                end
743                return false
744            end
745        end
746    end
747    report_backend("profile specification %a not found in %a",name,concat(filenames, ", "))
748end
749
750local function urls(url)
751    if not url or url == "" then
752        return nil
753    else
754        local u = pdfarray()
755        for url in gmatch(url,"([^, ]+)") do
756            if find(url,"^http") then
757                u[#u+1] = pdfdictionary {
758                    FS = pdfconstant("URL"),
759                    F  = pdfstring(url),
760                }
761            end
762        end
763        return u
764    end
765end
766
767local function profilename(filename)
768    return lower(file.basename(filename))
769end
770
771local internalprofiles = { }
772
773local function handleinternalprofile(s,include)
774    local filename, colorspace = s.filename or "", s.colorspace or ""
775    if filename == "" or colorspace == "" then
776        report_backend("error in internal profile specification: %s",serialize(s,false))
777    else
778        local tag = profilename(filename)
779        local profile = internalprofiles[tag]
780        if not profile then
781            local colorspace = lower(colorspace)
782            if include then
783             -- local fullname = resolvers.findctxfile(filename) or ""
784                local fullname = locatefile(filename)
785                local channel = channels[colorspace] or nil
786                if fullname == "" then
787                    report_backend("error, couldn't locate profile %a",filename)
788                elseif not channel then
789                    report_backend("error, couldn't resolve channel entry for colorspace %a",colorspace)
790                else
791                    profile = pdfflushstreamfileobject(fullname,pdfdictionary{ N = channel },false) -- uncompressed
792                    internalprofiles[tag] = profile
793                    if trace_format then
794                        report_backend("including %a color profile from %a",colorspace,fullname)
795                    end
796                end
797            else
798                internalprofiles[tag] = true
799                if trace_format then
800                    report_backend("not including %a color profile %a",colorspace,filename)
801                end
802            end
803        end
804        return profile
805    end
806end
807
808local externalprofiles = { }
809
810local function handleexternalprofile(s,include) -- specification (include ignored here)
811    local name, url, filename, checksum, version, colorspace =
812        s.info or s.filename or "", s.url or "", s.filename or "", s.checksum or "", s.version or "", s.colorspace or ""
813    if false then -- somehow leads to invalid pdf
814        local iccprofile = colors.iccprofile(filename)
815        if iccprofile then
816            name       = name       ~= "" and name       or iccprofile.tags.desc.cleaned       or ""
817            url        = url        ~= "" and url        or iccprofile.tags.dmnd.cleaned       or ""
818            checksum   = checksum   ~= "" and checksum   or file.checksum(iccprofile.fullname) or ""
819            version    = version    ~= "" and version    or iccprofile.header.version          or ""
820            colorspace = colorspace ~= "" and colorspace or iccprofile.header.colorspace       or ""
821        end
822     -- table.print(iccprofile)
823    end
824    if name == "" or url == "" or checksum == "" or version == "" or colorspace == "" or filename == "" then
825        local profile = handleinternalprofile(s)
826        if profile then
827            report_backend("incomplete external profile specification, falling back to internal")
828        else
829            report_backend("error in external profile specification: %s",serialize(s,false))
830        end
831    else
832        local tag = profilename(filename)
833        local profile = externalprofiles[tag]
834        if not profile then
835            local d = pdfdictionary {
836                ProfileName = name,                                     -- not file name!
837                ProfileCS   = colorspace,
838                URLs        = urls(url),                                -- array containing at least one URL
839                CheckSum    = pdfverbose { "<", lower(checksum), ">" }, -- 16byte MD5 hash
840                ICCVersion  = pdfverbose { "<", version, ">"  },        -- bytes 8..11 from the header of the ICC profile, as a hex string
841            }
842            profile = pdfflushobject(d)
843            externalprofiles[tag] = profile
844        end
845        return profile
846    end
847end
848
849local loadeddefaults = { }
850
851local function handledefaultprofile(s,spec) -- specification
852    local filename, colorspace = s.filename or "", lower(s.colorspace or "")
853    if filename == "" or colorspace == "" then
854        report_backend("error in default profile specification: %s",serialize(s,false))
855    elseif not loadeddefaults[colorspace] then
856        local tag = profilename(filename)
857        local n = internalprofiles[tag] -- or externalprofiles[tag]
858        if n == true then -- not internalized
859            report_backend("no default profile %a for colorspace %a",filename,colorspace)
860        elseif n then
861            local a = pdfarray {
862                pdfconstant("ICCBased"),
863                pdfreference(n),
864            }
865             -- used in page /Resources, so this must be inserted at runtime
866            lpdf.adddocumentcolorspace(prefixes[colorspace],pdfreference(pdfflushobject(a)))
867            loadeddefaults[colorspace] = true
868            report_backend("setting %a as default %a color space",filename,colorspace)
869        else
870            report_backend("no default profile %a for colorspace %a",filename,colorspace)
871        end
872    elseif trace_format then
873        report_backend("a default %a colorspace is already in use",colorspace)
874    end
875end
876
877local loadedintents = { }
878local intents       = pdfarray()
879
880local function handleoutputintent(s,spec)
881    local url             = s.url or ""
882    local filename        = s.filename or ""
883    local name            = s.info or filename
884    local id              = s.id or ""
885    local outputcondition = s.outputcondition or ""
886    local info            = s.info or ""
887    if name == "" or id == "" then
888        report_backend("error in output intent specification: %s",serialize(s,false))
889    elseif not loadedintents[name] then
890        local tag = profilename(filename)
891        local internal, external = internalprofiles[tag], externalprofiles[tag]
892        if internal or external then
893            local d = {
894                  Type                      = pdfconstant("OutputIntent"),
895                  S                         = pdfconstant(spec.gts_flag or "GTS_PDFX"),
896                  OutputConditionIdentifier = id,
897                  RegistryName              = url,
898                  OutputCondition           = outputcondition,
899                  Info                      = info,
900            }
901            if internal and internal ~= true then
902                d.DestOutputProfile    = pdfreference(internal)
903            elseif external and external ~= true then
904                d.DestOutputProfileRef = pdfreference(external)
905            else
906                report_backend("omitting reference to profile for intent %a",name)
907            end
908            intents[#intents+1] = pdfreference(pdfflushobject(pdfdictionary(d)))
909            if trace_format then
910                report_backend("setting output intent to %a with id %a for entry %a",name,id,#intents)
911            end
912        else
913            report_backend("invalid output intent %a",name)
914        end
915        loadedintents[name] = true
916    elseif trace_format then
917        report_backend("an output intent with name %a is already in use",name)
918    end
919end
920
921local function handleiccprofile(message,spec,name,filename,how,options,alwaysinclude,gts_flag)
922    if name and name ~= "" then
923        local list = settings_to_array(name)
924        for i=1,#list do
925            local name = list[i]
926            local profile = loadprofile(name,filename)
927            if trace_format then
928                report_backend("handling %s %a",message,name)
929            end
930            if profile then
931                if formatspecification.cmyk_colors then
932                    profile.colorspace = profile.colorspace or "CMYK"
933                else
934                    profile.colorspace = profile.colorspace or "RGB"
935                end
936                local external = formatspecification.external_icc_profiles
937                local internal = formatspecification.internal_icc_profiles
938                local include  = formatspecification.include_intents
939                local always, never = options[variables.always], options[variables.never]
940                if always or alwaysinclude then
941                    if trace_format then
942                        report_backend("forcing internal profiles") -- can make preflight unhappy
943                    end
944                 -- internal, external = true, false
945                    internal, external = not never, false
946                elseif never then
947                    if trace_format then
948                        report_backend("forcing external profiles") -- can make preflight unhappy
949                    end
950                    internal, external = false, true
951                end
952                if external then
953                    if trace_format then
954                        report_backend("handling external profiles cf. %a",name)
955                    end
956                    handleexternalprofile(profile,false)
957                else
958                    if trace_format then
959                        report_backend("handling internal profiles cf. %a",name)
960                    end
961                    if internal then
962                        handleinternalprofile(profile,always or include)
963                    else
964                        report_backend("no profile inclusion for %a",formatname)
965                    end
966                end
967                how(profile,spec)
968            elseif trace_format then
969                report_backend("unknown profile %a",name)
970            end
971        end
972    end
973end
974
975local function flushoutputintents()
976    if #intents > 0 then
977        lpdf.addtocatalog("OutputIntents",pdfreference(pdfflushobject(intents)))
978    end
979end
980
981lpdf.registerdocumentfinalizer(flushoutputintents,2,"output intents")
982
983function codeinjections.setformat(s)
984    local format   = s.format or ""
985    local level    = tonumber(s.level)
986    local intent   = s.intent or ""
987    local profile  = s.profile or ""
988    local option   = s.option or ""
989    local filename = s.file or ""
990    if format ~= "" then
991        local spec = formats.data[lower(format)]
992        if spec then
993            formatspecification = spec
994            formatname = spec.format_name
995            report_backend("setting format to %a",formatname)
996            local xmp_file = formatspecification.xmp_file or ""
997            if xmp_file == "" then
998                -- weird error
999            else
1000                codeinjections.setxmpfile(xmp_file)
1001            end
1002            if not level then
1003                level = 3 -- good compromise, default anyway
1004            end
1005            local pdf_version         = spec.pdf_version * 10
1006            local inject_metadata     = spec.inject_metadata
1007            local majorversion        = math.floor(math.div(pdf_version,10))
1008            local minorversion        = math.floor(math.mod(pdf_version,10))
1009            local objectcompression   = spec.object_compression and pdf_version >= 15
1010            local compresslevel       = level or lpdf.compresslevel() -- keep default
1011            local objectcompresslevel = (objectcompression and (level or lpdf.objectcompresslevel())) or 0
1012            lpdf.setcompression(compresslevel,objectcompresslevel)
1013            lpdf.setversion(majorversion,minorversion)
1014            if objectcompression then
1015                report_backend("forcing pdf version %s.%s, compression level %s, object compression level %s",
1016                    majorversion,minorversion,compresslevel,objectcompresslevel)
1017            elseif compresslevel > 0 then
1018                report_backend("forcing pdf version %s.%s, compression level %s, object compression disabled",
1019                    majorversion,minorversion,compresslevel)
1020            else
1021                report_backend("forcing pdf version %s.%s, compression disabled",
1022                    majorversion,minorversion)
1023            end
1024            --
1025            -- cid sets can always omitted now, but those validators still complain so let's
1026            -- for a while keep it (for luigi):
1027            --
1028            lpdf.setomitcidset (formatspecification.include_cidsets  == false and 1 or 0) -- why a number
1029            lpdf.setomitcharset(formatspecification.include_charsets == false and 1 or 0) -- why a number
1030            --
1031            -- maybe block by pdf version
1032            --
1033            codeinjections.settaggingsupport(formatspecification.tagging)
1034            codeinjections.setattachmentsupport(formatspecification.attachments)
1035            --
1036            -- context.setupcolors { -- not this way
1037            --     cmyk = spec.cmyk_colors and variables.yes or variables.no,
1038            --     rgb  = spec.rgb_colors  and variables.yes or variables.no,
1039            -- }
1040            --
1041            colors.forcesupport(
1042                spec.gray_scale          or false,
1043                spec.rgb_colors          or false,
1044                spec.cmyk_colors         or false,
1045                spec.spot_colors         or false,
1046                spec.nchannel_colorspace or false
1047            )
1048            transparencies.forcesupport(
1049                spec.transparency        or false
1050            )
1051            viewerlayers.forcesupport(
1052                spec.optional_content    or false
1053            )
1054            viewerlayers.setfeatures(
1055                spec.has_order           or false -- new
1056            )
1057            --
1058            -- spec.jbig2_compression    : todo, block in image inclusion
1059            -- spec.jpeg2000_compression : todo, block in image inclusion
1060            --
1061            if type(inject_metadata) == "function" then
1062                inject_metadata()
1063            end
1064            local options = settings_to_hash(option)
1065            handleiccprofile("color profile",spec,profile,filename,handledefaultprofile,options,true)
1066            handleiccprofile("output intent",spec,intent,filename,handleoutputintent,options,false)
1067            if trace_variables then
1068                for k, v in sortedhash(formats.default) do
1069                    local v = formatspecification[k]
1070                    if type(v) ~= "function" then
1071                        report_backend("%a = %a",k,v or false)
1072                    end
1073                end
1074            end
1075            function codeinjections.setformat(noname)
1076                if trace_format then
1077                    report_backend("error, format is already set to %a, ignoring %a",formatname,noname.format)
1078                end
1079            end
1080        else
1081            report_backend("error, format %a is not supported",format)
1082        end
1083    elseif level then
1084        lpdf.setcompression(level,level)
1085    else
1086        -- we ignore this as we hook it in \everysetupbackend
1087    end
1088end
1089
1090directives.register("backend.format", function(v) -- table !
1091    local tv = type(v)
1092    if tv == "table" then
1093        codeinjections.setformat(v)
1094    elseif tv == "string" then
1095        codeinjections.setformat { format = v }
1096    end
1097end)
1098
1099interfaces.implement {
1100    name      = "setformat",
1101    actions   = codeinjections.setformat,
1102    arguments = { { "*" } }
1103}
1104
1105function codeinjections.getformatoption(key)
1106    return formatspecification and formatspecification[key]
1107end
1108
1109-- function codeinjections.getformatspecification()
1110--     return formatspecification
1111-- end
1112
1113function codeinjections.supportedformats()
1114    local t = { }
1115    for k, v in sortedhash(formats.data) do
1116        t[#t+1] = k
1117    end
1118    return t
1119end
1120
1121-- The following is somewhat cleaner but then we need to flag that there are
1122-- color spaces set so that the page flusher does not optimize the (at that
1123-- moment) still empty array away. So, next(d_colorspaces) should then become
1124-- a different test, i.e. also on flag. I'll add that when we need more forward
1125-- referencing.
1126--
1127-- local function embedprofile = handledefaultprofile
1128--
1129-- local function flushembeddedprofiles()
1130--     for colorspace, filename in next, defaults do
1131--         embedprofile(colorspace,filename)
1132--     end
1133-- end
1134--
1135-- local function handledefaultprofile(s)
1136--     defaults[lower(s.colorspace)] = s.filename
1137-- end
1138--
1139-- lpdf.registerdocumentfinalizer(flushembeddedprofiles,1,"embedded color profiles")
1140