colo-ini.lua /size: 48 Kb    last modification: 2025-02-21 11:03
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
531local function definelabcolor(name,str,global,freeze) -- still inconsistent color vs transparent
532    local settings = settings_to_hash_strict(str)
533    if settings then
534        local s
535        local a = settings.a
536        local t = settings.t
537        local l = settings.l
538        if l then
539            local c = settings.c
540            local h = settings.h
541            -- lhc
542            if c and h then
543                local r, g, b = colors.lchtorgb(tonumber(l) or 0, tonumber(c) or 0, tonumber(h) or 0)
544                definecolor(name, register_color(name,'rgb',r,g,b), global)
545                goto TRANSPARENCY
546            end
547            -- lab
548            local b = settings.b
549            if a and b then
550                local r, g, b = colors.labtorgb(tonumber(l) or 0, tonumber(a) or 0, tonumber(b) or 0)
551                definecolor(name, register_color(name,'rgb',r,g,b), global)
552                goto TRANSPARENCY
553            end
554        else
555            local x = settings.x
556            local y = settings.z
557            local z = settings.z
558            if x and y and z then
559                local r, g, b = colors.xyztorgb(tonumber(x) or 0, tonumber(y) or 0, tonumber(z) or 0)
560                definecolor(name, register_color(name,'rgb',r,g,b), global)
561                goto TRANSPARENCY
562            end
563        end
564        -- todo srgb
565        s = settings.s
566        definecolor(name, register_color(name,'gray',tonumber(s) or 0), global)
567      ::TRANSPARENCY::
568        if a and t then
569            definetransparent(name, transparencies.register(name,transparent[a] or tonumber(a) or 1,tonumber(t) or 1), global)
570        elseif colors.couple then
571        --  definetransparent(name, transparencies.register(nil, 1, 1), global) -- can be sped up
572            definetransparent(name, 0, global) -- can be sped up
573        end
574    elseif freeze then
575        local ca = attributes_list[a_color]       [str]
576        local ta = attributes_list[a_transparency][str]
577        if ca then
578            definecolor(name, ca, global)
579        end
580        if ta then
581            definetransparent(name, ta, global)
582        end
583    else
584        inheritcolor(name, str, global)
585        inherittransparent(name, str, global)
586    --  if global and str ~= "" then -- For Peter Rolf who wants access to the numbers in Lua. (Currently only global is supported.)
587    --      attributes_list[a_color]       [name] = attributes_list[a_color]       [str] or attributes.unsetvalue  -- reset
588    --      attributes_list[a_transparency][name] = attributes_list[a_transparency][str] or attributes.unsetvalue
589    --  end
590    end
591    colorset[name] = true-- maybe we can store more
592end
593
594-- You cannot overload a local color so one then has to use some prefix, like
595-- mp:red. Kind of protection.
596
597local function defineprocesscolordirect(settings)
598    if settings then
599        local name = settings.name
600        if name then
601            local r = settings.r
602            local g = settings.g
603            local b = settings.b
604            local w = settings.w
605            if r or g or (b and not w) then
606                -- we can consider a combined rgb cmyk s definition
607                register_color(name,'rgb', r or 0, g or 0, b or 0)
608            else
609                local c = settings.c
610                local m = settings.m
611                local y = settings.y
612                local k = settings.k
613                if c or m or y or k then
614                    register_color(name,'cmyk',c or 0, m or 0, y or 0, k or 0)
615                else
616                    local h = settings.h
617                    local s = settings.s
618                    local v = settings.v
619                    if v then
620                        r, g, b = colors.hsvtorgb(h or 0, s or 1, v or 1) -- maybe later native
621                        register_color(name,'rgb',r,g,b)
622                    else
623                        if w then
624                            r, g, b = colors.hwbtorgb((tonumber(h) or 0) / 360, tonumber(b) or 1, tonumber(w) or 1) -- maybe later native
625                            register_color(name,'rgb',r,g,b)
626                        else
627                            local x = settings.x or h
628                            if x then
629                                r, g, b = lpegmatch(hexpattern,x) -- can be inlined
630                                if r and g and b then
631                                    register_color(name,'rgb',r,g,b)
632                                else
633                                    register_color(name,'gray',r or 0)
634                                end
635                            else
636                                register_color(name,'gray',s or 0)
637                            end
638                        end
639                    end
640                end
641            end
642            local a = settings.a
643            local t = settings.t
644            if a and t then
645                transparencies.register(name,transparent[a] or a or 1,t or 1)
646            end
647            colorset[name] = true-- maybe we can store more
648            valid[name] = true
649        end
650    end
651end
652
653local function isblack(ca) -- maybe commands
654    local cv = ca > 0 and colorvalues[ca]
655    return (cv and cv[2] == 0) or false
656end
657
658colors.isblack = isblack
659
660-- local m, c, t = attributes.colors.namedcolorattributes(parent)
661-- if c and c > 1 then -- 1 is black
662-- local v = attributes.colors.values[c]
663
664local function definespotcolor(name,parent,str,global)
665    if parent == "" or find(parent,"=",1,true) then
666        colors.registerspotcolor(name, parent) -- does that work? no attr
667    elseif name ~= parent then
668        local cp = attributes_list[a_color][parent]
669        if cp then
670            local t = settings_to_hash_strict(str)
671            if t then
672                local tp = tonumber(t.p) or 1
673                do_registerspotcolor(parent,cp,t.e,1,"",tp) -- p not really needed, only diagnostics
674                if name and name ~= "" then
675                    definecolor(name,register_color(name,'spot',parent,1,"",tp),true)
676                    local ta = t.a
677                    local tt = t.t
678                    if ta and tt then
679                        definetransparent(name, transparencies.register(name,transparent[ta] or tonumber(ta) or 1,tonumber(tt) or 1), global)
680                    elseif colors.couple then
681                     -- definetransparent(name, transparencies.register(nil, 1, 1), global) -- can be sped up
682                        definetransparent(name, 0, global) -- can be sped up
683                    end
684                end
685            end
686        end
687    end
688    colorset[name] = true-- maybe we can store more
689end
690
691function colors.registerspotcolor(parent, str)
692    local cp = attributes_list[a_color][parent]
693    if cp then
694        local e = ""
695        if str then
696            local t = settings_to_hash_strict(str)
697            e = (t and t.e) or ""
698        end
699        do_registerspotcolor(parent, cp, e, 1, "", 1) -- p not really needed, only diagnostics
700    end
701end
702
703local function f(i,colors,fraction)
704    local otf = 0
705    if type(fraction) == "table" then
706        for c=1,#colors do
707            otf = otf + (tonumber(fraction[c]) or 1) * colors[c][i]
708        end
709    else
710        fraction = tonumber(fraction)
711        for c=1,#colors do
712            otf = otf + fraction * colors[c]
713        end
714    end
715    if otf > 1 then
716        otf = 1
717    end
718    return otf
719end
720
721local function definemixcolor(makecolor,name,fractions,cs,global,freeze)
722    local values = { }
723    for i=1,#cs do -- do fraction in here
724        local v = colorvalues[cs[i]]
725        if not v then
726            return
727        end
728        colorvalues[i] = v
729    end
730    if #values > 0 then
731        csone = values[1][1]
732        local ca
733        if csone == 2 then
734            ca = register_color(name,'gray',f(2,values,fractions))
735        elseif csone == 3 then
736            ca = register_color(name,'rgb', f(3,values,fractions),
737                                            f(4,values,fractions),
738                                            f(5,values,fractions))
739        elseif csone == 4 then
740            ca = register_color(name,'cmyk',f(6,values,fractions),
741                                            f(7,values,fractions),
742                                            f(8,values,fractions),
743                                            f(9,values,fractions))
744        else
745            ca = register_color(name,'gray',f(2,values,fractions))
746        end
747        definecolor(name,ca,global,freeze)
748    else
749        report_colors("invalid specification of components for color %a",makecolor)
750    end
751end
752
753local function definemultitonecolor(name,multispec,colorspec,selfspec)
754    local dd  = { }
755    local pp  = { }
756    local nn  = { }
757    local max = 0
758    for k,v in gmatch(multispec,"([^=,]+)=([^%,]*)") do -- use settings_to_array
759        max = max + 1
760        dd[max] = k
761        pp[max] = v
762        nn[max] = formatters["%s_%1.3g"](k,tonumber(v) or 0) -- 0 can't happen
763    end
764    if max > 0 then
765        nn = concat(nn,'_')
766        local parent = gsub(lower(nn),"[^%d%a%.]+","_")
767        if not colorspec or colorspec == "" then
768            -- this can happens when we come from metapost
769            local cc = { }
770            for i=1,max do
771                cc[i] = resolvedname(dd[i])
772            end
773            definemixcolor(name,parent,pp,cc,true,true)
774        else
775            if selfspec ~= "" then
776                colorspec = colorspec .. "," .. selfspec
777            end
778            defineprocesscolor(parent,colorspec,true,true)
779        end
780        local cp = attributes_list[a_color][parent]
781        dd, pp = concat(dd,','), concat(pp,',')
782        if cp then
783            do_registerspotcolor(parent, cp, "", max, dd, pp)
784            definecolor(name, register_color(name, 'spot', parent, max, dd, pp), true)
785            local t = settings_to_hash_strict(selfspec)
786            if t and t.a and t.t then
787                definetransparent(name, transparencies.register(name,transparent[t.a] or tonumber(t.a) or 1,tonumber(t.t) or 1), global)
788            elseif colors.couple then
789            --  definetransparent(name, transparencies.register(nil, 1, 1), global) -- can be sped up
790                definetransparent(name, 0, global) -- can be sped up
791            end
792        end
793    end
794    colorset[name] = true-- maybe we can store more
795end
796
797colors.defineprocesscolor   = defineprocesscolor
798colors.definespotcolor      = definespotcolor
799colors.definemultitonecolor = definemultitonecolor
800
801colors.defineprocesscolordirect = defineprocesscolordirect -- test for mp
802
803-- will move to mlib-col as colors in mp are somewhat messy due to the fact
804-- that we cannot cast .. so we really need to use (s,s,s) for gray in order
805-- to be able to map onto 'color'
806
807local function mpcolor(model,ca,ta,default,name)
808    local cv = colorvalues[ca]
809    if cv then
810        local tv = transparencyvalues[ta]
811        -- maybe move the 5 logic into the forcedmodel call
812        local cm = cv[1]
813        if model == 1 then
814            model = cm
815        end
816        model = forcedmodel(model)
817        if cm == 5 and model == 4 then
818            model = 5 -- a cheat but ok as spot colors have a representation
819        end
820        if tv then
821            if model == 2 then
822                return formatters["transparent(%s,%s,(%s,%s,%s))"](tv[1],tv[2],cv[3],cv[4],cv[5])
823            elseif model == 3 then
824                return formatters["transparent(%s,%s,(%s,%s,%s))"](tv[1],tv[2],cv[3],cv[4],cv[5])
825            elseif model == 4 then
826                return formatters["transparent(%s,%s,(%s,%s,%s,%s))"](tv[1],tv[2],cv[6],cv[7],cv[8],cv[9])
827            elseif model == 5 then
828             -- return formatters['transparent(%s,%s,multitonecolor("%s",%s,"%s","%s"))'](tv[1],tv[2],cv[10],cv[11],cv[12],cv[13])
829                return formatters['transparent(%s,%s,namedcolor("%s"))'](tv[1],tv[2],name or cv[10])
830            else -- see ** in meta-ini.mkiv: return formatters["transparent(%s,%s,(%s))"](tv[1],tv[2],cv[2])
831                return formatters["transparent(%s,%s,(%s,%s,%s))"](tv[1],tv[2],cv[3],cv[4],cv[5])
832            end
833        else
834            if model == 2 then
835                return formatters["(%s,%s,%s)"](cv[3],cv[4],cv[5])
836            elseif model == 3 then
837                return formatters["(%s,%s,%s)"](cv[3],cv[4],cv[5])
838            elseif model == 4 then
839                return formatters["(%s,%s,%s,%s)"](cv[6],cv[7],cv[8],cv[9])
840            elseif model == 5 then
841                return formatters['namedcolor("%s")'](name or cv[10])
842            else -- see ** in meta-ini.mkiv: return formatters["%s"]((cv[2]))
843                return formatters["(%s,%s,%s)"](cv[3],cv[4],cv[5])
844            end
845        end
846    end
847    local tv = transparencyvalues[ta]
848    if tv then
849        return formatters["(%s,%s)"](tv[1],tv[2])
850    end
851    default = default or 0 -- rgb !
852    return formatters["(%s,%s,%s)"](default,default,default)
853end
854
855-- local function mpnamedcolor(name)
856--     return mpcolor(texgetattribute(a_colormodel),l_color[name] or l_color.black,l_transparency[name] or false)
857-- end
858
859local colornamespace  = getnamespace("colornumber")
860local paletnamespace  = getnamespace("colorpalet")
861
862local function namedcolorattributes(name)
863    local space  = texgetattribute(a_colormodel)
864    ----- prefix = texgettoks("t_colo_prefix")
865    local prefix = texgetmacro("currentcolorprefix")
866    local color
867    if prefix ~= "" then
868        color = valid[prefix..name]
869        if not color then
870            local n = paletnamespace .. prefix .. name
871            color = valid[n]
872            if not color then
873                color = name
874            elseif color == true then
875                color = n
876            end
877        elseif color == true then
878            color = paletnamespace .. prefix .. name
879        end
880    else
881        color = valid[name]
882        if not color then
883            return space, l_color.black
884        elseif color == true then
885            color = name
886        end
887    end
888    color = counts[color]
889    if color then
890        color = texgetcount(color)
891    else
892        color = l_color[name] -- fall back on old method
893    end
894    if color then
895        return space, color, l_transparency[name]
896    else
897        return space, l_color.black
898    end
899end
900
901colors.namedcolorattributes = namedcolorattributes -- can be used local
902
903local function mpnamedcolor(name)
904    local model, ca, ta = namedcolorattributes(name)
905    return mpcolor(model,ca,ta,nil,name)
906end
907
908local function mpoptions(model,ca,ta,default) -- will move to mlib-col .. not really needed
909    return formatters["withcolor %s"](mpcolor(model,ca,ta,default))
910end
911
912colors.mpcolor      = mpcolor
913colors.mpnamedcolor = mpnamedcolor
914colors.mpoptions    = mpoptions
915
916-- elsewhere:
917--
918-- mp.NamedColor = function(str)
919--     mpprint(mpnamedcolor(str))
920-- end
921
922-- local function formatcolor(ca,separator)
923--     local cv = colorvalues[ca]
924--     if cv then
925--         local c, cn, f, t, model = { }, 0, 13, 13, cv[1]
926--         if model == 2 then
927--             return c[2]
928--         elseif model == 3 then
929--             return concat(c,separator,3,5)
930--         elseif model == 4 then
931--             return concat(c,separator,6,9)
932--         end
933--     else
934--         return 0
935--     end
936-- end
937
938local function formatcolor(ca,separator)
939    local cv = colorvalues[ca]
940    if cv then
941        local c, cn, f, t, model = { }, 0, 13, 13, cv[1]
942        if model == 2 then
943            f, t = 2, 2
944        elseif model == 3 then
945            f, t = 3, 5
946        elseif model == 4 then
947            f, t = 6, 9
948        end
949        for i=f,t do
950            cn = cn + 1
951            c[cn] = format("%0.3f",cv[i])
952        end
953        return concat(c,separator)
954    else
955        return "0.000" -- format("%0.3f",0)
956    end
957end
958
959local function formatgray(ca,separator)
960    local cv = colorvalues[ca]
961    return format("%0.3f",(cv and cv[2]) or 0)
962end
963
964colors.formatcolor = formatcolor
965colors.formatgray  = formatgray
966
967local f_gray         = formatters["s=%1.3f"]
968local f_rgb          = formatters["r=%1.3f%sg=%1.3f%sb=%1.3f"]
969local f_cmyk         = formatters["c=%1.3f%sm=%1.3f%sy=%1.3f%sk=%1.3f"]
970local f_spot_name    = formatters["p=%s"]
971local f_spot_value   = formatters["p=%1.3f"]
972local f_transparency = formatters["a=%1.3f%st=%1.3f"]
973local f_both         = formatters["%s%s%s"]
974
975local function colorcomponents(ca,separator) -- return list
976    local cv = colorvalues[ca]
977    if cv then
978        local model = cv[1]
979        if model == 2 then
980            return f_gray(cv[2])
981        elseif model == 3 then
982            return f_rgb(cv[3],separator or " ",cv[4],separator or " ",cv[5])
983        elseif model == 4 then
984            return f_cmyk(cv[6],separator or " ",cv[7],separator or " ",cv[8],separator or " ",cv[9])
985        elseif type(cv[13]) == "string" then
986            return f_spot_name(cv[13])
987        else
988            return f_spot_value(cv[13])
989        end
990    else
991        return ""
992    end
993end
994
995local function transparencycomponents(ta,separator)
996    local tv = transparencyvalues[ta]
997    if tv then
998        return f_transparency(tv[1],separator or " ",tv[2])
999    else
1000        return ""
1001    end
1002end
1003
1004local function processcolorcomponents(ca,separator)
1005    local cs = colorcomponents(ca,separator)
1006    local ts = transparencycomponents(ca,separator)
1007    if cs == "" then
1008        return ts
1009    elseif ts == "" then
1010        return cs
1011    else
1012        return f_both(cs,separator or " ",ts)
1013    end
1014end
1015
1016local function spotcolorname(ca,default)
1017    local cv, v = colorvalues[ca], "unknown"
1018    if not cv and type(ca) == "string" then
1019        ca = resolvedname(ca) -- we could metatable colorvalues
1020        cv = colorvalues[ca]
1021    end
1022    if cv and cv[1] == 5 then
1023        v = cv[10]
1024    end
1025    return tostring(v)
1026end
1027
1028local function spotcolorparent(ca,default)
1029    local cv, v = colorvalues[ca], "unknown"
1030    if not cv and type(ca) == "string" then
1031        ca = resolvedname(ca) -- we could metatable colorvalues
1032        cv = colorvalues[ca]
1033    end
1034    if cv and cv[1] == 5 then
1035        v = cv[12]
1036        if v == "" then
1037            v = cv[10]
1038        end
1039    end
1040    return tostring(v)
1041end
1042
1043local function spotcolorvalue(ca,default)
1044    local cv, v = colorvalues[ca], 0
1045    if not cv and type(ca) == "string" then
1046        ca = resolvedname(ca) -- we could metatable colorvalues
1047        cv = colorvalues[ca]
1048    end
1049    if cv and cv[1] == 5 then
1050       v = cv[13]
1051    end
1052    return tostring(v)
1053end
1054
1055colors.colorcomponents        = colorcomponents
1056colors.transparencycomponents = transparencycomponents
1057colors.processcolorcomponents = processcolorcomponents
1058colors.spotcolorname          = spotcolorname
1059colors.spotcolorparent        = spotcolorparent
1060colors.spotcolorvalue         = spotcolorvalue
1061
1062-- experiment  (a bit of a hack, as we need to get the attribute number)
1063
1064local min = math.min
1065
1066-- a[b,c] -> b+a*(c-b)
1067
1068local function inbetween(one,two,i,fraction)
1069    local o, t = one[i], two[i]
1070    local c = fraction < 0
1071    if c then
1072        fraction = - fraction
1073    end
1074    local otf = o + fraction * (t - o)
1075    if otf > 1 then
1076        otf = 1
1077    end
1078    if c then
1079        return 1 - otf
1080    else
1081        return otf
1082    end
1083end
1084
1085local function justone(one,fraction,i)
1086    local otf = fraction * one[i]
1087    if otf > 1 then
1088        otf = 1
1089    end
1090    return otf
1091end
1092
1093local function complement(one,fraction,i)
1094    local otf = - fraction * (1 - one[i])
1095    if otf > 1 then
1096        otf = 1
1097    end
1098    return otf
1099end
1100
1101colors.helpers = {
1102    inbetween  = inbetween,
1103    justone    = justone,
1104    complement = complement,
1105}
1106
1107defineintermediatecolor = function(name,fraction,c_one,c_two,a_one,a_two,specs,global,freeze)
1108    fraction = tonumber(fraction) or 1
1109    local one, two = colorvalues[c_one], colorvalues[c_two] -- beware, it uses the globals
1110    if one then
1111        if two then
1112            local csone, cstwo = one[1], two[1]
1113         -- if csone == cstwo then
1114                -- actually we can set all 8 values at once here but this is cleaner as we avoid
1115                -- problems with weighted gray conversions and work with original values
1116                local ca
1117                if csone == 2 then
1118                    ca = register_color(name,'gray',inbetween(one,two,2,fraction))
1119                elseif csone == 3 then
1120                    ca = register_color(name,'rgb', inbetween(one,two,3,fraction),
1121                                                    inbetween(one,two,4,fraction),
1122                                                    inbetween(one,two,5,fraction))
1123                elseif csone == 4 then
1124                    ca = register_color(name,'cmyk',inbetween(one,two,6,fraction),
1125                                                    inbetween(one,two,7,fraction),
1126                                                    inbetween(one,two,8,fraction),
1127                                                    inbetween(one,two,9,fraction))
1128                else
1129                    ca = register_color(name,'gray',inbetween(one,two,2,fraction))
1130                end
1131                definecolor(name,ca,global,freeze)
1132         -- end
1133        else
1134            local inbetween = fraction < 0 and complement or justone
1135            local csone = one[1]
1136            local ca
1137            if csone == 2 then
1138                ca = register_color(name,'gray',inbetween(one,fraction,2))
1139            elseif csone == 3 then
1140                ca = register_color(name,'rgb', inbetween(one,fraction,3),
1141                                                inbetween(one,fraction,4),
1142                                                inbetween(one,fraction,5))
1143            elseif csone == 4 then
1144                ca = register_color(name,'cmyk',inbetween(one,fraction,6),
1145                                                inbetween(one,fraction,7),
1146                                                inbetween(one,fraction,8),
1147                                                inbetween(one,fraction,9))
1148            else
1149                ca = register_color(name,'gray',inbetween(one,fraction,2))
1150            end
1151            definecolor(name,ca,global,freeze)
1152        end
1153    end
1154    local one, two = transparencyvalues[a_one], transparencyvalues[a_two]
1155    local t = settings_to_hash_strict(specs)
1156    local ta = tonumber((t and t.a) or (one and one[1]) or (two and two[1]))
1157    local tt = tonumber((t and t.t) or (one and two and f(one,two,2,fraction)))
1158    if ta and tt then
1159        definetransparent(name,transparencies.register(name,ta,tt),global)
1160    end
1161end
1162
1163colors.defineintermediatecolor = defineintermediatecolor
1164
1165-- for the moment downward compatible
1166
1167local patterns = {
1168    CONTEXTLMTXMODE > 0 and "colo-imp-%s.mkxl" or "",
1169    "colo-imp-%s.mkiv",
1170    "colo-imp-%s.tex",
1171    -- obsolete:
1172    "colo-%s.mkiv",
1173    "colo-%s.tex"
1174}
1175
1176local function action(name,foundname)
1177    context.loadfoundcolorsetfile(name,foundname)
1178end
1179
1180local function failure(name)
1181 -- context.showmessage("colors",5,name)
1182    report_colors("unknown library %a",name)
1183end
1184
1185local function usecolors(name)
1186    resolvers.uselibrary {
1187        category = "color definition",
1188        name     = name,
1189        patterns = patterns,
1190        action   = action,
1191        failure  = failure,
1192        onlyonce = true,
1193    }
1194end
1195
1196colors.usecolors = usecolors
1197
1198-- backend magic
1199
1200local currentpagecolormodel
1201
1202function colors.setpagecolormodel(model)
1203    currentpagecolormodel = model
1204end
1205
1206function colors.getpagecolormodel()
1207    return currentpagecolormodel
1208end
1209
1210-- interface
1211
1212local setcolormodel = colors.setmodel
1213
1214implement {
1215    name      = "synccolorcount",
1216    actions   = synccolorcount,
1217    arguments = { "string", CONTEXTLMTXMODE > 0 and "string" or "integer" }
1218}
1219
1220implement {
1221    name      = "synccolor",
1222    actions   = synccolor,
1223    arguments = "string",
1224}
1225
1226implement {
1227    name      = "synccolorclone",
1228    actions   = synccolorclone,
1229    arguments = "2 strings",
1230}
1231
1232implement {
1233    name      = "setcolormodel",
1234    arguments = "2 strings",
1235    actions   = function(model,weight)
1236        texsetattribute(a_colormodel,setcolormodel(model,weight))
1237    end
1238}
1239
1240implement {
1241    name      = "setpagecolormodel",
1242    actions   = colors.setpagecolormodel,
1243    arguments = "string",
1244}
1245
1246implement {
1247    name      = "defineprocesscolorlocal",
1248    actions   = defineprocesscolor,
1249    arguments = { "string", "string", false, "boolean" }
1250}
1251
1252implement {
1253    name      = "defineprocesscolorglobal",
1254    actions   = defineprocesscolor,
1255    arguments = { "string", "string", true, "boolean" }
1256}
1257
1258implement {
1259    name      = "definelabcolorlocal",
1260    actions   = definelabcolor,
1261    arguments = { "string", "string", false, "boolean" }
1262}
1263
1264implement {
1265    name      = "definelabcolorglobal",
1266    actions   = definelabcolor,
1267    arguments = { "string", "string", true, "boolean" }
1268}
1269
1270implement {
1271    name      = "defineprocesscolordummy",
1272    actions   = defineprocesscolor,
1273    arguments = { "'c_o_l_o_r'", "string", false, false }
1274}
1275
1276implement {
1277    name      = "definespotcolorglobal",
1278    actions   = definespotcolor,
1279    arguments = { "string", "string", "string", true }
1280}
1281
1282implement {
1283    name      = "definemultitonecolorglobal",
1284    actions   = definemultitonecolor,
1285    arguments = { "string", "string", "string", "string", true }
1286}
1287
1288implement {
1289    name      = "registermaintextcolor",
1290    actions   = function(main)
1291        colors.main = main
1292    end,
1293    arguments = "integer"
1294}
1295
1296implement {
1297    name      = "definetransparency",
1298    actions   = definetransparency,
1299    arguments = "2 strings"
1300}
1301
1302implement {
1303    name      = "definetransparencyglobal",
1304    actions   = definetransparency,
1305    arguments = { "string", "string", true }
1306}
1307
1308implement {
1309    name      = "defineintermediatecolor",
1310    actions   = defineintermediatecolor,
1311    arguments = { "string", "string", "integer", "integer", "integer", "integer", "string", false, "boolean" }
1312}
1313
1314implement { name = "spotcolorname",          actions = { spotcolorname,          context }, arguments = "integer" }
1315implement { name = "spotcolorparent",        actions = { spotcolorparent,        context }, arguments = "integer" }
1316implement { name = "spotcolorvalue",         actions = { spotcolorvalue,         context }, arguments = "integer" }
1317implement { name = "colorcomponents",        actions = { colorcomponents,        context }, arguments = { "integer", tokens.constant(",") } }
1318implement { name = "transparencycomponents", actions = { transparencycomponents, context }, arguments = { "integer", tokens.constant(",") } }
1319implement { name = "processcolorcomponents", actions = { processcolorcomponents, context }, arguments = { "integer", tokens.constant(",") } }
1320implement { name = "formatcolor",            actions = { formatcolor,            context }, arguments = { "integer", "string" } }
1321implement { name = "formatgray",             actions = { formatgray,             context }, arguments = { "integer", "string" } }
1322
1323implement {
1324    name      = "mpcolor",
1325    actions   = { mpcolor, context },
1326    arguments = { "integer", "integer", "integer" }
1327}
1328
1329implement {
1330    name      = "mpoptions",
1331    actions   = { mpoptions, context },
1332    arguments = { "integer", "integer", "integer" }
1333}
1334
1335local ctx_doifelse = commands.doifelse
1336
1337implement {
1338    name      = "doifelsedrawingblack",
1339    actions   = function() ctx_doifelse(isblack(texgetattribute(a_color))) end
1340}
1341
1342implement {
1343    name      = "doifelseblack",
1344    actions   = { isblack, ctx_doifelse },
1345    arguments = "integer"
1346}
1347
1348-- function commands.withcolorsinset(name,command)
1349--     local set
1350--     if name and name ~= "" then
1351--         set = colorsets[name]
1352--     else
1353--         set = colorsets.default
1354--     end
1355--     if set then
1356--         if command then
1357--             for name in table.sortedhash(set) do
1358--                 context[command](name)
1359--             end
1360--         else
1361--             context(concat(table.sortedkeys(set),","))
1362--         end
1363--     end
1364-- end
1365
1366implement { name = "startcolorset", actions = pushset,   arguments = "string" }
1367implement { name = "stopcolorset",  actions = popset }
1368implement { name = "usecolors",     actions = usecolors, arguments = "string" }
1369
1370-- bonus
1371
1372do
1373
1374    local function pgfxcolorspec(model,ca) -- {}{}{colorspace}{list}
1375     -- local cv = attributes.colors.values[ca]
1376        local cv = colorvalues[ca]
1377        local str
1378        if cv then
1379            if model and model ~= 0 then
1380                model = model
1381            else
1382                model = forcedmodel(texgetattribute(a_colormodel))
1383                if model == 1 then
1384                    model = cv[1]
1385                end
1386            end
1387            if model == 3 then
1388                str = formatters["{rgb}{%1.3f,%1.3f,%1.3f}"](cv[3],cv[4],cv[5])
1389            elseif model == 4 then
1390                str = formatters["{cmyk}{%1.3f,%1.3f,%1.3f,%1.3f}"](cv[6],cv[7],cv[8],cv[9])
1391            else -- there is no real gray
1392                str = formatters["{rgb}{%1.3f,%1.3f,%1.3f}"](cv[2],cv[2],cv[2])
1393            end
1394        else
1395            str = "{rgb}{0,0,0}"
1396        end
1397        if trace_pgf then
1398            report_pgf("model %a, string %a",model,str)
1399        end
1400        return str
1401    end
1402
1403    implement {
1404        name      = "pgfxcolorspec",
1405        actions   = { pgfxcolorspec, context },
1406        arguments = { "integer", "integer" }
1407    }
1408
1409end
1410
1411-- handy
1412
1413local models = storage.allocate { "all", "gray", "rgb", "cmyk", "spot" }
1414
1415colors.models = models -- check for usage elsewhere
1416
1417function colors.spec(name)
1418    local l = attributes_list[a_color]
1419    local t = colorvalues[l[name]] or colorvalues[l.black]
1420    return {
1421        model = models[t[1]] or models[1],
1422        s = t[2],
1423        r = t[3], g = t[4], b = t[5],
1424        c = t[6], m = t[7], y = t[8], k = t[9],
1425    }
1426end
1427
1428function colors.currentnamedmodel()
1429    return models[texgetattribute(a_colormodel)] or "gray"
1430end
1431
1432-- inspect(attributes.colors.spec("red"))
1433-- inspect(attributes.colors.spec("red socks"))
1434
1435implement {
1436    name      = "negatedcolorcomponent",
1437    arguments = "string",
1438    actions   = function(s)
1439        s = 1 - (tonumber(s) or 0)
1440        context((s < 0 and 0) or (s > 1 and 1) or s)
1441    end
1442}
1443
1444-- This is a playground for MS and HH:
1445--
1446-- Required Contrast Ratios for WCAG Conformance (how about small text)
1447--
1448-- Level AA  Text      4.5:1  for regular text and 3.0:1 for large text (18pt or 14pt/bold)
1449-- Level AAA Text      7.0:1  for regular text and 4.5:1 for large text (18pt or 14pt/bold)
1450--
1451-- Level AA  Non-Text  3.0:1  for user interface components and graphics
1452
1453do
1454
1455    -- https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio
1456    -- https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
1457
1458    local function crap(v)
1459        return v <= 0.03928 and v/12.92 or (v+0.055/1.055)^2.4
1460    end
1461
1462    local function luminance(color)
1463        color = colorvalues[color]
1464        if color then
1465            return (0.2126 * crap(color[2]) + 0.7152 * crap(color[3]) + 0.0722 * crap(color[4])) + 0.05
1466        end
1467    end
1468
1469    local function formatluminance(color)
1470        local l = luminance(color)
1471        if l then
1472            return format("%0.3f",l)
1473        end
1474    end
1475
1476    local function formatluminanceratio(one,two)
1477        local one = luminance(one)
1478        local two = luminance(two)
1479        if one and two then
1480            return format("%0.3f",one > two and one/two or two/one)
1481        end
1482    end
1483
1484    colors.formatluminance      = formatluminance
1485    colors.formatluminanceratio = formatluminanceratio
1486
1487    implement {
1488        name      = "formatluminance",
1489     -- protected = true,
1490        arguments = "integer",
1491        actions   = { formatluminance, context },
1492    }
1493
1494    implement {
1495        name      = "formatluminanceratio",
1496     -- protected = true,
1497        arguments = { "integer", "integer" },
1498        actions   = { formatluminanceratio, context },
1499    }
1500
1501end
1502