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