1if not modules then modules = { } end modules ['lpdf-col'] = {
2 version = 1.001,
3 comment = "companion to lpdf-ini.mkiv",
4 author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5 copyright = "PRAGMA ADE / ConTeXt Development Team",
6 license = "see context related readme files"
7}
8
9local type, next, tostring, tonumber = type, next, tostring, tonumber
10local char, byte, format, gsub, rep, gmatch = string.char, string.byte, string.format, string.gsub, string.rep, string.gmatch
11local settings_to_array, settings_to_numbers = utilities.parsers.settings_to_array, utilities.parsers.settings_to_numbers
12local concat = table.concat
13local round = math.round
14local formatters = string.formatters
15
16local allocate = utilities.storage.allocate
17
18local pdfbackend = backends.registered.pdf
19local nodeinjections = pdfbackend.nodeinjections
20local codeinjections = pdfbackend.codeinjections
21local registrations = pdfbackend.registrations
22
23local nodes = nodes
24local nodepool = nodes.nuts.pool
25local register = nodepool.register
26local setstate = nodepool.setstate
27
28local lpdf = lpdf
29local pdfconstant = lpdf.constant
30local pdfdictionary = lpdf.dictionary
31local pdfarray = lpdf.array
32local pdfreference = lpdf.reference
33local pdfverbose = lpdf.verbose
34
35local pdfflushobject = lpdf.flushobject
36local pdfdelayedobject = lpdf.delayedobject
37local pdfflushstreamobject = lpdf.flushstreamobject
38local pdfshareobjectreference = lpdf.shareobjectreference
39
40local addtopageattributes = lpdf.addtopageattributes
41local adddocumentcolorspace = lpdf.adddocumentcolorspace
42local adddocumentextgstate = lpdf.adddocumentextgstate
43
44local colors = attributes.colors
45local registercolor = colors.register
46local colorsvalue = colors.value
47local forcedmodel = colors.forcedmodel
48local getpagecolormodel = colors.getpagecolormodel
49local colortoattributes = colors.toattributes
50
51local transparencies = attributes.transparencies
52local registertransparancy = transparencies.register
53local transparenciesvalue = transparencies.value
54local transparencytoattribute = transparencies.toattribute
55
56local unsetvalue = attributes.unsetvalue
57
58local setmetatableindex = table.setmetatableindex
59
60local c_transparency = pdfconstant("Transparency")
61
62local f_gray = formatters["%.3N g %.3N G"]
63local f_rgb = formatters["%.3N %.3N %.3N rg %.3N %.3N %.3N RG"]
64local f_cmyk = formatters["%.3N %.3N %.3N %.3N k %.3N %.3N %.3N %.3N K"]
65local f_spot = formatters["/%s cs /%s CS %s SCN %s scn"]
66local f_tr = formatters["Tr%s"]
67local f_cm = formatters["q %.6N %.6N %.6N %.6N %.6N %.6N cm"]
68local f_effect = formatters["%s Tc %s w %s Tr"]
69local f_tr_gs = formatters["/Tr%s gs"]
70local f_num_1 = formatters["%.3N %.3N"]
71local f_num_2 = formatters["%.3N %.3N"]
72local f_num_3 = formatters["%.3N %.3N %.3N"]
73local f_num_4 = formatters["%.3N %.3N %.3N %.3N"]
74
75local report_color = logs.reporter("colors","backend")
76
77
78
79local colorspaceconstants = allocate {
80 gray = pdfconstant("DeviceGray"),
81 rgb = pdfconstant("DeviceRGB"),
82 cmyk = pdfconstant("DeviceCMYK"),
83 all = pdfconstant("DeviceRGB"),
84}
85
86local transparencygroups = { }
87
88lpdf.colorspaceconstants = colorspaceconstants
89lpdf.transparencygroups = transparencygroups
90
91setmetatableindex(transparencygroups, function(transparencygroups,colormodel)
92 local cs = colorspaceconstants[colormodel]
93 if cs then
94 local d = pdfdictionary {
95 S = c_transparency,
96 CS = cs,
97 I = true,
98 }
99
100 local g = pdfreference(pdfdelayedobject(tostring(d)))
101 transparencygroups[colormodel] = g
102 return g
103 else
104 transparencygroups[colormodel] = false
105 return false
106 end
107end)
108
109local function addpagegroup()
110 local model = getpagecolormodel()
111 if model then
112 local g = transparencygroups[model]
113 if g then
114 addtopageattributes("Group",g)
115 end
116 end
117end
118
119lpdf.registerpagefinalizer(addpagegroup,3,"pagegroup")
120
121
122
123
124
125function nodeinjections.rgbcolor(r,g,b)
126 return register(setstate(f_rgb(r,g,b,r,g,b)))
127end
128
129function nodeinjections.cmykcolor(c,m,y,k)
130 return register(setstate(f_cmyk(c,m,y,k,c,m,y,k)))
131end
132
133function nodeinjections.graycolor(s)
134 return register(setstate(f_gray(s,s)))
135end
136
137function nodeinjections.spotcolor(n,f,d,p)
138 if type(p) == "string" then
139 p = gsub(p,","," ")
140 end
141 return register(setstate(f_spot(n,n,p,p)))
142end
143
144function nodeinjections.transparency(n)
145 return register(setstate(f_tr_gs(n)))
146end
147
148
149
150local effects = {
151 normal = 0,
152 inner = 0,
153 outer = 1,
154 both = 2,
155 hidden = 3,
156}
157
158local bp = number.dimenfactors.bp
159
160function nodeinjections.effect(effect,stretch,rulethickness)
161
162 rulethickness = bp * rulethickness
163 effect = effects[effect] or effects['normal']
164 return register(setstate(f_effect(stretch,rulethickness,effect)))
165end
166
167
168
169local pdf_separation = pdfconstant("Separation")
170local pdf_indexed = pdfconstant("Indexed")
171local pdf_device_n = pdfconstant("DeviceN")
172local pdf_device_rgb = pdfconstant("DeviceRGB")
173local pdf_device_cmyk = pdfconstant("DeviceCMYK")
174local pdf_device_gray = pdfconstant("DeviceGray")
175local pdf_extgstate = pdfconstant("ExtGState")
176
177local pdf_rgb_range = pdfarray { 0, 1, 0, 1, 0, 1 }
178local pdf_cmyk_range = pdfarray { 0, 1, 0, 1, 0, 1, 0, 1 }
179local pdf_gray_range = pdfarray { 0, 1 }
180
181local f_rgb_function = formatters["dup %s mul exch dup %s mul exch %s mul"]
182local f_cmyk_function = formatters["dup %s mul exch dup %s mul exch dup %s mul exch %s mul"]
183local f_gray_function = formatters["%s mul"]
184
185local documentcolorspaces = pdfdictionary()
186
187local spotcolorhash = { }
188local spotcolornames = { }
189local indexcolorhash = { }
190local delayedindexcolors = { }
191
192function registrations.spotcolorname(name,e)
193 spotcolornames[name] = e or name
194end
195
196function registrations.getspotcolorreference(name)
197 return spotcolorhash[name]
198end
199
200
201
202
203
204
205
206
207local processcolors
208
209local function registersomespotcolor(name,noffractions,names,p,colorspace,range,funct)
210 noffractions = tonumber(noffractions) or 1
211 if noffractions == 0 then
212
213 elseif noffractions == 1 then
214 local dictionary = pdfdictionary {
215 FunctionType = 4,
216 Domain = { 0, 1 },
217 Range = range,
218 }
219 local calculations = pdfflushstreamobject(format("{ %s }",funct),dictionary)
220
221
222
223
224
225
226 local array = pdfarray {
227 pdf_separation,
228 pdfconstant(spotcolornames[name] or name),
229 colorspace,
230 pdfreference(calculations),
231 }
232 local m = pdfflushobject(array)
233 local mr = pdfreference(m)
234 spotcolorhash[name] = m
235 documentcolorspaces[name] = mr
236 adddocumentcolorspace(name,mr)
237 else
238 local cnames = pdfarray()
239 local domain = pdfarray()
240 local colorants = pdfdictionary()
241 for n in gmatch(names,"[^,]+") do
242 local name = spotcolornames[n] or n
243
244 if n == "cyan" then
245 name = "Cyan"
246 elseif n == "magenta" then
247 name = "Magenta"
248 elseif n == "yellow" then
249 name = "Yellow"
250 elseif n == "black" then
251 name = "Black"
252 else
253 local sn = spotcolorhash[name] or spotcolorhash[n]
254 if not sn then
255 report_color("defining %a as colorant",name)
256 colors.definespotcolor("",name,"p=1",true)
257 sn = spotcolorhash[name] or spotcolorhash[n]
258 end
259 if sn then
260 colorants[name] = pdfreference(sn)
261 else
262
263 report_color("unknown colorant %a, using black instead",name or n)
264 name = "Black"
265 end
266 end
267 cnames[#cnames+1] = pdfconstant(name)
268 domain[#domain+1] = 0
269 domain[#domain+1] = 1
270 end
271 if not processcolors then
272 local specification = pdfdictionary {
273 ColorSpace = pdfconstant("DeviceCMYK"),
274 Components = pdfarray {
275 pdfconstant("Cyan"),
276 pdfconstant("Magenta"),
277 pdfconstant("Yellow"),
278 pdfconstant("Black")
279 }
280 }
281 processcolors = pdfreference(pdfflushobject(specification))
282 end
283 local dictionary = pdfdictionary {
284 FunctionType = 4,
285 Domain = domain,
286 Range = range,
287 }
288 local calculation = pdfflushstreamobject(format("{ %s %s }",rep("pop ",noffractions),funct),dictionary)
289 local channels = pdfdictionary {
290 Subtype = pdfconstant("NChannel"),
291 Colorants = colorants,
292 Process = processcolors,
293 }
294 local array = pdfarray {
295 pdf_device_n,
296 cnames,
297 colorspace,
298 pdfreference(calculation),
299 pdfshareobjectreference(tostring(channels)),
300 }
301 local m = pdfflushobject(array)
302 local mr = pdfreference(m)
303 spotcolorhash[name] = m
304 documentcolorspaces[name] = mr
305 adddocumentcolorspace(name,mr)
306 end
307end
308
309
310
311local function registersomeindexcolor(name,noffractions,names,p,colorspace,range,funct)
312 noffractions = tonumber(noffractions) or 1
313 local cnames = pdfarray()
314 local domain = pdfarray()
315 local names = settings_to_array(#names == 0 and name or names)
316 local values = settings_to_numbers(p)
317 names [#names +1] = "None"
318 values[#values+1] = 1
319
320 for i=1,#names do
321 local name = names[i]
322 local spot = spotcolornames[name]
323 cnames[#cnames+1] = pdfconstant(spot ~= "" and spot or name)
324 domain[#domain+1] = 0
325 domain[#domain+1] = 1
326 end
327 local dictionary = pdfdictionary {
328 FunctionType = 4,
329 Domain = domain,
330 Range = range,
331 }
332 local n = pdfflushstreamobject(format("{ %s %s }",rep("exch pop ",noffractions),funct),dictionary)
333 local a = pdfarray {
334 pdf_device_n,
335 cnames,
336 colorspace,
337 pdfreference(n),
338 }
339 local vector = { }
340 local set = { }
341 local n = #values
342 for i=255,0,-1 do
343 for j=1,n do
344 set[j] = format("%02X",round(values[j]*i))
345 end
346 vector[#vector+1] = concat(set)
347 end
348 vector = pdfverbose { "<", concat(vector, " "), ">" }
349 local n = pdfflushobject(pdfarray{ pdf_indexed, a, 255, vector })
350 adddocumentcolorspace(format("%s_indexed",name),pdfreference(n))
351 return n
352end
353
354
355
356local function delayindexcolor(name,names,func)
357 local hash = (names ~= "" and names) or name
358 delayedindexcolors[hash] = func
359end
360
361local function indexcolorref(name)
362 local parent = colors.spotcolorparent(name)
363 local data = indexcolorhash[name]
364 if data == nil then
365 local delayedindexcolor = delayedindexcolors[parent]
366 if type(delayedindexcolor) == "function" then
367 data = delayedindexcolor()
368 delayedindexcolors[parent] = true
369 end
370 indexcolorhash[parent] = data or false
371 end
372 return data
373end
374
375function registrations.rgbspotcolor(name,noffractions,names,p,r,g,b)
376 if noffractions == 1 then
377 registersomespotcolor(name,noffractions,names,p,pdf_device_rgb,pdf_rgb_range,f_rgb_function(r,g,b))
378 else
379 registersomespotcolor(name,noffractions,names,p,pdf_device_rgb,pdf_rgb_range,f_num_3(r,g,b))
380 end
381 delayindexcolor(name,names,function()
382 return registersomeindexcolor(name,noffractions,names,p,pdf_device_rgb,pdf_rgb_range,f_rgb_function(r,g,b))
383 end)
384end
385
386function registrations.cmykspotcolor(name,noffractions,names,p,c,m,y,k)
387 if noffractions == 1 then
388 registersomespotcolor(name,noffractions,names,p,pdf_device_cmyk,pdf_cmyk_range,f_cmyk_function(c,m,y,k))
389 else
390 registersomespotcolor(name,noffractions,names,p,pdf_device_cmyk,pdf_cmyk_range,f_num_4(c,m,y,k))
391 end
392 delayindexcolor(name,names,function()
393 return registersomeindexcolor(name,noffractions,names,p,pdf_device_cmyk,pdf_cmyk_range,f_cmyk_function(c,m,y,k))
394 end)
395end
396
397function registrations.grayspotcolor(name,noffractions,names,p,s)
398 if noffractions == 1 then
399 registersomespotcolor(name,noffractions,names,p,pdf_device_gray,pdf_gray_range,f_gray_function(s))
400 else
401 registersomespotcolor(name,noffractions,names,p,pdf_device_gray,pdf_gray_range,f_num_1(s))
402 end
403 delayindexcolor(name,names,function()
404 return registersomeindexcolor(name,noffractions,names,p,pdf_device_gray,pdf_gray_range,f_gray_function(s))
405 end)
406end
407
408function registrations.rgbindexcolor(name,noffractions,names,p,r,g,b)
409 registersomeindexcolor(name,noffractions,names,p,pdf_device_rgb,pdf_rgb_range,f_rgb_function(r,g,b))
410end
411
412function registrations.cmykindexcolor(name,noffractions,names,p,c,m,y,k)
413 registersomeindexcolor(name,noffractions,names,p,pdf_device_cmyk,pdf_cmyk_range,f_cmyk_function(c,m,y,k))
414end
415
416function registrations.grayindexcolor(name,noffractions,names,p,s)
417 registersomeindexcolor(name,noffractions,names,p,pdf_device_gray,pdf_gray_range,f_gray_function(s))
418end
419
420function codeinjections.setfigurecolorspace(data,figure)
421 local color = data.request.color
422 if color then
423 local ref = indexcolorref(color)
424 if ref then
425 figure.colorspace = ref
426 data.used.color = color
427 data.used.colorref = ref
428 end
429 end
430end
431
432
433
434local pdftransparencies = { [0] =
435 pdfconstant("Normal"),
436 pdfconstant("Normal"),
437 pdfconstant("Multiply"),
438 pdfconstant("Screen"),
439 pdfconstant("Overlay"),
440 pdfconstant("SoftLight"),
441 pdfconstant("HardLight"),
442 pdfconstant("ColorDodge"),
443 pdfconstant("ColorBurn"),
444 pdfconstant("Darken"),
445 pdfconstant("Lighten"),
446 pdfconstant("Difference"),
447 pdfconstant("Exclusion"),
448 pdfconstant("Hue"),
449 pdfconstant("Saturation"),
450 pdfconstant("Color"),
451 pdfconstant("Luminosity"),
452 pdfconstant("Compatible"),
453}
454
455local documenttransparencies = { }
456local transparencyhash = { }
457
458local done, signaled = false, false
459
460function registrations.transparency(n,a,t)
461 if not done then
462 local d = pdfdictionary {
463 Type = pdf_extgstate,
464 ca = 1,
465 CA = 1,
466 BM = pdftransparencies[1],
467 AIS = false,
468 }
469 local m = pdfflushobject(d)
470 local mr = pdfreference(m)
471 transparencyhash[0] = m
472 documenttransparencies[0] = mr
473 adddocumentextgstate("Tr0",mr)
474 done = true
475 end
476 if n > 0 and not transparencyhash[n] then
477 local d = pdfdictionary {
478 Type = pdf_extgstate,
479 ca = tonumber(t),
480 CA = tonumber(t),
481 BM = pdftransparencies[tonumber(a)] or pdftransparencies[0],
482 AIS = false,
483 }
484 local m = pdfflushobject(d)
485 local mr = pdfreference(m)
486 transparencyhash[n] = m
487 documenttransparencies[n] = mr
488 adddocumentextgstate(f_tr(n),mr)
489 end
490end
491
492statistics.register("page group warning", function()
493 if done then
494 local model = getpagecolormodel()
495 if model and not transparencygroups[model] then
496 return "transparencies are used but no pagecolormodel is set"
497 end
498 end
499end)
500
501
502
503
504
505
506
507local function lpdfcolor(model,ca,default)
508 if colors.supported then
509 local cv = colorsvalue(ca)
510 if cv then
511 if model == 1 then
512 model = cv[1]
513 end
514 model = forcedmodel(model)
515 if model == 2 then
516 local s = cv[2]
517 return f_gray(s,s)
518 elseif model == 3 then
519 local r = cv[3]
520 local g = cv[4]
521 local b = cv[5]
522 return f_rgb(r,g,b,r,g,b)
523 elseif model == 4 then
524 local c = cv[6]
525 local m = cv[7]
526 local y = cv[8]
527 local k = cv[9]
528 return f_cmyk(c,m,y,k,c,m,y,k)
529 else
530 local n = cv[10]
531 local f = cv[11]
532 local d = cv[12]
533 local p = cv[13]
534 if type(p) == "string" then
535 p = gsub(p,","," ")
536 end
537 return f_spot(n,n,p,p)
538 end
539 else
540 return f_gray(default or 0,default or 0)
541 end
542 else
543 return ""
544 end
545end
546
547lpdf.color = lpdfcolor
548
549interfaces.implement {
550 name = "pdfcolor",
551 public = true,
552 untraced = true,
553 actions = { lpdfcolor, context },
554 arguments = "integer"
555}
556
557function lpdf.colorspec(model,ca,default)
558 if ca and ca > 0 then
559 local cv = colors.value(ca)
560 if cv then
561 if model == 1 then
562 model = cv[1]
563 end
564 if model == 2 then
565 return pdfarray { cv[2] }
566 elseif model == 3 then
567 return pdfarray { cv[3],cv[4],cv[5] }
568 elseif model == 4 then
569 return pdfarray { cv[6],cv[7],cv[8],cv[9] }
570 elseif model == 5 then
571 return pdfarray { cv[13] }
572 end
573 end
574 end
575 if default then
576 return default
577 end
578end
579
580function lpdf.pdfcolor(attribute)
581 return lpdfcolor(1,attribute)
582end
583
584local function lpdftransparency(ct,default)
585
586
587
588 if transparencies.supported then
589 local ct = transparenciesvalue(ct)
590 if ct then
591 return f_tr_gs(registertransparancy(nil,ct[1],ct[2],true))
592 else
593 return f_tr_gs(0)
594 end
595 else
596 return ""
597 end
598end
599
600lpdf.transparency = lpdftransparency
601
602function lpdf.colorvalue(model,ca,default)
603 local cv = colorsvalue(ca)
604 if cv then
605 if model == 1 then
606 model = cv[1]
607 end
608 model = forcedmodel(model)
609 if model == 2 then
610 return f_num_1(cv[2])
611 elseif model == 3 then
612 return f_num_3(cv[3],cv[4],cv[5])
613 elseif model == 4 then
614 return f_num_4(cv[6],cv[7],cv[8],cv[9])
615 else
616 return f_num_1(cv[13])
617 end
618 else
619 return f_num_1(default or 0)
620 end
621end
622
623function lpdf.colorvalues(model,ca,default)
624 local cv = colorsvalue(ca)
625 if cv then
626 if model == 1 then
627 model = cv[1]
628 end
629 model = forcedmodel(model)
630 if model == 2 then
631 return cv[2]
632 elseif model == 3 then
633 return cv[3], cv[4], cv[5]
634 elseif model == 4 then
635 return cv[6], cv[7], cv[8], cv[9]
636 elseif model == 5 then
637 return cv[13]
638 end
639 else
640 return default or 0
641 end
642end
643
644function lpdf.transparencyvalue(ta,default)
645 local tv = transparenciesvalue(ta)
646 if tv then
647 return tv[2]
648 else
649 return default or 1
650 end
651end
652
653function lpdf.colorspace(model,ca)
654 local cv = colorsvalue(ca)
655 if cv then
656 if model == 1 then
657 model = cv[1]
658 end
659 model = forcedmodel(model)
660 if model == 2 then
661 return "DeviceGray"
662 elseif model == 3 then
663 return "DeviceRGB"
664 elseif model == 4 then
665 return "DeviceCMYK"
666 end
667 end
668 return "DeviceGRAY"
669end
670
671
672
673local intransparency = false
674
675function lpdf.rgbcode(model,r,g,b)
676 if colors.supported then
677 return lpdfcolor(model,registercolor(nil,'rgb',r,g,b))
678 else
679 return ""
680 end
681end
682
683function lpdf.cmykcode(model,c,m,y,k)
684 if colors.supported then
685 return lpdfcolor(model,registercolor(nil,'cmyk',c,m,y,k))
686 else
687 return ""
688 end
689end
690
691function lpdf.graycode(model,s)
692 if colors.supported then
693 return lpdfcolor(model,registercolor(nil,'gray',s))
694 else
695 return ""
696 end
697end
698
699function lpdf.spotcode(model,n,f,d,p)
700 if colors.supported then
701 return lpdfcolor(model,registercolor(nil,'spot',n,f,d,p))
702 else
703 return ""
704 end
705end
706
707function lpdf.transparencycode(a,t)
708 if transparencies.supported then
709 intransparency = true
710 return f_tr_gs(registertransparancy(nil,a,t,true))
711 else
712 return ""
713 end
714end
715
716function lpdf.finishtransparencycode()
717 if transparencies.supported and intransparency then
718 intransparency = false
719 return f_tr_gs(0)
720 else
721 return ""
722 end
723end
724
725do
726
727 local lpdfprint = lpdf.print
728
729 local c_cache = setmetatableindex(function(t,m)
730
731 local v = setmetatableindex(function(t,c)
732 local p = "q " .. lpdfcolor(m,c)
733 t[c] = p
734 return p
735 end)
736 t[m] = v
737 return v
738 end)
739
740 local t_cache = setmetatableindex(function(t,transparency)
741 local p = lpdftransparency(transparency)
742 local v = setmetatableindex(function(t,colormodel)
743 local v = setmetatableindex(function(t,color)
744 local v = "q " .. lpdfcolor(colormodel,color) .. " " .. p
745 t[color] = v
746 return v
747 end)
748 t[colormodel] = v
749 return v
750 end)
751 t[transparency] = v
752 return v
753 end)
754
755
756
757
758
759 function codeinjections.vfstartcolor(pos_h,pos_v,packet)
760 local color = type(packet) == "table" and packet[2] or packet
761 if color then
762 local m, c = colortoattributes(color)
763 local t = transparencytoattribute(color)
764 if t and t ~= unsetvalue then
765 lpdfprint("page", t_cache[t][m][c])
766 else
767 lpdfprint("page", c_cache[m][c])
768 end
769 else
770 lpdfprint("page", "q")
771 end
772 end
773
774 function codeinjections.vfstopcolor()
775
776 lpdfprint("page", "Q")
777 end
778
779end
780
781
782
783do
784
785 local fonts = { }
786 lpdf.fonts = fonts
787
788 fonts.color_indirect = function(c,t)
789 if c and t then
790 return lpdfcolor(1,c) .. " " .. lpdftransparency(t)
791 elseif c then
792 return lpdfcolor(1,c)
793 elseif t then
794 return lpdftransparency(t)
795 else
796 return false
797 end
798 end
799
800 local colors = attributes.colors
801 local rgbtocmyk = colors.rgbtocmyk
802
803 local f_cmyk = formatters["%.3N %.3N %.3N %.3N k"]
804 local f_rgb = formatters["%.3N %.3N %.3N rg"]
805 local f_gray = formatters["%.3N g"]
806
807 fonts.color_direct = function(r,g,b)
808 local m = colors.model
809 if r == g and g == b then
810 return f_gray(r)
811 elseif m == "cmyk" then
812 return f_cmyk(rgbtocmyk(r,g,b))
813 else
814 return f_rgb(r,g,b)
815 end
816 end
817
818end
819
820 |