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