1if not modules then modules = { } end modules ['attr-col'] = {
2 version = 1.001,
3 comment = "companion to attr-col.mkiv",
4 author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5 copyright = "PRAGMA ADE / ConTeXt Development Team",
6 license = "see context related readme files"
7}
8
9
10
11
12
13
14
15local type, tonumber = type, tonumber
16local concat = table.concat
17local min, max, floor, mod = math.min, math.max, math.floor, math.mod
18
19local attributes = attributes
20local nodes = nodes
21local utilities = utilities
22local logs = logs
23local backends = backends
24local storage = storage
25local context = context
26local tex = tex
27
28local variables = interfaces.variables
29local v_yes = variables.yes
30local v_no = variables.no
31
32local p_split_comma = lpeg.tsplitat(",")
33local p_split_colon = lpeg.splitat(":")
34local lpegmatch = lpeg.match
35
36local allocate = utilities.storage.allocate
37local setmetatableindex = table.setmetatableindex
38
39local report_attributes = logs.reporter("attributes","colors")
40local report_colors = logs.reporter("colors","support")
41local report_transparencies = logs.reporter("transparencies","support")
42
43
44
45
46
47
48
49local states = attributes.states
50local registrations = backends.registrations
51local nodeinjections = backends.nodeinjections
52local unsetvalue = attributes.unsetvalue
53
54local enableaction = nodes.tasks.enableaction
55local setaction = nodes.tasks.setaction
56
57local registerstorage = storage.register
58local formatters = string.formatters
59
60local interfaces = interfaces
61local implement = interfaces.implement
62
63local texgetattribute = tex.getattribute
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95attributes.colors = attributes.colors or { }
96local colors = attributes.colors
97
98local a_color = attributes.private('color')
99local a_selector = attributes.private('colormodel')
100
101colors.data = allocate()
102colors.values = colors.values or { }
103colors.registered = colors.registered or { }
104colors.weightgray = true
105colors.attribute = a_color
106colors.selector = a_selector
107colors.default = 1
108colors.main = nil
109colors.triggering = true
110colors.supported = true
111colors.model = "all"
112
113local data = colors.data
114local values = colors.values
115local registered = colors.registered
116
117local cmykrgbmode = 0
118
119local numbers = attributes.numbers
120local list = attributes.list
121
122registerstorage("attributes/colors/values", values, "attributes.colors.values")
123registerstorage("attributes/colors/registered", registered, "attributes.colors.registered")
124
125directives.register("colors.cmykrgbmode", function(v) cmykrgbmode = tonumber(v) or 0 end)
126
127local f_colors = {
128 rgb = formatters["r:%s:%s:%s"],
129 cmyk = formatters["c:%s:%s:%s:%s"],
130 gray = formatters["s:%s"],
131 spot = formatters["p:%s:%s:%s:%s"],
132}
133
134local models = {
135 [interfaces.variables.none] = unsetvalue,
136 black = unsetvalue,
137 bw = unsetvalue,
138 all = 1,
139 gray = 2,
140 rgb = 3,
141 cmyk = 4,
142}
143
144local function rgbtocmyk(r,g,b)
145 if not r then
146 return 0, 0, 0
147 else
148 return 1-r, 1-g, 1-b, 0
149 end
150end
151
152local function cmyktorgb(c,m,y,k)
153 if not c then
154 return 0, 0, 0, 1
155 elseif cmykrgbmode == 1 then
156 local d = 1.0 - k
157 return 1.0 - min(1.0,c*d+k), 1.0 - min(1.0,m*d+k), 1.0 - min(1.0,y*d+k)
158 else
159 return 1.0 - min(1.0,c +k), 1.0 - min(1.0,m +k), 1.0 - min(1.0,y +k)
160 end
161end
162
163local function rgbtogray(r,g,b)
164 if not r then
165 return 0
166 end
167 local w = colors.weightgray
168 if w == true then
169 return .30*r + .59*g + .11*b
170 elseif not w then
171 return r/3 + g/3 + b/3
172 else
173 return w[1]*r + w[2]*g + w[3]*b
174 end
175end
176
177local function cmyktogray(c,m,y,k)
178 return rgbtogray(cmyktorgb(c,m,y,k))
179end
180
181
182
183
184
185
186
187
188local function hsvtorgb(h,s,v)
189 if s > 1 then
190 s = 1
191 elseif s < 0 then
192 s = 0
193 elseif s == 0 then
194 return v, v, v
195 end
196 if v > 1 then
197 s = 1
198 elseif v < 0 then
199 v = 0
200 end
201 if h < 0 then
202 h = 0
203 elseif h >= 360 then
204 h = mod(h,360)
205 end
206 local hd = h / 60
207 local hi = floor(hd)
208 local f = hd - hi
209 local p = v * (1 - s)
210 local q = v * (1 - f * s)
211 local t = v * (1 - (1 - f) * s)
212 if hi == 0 then
213 return v, t, p
214 elseif hi == 1 then
215 return q, v, p
216 elseif hi == 2 then
217 return p, v, t
218 elseif hi == 3 then
219 return p, q, v
220 elseif hi == 4 then
221 return t, p, v
222 elseif hi == 5 then
223 return v, p, q
224 else
225 print("error in hsv -> rgb",h,s,v)
226 return 0, 0, 0
227 end
228end
229
230local function rgbtohsv(r,g,b)
231 local offset, maximum, other_1, other_2
232 if r >= g and r >= b then
233 offset, maximum, other_1, other_2 = 0, r, g, b
234 elseif g >= r and g >= b then
235 offset, maximum, other_1, other_2 = 2, g, b, r
236 else
237 offset, maximum, other_1, other_2 = 4, b, r, g
238 end
239 if maximum == 0 then
240 return 0, 0, 0
241 end
242 local minimum = other_1 < other_2 and other_1 or other_2
243 if maximum == minimum then
244 return 0, 0, maximum
245 end
246 local delta = maximum - minimum
247 return (offset + (other_1-other_2)/delta)*60, delta/maximum, maximum
248end
249
250local function graytorgb(s)
251 return 1-s, 1-s, 1-s
252end
253
254local function hsvtogray(h,s,v)
255 return rgb_to_gray(hsv_to_rgb(h,s,v))
256end
257
258local function graytohsv(s)
259 return 0, 0, s
260end
261
262local function hwbtorgb(hue,black,white)
263 local r, g, b = hsvtorgb(hue,1,.5)
264 local f = 1 - white - black
265 return f * r + white, f * g + white , f * b + white
266end
267
268colors.rgbtocmyk = rgbtocmyk
269colors.rgbtogray = rgbtogray
270colors.cmyktorgb = cmyktorgb
271colors.cmyktogray = cmyktogray
272colors.rgbtohsv = rgbtohsv
273colors.hsvtorgb = hsvtorgb
274colors.hwbtorgb = hwbtorgb
275colors.hsvtogray = hsvtogray
276colors.graytohsv = graytohsv
277
278
279
280
281
282
283
284function colors.gray(s)
285 return { 2, s, s, s, s, 0, 0, 0, 1-s }
286end
287
288function colors.rgb(r,g,b)
289 local s = rgbtogray(r,g,b)
290 local c, m, y, k = rgbtocmyk(r,g,b)
291 return { 3, s, r, g, b, c, m, y, k }
292end
293
294function colors.cmyk(c,m,y,k)
295 local s = cmyktogray(c,m,y,k)
296 local r, g, b = cmyktorgb(c,m,y,k)
297 return { 4, s, r, g, b, c, m, y, k }
298end
299
300
301
302
303
304function colors.spot(parent,f,d,p)
305
306 if type(p) == "number" then
307 local n = list[numbers.color][parent]
308 if n then
309 local v = values[n]
310 if v then
311
312 local c, m, y, k = p*v[6], p*v[7], p*v[8], p*v[9]
313 local r, g, b = cmyktorgb(c,m,y,k)
314 local s = cmyktogray(c,m,y,k)
315 return { 5, s, r, g, b, c, m, y, k, parent, f, d, p }
316 end
317 end
318 else
319
320 local ps = lpegmatch(p_split_comma,p)
321 local ds = lpegmatch(p_split_comma,d)
322 local c, m, y, k = 0, 0, 0, 0
323 local done = false
324 for i=1,#ps do
325 local p = tonumber(ps[i])
326 local d = ds[i]
327 if p and d then
328 local n = list[numbers.color][d]
329 if n then
330 local v = values[n]
331 if v then
332 c = c + p*v[6]
333 m = m + p*v[7]
334 y = y + p*v[8]
335 k = k + p*v[9]
336 done = true
337 end
338 end
339 end
340 end
341 if done then
342 local r, g, b = cmyktorgb(c,m,y,k)
343 local s = cmyktogray(c,m,y,k)
344 local f = tonumber(f)
345 return { 5, s, r, g, b, c, m, y, k, parent, f, d, p }
346 end
347 end
348 return { 5, .5, .5, .5, .5, 0, 0, 0, .5, parent, f, d, p }
349end
350
351local graycolor = nodeinjections.graycolor
352local rgbcolor = nodeinjections.rgbcolor
353local cmykcolor = nodeinjections.cmykcolor
354local spotcolor = nodeinjections.spotcolor
355
356updaters.register("backends.injections.latebindings",function()
357 local nodeinjections = backends.nodeinjections
358 graycolor = nodeinjections.graycolor
359 rgbcolor = nodeinjections.rgbcolor
360 cmykcolor = nodeinjections.cmykcolor
361 spotcolor = nodeinjections.spotcolor
362end)
363
364local function extender(colors,key)
365 if colors.supported and key == "none" then
366 local d = graycolor(0)
367 colors.none = d
368 return d
369 end
370end
371
372local function reviver(data,n)
373 if colors.supported then
374 local v = values[n]
375 local d
376 if not v then
377 local gray = graycolor(0)
378 d = { gray, gray, gray, gray }
379 report_attributes("unable to revive color %a",n)
380 else
381 local model = colors.forcedmodel(v[1])
382 if model == 2 then
383 local gray = graycolor(v[2])
384 d = { gray, gray, gray, gray }
385 elseif model == 3 then
386 local gray = graycolor(v[2])
387 local rgb = rgbcolor(v[3],v[4],v[5])
388 local cmyk = cmykcolor(v[6],v[7],v[8],v[9])
389 d = { rgb, gray, rgb, cmyk }
390 elseif model == 4 then
391 local gray = graycolor(v[2])
392 local rgb = rgbcolor(v[3],v[4],v[5])
393 local cmyk = cmykcolor(v[6],v[7],v[8],v[9])
394 d = { cmyk, gray, rgb, cmyk }
395 elseif model == 5 then
396 local spot = spotcolor(v[10],v[11],v[12],v[13])
397
398 d = { spot, spot, spot, spot }
399 end
400 end
401 data[n] = d
402 return d
403 end
404end
405
406setmetatableindex(colors, extender)
407setmetatableindex(colors.data, reviver)
408
409function colors.filter(n)
410 return concat(data[n],":",5)
411end
412
413
414
415
416
417
418
419
420
421
422function colors.setmodel(name,weightgray)
423 if weightgray == true or weightgray == v_yes then
424 weightgray = true
425 elseif weightgray == false or weightgray == v_no then
426 weightgray = false
427 else
428 local r, g, b = lpegmatch(p_split_colon,weightgray)
429 if r and g and b then
430 weightgray = { r, g, b }
431 else
432 weightgray = true
433 end
434 end
435 local default = models[name] or 1
436
437 colors.model = name
438 colors.default = default
439 colors.weightgray = weightgray
440
441
442
443 local forced = colors.forced
444
445 if forced == nil then
446
447 colors.forced = default
448 elseif forced == false then
449
450 elseif forced ~= default then
451
452 colors.forced = false
453 else
454
455 end
456 return default
457end
458
459function colors.register(name, colorspace, ...)
460 local stamp = f_colors[colorspace](...)
461 local color = registered[stamp]
462 if not color then
463 color = #values + 1
464 values[color] = colors[colorspace](...)
465 registered[stamp] = color
466
467 end
468 if name then
469 list[a_color][name] = color
470 end
471 return registered[stamp]
472end
473
474function colors.value(id)
475 return values[id]
476end
477
478attributes.colors.handler = nodes.installattributehandler {
479 name = "color",
480 namespace = colors,
481 initializer = states.initialize,
482 finalizer = states.finalize,
483 processor = states.selective,
484 resolver = function() return colors.main end,
485}
486
487function colors.enable(value)
488 setaction("shipouts","attributes.colors.handler",not (value == false or not colors.supported))
489end
490
491function colors.forcesupport(value)
492 colors.supported = value
493 report_colors("color is %ssupported",value and "" or "not ")
494 colors.enable(value)
495end
496
497function colors.toattributes(name)
498 local mc = list[a_color][name]
499 local mm = texgetattribute(a_selector)
500 return (mm == unsetvalue and 1) or mm or 1, mc or list[a_color][1] or unsetvalue
501end
502
503
504
505local a_transparency = attributes.private('transparency')
506
507attributes.transparencies = attributes.transparencies or { }
508local transparencies = attributes.transparencies
509transparencies.registered = transparencies.registered or { }
510transparencies.data = allocate()
511transparencies.values = transparencies.values or { }
512transparencies.triggering = true
513transparencies.attribute = a_transparency
514transparencies.supported = true
515
516local registered = transparencies.registered
517local data = transparencies.data
518local values = transparencies.values
519local f_transparency = formatters["%s:%s"]
520
521registerstorage("attributes/transparencies/registered", registered, "attributes.transparencies.registered")
522registerstorage("attributes/transparencies/values", values, "attributes.transparencies.values")
523
524local register_transparency = registrations.transparency
525local inject_transparency = nodeinjections.transparency
526
527updaters.register("backends.injections.latebindings",function()
528 register_transparency = backends.registrations.transparency
529 inject_transparency = backends.nodeinjections.transparency
530end)
531
532function transparencies.register(name,a,t,force)
533
534
535
536
537 local stamp = f_transparency(a,t)
538 local n = registered[stamp]
539 if not n then
540 n = #values + 1
541 values[n] = { a, t }
542 registered[stamp] = n
543 if force then
544 register_transparency(n,a,t)
545 end
546 elseif force and not data[n] then
547 register_transparency(n,a,t)
548 end
549 if name then
550 list[a_transparency][name] = n
551 end
552 return registered[stamp]
553end
554
555local function extender(transparencies,key)
556 if colors.supported and key == "none" then
557 local d = inject_transparency(0)
558 transparencies.none = d
559 return d
560 end
561end
562
563local function reviver(data,n)
564 if n and transparencies.supported then
565 local v = values[n]
566 local d
567 if not v then
568 d = inject_transparency(0)
569 else
570 d = inject_transparency(n)
571 register_transparency(n,v[1],v[2])
572 end
573 data[n] = d
574 return d
575 else
576 return ""
577 end
578end
579
580setmetatableindex(transparencies,extender)
581setmetatableindex(transparencies.data,reviver)
582
583
584
585function transparencies.value(id)
586 return values[id]
587end
588
589attributes.transparencies.handler = nodes.installattributehandler {
590 name = "transparency",
591 namespace = transparencies,
592 initializer = states.initialize,
593 finalizer = states.finalize,
594 processor = states.process,
595}
596
597function transparencies.enable(value)
598 setaction("shipouts","attributes.transparencies.handler",not (value == false or not transparencies.supported))
599end
600
601function transparencies.forcesupport(value)
602 transparencies.supported = value
603 report_transparencies("transparency is %ssupported",value and "" or "not ")
604 transparencies.enable(value)
605end
606
607function transparencies.toattribute(name)
608 return list[a_transparency][name] or unsetvalue
609end
610
611
612
613attributes.colorintents = attributes.colorintents or { }
614local colorintents = attributes.colorintents
615colorintents.data = allocate()
616colorintents.attribute = attributes.private('colorintent')
617
618colorintents.registered = allocate {
619 overprint = 1,
620 knockout = 2,
621}
622
623local inject_overprint = nodeinjections.injectoverprint
624local inject_knockout = nodeinjections.injectknockout
625
626updaters.register("backends.injections.latebindings",function()
627 local nodeinjections = backends.nodeinjections
628 inject_overprint = nodeinjections.injectoverprint
629 inject_knockout = nodeinjections.injectknockout
630end)
631
632local data = colorintents.data
633local registered = colorintents.registered
634
635local function extender(colorintents,key)
636 if key == "none" then
637 local d = data[2]
638 colorintents.none = d
639 return d
640 end
641end
642
643local function reviver(data,n)
644 if n == 1 then
645 local d = inject_overprint()
646 data[1] = d
647 return d
648 elseif n == 2 then
649 local d = inject_knockout()
650 data[2] = d
651 return d
652 end
653end
654
655setmetatableindex(colorintents, extender)
656setmetatableindex(colorintents.data, reviver)
657
658function colorintents.register(stamp)
659 return registered[stamp] or registered.overprint
660end
661
662colorintents.handler = nodes.installattributehandler {
663 name = "colorintent",
664 namespace = colorintents,
665 initializer = states.initialize,
666 finalizer = states.finalize,
667 processor = states.process,
668}
669
670function colorintents.enable()
671 enableaction("shipouts","attributes.colorintents.handler")
672end
673
674
675
676implement { name = "enablecolor", onlyonce = true, actions = colors.enable }
677implement { name = "enabletransparency", onlyonce = true, actions = transparencies.enable }
678implement { name = "enablecolorintents", onlyonce = true, actions = colorintents.enable }
679
680
681
682implement { name = "registercolorintent", actions = { colorintents .register, context }, arguments = "string" }
683 |