lpdf-col.lua /size: 26 Kb    last modification: 2021-10-28 13:50
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
9-- slants also page ?
10
11local type, next, tostring, tonumber = type, next, tostring, tonumber
12local char, byte, format, gsub, rep, gmatch = string.char, string.byte, string.format, string.gsub, string.rep, string.gmatch
13local settings_to_array, settings_to_numbers = utilities.parsers.settings_to_array, utilities.parsers.settings_to_numbers
14local concat = table.concat
15local round = math.round
16local formatters = string.formatters
17
18local backends, lpdf, nodes = backends, lpdf, nodes
19
20local allocate                = utilities.storage.allocate
21
22local nodeinjections          = backends.pdf.nodeinjections
23local codeinjections          = backends.pdf.codeinjections
24local registrations           = backends.pdf.registrations
25
26local nodepool                = nodes.nuts.pool
27local register                = nodepool.register
28local pageliteral             = nodepool.pageliteral
29
30local pdfconstant             = lpdf.constant
31local pdfdictionary           = lpdf.dictionary
32local pdfarray                = lpdf.array
33local pdfreference            = lpdf.reference
34local pdfverbose              = lpdf.verbose
35local pdfflushobject          = lpdf.flushobject
36local pdfdelayedobject        = lpdf.delayedobject
37local pdfflushstreamobject    = lpdf.flushstreamobject
38
39local pdfshareobjectreference = lpdf.shareobjectreference
40
41local addtopageattributes     = lpdf.addtopageattributes
42local adddocumentcolorspace   = lpdf.adddocumentcolorspace
43local adddocumentextgstate    = lpdf.adddocumentextgstate
44
45local colors                  = attributes.colors
46local registercolor           = colors.register
47local colorsvalue             = colors.value
48local forcedmodel             = colors.forcedmodel
49local getpagecolormodel       = colors.getpagecolormodel
50local colortoattributes       = colors.toattributes
51
52local transparencies          = attributes.transparencies
53local registertransparancy    = transparencies.register
54local transparenciesvalue     = transparencies.value
55local transparencytoattribute = transparencies.toattribute
56
57local unsetvalue              = attributes.unsetvalue
58
59local setmetatableindex       = table.setmetatableindex
60
61local c_transparency          = pdfconstant("Transparency")
62
63local f_gray   = formatters["%.3N g %.3N G"]
64local f_rgb    = formatters["%.3N %.3N %.3N rg %.3N %.3N %.3N RG"]
65local f_cmyk   = formatters["%.3N %.3N %.3N %.3N k %.3N %.3N %.3N %.3N K"]
66local f_spot   = formatters["/%s cs /%s CS %s SCN %s scn"]
67local f_tr     = formatters["Tr%s"]
68local f_cm     = formatters["q %.6N %.6N %.6N %.6N %.6N %.6N cm"]
69local f_effect = formatters["%s Tc %s w %s Tr"] -- %.6N ?
70local f_tr_gs  = formatters["/Tr%s gs"]
71local f_num_1  = formatters["%.3N %.3N"]
72local f_num_2  = formatters["%.3N %.3N"]
73local f_num_3  = formatters["%.3N %.3N %.3N"]
74local f_num_4  = formatters["%.3N %.3N %.3N %.3N"]
75
76local report_color = logs.reporter("colors","backend")
77
78-- page groups (might move to lpdf-ini.lua)
79
80local colorspaceconstants = allocate { -- v_none is ignored
81    gray = pdfconstant("DeviceGray"),
82    rgb  = pdfconstant("DeviceRGB"),
83    cmyk = pdfconstant("DeviceCMYK"),
84    all  = pdfconstant("DeviceRGB"), -- brr
85}
86
87local transparencygroups = { }
88
89lpdf.colorspaceconstants = colorspaceconstants
90lpdf.transparencygroups  = transparencygroups
91
92setmetatableindex(transparencygroups, function(transparencygroups,colormodel)
93    local cs = colorspaceconstants[colormodel]
94    if cs then
95        local d = pdfdictionary {
96            S  = c_transparency,
97            CS = cs,
98            I  = true,
99        }
100     -- local g = pdfreference(pdfflushobject(tostring(d)))
101        local g = pdfreference(pdfdelayedobject(tostring(d)))
102        transparencygroups[colormodel] = g
103        return g
104    else
105        transparencygroups[colormodel] = false
106        return false
107    end
108end)
109
110local function addpagegroup()
111    local model = getpagecolormodel()
112    if model then
113        local g = transparencygroups[model]
114        if g then
115            addtopageattributes("Group",g)
116        end
117    end
118end
119
120lpdf.registerpagefinalizer(addpagegroup,3,"pagegroup")
121
122-- injection code (needs a bit reordering)
123
124-- color injection
125
126function nodeinjections.rgbcolor(r,g,b)
127    return register(pageliteral(f_rgb(r,g,b,r,g,b)))
128end
129
130function nodeinjections.cmykcolor(c,m,y,k)
131    return register(pageliteral(f_cmyk(c,m,y,k,c,m,y,k)))
132end
133
134function nodeinjections.graycolor(s) -- caching 0/1 does not pay off
135    return register(pageliteral(f_gray(s,s)))
136end
137
138function nodeinjections.spotcolor(n,f,d,p)
139    if type(p) == "string" then
140        p = gsub(p,","," ") -- brr misuse of spot
141    end
142    return register(pageliteral(f_spot(n,n,p,p)))
143end
144
145function nodeinjections.transparency(n)
146    return register(pageliteral(f_tr_gs(n)))
147end
148
149-- a bit weird but let's keep it here for a while
150
151local effects = {
152    normal = 0,
153    inner  = 0,
154    outer  = 1,
155    both   = 2,
156    hidden = 3,
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(pageliteral(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      = "lpdf_color",
552    actions   = { lpdfcolor, context },
553    arguments = "integer"
554}
555
556function lpdf.colorspec(model,ca,default)
557    if ca and ca > 0 then
558        local cv = colors.value(ca)
559        if cv then
560            if model == 1 then
561                model = cv[1]
562            end
563            if model == 2 then
564                return pdfarray { cv[2] }
565            elseif model == 3 then
566                return pdfarray { cv[3],cv[4],cv[5] }
567            elseif model == 4 then
568                return pdfarray { cv[6],cv[7],cv[8],cv[9] }
569            elseif model == 5 then
570                return pdfarray { cv[13] }
571            end
572        end
573    end
574    if default then
575        return default
576    end
577end
578
579function lpdf.pdfcolor(attribute) -- bonus, for pgf and friends
580    return lpdfcolor(1,attribute)
581end
582
583function lpdf.transparency(ct,default) -- kind of overlaps with transparencycode
584    -- beware, we need this hack because normally transparencies are not
585    -- yet registered and therefore the number is not not known ... we
586    -- might use the attribute number itself in the future
587    if transparencies.supported then
588        local ct = transparenciesvalue(ct)
589        if ct then
590            return f_tr_gs(registertransparancy(nil,ct[1],ct[2],true))
591        else
592            return f_tr_gs(0)
593        end
594    else
595        return ""
596    end
597end
598
599function lpdf.colorvalue(model,ca,default)
600    local cv = colorsvalue(ca)
601    if cv then
602        if model == 1 then
603            model = cv[1]
604        end
605        model = forcedmodel(model)
606        if model == 2 then
607            return f_num_1(cv[2])
608        elseif model == 3 then
609            return f_num_3(cv[3],cv[4],cv[5])
610        elseif model == 4 then
611            return f_num_4(cv[6],cv[7],cv[8],cv[9])
612        else
613            return f_num_1(cv[13])
614        end
615    else
616        return f_num_1(default or 0)
617    end
618end
619
620function lpdf.colorvalues(model,ca,default)
621    local cv = colorsvalue(ca)
622    if cv then
623        if model == 1 then
624            model = cv[1]
625        end
626        model = forcedmodel(model)
627        if model == 2 then
628            return cv[2]
629        elseif model == 3 then
630            return cv[3], cv[4], cv[5]
631        elseif model == 4 then
632            return cv[6], cv[7], cv[8], cv[9]
633        elseif model == 5 then
634            return cv[13]
635        end
636    else
637        return default or 0
638    end
639end
640
641function lpdf.transparencyvalue(ta,default)
642    local tv = transparenciesvalue(ta)
643    if tv then
644        return tv[2]
645    else
646        return default or 1
647    end
648end
649
650function lpdf.colorspace(model,ca)
651    local cv = colorsvalue(ca)
652    if cv then
653        if model == 1 then
654            model = cv[1]
655        end
656        model = forcedmodel(model)
657        if model == 2 then
658            return "DeviceGray"
659        elseif model == 3 then
660            return "DeviceRGB"
661        elseif model == 4 then
662            return "DeviceCMYK"
663        end
664    end
665    return "DeviceGRAY"
666end
667
668-- by registering we getconversion for free (ok, at the cost of overhead)
669
670local intransparency = false
671local pdfcolor       = lpdf.color
672
673function lpdf.rgbcode(model,r,g,b)
674    if colors.supported then
675        return pdfcolor(model,registercolor(nil,'rgb',r,g,b))
676    else
677        return ""
678    end
679end
680
681function lpdf.cmykcode(model,c,m,y,k)
682    if colors.supported then
683        return pdfcolor(model,registercolor(nil,'cmyk',c,m,y,k))
684    else
685        return ""
686    end
687end
688
689function lpdf.graycode(model,s)
690    if colors.supported then
691        return pdfcolor(model,registercolor(nil,'gray',s))
692    else
693        return ""
694    end
695end
696
697function lpdf.spotcode(model,n,f,d,p)
698    if colors.supported then
699        return pdfcolor(model,registercolor(nil,'spot',n,f,d,p)) -- incorrect
700    else
701        return ""
702    end
703end
704
705function lpdf.transparencycode(a,t)
706    if transparencies.supported then
707        intransparency = true
708        return f_tr_gs(registertransparancy(nil,a,t,true)) -- true forces resource
709    else
710        return ""
711    end
712end
713
714function lpdf.finishtransparencycode()
715    if transparencies.supported and intransparency then
716        intransparency = false
717        return f_tr_gs(0) -- we happen to know this -)
718    else
719        return ""
720    end
721end
722
723-- this will move to lpdf-spe.lua an dwe then can also add a metatable with
724-- normal context colors
725
726do
727
728    local pdfcolor        = lpdf.color
729    local pdftransparency = lpdf.transparency
730
731    local f_slant = formatters["q 1 0 %N 1 0 0 cm"]
732
733 -- local fillcolors = {
734 --     red        = { "pdf", "page", "1 0 0 rg" },
735 --     green      = { "pdf", "page", "0 1 0 rg" },
736 --     blue       = { "pdf", "page", "0 0 1 rg" },
737 --     gray       = { "pdf", "page", ".5 g" },
738 --     black      = { "pdf", "page", "0 g" },
739 --     palered    = { "pdf", "page", "1 .75 .75 rg" },
740 --     palegreen  = { "pdf", "page", ".75 1 .75 rg" },
741 --     paleblue   = { "pdf", "page", ".75 .75 1 rg" },
742 --     palegray   = { "pdf", "page", ".75 g" },
743 -- }
744 --
745 -- local strokecolors = {
746 --     red        = { "pdf", "page", "1 0 0 RG" },
747 --     green      = { "pdf", "page", "0 1 0 RG" },
748 --     blue       = { "pdf", "page", "0 0 1 RG" },
749 --     gray       = { "pdf", "page", ".5 G" },
750 --     black      = { "pdf", "page", "0 G" },
751 --     palered    = { "pdf", "page", "1 .75 .75 RG" },
752 --     palegreen  = { "pdf", "page", ".75 1 .75 RG" },
753 --     paleblue   = { "pdf", "page", ".75 .75 1 RG" },
754 --     palegray   = { "pdf", "page", ".75 G" },
755 -- }
756 --
757 -- backends.pdf.tables.vfspecials = allocate { -- todo: distinguish between glyph and rule color
758 --
759 --     red          = { "pdf", "page", "1 0 0 rg 1 0 0 RG" },
760 --     green        = { "pdf", "page", "0 1 0 rg 0 1 0 RG" },
761 --     blue         = { "pdf", "page", "0 0 1 rg 0 0 1 RG" },
762 --     gray         = { "pdf", "page", ".75 g .75 G" },
763 --     black        = { "pdf", "page", "0 g 0 G" },
764 --
765 --  -- rulecolors   = fillcolors,
766 --  -- fillcolors   = fillcolors,
767 --  -- strokecolors = strokecolors,
768 --
769 --     startslant   = function(a) return { "pdf", "origin", f_slant(a) } end,
770 --     stopslant    = { "pdf", "origin", "Q" },
771 --
772 -- }
773
774    local slants = setmetatableindex(function(t,k)
775        local v = { "pdf", "origin", f_slant(a) }
776        t[k] = v
777        return k
778    end)
779
780    local function startslant(a)
781        return slants[a]
782    end
783
784    local c_cache = setmetatableindex(function(t,m)
785        local v = setmetatableindex(function(t,c)
786            local p = { "pdf", "page", "q " .. pdfcolor(m,c) }
787            t[c] = p
788            return p
789        end)
790        t[m] = v
791        return v
792    end)
793
794    -- we inherit the outer transparency
795
796    local t_cache = setmetatableindex(function(t,transparency)
797        local p = pdftransparency(transparency)
798        local v = setmetatableindex(function(t,colormodel)
799            local v = setmetatableindex(function(t,color)
800                local v = { "pdf", "page", "q " .. pdfcolor(colormodel,color) .. " " .. p }
801                t[color] = v
802                return v
803            end)
804            t[colormodel] = v
805            return v
806        end)
807        t[transparency] = v
808        return v
809    end)
810
811    local function startcolor(k)
812        local m, c = colortoattributes(k)
813        local t = transparencytoattribute(k)
814        if t then
815            return t_cache[t][m][c]
816        else
817            return c_cache[m][c]
818        end
819    end
820
821    -- A problem is that we need to transfer back and this is kind of
822    -- messy so we force text mode .. i'll do a better job on that but
823    -- will experiment first (both engines). Virtual fonts will change
824    -- anyway.
825
826    backends.pdf.tables.vfspecials = allocate { -- todo: distinguish between glyph and rule color
827
828        startcolor = startcolor,
829     -- stopcolor  = { "pdf", "page", "0 g 0 G Q" },
830        stopcolor  = { "pdf", "text", "Q" },
831
832        startslant = startslant,
833     -- stopslant  = { "pdf", "origin", "Q" },
834        stopslant  = { "pdf", "text", "Q" },
835
836    }
837
838end
839