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