lpdf-col.lmt /size: 25 Kb    last modification: 2025-02-21 11:03
1if not modules then modules = { } end modules ['lpdf-col'] = {
2    version   = 1.001,
3    comment   = "companion to lpdf-ini.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9local type, next, tostring, tonumber = type, next, tostring, tonumber
10local char, byte, format, gsub, rep, gmatch = string.char, string.byte, string.format, string.gsub, string.rep, string.gmatch
11local settings_to_array, settings_to_numbers = utilities.parsers.settings_to_array, utilities.parsers.settings_to_numbers
12local concat = table.concat
13local round = math.round
14local formatters = string.formatters
15
16local allocate                = utilities.storage.allocate
17
18local pdfbackend              = backends.registered.pdf
19local nodeinjections          = pdfbackend.nodeinjections
20local codeinjections          = pdfbackend.codeinjections
21local registrations           = pdfbackend.registrations
22
23local nodes                   = nodes
24local nodepool                = nodes.nuts.pool
25local register                = nodepool.register
26local setstate                = nodepool.setstate
27
28local lpdf                    = lpdf
29local pdfconstant             = lpdf.constant
30local pdfdictionary           = lpdf.dictionary
31local pdfarray                = lpdf.array
32local pdfreference            = lpdf.reference
33local pdfverbose              = lpdf.verbose
34
35local pdfflushobject          = lpdf.flushobject
36local pdfdelayedobject        = lpdf.delayedobject
37local pdfflushstreamobject    = lpdf.flushstreamobject
38local pdfshareobjectreference = lpdf.shareobjectreference
39
40local addtopageattributes     = lpdf.addtopageattributes
41local adddocumentcolorspace   = lpdf.adddocumentcolorspace
42local adddocumentextgstate    = lpdf.adddocumentextgstate
43
44local colors                  = attributes.colors
45local registercolor           = colors.register
46local colorsvalue             = colors.value
47local forcedmodel             = colors.forcedmodel
48local getpagecolormodel       = colors.getpagecolormodel
49local colortoattributes       = colors.toattributes
50
51local transparencies          = attributes.transparencies
52local registertransparancy    = transparencies.register
53local transparenciesvalue     = transparencies.value
54local transparencytoattribute = transparencies.toattribute
55
56local unsetvalue              <const> = attributes.unsetvalue
57
58local setmetatableindex       = table.setmetatableindex
59
60local c_transparency          = pdfconstant("Transparency")
61
62local f_gray   = formatters["%.3N g %.3N G"]
63local f_rgb    = formatters["%.3N %.3N %.3N rg %.3N %.3N %.3N RG"]
64local f_cmyk   = formatters["%.3N %.3N %.3N %.3N k %.3N %.3N %.3N %.3N K"]
65local f_spot   = formatters["/%s cs /%s CS %s SCN %s scn"]
66local f_tr     = formatters["Tr%s"]
67local f_cm     = formatters["q %.6N %.6N %.6N %.6N %.6N %.6N cm"]
68local f_effect = formatters["%s Tc %s w %s Tr"] -- %.6N ?
69local f_tr_gs  = formatters["/Tr%s gs"]
70local f_num_1  = formatters["%.3N %.3N"]
71local f_num_2  = formatters["%.3N %.3N"]
72local f_num_3  = formatters["%.3N %.3N %.3N"]
73local f_num_4  = formatters["%.3N %.3N %.3N %.3N"]
74
75local report_color = logs.reporter("colors","backend")
76
77-- page groups (might move to lpdf-ini.lua)
78
79local colorspaceconstants = allocate { -- v_none is ignored
80    gray = pdfconstant("DeviceGray"),
81    rgb  = pdfconstant("DeviceRGB"),
82    cmyk = pdfconstant("DeviceCMYK"),
83    all  = pdfconstant("DeviceRGB"), -- brr
84}
85
86local transparencygroups = { }
87
88lpdf.colorspaceconstants = colorspaceconstants
89lpdf.transparencygroups  = transparencygroups
90
91setmetatableindex(transparencygroups, function(transparencygroups,colormodel)
92    local cs = colorspaceconstants[colormodel]
93    if cs then
94        local d = pdfdictionary {
95            S  = c_transparency,
96            CS = cs,
97            I  = true,
98        }
99     -- local g = pdfreference(pdfflushobject(tostring(d)))
100        local g = pdfreference(pdfdelayedobject(tostring(d)))
101        transparencygroups[colormodel] = g
102        return g
103    else
104        transparencygroups[colormodel] = false
105        return false
106    end
107end)
108
109local function addpagegroup()
110    local model = getpagecolormodel()
111    if model then
112        local g = transparencygroups[model]
113        if g then
114            addtopageattributes("Group",g)
115        end
116    end
117end
118
119lpdf.registerpagefinalizer(addpagegroup,3,"pagegroup")
120
121-- injection code (needs a bit reordering)
122
123-- color injection
124
125function nodeinjections.rgbcolor(r,g,b)
126    return register(setstate(f_rgb(r,g,b,r,g,b)))
127end
128
129function nodeinjections.cmykcolor(c,m,y,k)
130    return register(setstate(f_cmyk(c,m,y,k,c,m,y,k)))
131end
132
133function nodeinjections.graycolor(s) -- caching 0/1 does not pay off
134    return register(setstate(f_gray(s,s)))
135end
136
137function nodeinjections.spotcolor(n,f,d,p)
138    if type(p) == "string" then
139        p = gsub(p,","," ") -- brr misuse of spot
140    end
141    return register(setstate(f_spot(n,n,p,p)))
142end
143
144function nodeinjections.transparency(n)
145    return register(setstate(f_tr_gs(n)))
146end
147
148-- a bit weird but let's keep it here for a while
149
150local effects = {
151    normal = 0,
152    inner  = 0,
153    outer  = 1,
154    both   = 2,
155    hidden = 3,
156    clip   = 7,
157}
158
159local bp = number.dimenfactors.bp
160
161function nodeinjections.effect(effect,stretch,rulethickness)
162    -- always, no zero test (removed)
163    rulethickness = bp * rulethickness
164    effect = effects[effect] or effects['normal']
165    return register(setstate(f_effect(stretch,rulethickness,effect))) -- watch order
166end
167
168-- spot- and indexcolors
169
170local pdf_separation  = pdfconstant("Separation")
171local pdf_indexed     = pdfconstant("Indexed")
172local pdf_device_n    = pdfconstant("DeviceN")
173local pdf_device_rgb  = pdfconstant("DeviceRGB")
174local pdf_device_cmyk = pdfconstant("DeviceCMYK")
175local pdf_device_gray = pdfconstant("DeviceGray")
176local pdf_extgstate   = pdfconstant("ExtGState")
177
178local pdf_rgb_range   = pdfarray { 0, 1, 0, 1, 0, 1 }
179local pdf_cmyk_range  = pdfarray { 0, 1, 0, 1, 0, 1, 0, 1 }
180local pdf_gray_range  = pdfarray { 0, 1 }
181
182local f_rgb_function  = formatters["dup %s mul exch dup %s mul exch %s mul"]
183local f_cmyk_function = formatters["dup %s mul exch dup %s mul exch dup %s mul exch %s mul"]
184local f_gray_function = formatters["%s mul"]
185
186local documentcolorspaces = pdfdictionary()
187
188local spotcolorhash       = { } -- not needed
189local spotcolornames      = { }
190local indexcolorhash      = { }
191local delayedindexcolors  = { }
192
193function registrations.spotcolorname(name,e)
194    spotcolornames[name] = e or name
195end
196
197function registrations.getspotcolorreference(name)
198    return spotcolorhash[name]
199end
200
201-- beware: xpdf/okular/evince cannot handle the spot->process shade
202
203-- This should become delayed i.e. only flush when used; in that case we need
204-- need to store the specification and then flush them when accesssomespotcolor
205-- is called. At this moment we assume that splotcolors that get defined are
206-- also used which keeps the overhad small anyway. Tricky for mp ...
207
208local processcolors
209
210local function registersomespotcolor(name,noffractions,names,p,colorspace,range,funct)
211    noffractions = tonumber(noffractions) or 1 -- to be checked
212    if noffractions == 0 then
213        -- can't happen
214    elseif noffractions == 1 then
215        local dictionary = pdfdictionary {
216            FunctionType = 4,
217            Domain       = { 0, 1 },
218            Range        = range,
219        }
220        local calculations = pdfflushstreamobject(format("{ %s }",funct),dictionary)
221     -- local calculations = pdfobject {
222     --     type      = "stream",
223     --     immediate = true,
224     --     string    = format("{ %s }",funct),
225     --     attr      = dictionary(),
226     -- }
227        local array = pdfarray {
228            pdf_separation,
229            pdfconstant(spotcolornames[name] or name),
230            colorspace,
231            pdfreference(calculations),
232        }
233        local m = pdfflushobject(array)
234        local mr = pdfreference(m)
235        spotcolorhash[name] = m
236        documentcolorspaces[name] = mr
237        adddocumentcolorspace(name,mr)
238    else
239        local cnames = pdfarray()
240        local domain = pdfarray()
241        local colorants = pdfdictionary()
242        for n in gmatch(names,"[^,]+") do
243            local name = spotcolornames[n] or n
244            -- the cmyk names assume that they are indeed these colors
245            if n == "cyan" then
246                name = "Cyan"
247            elseif n == "magenta" then
248                name = "Magenta"
249            elseif n == "yellow" then
250                name = "Yellow"
251            elseif n == "black" then
252                name = "Black"
253            else
254                local sn = spotcolorhash[name] or spotcolorhash[n]
255                if not sn then
256                    report_color("defining %a as colorant",name)
257                    colors.definespotcolor("",name,"p=1",true)
258                    sn = spotcolorhash[name] or spotcolorhash[n]
259                end
260                if sn then
261                    colorants[name] = pdfreference(sn)
262                else
263                    -- maybe some day generate colorants (spot colors for multi) automatically
264                    report_color("unknown colorant %a, using black instead",name or n)
265                    name = "Black"
266                end
267            end
268            cnames[#cnames+1] = pdfconstant(name)
269            domain[#domain+1] = 0
270            domain[#domain+1] = 1
271        end
272        if not processcolors then
273            local specification = pdfdictionary {
274                ColorSpace = pdfconstant("DeviceCMYK"),
275                Components = pdfarray {
276                    pdfconstant("Cyan"),
277                    pdfconstant("Magenta"),
278                    pdfconstant("Yellow"),
279                    pdfconstant("Black")
280                }
281            }
282            processcolors = pdfreference(pdfflushobject(specification))
283        end
284        local dictionary = pdfdictionary {
285            FunctionType = 4,
286            Domain       = domain,
287            Range        = range,
288        }
289        local calculation = pdfflushstreamobject(format("{ %s %s }",rep("pop ",noffractions),funct),dictionary)
290        local channels = pdfdictionary {
291            Subtype   = pdfconstant("NChannel"),
292            Colorants = colorants,
293            Process   = processcolors,
294        }
295        local array = pdfarray {
296            pdf_device_n,
297            cnames,
298            colorspace,
299            pdfreference(calculation),
300            pdfshareobjectreference(tostring(channels)), -- optional but needed for shades
301        }
302        local m = pdfflushobject(array)
303        local mr = pdfreference(m)
304        spotcolorhash[name] = m
305        documentcolorspaces[name] = mr
306        adddocumentcolorspace(name,mr)
307    end
308end
309
310-- wrong name
311
312local function registersomeindexcolor(name,noffractions,names,p,colorspace,range,funct)
313    noffractions = tonumber(noffractions) or 1 -- to be checked
314    local cnames = pdfarray()
315    local domain = pdfarray()
316    local names  = settings_to_array(#names == 0 and name or names)
317    local values = settings_to_numbers(p)
318    names [#names +1] = "None"
319    values[#values+1] = 1
320    -- check for #names == #values
321    for i=1,#names do
322        local name = names[i]
323        local spot = spotcolornames[name]
324        cnames[#cnames+1] = pdfconstant(spot ~= "" and spot or name)
325        domain[#domain+1] = 0
326        domain[#domain+1] = 1
327    end
328    local dictionary = pdfdictionary {
329        FunctionType = 4,
330        Domain       = domain,
331        Range        = range,
332    }
333    local n = pdfflushstreamobject(format("{ %s %s }",rep("exch pop ",noffractions),funct),dictionary) -- exch pop
334    local a = pdfarray {
335        pdf_device_n,
336        cnames,
337        colorspace,
338        pdfreference(n),
339    }
340    local vector = { }
341    local set    = { }
342    local n      = #values
343    for i=255,0,-1 do
344        for j=1,n do
345            set[j] = format("%02X",round(values[j]*i))
346        end
347        vector[#vector+1] = concat(set)
348    end
349    vector = pdfverbose { "<", concat(vector, " "), ">" }
350    local n = pdfflushobject(pdfarray{ pdf_indexed, a, 255, vector })
351    adddocumentcolorspace(format("%s_indexed",name),pdfreference(n))
352    return n
353end
354
355-- actually, names (parent) is the hash
356
357local function delayindexcolor(name,names,func)
358    local hash = (names ~= "" and names) or name
359    delayedindexcolors[hash] = func
360end
361
362local function indexcolorref(name) -- actually, names (parent) is the hash
363    local parent = colors.spotcolorparent(name)
364    local data   = indexcolorhash[name]
365    if data == nil then
366        local delayedindexcolor = delayedindexcolors[parent]
367        if type(delayedindexcolor) == "function" then
368            data = delayedindexcolor()
369            delayedindexcolors[parent] = true
370        end
371        indexcolorhash[parent] = data or false
372    end
373    return data
374end
375
376function registrations.rgbspotcolor(name,noffractions,names,p,r,g,b)
377    if noffractions == 1 then
378        registersomespotcolor(name,noffractions,names,p,pdf_device_rgb,pdf_rgb_range,f_rgb_function(r,g,b))
379    else
380        registersomespotcolor(name,noffractions,names,p,pdf_device_rgb,pdf_rgb_range,f_num_3(r,g,b))
381    end
382    delayindexcolor(name,names,function()
383        return registersomeindexcolor(name,noffractions,names,p,pdf_device_rgb,pdf_rgb_range,f_rgb_function(r,g,b))
384    end)
385end
386
387function registrations.cmykspotcolor(name,noffractions,names,p,c,m,y,k)
388    if noffractions == 1 then
389        registersomespotcolor(name,noffractions,names,p,pdf_device_cmyk,pdf_cmyk_range,f_cmyk_function(c,m,y,k))
390    else
391        registersomespotcolor(name,noffractions,names,p,pdf_device_cmyk,pdf_cmyk_range,f_num_4(c,m,y,k))
392    end
393    delayindexcolor(name,names,function()
394        return registersomeindexcolor(name,noffractions,names,p,pdf_device_cmyk,pdf_cmyk_range,f_cmyk_function(c,m,y,k))
395    end)
396end
397
398function registrations.grayspotcolor(name,noffractions,names,p,s)
399    if noffractions == 1 then
400        registersomespotcolor(name,noffractions,names,p,pdf_device_gray,pdf_gray_range,f_gray_function(s))
401    else
402        registersomespotcolor(name,noffractions,names,p,pdf_device_gray,pdf_gray_range,f_num_1(s))
403    end
404    delayindexcolor(name,names,function()
405        return registersomeindexcolor(name,noffractions,names,p,pdf_device_gray,pdf_gray_range,f_gray_function(s))
406    end)
407end
408
409function registrations.rgbindexcolor(name,noffractions,names,p,r,g,b)
410    registersomeindexcolor(name,noffractions,names,p,pdf_device_rgb,pdf_rgb_range,f_rgb_function(r,g,b))
411end
412
413function registrations.cmykindexcolor(name,noffractions,names,p,c,m,y,k)
414    registersomeindexcolor(name,noffractions,names,p,pdf_device_cmyk,pdf_cmyk_range,f_cmyk_function(c,m,y,k))
415end
416
417function registrations.grayindexcolor(name,noffractions,names,p,s)
418    registersomeindexcolor(name,noffractions,names,p,pdf_device_gray,pdf_gray_range,f_gray_function(s))
419end
420
421function codeinjections.setfigurecolorspace(data,figure)
422    local color = data.request.color
423    if color then -- != v_default
424        local ref = indexcolorref(color)
425        if ref then
426            figure.colorspace = ref
427            data.used.color    = color
428            data.used.colorref = ref
429        end
430    end
431end
432
433-- transparency
434
435local pdftransparencies = { [0] =
436    pdfconstant("Normal"),
437    pdfconstant("Normal"),
438    pdfconstant("Multiply"),
439    pdfconstant("Screen"),
440    pdfconstant("Overlay"),
441    pdfconstant("SoftLight"),
442    pdfconstant("HardLight"),
443    pdfconstant("ColorDodge"),
444    pdfconstant("ColorBurn"),
445    pdfconstant("Darken"),
446    pdfconstant("Lighten"),
447    pdfconstant("Difference"),
448    pdfconstant("Exclusion"),
449    pdfconstant("Hue"),
450    pdfconstant("Saturation"),
451    pdfconstant("Color"),
452    pdfconstant("Luminosity"),
453    pdfconstant("Compatible"), -- obsolete; 'Normal' is used in this case
454}
455
456local documenttransparencies = { }
457local transparencyhash       = { } -- share objects
458
459local done, signaled = false, false
460
461function registrations.transparency(n,a,t)
462    if not done then
463        local d = pdfdictionary {
464              Type = pdf_extgstate,
465              ca   = 1,
466              CA   = 1,
467              BM   = pdftransparencies[1],
468              AIS  = false,
469            }
470        local m = pdfflushobject(d)
471        local mr = pdfreference(m)
472        transparencyhash[0] = m
473        documenttransparencies[0] = mr
474        adddocumentextgstate("Tr0",mr)
475        done = true
476    end
477    if n > 0 and not transparencyhash[n] then
478        local d = pdfdictionary {
479              Type = pdf_extgstate,
480              ca   = tonumber(t),
481              CA   = tonumber(t),
482              BM   = pdftransparencies[tonumber(a)] or pdftransparencies[0],
483              AIS  = false,
484            }
485        local m = pdfflushobject(d)
486        local mr = pdfreference(m)
487        transparencyhash[n] = m
488        documenttransparencies[n] = mr
489        adddocumentextgstate(f_tr(n),mr)
490    end
491end
492
493statistics.register("page group warning", function()
494    if done then
495        local model = getpagecolormodel()
496        if model and not transparencygroups[model] then
497            return "transparencies are used but no pagecolormodel is set"
498        end
499    end
500end)
501
502-- Literals needed to inject code in the mp stream, we cannot use attributes there
503-- since literals may have qQ's, much may go away once we have mplib code in place.
504--
505-- This module assumes that some functions are defined in the colors namespace
506-- which most likely will be loaded later.
507
508local function lpdfcolor(model,ca,default) -- todo: use gray when no color
509    if colors.supported then
510        local cv = colorsvalue(ca)
511        if cv then
512            if model == 1 then
513                model = cv[1]
514            end
515            model = forcedmodel(model)
516            if model == 2 then
517                local s = cv[2]
518                return f_gray(s,s)
519            elseif model == 3 then
520                local r = cv[3]
521                local g = cv[4]
522                local b = cv[5]
523                return f_rgb(r,g,b,r,g,b)
524            elseif model == 4 then
525                local c = cv[6]
526                local m = cv[7]
527                local y = cv[8]
528                local k = cv[9]
529                return f_cmyk(c,m,y,k,c,m,y,k)
530            else
531                local n = cv[10]
532                local f = cv[11]
533                local d = cv[12]
534                local p = cv[13]
535                if type(p) == "string" then
536                    p = gsub(p,","," ") -- brr misuse of spot
537                end
538                return f_spot(n,n,p,p)
539            end
540        else
541            return f_gray(default or 0,default or 0)
542        end
543    else
544        return ""
545    end
546end
547
548lpdf.color = lpdfcolor
549
550interfaces.implement {
551    name      = "pdfcolor",
552    public    = true,
553    untraced  = true,
554    actions   = { lpdfcolor, context },
555    arguments = "integer"
556}
557
558function lpdf.colorspec(model,ca,default)
559    if ca and ca > 0 then
560        local cv = colors.value(ca)
561        if cv then
562            if model == 1 then
563                model = cv[1]
564            end
565            if model == 2 then
566                return pdfarray { cv[2] }
567            elseif model == 3 then
568                return pdfarray { cv[3],cv[4],cv[5] }
569            elseif model == 4 then
570                return pdfarray { cv[6],cv[7],cv[8],cv[9] }
571            elseif model == 5 then
572                return pdfarray { cv[13] }
573            end
574        end
575    end
576    if default then
577        return default
578    end
579end
580
581function lpdf.pdfcolor(attribute) -- bonus, for pgf and friends
582    return lpdfcolor(1,attribute)
583end
584
585local function lpdftransparency(ct,default) -- kind of overlaps with transparencycode
586    -- beware, we need this hack because normally transparencies are not
587    -- yet registered and therefore the number is not not known ... we
588    -- might use the attribute number itself in the future
589    if transparencies.supported then
590        local ct = transparenciesvalue(ct)
591        if ct then
592            return f_tr_gs(registertransparancy(nil,ct[1],ct[2],true))
593        else
594            return f_tr_gs(0)
595        end
596    else
597        return ""
598    end
599end
600
601lpdf.transparency = lpdftransparency
602
603function lpdf.colorvalue(model,ca,default)
604    local cv = colorsvalue(ca)
605    if cv then
606        if model == 1 then
607            model = cv[1]
608        end
609        model = forcedmodel(model)
610        if model == 2 then
611            return f_num_1(cv[2])
612        elseif model == 3 then
613            return f_num_3(cv[3],cv[4],cv[5])
614        elseif model == 4 then
615            return f_num_4(cv[6],cv[7],cv[8],cv[9])
616        else
617            return f_num_1(cv[13])
618        end
619    else
620        return f_num_1(default or 0)
621    end
622end
623
624function lpdf.colorvalues(model,ca,default)
625    local cv = colorsvalue(ca)
626    if cv then
627        if model == 1 then
628            model = cv[1]
629        end
630        model = forcedmodel(model)
631        if model == 2 then
632            return cv[2]
633        elseif model == 3 then
634            return cv[3], cv[4], cv[5]
635        elseif model == 4 then
636            return cv[6], cv[7], cv[8], cv[9]
637        elseif model == 5 then
638            return cv[13]
639        end
640    else
641        return default or 0
642    end
643end
644
645function lpdf.transparencyvalue(ta,default)
646    local tv = transparenciesvalue(ta)
647    if tv then
648        return tv[2]
649    else
650        return default or 1
651    end
652end
653
654function lpdf.colorspace(model,ca)
655    local cv = colorsvalue(ca)
656    if cv then
657        if model == 1 then
658            model = cv[1]
659        end
660        model = forcedmodel(model)
661        if model == 2 then
662            return "DeviceGray"
663        elseif model == 3 then
664            return "DeviceRGB"
665        elseif model == 4 then
666            return "DeviceCMYK"
667        end
668    end
669    return "DeviceGRAY"
670end
671
672-- by registering we getconversion for free (ok, at the cost of overhead)
673
674local intransparency = false
675
676function lpdf.rgbcode(model,r,g,b)
677    if colors.supported then
678        return lpdfcolor(model,registercolor(nil,'rgb',r,g,b))
679    else
680        return ""
681    end
682end
683
684function lpdf.cmykcode(model,c,m,y,k)
685    if colors.supported then
686        return lpdfcolor(model,registercolor(nil,'cmyk',c,m,y,k))
687    else
688        return ""
689    end
690end
691
692function lpdf.graycode(model,s)
693    if colors.supported then
694        return lpdfcolor(model,registercolor(nil,'gray',s))
695    else
696        return ""
697    end
698end
699
700function lpdf.spotcode(model,n,f,d,p)
701    if colors.supported then
702        return lpdfcolor(model,registercolor(nil,'spot',n,f,d,p)) -- incorrect
703    else
704        return ""
705    end
706end
707
708function lpdf.transparencycode(a,t)
709    if transparencies.supported then
710        intransparency = true
711        return f_tr_gs(registertransparancy(nil,a,t,true)) -- true forces resource
712    else
713        return ""
714    end
715end
716
717function lpdf.finishtransparencycode()
718    if transparencies.supported and intransparency then
719        intransparency = false
720        return f_tr_gs(0) -- we happen to know this -)
721    else
722        return ""
723    end
724end
725
726do
727
728    local lpdfprint = lpdf.print
729
730    local c_cache = setmetatableindex(function(t,m)
731        -- We inherit the outer transparency.
732        local v = setmetatableindex(function(t,c)
733            local p = "q " .. lpdfcolor(m,c)
734            t[c] = p
735            return p
736        end)
737        t[m] = v
738        return v
739    end)
740
741    local t_cache = setmetatableindex(function(t,transparency)
742        local p = lpdftransparency(transparency)
743        local v = setmetatableindex(function(t,colormodel)
744            local v = setmetatableindex(function(t,color)
745                local v = "q " .. lpdfcolor(colormodel,color) .. " " .. p
746                t[color] = v
747                return v
748            end)
749            t[colormodel] = v
750            return v
751        end)
752        t[transparency] = v
753        return v
754    end)
755
756 -- function codeinjections.vfliteral(pos_h,pos_v,packet)
757 --     lpdfprint(packet[2],packet[3])
758 -- end
759
760    function codeinjections.vfstartcolor(pos_h,pos_v,packet)
761        local color = type(packet) == "table" and packet[2] or packet
762        if color then
763            local m, c = colortoattributes(color)
764            local t = transparencytoattribute(color)
765            if t and t ~= unsetvalue then
766                lpdfprint("page", t_cache[t][m][c]) -- "q " .. lpdfcolor(m,c) .. " " .. lpdftransparency(t)
767            else
768                lpdfprint("page", c_cache[m][c])    -- "q " .. lpdfcolor(m,c))
769            end
770        else
771            lpdfprint("page", "q")
772        end
773    end
774
775    function codeinjections.vfstopcolor()
776     -- lpdfprint("text", "Q")
777        lpdfprint("page", "Q")
778    end
779
780end
781
782-- These generate the in-stream color commands:
783
784do
785
786    local fonts = { }
787    lpdf.fonts  = fonts
788
789    fonts.color_indirect = function(c,t)
790        if c and t then
791            return lpdfcolor(1,c) .. " " .. lpdftransparency(t)
792        elseif c then
793            return lpdfcolor(1,c)
794        elseif t then
795            return lpdftransparency(t)
796        else
797            return false
798        end
799    end
800
801    local colors    = attributes.colors
802    local rgbtocmyk = colors.rgbtocmyk
803
804    local f_cmyk = formatters["%.3N %.3N %.3N %.3N k"]
805    local f_rgb  = formatters["%.3N %.3N %.3N rg"]
806    local f_gray = formatters["%.3N g"]
807
808    fonts.color_direct = function(r,g,b)
809        local m = colors.model
810        if r == g and g == b then
811            return f_gray(r)
812        elseif m == "cmyk" then
813            return f_cmyk(rgbtocmyk(r,g,b))
814        else
815            return f_rgb(r,g,b)
816        end
817    end
818
819end
820
821