colo-ini.lua /size: 43 Kb    last modification: 2020-07-01 14:35
1if not modules then modules = { } end modules ['colo-ini'] = {
2    version   = 1.000,
3    comment   = "companion to colo-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, tonumber, tostring = type, tonumber, tostring
10local concat, insert, remove = table.concat, table.insert, table.remove
11local format, gmatch, gsub, lower, match, find = string.format, string.gmatch, string.gsub, string.lower, string.match, string.find
12local P, R, C, Cc = lpeg.P, lpeg.R, lpeg.C, lpeg.Cc
13local lpegmatch, lpegpatterns = lpeg.match, lpeg.patterns
14local formatters = string.formatters
15
16local trace_define = false  trackers.register("colors.define",function(v) trace_define = v end)
17local trace_pgf    = false  trackers.register("colors.pgf",   function(v) trace_pgf    = v end)
18
19local report_colors = logs.reporter("colors","defining")
20local report_pgf    = logs.reporter("colors","pgf")
21
22local attributes          = attributes
23local backends            = backends
24local storage             = storage
25local context             = context
26local commands            = commands
27
28local implement           = interfaces.implement
29local getnamespace        = interfaces.getnamespace
30
31local mark                = utilities.storage.mark
32
33local settings_to_hash_strict = utilities.parsers.settings_to_hash_strict
34
35local colors              = attributes.colors
36local transparencies      = attributes.transparencies
37local colorintents        = attributes.colorintents
38local registrations       = backends.registrations
39
40local v_reset             = interfaces.variables.reset
41
42local texsetattribute     = tex.setattribute
43local texgetattribute     = tex.getattribute
44local texgetcount         = tex.getcount
45local texgettoks          = tex.gettoks
46local texgetmacro         = tokens.getters.macro
47
48local a_color             = attributes.private('color')
49local a_transparency      = attributes.private('transparency')
50local a_colormodel        = attributes.private('colormodel')
51
52local register_color      = colors.register
53local attributes_list     = attributes.list
54
55local colorvalues         = colors.values
56local transparencyvalues  = transparencies.values
57
58colors.sets               = mark(colors.sets or { }) -- sets are mostly used for
59local colorsets           = colors.sets        -- showing lists of defined
60local colorset            = { }                -- colors
61colorsets.default         = colorset
62local valid               = mark(colors.valid or { })
63colors.valid              = valid
64local counts              = mark(colors.counts or { })
65colors.counts             = counts
66
67storage.register("attributes/colors/sets",   colorsets, "attributes.colors.sets")
68storage.register("attributes/colors/valid",  valid,     "attributes.colors.valid")
69storage.register("attributes/colors/counts", counts,    "attributes.colors.counts")
70
71local function currentmodel()
72    return texgetattribute(a_colormodel)
73end
74
75colors.currentmodel = currentmodel
76
77local function synccolor(name)
78    valid[name] = true
79end
80
81local function synccolorclone(name,clone)
82    valid[name] = clone
83end
84
85local function synccolorcount(name,n)
86    counts[name] = n
87end
88
89local stack = { }
90
91local function pushset(name)
92    insert(stack,colorset)
93    colorset = colorsets[name]
94    if not colorset then
95        colorset = { }
96        colorsets[name] = colorset
97    end
98end
99
100local function popset()
101    colorset = remove(stack)
102end
103
104local function setlist(name)
105    return table.sortedkeys(name and name ~= "" and colorsets[name] or colorsets.default or {})
106end
107
108colors.pushset = pushset
109colors.popset  = popset
110colors.setlist = setlist
111
112-- todo: set at the lua end
113
114local ctx_colordefagc = context.colordefagc
115local ctx_colordefagt = context.colordefagt
116local ctx_colordefalc = context.colordefalc
117local ctx_colordefalt = context.colordefalt
118local ctx_colordeffgc = context.colordeffgc
119local ctx_colordeffgt = context.colordeffgt
120local ctx_colordefflc = context.colordefflc
121local ctx_colordefflt = context.colordefflt
122local ctx_colordefrgc = context.colordefrgc
123local ctx_colordefrgt = context.colordefrgt
124local ctx_colordefrlc = context.colordefrlc
125local ctx_colordefrlt = context.colordefrlt
126
127local function definecolor(name, ca, global)
128    if ca and ca > 0 then
129        if global then
130            if trace_define then
131                report_colors("define global color %a with attribute %a",name,ca)
132            end
133            ctx_colordefagc(name,ca)
134        else
135            if trace_define then
136                report_colors("define local color %a with attribute %a",name,ca)
137            end
138            ctx_colordefalc(name,ca)
139        end
140    else
141        if global then
142            ctx_colordefrgc(name)
143        else
144            ctx_colordefrlc(name)
145        end
146    end
147    colorset[name] = true-- maybe we can store more
148end
149
150local function inheritcolor(name, ca, global)
151    if ca and ca ~= "" then
152        if global then
153            if trace_define then
154                report_colors("inherit global color %a with attribute %a",name,ca)
155            end
156            ctx_colordeffgc(name,ca) -- some day we will set the macro directly
157        else
158            if trace_define then
159                report_colors("inherit local color %a with attribute %a",name,ca)
160            end
161            ctx_colordefflc(name,ca)
162        end
163    else
164        if global then
165            ctx_colordefrgc(name)
166        else
167            ctx_colordefrlc(name)
168        end
169    end
170    colorset[name] = true-- maybe we can store more
171end
172
173local function definetransparent(name, ta, global)
174    if ta and ta > 0 then
175        if global then
176            if trace_define then
177                report_colors("define global transparency %a with attribute %a",name,ta)
178            end
179            ctx_colordefagt(name,ta)
180        else
181            if trace_define then
182                report_colors("define local transparency %a with attribute %a",name,ta)
183            end
184            ctx_colordefalt(name,ta)
185        end
186    else
187        if global then
188            ctx_colordefrgt(name)
189        else
190            ctx_colordefrlt(name)
191        end
192    end
193end
194
195local function inherittransparent(name, ta, global)
196    if ta and ta ~= "" then
197        if global then
198            if trace_define then
199                report_colors("inherit global transparency %a with attribute %a",name,ta)
200            end
201            ctx_colordeffgt(name,ta)
202        else
203            if trace_define then
204                report_colors("inherit local transparency %a with attribute %a",name,ta)
205            end
206            ctx_colordefflt(name,ta)
207        end
208    else
209        if global then
210            ctx_colordefrgt(name)
211        else
212            ctx_colordefrlt(name)
213        end
214    end
215end
216
217local transparent = {
218    none       =  0,
219    normal     =  1,
220    multiply   =  2,
221    screen     =  3,
222    overlay    =  4,
223    softlight  =  5,
224    hardlight  =  6,
225    colordodge =  7,
226    colorburn  =  8,
227    darken     =  9,
228    lighten    = 10,
229    difference = 11,
230    exclusion  = 12,
231    hue        = 13,
232    saturation = 14,
233    color      = 15,
234    luminosity = 16,
235}
236
237transparencies.names = transparent
238
239local gray_okay   = true
240local rgb_okay    = true
241local cmyk_okay   = true
242local spot_okay   = true
243local multi_okay  = true
244local forced      = false
245
246function colors.forcesupport(gray,rgb,cmyk,spot,multi) -- pdfx driven
247    gray_okay, rgb_okay, cmyk_okay, spot_okay, multi_okay, forced = gray, rgb, cmyk, spot, multi, true
248    report_colors("supported models: gray %a, rgb %a, cmyk %a, spot %a",gray,rgb,cmyk,spot)
249end
250
251local function forcedmodel(model) -- delayed till the backend but mp directly
252    if not forced then
253        return model
254    elseif model == 2 then -- gray
255        if gray_okay then
256            -- okay
257        elseif cmyk_okay then
258            return 4
259        elseif rgb_okay then
260            return 3
261        end
262    elseif model == 3 then -- rgb
263        if rgb_okay then
264            -- okay
265        elseif cmyk_okay then
266            return 4
267        elseif gray_okay then
268            return 2
269        end
270    elseif model == 4 then -- cmyk
271        if cmyk_okay then
272            -- okay
273        elseif rgb_okay then
274            return 3
275        elseif gray_okay then
276            return 2
277        end
278    elseif model == 5 then -- spot
279        if spot_okay then
280            return 5
281        elseif cmyk_okay then
282            return 4
283        elseif rgb_okay then
284            return 3
285        elseif gray_okay then
286            return 2
287        end
288    end
289    return model
290end
291
292colors.forcedmodel = forcedmodel
293
294-- By coupling we are downward compatible. When we decouple we need to do more tricky
295-- housekeeping (e.g. persist color independent transparencies when color bound ones
296-- are nil.)
297
298colors.couple = true
299
300local function definetransparency(name,n,global)
301    if n == v_reset then
302        definetransparent(name, 0, global) -- or attributes.unsetvalue
303        return
304    end
305    local a = tonumber(n)
306    if a then
307        transparent[name] = a -- 0 .. 16
308        return
309    end
310    local a = transparent[name]
311    if a then
312        transparent[name] = a
313        return
314    end
315    local settings = settings_to_hash_strict(n)
316    if settings then
317        local a = settings.a
318        local t = settings.t
319        if a and t then
320            definetransparent(name, transparencies.register(name,transparent[a] or tonumber(a) or 1,tonumber(t) or 1), global)
321        else
322            definetransparent(name, 0, global)
323        end
324    else
325        inherittransparent(name, n, global)
326    end
327end
328
329colors.definetransparency = definetransparency
330
331local registered = { }
332
333local function do_registerspotcolor(parent,parentnumber,e,f,d,p)
334    if not registered[parent] then
335        local v = colorvalues[parentnumber]
336        if v then
337            local model = currentmodel()
338            if model == 1 then
339                model = v[1]
340            end
341            if e and e ~= "" then
342                registrations.spotcolorname(parent,e) -- before registration of the color
343            end
344            if     model == 2 then -- name noffractions names p's r g b
345                registrations.grayspotcolor(parent,f,d,p,v[2])
346            elseif model == 3 then
347                registrations.rgbspotcolor (parent,f,d,p,v[3],v[4],v[5])
348            elseif model == 4 then
349                registrations.cmykspotcolor(parent,f,d,p,v[6],v[7],v[8],v[9])
350            end
351        end
352        registered[parent] = true
353    end
354end
355
356-- local function do_registermultitonecolor(parent,name,parentnumber,e,f,d,p) -- same as spot but different template
357--     if not registered[parent] then
358--         local v = colorvalues[parentnumber]
359--         if v then
360--             local model = currentmodel()
361--             if model == 1 then
362--                 model = v[1]
363--             end
364--             if     model == 2 then
365--                 registrations.grayindexcolor(parent,f,d,p,v[2])
366--             elseif model == 3 then
367--                 registrations.rgbindexcolor (parent,f,d,p,v[3],v[4],v[5])
368--             elseif model == 4 then
369--                 registrations.cmykindexcolor(parent,f,d,p,v[6],v[7],v[8],v[9])
370--             end
371--         end
372--         registered[parent] = true
373--     end
374-- end
375
376function colors.definesimplegray(name,s)
377    return register_color(name,'gray',s) -- we still need to get rid of 'color'
378end
379
380local hexdigit    = R("09","AF","af")
381local hexnumber   = hexdigit * hexdigit / function(s) return tonumber(s,16)/255 end
382local hexpattern  = hexnumber * (P(-1) + hexnumber * hexnumber * P(-1))
383local hexcolor    = Cc("H") * P("#") * hexpattern
384
385local left        = P("(")
386local right       = P(")")
387local comma       = P(",")
388local mixnumber   = lpegpatterns.number / tonumber
389                  + P("-") / function() return -1 end
390local mixname     = C(P(1-left-right-comma)^1)
391----- mixcolor    = Cc("M") * mixnumber * left * mixname * (comma * mixname)^-1 * right * P(-1)
392local mixcolor    = Cc("M") * mixnumber * left * mixname * (comma * mixname)^0 * right * P(-1) -- one is also ok
393
394local exclamation = P("!")
395local pgfnumber   = lpegpatterns.digit^0 / function(s) return tonumber(s)/100 end
396local pgfname     = C(P(1-exclamation)^1)
397local pgfcolor    = Cc("P") * pgfname * exclamation * pgfnumber * (exclamation * pgfname)^-1 * P(-1)
398
399local specialcolor = hexcolor + mixcolor
400
401local l_color        = attributes.list[a_color]
402local l_transparency = attributes.list[a_transparency]
403
404directives.register("colors.pgf",function(v)
405    if v then
406        specialcolor = hexcolor + mixcolor + pgfcolor
407    else
408        specialcolor = hexcolor + mixcolor
409    end
410end)
411
412local defineintermediatecolor
413
414local function resolvedname(name)
415    local color
416    if valid[name] then
417        color = counts[name]
418        if color then
419            color = texgetcount(color)
420        else
421            color = l_color[name] -- fall back on old method
422        end
423    else
424        color = l_color[name] -- fall back on old method
425    end
426    return color, l_transparency[name]
427end
428
429local function defineprocesscolor(name,str,global,freeze) -- still inconsistent color vs transparent
430    local what, one, two, three = lpegmatch(specialcolor,str)
431    if what == "H" then
432        -- for old times sake (if we need to feed from xml or so)
433        definecolor(name, register_color(name,'rgb',one,two,three),global)
434    elseif what == "M" then
435        -- intermediate
436     -- return defineintermediatecolor(name,one,l_color[two],l_color[three],l_transparency[two],l_transparency[three],"",global,freeze)
437        local c1, t1 = resolvedname(two)
438        local c2, t2 = resolvedname(three)
439        return defineintermediatecolor(name,one,c1,c2,t1,t2,"",global,freeze)
440    elseif what == "P" then
441        -- pgf for tikz
442     -- return defineintermediatecolor(name,two,l_color[one],l_color[three],l_transparency[one],l_transparency[three],"",global,freeze)
443        local c1, t1 = resolvedname(one)
444        local c2, t2 = resolvedname(three)
445        return defineintermediatecolor(name,two,c1,c2,t1,t2,"",global,freeze)
446    else
447        local settings = settings_to_hash_strict(str)
448        if settings then
449            local r = settings.r
450            local g = settings.g
451            local b = settings.b
452            local w = settings.w
453            if r or g or (b and not w) then
454                -- we can consider a combined rgb cmyk s definition
455                definecolor(name, register_color(name,'rgb', tonumber(r) or 0, tonumber(g) or 0, tonumber(b) or 0), global)
456            else
457                local c = settings.c
458                local m = settings.m
459                local y = settings.y
460                local k = settings.k
461                if c or m or y or k then
462                    definecolor(name, register_color(name,'cmyk',tonumber(c) or 0, tonumber(m) or 0, tonumber(y) or 0, tonumber(k) or 0), global)
463                else
464                    local h = settings.h
465                    local s = settings.s
466                    local v = settings.v
467                    if v then
468                        r, g, b = colors.hsvtorgb(tonumber(h) or 0, tonumber(s) or 1, tonumber(v) or 1) -- maybe later native
469                        definecolor(name, register_color(name,'rgb',r,g,b), global)
470                    else
471                        if w then
472                            r, g, b = colors.hwbtorgb(tonumber(h) or 0, tonumber(b) or 1, tonumber(w) or 1) -- maybe later native
473                            definecolor(name, register_color(name,'rgb',r,g,b), global)
474                        else
475                            local x = settings.x or h
476                            if x then
477                                r, g, b = lpegmatch(hexpattern,x) -- can be inlined
478                                if r and g and b then
479                                    definecolor(name, register_color(name,'rgb',r,g,b), global)
480                                else
481                                    definecolor(name, register_color(name,'gray',r or 0), global)
482                                end
483                            else
484                                definecolor(name, register_color(name,'gray',tonumber(s) or 0), global)
485                            end
486                        end
487                    end
488                end
489            end
490            local a = settings.a
491            local t = settings.t
492            if a and t then
493                definetransparent(name, transparencies.register(name,transparent[a] or tonumber(a) or 1,tonumber(t) or 1), global)
494            elseif colors.couple then
495            --  definetransparent(name, transparencies.register(nil, 1, 1), global) -- can be sped up
496                definetransparent(name, 0, global) -- can be sped up
497            end
498        elseif freeze then
499            local ca = attributes_list[a_color]       [str]
500            local ta = attributes_list[a_transparency][str]
501            if ca then
502                definecolor(name, ca, global)
503            end
504            if ta then
505                definetransparent(name, ta, global)
506            end
507        else
508            inheritcolor(name, str, global)
509            inherittransparent(name, str, global)
510        --  if global and str ~= "" then -- For Peter Rolf who wants access to the numbers in Lua. (Currently only global is supported.)
511        --      attributes_list[a_color]       [name] = attributes_list[a_color]       [str] or attributes.unsetvalue  -- reset
512        --      attributes_list[a_transparency][name] = attributes_list[a_transparency][str] or attributes.unsetvalue
513        --  end
514        end
515    end
516    colorset[name] = true-- maybe we can store more
517end
518
519-- You cannot overload a local color so one then has to use some prefix, like
520-- mp:red. Kind of protection.
521
522local function defineprocesscolordirect(settings)
523    if settings then
524        local name = settings.name
525        if name then
526            local r = settings.r
527            local g = settings.g
528            local b = settings.b
529            local w = settings.w
530            if r or g or (b and not w) then
531                -- we can consider a combined rgb cmyk s definition
532                register_color(name,'rgb', r or 0, g or 0, b or 0)
533            else
534                local c = settings.c
535                local m = settings.m
536                local y = settings.y
537                local k = settings.k
538                if c or m or y or k then
539                    register_color(name,'cmyk',c or 0, m or 0, y or 0, k or 0)
540                else
541                    local h = settings.h
542                    local s = settings.s
543                    local v = settings.v
544                    if v then
545                        r, g, b = colors.hsvtorgb(h or 0, s or 1, v or 1) -- maybe later native
546                        register_color(name,'rgb',r,g,b)
547                    else
548                        if w then
549                            r, g, b = colors.hwbtorgb((tonumber(h) or 0) / 360, tonumber(b) or 1, tonumber(w) or 1) -- maybe later native
550                            register_color(name,'rgb',r,g,b)
551                        else
552                            local x = settings.x or h
553                            if x then
554                                r, g, b = lpegmatch(hexpattern,x) -- can be inlined
555                                if r and g and b then
556                                    register_color(name,'rgb',r,g,b)
557                                else
558                                    register_color(name,'gray',r or 0)
559                                end
560                            else
561                                register_color(name,'gray',s or 0)
562                            end
563                        end
564                    end
565                end
566            end
567            local a = settings.a
568            local t = settings.t
569            if a and t then
570                transparencies.register(name,transparent[a] or a or 1,t or 1)
571            end
572            colorset[name] = true-- maybe we can store more
573            valid[name] = true
574        end
575    end
576end
577
578local function isblack(ca) -- maybe commands
579    local cv = ca > 0 and colorvalues[ca]
580    return (cv and cv[2] == 0) or false
581end
582
583colors.isblack = isblack
584
585-- local m, c, t = attributes.colors.namedcolorattributes(parent)
586-- if c and c > 1 then -- 1 is black
587-- local v = attributes.colors.values[c]
588
589local function definespotcolor(name,parent,str,global)
590    if parent == "" or find(parent,"=",1,true) then
591        colors.registerspotcolor(name, parent) -- does that work? no attr
592    elseif name ~= parent then
593        local cp = attributes_list[a_color][parent]
594        if cp then
595            local t = settings_to_hash_strict(str)
596            if t then
597                local tp = tonumber(t.p) or 1
598                do_registerspotcolor(parent,cp,t.e,1,"",tp) -- p not really needed, only diagnostics
599                if name and name ~= "" then
600                    definecolor(name,register_color(name,'spot',parent,1,"",tp),true)
601                    local ta = t.a
602                    local tt = t.t
603                    if ta and tt then
604                        definetransparent(name, transparencies.register(name,transparent[ta] or tonumber(ta) or 1,tonumber(tt) or 1), global)
605                    elseif colors.couple then
606                     -- definetransparent(name, transparencies.register(nil, 1, 1), global) -- can be sped up
607                        definetransparent(name, 0, global) -- can be sped up
608                    end
609                end
610            end
611        end
612    end
613    colorset[name] = true-- maybe we can store more
614end
615
616function colors.registerspotcolor(parent, str)
617    local cp = attributes_list[a_color][parent]
618    if cp then
619        local e = ""
620        if str then
621            local t = settings_to_hash_strict(str)
622            e = (t and t.e) or ""
623        end
624        do_registerspotcolor(parent, cp, e, 1, "", 1) -- p not really needed, only diagnostics
625    end
626end
627
628local function f(i,colors,fraction)
629    local otf = 0
630    if type(fraction) == "table" then
631        for c=1,#colors do
632            otf = otf + (tonumber(fraction[c]) or 1) * colors[c][i]
633        end
634    else
635        fraction = tonumber(fraction)
636        for c=1,#colors do
637            otf = otf + fraction * colors[c]
638        end
639    end
640    if otf > 1 then
641        otf = 1
642    end
643    return otf
644end
645
646local function definemixcolor(makecolor,name,fractions,cs,global,freeze)
647    local values = { }
648    for i=1,#cs do -- do fraction in here
649        local v = colorvalues[cs[i]]
650        if not v then
651            return
652        end
653        values[i] = v
654    end
655    if #values > 0 then
656        csone = values[1][1]
657        local ca
658        if csone == 2 then
659            ca = register_color(name,'gray',f(2,values,fractions))
660        elseif csone == 3 then
661            ca = register_color(name,'rgb', f(3,values,fractions),
662                                            f(4,values,fractions),
663                                            f(5,values,fractions))
664        elseif csone == 4 then
665            ca = register_color(name,'cmyk',f(6,values,fractions),
666                                            f(7,values,fractions),
667                                            f(8,values,fractions),
668                                            f(9,values,fractions))
669        else
670            ca = register_color(name,'gray',f(2,values,fractions))
671        end
672        definecolor(name,ca,global,freeze)
673    else
674        report_colors("invalid specification of components for color %a",makecolor)
675    end
676end
677
678local function definemultitonecolor(name,multispec,colorspec,selfspec)
679    local dd  = { }
680    local pp  = { }
681    local nn  = { }
682    local max = 0
683    for k,v in gmatch(multispec,"([^=,]+)=([^%,]*)") do -- use settings_to_array
684        max = max + 1
685        dd[max] = k
686        pp[max] = v
687        nn[max] = formatters["%s_%1.3g"](k,tonumber(v) or 0) -- 0 can't happen
688    end
689    if max > 0 then
690        nn = concat(nn,'_')
691        local parent = gsub(lower(nn),"[^%d%a%.]+","_")
692        if not colorspec or colorspec == "" then
693            -- this can happens when we come from metapost
694            local cc = { }
695            for i=1,max do
696                cc[i] = resolvedname(dd[i])
697            end
698            definemixcolor(name,parent,pp,cc,true,true)
699        else
700            if selfspec ~= "" then
701                colorspec = colorspec .. "," .. selfspec
702            end
703            defineprocesscolor(parent,colorspec,true,true)
704        end
705        local cp = attributes_list[a_color][parent]
706        dd, pp = concat(dd,','), concat(pp,',')
707        if cp then
708            do_registerspotcolor(parent, cp, "", max, dd, pp)
709            definecolor(name, register_color(name, 'spot', parent, max, dd, pp), true)
710            local t = settings_to_hash_strict(selfspec)
711            if t and t.a and t.t then
712                definetransparent(name, transparencies.register(name,transparent[t.a] or tonumber(t.a) or 1,tonumber(t.t) or 1), global)
713            elseif colors.couple then
714            --  definetransparent(name, transparencies.register(nil, 1, 1), global) -- can be sped up
715                definetransparent(name, 0, global) -- can be sped up
716            end
717        end
718    end
719    colorset[name] = true-- maybe we can store more
720end
721
722colors.defineprocesscolor   = defineprocesscolor
723colors.definespotcolor      = definespotcolor
724colors.definemultitonecolor = definemultitonecolor
725
726colors.defineprocesscolordirect = defineprocesscolordirect -- test for mp
727
728-- will move to mlib-col as colors in mp are somewhat messy due to the fact
729-- that we cannot cast .. so we really need to use (s,s,s) for gray in order
730-- to be able to map onto 'color'
731
732local function mpcolor(model,ca,ta,default,name)
733    local cv = colorvalues[ca]
734    if cv then
735        local tv = transparencyvalues[ta]
736        -- maybe move the 5 logic into the forcedmodel call
737        local cm = cv[1]
738        if model == 1 then
739            model = cm
740        end
741        model = forcedmodel(model)
742        if cm == 5 and model == 4 then
743            model = 5 -- a cheat but ok as spot colors have a representation
744        end
745        if tv then
746            if model == 2 then
747                return formatters["transparent(%s,%s,(%s,%s,%s))"](tv[1],tv[2],cv[3],cv[4],cv[5])
748            elseif model == 3 then
749                return formatters["transparent(%s,%s,(%s,%s,%s))"](tv[1],tv[2],cv[3],cv[4],cv[5])
750            elseif model == 4 then
751                return formatters["transparent(%s,%s,(%s,%s,%s,%s))"](tv[1],tv[2],cv[6],cv[7],cv[8],cv[9])
752            elseif model == 5 then
753             -- return formatters['transparent(%s,%s,multitonecolor("%s",%s,"%s","%s"))'](tv[1],tv[2],cv[10],cv[11],cv[12],cv[13])
754                return formatters['transparent(%s,%s,namedcolor("%s"))'](tv[1],tv[2],name or cv[10])
755            else -- see ** in meta-ini.mkiv: return formatters["transparent(%s,%s,(%s))"](tv[1],tv[2],cv[2])
756                return formatters["transparent(%s,%s,(%s,%s,%s))"](tv[1],tv[2],cv[3],cv[4],cv[5])
757            end
758        else
759            if model == 2 then
760                return formatters["(%s,%s,%s)"](cv[3],cv[4],cv[5])
761            elseif model == 3 then
762                return formatters["(%s,%s,%s)"](cv[3],cv[4],cv[5])
763            elseif model == 4 then
764                return formatters["(%s,%s,%s,%s)"](cv[6],cv[7],cv[8],cv[9])
765            elseif model == 5 then
766                return formatters['namedcolor("%s")'](name or cv[10])
767            else -- see ** in meta-ini.mkiv: return formatters["%s"]((cv[2]))
768                return formatters["(%s,%s,%s)"](cv[3],cv[4],cv[5])
769            end
770        end
771    end
772    local tv = transparencyvalues[ta]
773    if tv then
774        return formatters["(%s,%s)"](tv[1],tv[2])
775    end
776    default = default or 0 -- rgb !
777    return formatters["(%s,%s,%s)"](default,default,default)
778end
779
780-- local function mpnamedcolor(name)
781--     return mpcolor(texgetattribute(a_colormodel),l_color[name] or l_color.black,l_transparency[name] or false)
782-- end
783
784local colornamespace  = getnamespace("colornumber")
785local paletnamespace  = getnamespace("colorpalet")
786
787local function namedcolorattributes(name)
788    local space  = texgetattribute(a_colormodel)
789    ----- prefix = texgettoks("t_colo_prefix")
790    local prefix = texgetmacro("currentcolorprefix")
791    local color
792    if prefix ~= "" then
793        color = valid[prefix..name]
794        if not color then
795            local n = paletnamespace .. prefix .. name
796            color = valid[n]
797            if not color then
798                color = name
799            elseif color == true then
800                color = n
801            end
802        elseif color == true then
803            color = paletnamespace .. prefix .. name
804        end
805    else
806        color = valid[name]
807        if not color then
808            return space, l_color.black
809        elseif color == true then
810            color = name
811        end
812    end
813    color = counts[color]
814    if color then
815        color = texgetcount(color)
816    else
817        color = l_color[name] -- fall back on old method
818    end
819    if color then
820        return space, color, l_transparency[name]
821    else
822        return space, l_color.black
823    end
824end
825
826colors.namedcolorattributes = namedcolorattributes -- can be used local
827
828local function mpnamedcolor(name)
829    local model, ca, ta = namedcolorattributes(name)
830    return mpcolor(model,ca,ta,nil,name)
831end
832
833local function mpoptions(model,ca,ta,default) -- will move to mlib-col .. not really needed
834    return formatters["withcolor %s"](mpcolor(model,ca,ta,default))
835end
836
837colors.mpcolor      = mpcolor
838colors.mpnamedcolor = mpnamedcolor
839colors.mpoptions    = mpoptions
840
841-- elsewhere:
842--
843-- mp.NamedColor = function(str)
844--     mpprint(mpnamedcolor(str))
845-- end
846
847-- local function formatcolor(ca,separator)
848--     local cv = colorvalues[ca]
849--     if cv then
850--         local c, cn, f, t, model = { }, 0, 13, 13, cv[1]
851--         if model == 2 then
852--             return c[2]
853--         elseif model == 3 then
854--             return concat(c,separator,3,5)
855--         elseif model == 4 then
856--             return concat(c,separator,6,9)
857--         end
858--     else
859--         return 0
860--     end
861-- end
862
863local function formatcolor(ca,separator)
864    local cv = colorvalues[ca]
865    if cv then
866        local c, cn, f, t, model = { }, 0, 13, 13, cv[1]
867        if model == 2 then
868            f, t = 2, 2
869        elseif model == 3 then
870            f, t = 3, 5
871        elseif model == 4 then
872            f, t = 6, 9
873        end
874        for i=f,t do
875            cn = cn + 1
876            c[cn] = format("%0.3f",cv[i])
877        end
878        return concat(c,separator)
879    else
880        return format("%0.3f",0)
881    end
882end
883
884local function formatgray(ca,separator)
885    local cv = colorvalues[ca]
886    return format("%0.3f",(cv and cv[2]) or 0)
887end
888
889colors.formatcolor = formatcolor
890colors.formatgray  = formatgray
891
892local f_gray         = formatters["s=%1.3f"]
893local f_rgb          = formatters["r=%1.3f%sg=%1.3f%sb=%1.3f"]
894local f_cmyk         = formatters["c=%1.3f%sm=%1.3f%sy=%1.3f%sk=%1.3f"]
895local f_spot_name    = formatters["p=%s"]
896local f_spot_value   = formatters["p=%1.3f"]
897local f_transparency = formatters["a=%1.3f%st=%1.3f"]
898local f_both         = formatters["%s%s%s"]
899
900local function colorcomponents(ca,separator) -- return list
901    local cv = colorvalues[ca]
902    if cv then
903        local model = cv[1]
904        if model == 2 then
905            return f_gray(cv[2])
906        elseif model == 3 then
907            return f_rgb(cv[3],separator or " ",cv[4],separator or " ",cv[5])
908        elseif model == 4 then
909            return f_cmyk(cv[6],separator or " ",cv[7],separator or " ",cv[8],separator or " ",cv[9])
910        elseif type(cv[13]) == "string" then
911            return f_spot_name(cv[13])
912        else
913            return f_spot_value(cv[13])
914        end
915    else
916        return ""
917    end
918end
919
920local function transparencycomponents(ta,separator)
921    local tv = transparencyvalues[ta]
922    if tv then
923        return f_transparency(tv[1],separator or " ",tv[2])
924    else
925        return ""
926    end
927end
928
929local function processcolorcomponents(ca,separator)
930    local cs = colorcomponents(ca,separator)
931    local ts = transparencycomponents(ca,separator)
932    if cs == "" then
933        return ts
934    elseif ts == "" then
935        return cs
936    else
937        return f_both(cs,separator or " ",ts)
938    end
939end
940
941local function spotcolorname(ca,default)
942    local cv, v = colorvalues[ca], "unknown"
943    if not cv and type(ca) == "string" then
944        ca = resolvedname(ca) -- we could metatable colorvalues
945        cv = colorvalues[ca]
946    end
947    if cv and cv[1] == 5 then
948        v = cv[10]
949    end
950    return tostring(v)
951end
952
953local function spotcolorparent(ca,default)
954    local cv, v = colorvalues[ca], "unknown"
955    if not cv and type(ca) == "string" then
956        ca = resolvedname(ca) -- we could metatable colorvalues
957        cv = colorvalues[ca]
958    end
959    if cv and cv[1] == 5 then
960        v = cv[12]
961        if v == "" then
962            v = cv[10]
963        end
964    end
965    return tostring(v)
966end
967
968local function spotcolorvalue(ca,default)
969    local cv, v = colorvalues[ca], 0
970    if not cv and type(ca) == "string" then
971        ca = resolvedname(ca) -- we could metatable colorvalues
972        cv = colorvalues[ca]
973    end
974    if cv and cv[1] == 5 then
975       v = cv[13]
976    end
977    return tostring(v)
978end
979
980colors.colorcomponents        = colorcomponents
981colors.transparencycomponents = transparencycomponents
982colors.processcolorcomponents = processcolorcomponents
983colors.spotcolorname          = spotcolorname
984colors.spotcolorparent        = spotcolorparent
985colors.spotcolorvalue         = spotcolorvalue
986
987-- experiment  (a bit of a hack, as we need to get the attribute number)
988
989local min = math.min
990
991-- a[b,c] -> b+a*(c-b)
992
993local function inbetween(one,two,i,fraction)
994    local o, t = one[i], two[i]
995    local c = fraction < 0
996    if c then
997        fraction = - fraction
998    end
999    local otf = o + fraction * (t - o)
1000    if otf > 1 then
1001        otf = 1
1002    end
1003    if c then
1004        return 1 - otf
1005    else
1006        return otf
1007    end
1008end
1009
1010local function justone(one,fraction,i)
1011    local otf = fraction * one[i]
1012    if otf > 1 then
1013        otf = 1
1014    end
1015    return otf
1016end
1017
1018local function complement(one,fraction,i)
1019    local otf = - fraction * (1 - one[i])
1020    if otf > 1 then
1021        otf = 1
1022    end
1023    return otf
1024end
1025
1026colors.helpers = {
1027    inbetween  = inbetween,
1028    justone    = justone,
1029    complement = complement,
1030}
1031
1032defineintermediatecolor = function(name,fraction,c_one,c_two,a_one,a_two,specs,global,freeze)
1033    fraction = tonumber(fraction) or 1
1034    local one, two = colorvalues[c_one], colorvalues[c_two] -- beware, it uses the globals
1035    if one then
1036        if two then
1037            local csone, cstwo = one[1], two[1]
1038         -- if csone == cstwo then
1039                -- actually we can set all 8 values at once here but this is cleaner as we avoid
1040                -- problems with weighted gray conversions and work with original values
1041                local ca
1042                if csone == 2 then
1043                    ca = register_color(name,'gray',inbetween(one,two,2,fraction))
1044                elseif csone == 3 then
1045                    ca = register_color(name,'rgb', inbetween(one,two,3,fraction),
1046                                                    inbetween(one,two,4,fraction),
1047                                                    inbetween(one,two,5,fraction))
1048                elseif csone == 4 then
1049                    ca = register_color(name,'cmyk',inbetween(one,two,6,fraction),
1050                                                    inbetween(one,two,7,fraction),
1051                                                    inbetween(one,two,8,fraction),
1052                                                    inbetween(one,two,9,fraction))
1053                else
1054                    ca = register_color(name,'gray',inbetween(one,two,2,fraction))
1055                end
1056                definecolor(name,ca,global,freeze)
1057         -- end
1058        else
1059            local inbetween = fraction < 0 and complement or justone
1060            local csone = one[1]
1061            local ca
1062            if csone == 2 then
1063                ca = register_color(name,'gray',inbetween(one,fraction,2))
1064            elseif csone == 3 then
1065                ca = register_color(name,'rgb', inbetween(one,fraction,3),
1066                                                inbetween(one,fraction,4),
1067                                                inbetween(one,fraction,5))
1068            elseif csone == 4 then
1069                ca = register_color(name,'cmyk',inbetween(one,fraction,6),
1070                                                inbetween(one,fraction,7),
1071                                                inbetween(one,fraction,8),
1072                                                inbetween(one,fraction,9))
1073            else
1074                ca = register_color(name,'gray',inbetween(one,fraction,2))
1075            end
1076            definecolor(name,ca,global,freeze)
1077        end
1078    end
1079    local one, two = transparencyvalues[a_one], transparencyvalues[a_two]
1080    local t = settings_to_hash_strict(specs)
1081    local ta = tonumber((t and t.a) or (one and one[1]) or (two and two[1]))
1082    local tt = tonumber((t and t.t) or (one and two and f(one,two,2,fraction)))
1083    if ta and tt then
1084        definetransparent(name,transparencies.register(name,ta,tt),global)
1085    end
1086end
1087
1088colors.defineintermediatecolor = defineintermediatecolor
1089
1090-- for the moment downward compatible
1091
1092local patterns = {
1093    CONTEXTLMTXMODE > 0 and "colo-imp-%s.mkxl" or "",
1094    "colo-imp-%s.mkiv",
1095    "colo-imp-%s.tex",
1096    -- obsolete:
1097    "colo-%s.mkiv",
1098    "colo-%s.tex"
1099}
1100
1101local function action(name,foundname)
1102    context.loadfoundcolorsetfile(name,foundname)
1103end
1104
1105local function failure(name)
1106 -- context.showmessage("colors",5,name)
1107    report_colors("unknown library %a",name)
1108end
1109
1110local function usecolors(name)
1111    resolvers.uselibrary {
1112        category = "color definition",
1113        name     = name,
1114        patterns = patterns,
1115        action   = action,
1116        failure  = failure,
1117        onlyonce = true,
1118    }
1119end
1120
1121colors.usecolors = usecolors
1122
1123-- backend magic
1124
1125local currentpagecolormodel
1126
1127function colors.setpagecolormodel(model)
1128    currentpagecolormodel = model
1129end
1130
1131function colors.getpagecolormodel()
1132    return currentpagecolormodel
1133end
1134
1135-- interface
1136
1137local setcolormodel = colors.setmodel
1138
1139implement {
1140    name      = "synccolorcount",
1141    actions   = synccolorcount,
1142    arguments = { "string", "integer" }
1143}
1144
1145implement {
1146    name      = "synccolor",
1147    actions   = synccolor,
1148    arguments = "string",
1149}
1150
1151implement {
1152    name      = "synccolorclone",
1153    actions   = synccolorclone,
1154    arguments = "2 strings",
1155}
1156
1157implement {
1158    name      = "setcolormodel",
1159    arguments = "2 strings",
1160    actions   = function(model,weight)
1161        texsetattribute(a_colormodel,setcolormodel(model,weight))
1162    end
1163}
1164
1165implement {
1166    name      = "setpagecolormodel",
1167    actions   = colors.setpagecolormodel,
1168    arguments = "string",
1169}
1170
1171implement {
1172    name      = "defineprocesscolorlocal",
1173    actions   = defineprocesscolor,
1174    arguments = { "string", "string", false, "boolean" }
1175}
1176
1177implement {
1178    name      = "defineprocesscolorglobal",
1179    actions   = defineprocesscolor,
1180    arguments = { "string", "string", true, "boolean" }
1181}
1182
1183implement {
1184    name      = "defineprocesscolordummy",
1185    actions   = defineprocesscolor,
1186    arguments = { "'c_o_l_o_r'", "string", false, false }
1187}
1188
1189implement {
1190    name      = "definespotcolorglobal",
1191    actions   = definespotcolor,
1192    arguments = { "string", "string", "string", true }
1193}
1194
1195implement {
1196    name      = "definemultitonecolorglobal",
1197    actions   = definemultitonecolor,
1198    arguments = { "string", "string", "string", "string", true }
1199}
1200
1201implement {
1202    name      = "registermaintextcolor",
1203    actions   = function(main)
1204        colors.main = main
1205    end,
1206    arguments = "integer"
1207}
1208
1209implement {
1210    name      = "definetransparency",
1211    actions   = definetransparency,
1212    arguments = "2 strings"
1213}
1214
1215implement {
1216    name      = "definetransparencyglobal",
1217    actions   = definetransparency,
1218    arguments = { "string", "string", true }
1219}
1220
1221implement {
1222    name      = "defineintermediatecolor",
1223    actions   = defineintermediatecolor,
1224    arguments = { "string", "string", "integer", "integer", "integer", "integer", "string", false, "boolean" }
1225}
1226
1227implement { name = "spotcolorname",          actions = { spotcolorname,          context }, arguments = "integer" }
1228implement { name = "spotcolorparent",        actions = { spotcolorparent,        context }, arguments = "integer" }
1229implement { name = "spotcolorvalue",         actions = { spotcolorvalue,         context }, arguments = "integer" }
1230implement { name = "colorcomponents",        actions = { colorcomponents,        context }, arguments = { "integer", tokens.constant(",") } }
1231implement { name = "transparencycomponents", actions = { transparencycomponents, context }, arguments = { "integer", tokens.constant(",") } }
1232implement { name = "processcolorcomponents", actions = { processcolorcomponents, context }, arguments = { "integer", tokens.constant(",") } }
1233implement { name = "formatcolor",            actions = { formatcolor,            context }, arguments = { "integer", "string" } }
1234implement { name = "formatgray",             actions = { formatgray,             context }, arguments = { "integer", "string" } }
1235
1236implement {
1237    name      = "mpcolor",
1238    actions   = { mpcolor, context },
1239    arguments = { "integer", "integer", "integer" }
1240}
1241
1242implement {
1243    name      = "mpoptions",
1244    actions   = { mpoptions, context },
1245    arguments = { "integer", "integer", "integer" }
1246}
1247
1248local ctx_doifelse = commands.doifelse
1249
1250implement {
1251    name      = "doifelsedrawingblack",
1252    actions   = function() ctx_doifelse(isblack(texgetattribute(a_color))) end
1253}
1254
1255implement {
1256    name      = "doifelseblack",
1257    actions   = { isblack, ctx_doifelse },
1258    arguments = "integer"
1259}
1260
1261-- function commands.withcolorsinset(name,command)
1262--     local set
1263--     if name and name ~= "" then
1264--         set = colorsets[name]
1265--     else
1266--         set = colorsets.default
1267--     end
1268--     if set then
1269--         if command then
1270--             for name in table.sortedhash(set) do
1271--                 context[command](name)
1272--             end
1273--         else
1274--             context(concat(table.sortedkeys(set),","))
1275--         end
1276--     end
1277-- end
1278
1279implement { name = "startcolorset", actions = pushset,   arguments = "string" }
1280implement { name = "stopcolorset",  actions = popset }
1281implement { name = "usecolors",     actions = usecolors, arguments = "string" }
1282
1283-- bonus
1284
1285do
1286
1287    local function pgfxcolorspec(model,ca) -- {}{}{colorspace}{list}
1288     -- local cv = attributes.colors.values[ca]
1289        local cv = colorvalues[ca]
1290        local str
1291        if cv then
1292            if model and model ~= 0 then
1293                model = model
1294            else
1295                model = forcedmodel(texgetattribute(a_colormodel))
1296                if model == 1 then
1297                    model = cv[1]
1298                end
1299            end
1300            if model == 3 then
1301                str = formatters["{rgb}{%1.3f,%1.3f,%1.3f}"](cv[3],cv[4],cv[5])
1302            elseif model == 4 then
1303                str = formatters["{cmyk}{%1.3f,%1.3f,%1.3f,%1.3f}"](cv[6],cv[7],cv[8],cv[9])
1304            else -- there is no real gray
1305                str = formatters["{rgb}{%1.3f,%1.3f,%1.3f}"](cv[2],cv[2],cv[2])
1306            end
1307        else
1308            str = "{rgb}{0,0,0}"
1309        end
1310        if trace_pgf then
1311            report_pgf("model %a, string %a",model,str)
1312        end
1313        return str
1314    end
1315
1316    implement {
1317        name      = "pgfxcolorspec",
1318        actions   = { pgfxcolorspec, context },
1319        arguments = { "integer", "integer" }
1320    }
1321
1322end
1323
1324-- handy
1325
1326local models = storage.allocate { "all", "gray", "rgb", "cmyk", "spot" }
1327
1328colors.models = models -- check for usage elsewhere
1329
1330function colors.spec(name)
1331    local l = attributes_list[a_color]
1332    local t = colorvalues[l[name]] or colorvalues[l.black]
1333    return {
1334        model = models[t[1]] or models[1],
1335        s = t[2],
1336        r = t[3], g = t[4], b = t[5],
1337        c = t[6], m = t[7], y = t[8], k = t[9],
1338    }
1339end
1340
1341function colors.currentnamedmodel()
1342    return models[texgetattribute(a_colormodel)] or "gray"
1343end
1344
1345-- inspect(attributes.colors.spec("red"))
1346-- inspect(attributes.colors.spec("red socks"))
1347
1348implement {
1349    name      = "negatedcolorcomponent",
1350    arguments = "string",
1351    actions   = function(s)
1352        s = 1 - (tonumber(s) or 0)
1353        context((s < 0 and 0) or (s > 1 and 1) or s)
1354    end
1355}
1356