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