1if not modules then modules = { } end modules ['lpdf-ini'] = {
2 version = 1.001,
3 optimize = true,
4 comment = "companion to lpdf-ini.mkiv",
5 author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
6 copyright = "PRAGMA ADE / ConTeXt Development Team",
7 license = "see context related readme files"
8}
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62local setmetatable, getmetatable, type, next, tostring, tonumber, rawset = setmetatable, getmetatable, type, next, tostring, tonumber, rawset
63local concat = table.concat
64local char, byte, format, sub, tohex = string.char, string.byte, string.format, string.sub, string.tohex
65local utfchar, utfbyte, utfvalues = utf.char, utf.byte, utf.values
66local sind, cosd, max, min = math.sind, math.cosd, math.max, math.min
67local sort, sortedhash = table.sort, table.sortedhash
68local P, C, R, S, Cc, Cs, V = lpeg.P, lpeg.C, lpeg.R, lpeg.S, lpeg.Cc, lpeg.Cs, lpeg.V
69local lpegmatch, lpegpatterns = lpeg.match, lpeg.patterns
70local formatters = string.formatters
71local isboolean = string.is_boolean
72local hextointeger, octtointeger = string.hextointeger,string.octtointeger
73
74local report_objects = logs.reporter("backend","objects")
75local report_finalizing = logs.reporter("backend","finalizing")
76local report_blocked = logs.reporter("backend","blocked")
77
78local implement = interfaces and interfaces.implement
79local context = context
80
81
82
83
84
85pdf = type(pdf) == "table" and pdf or { }
86local factor = number.dimenfactors.bp
87
88local pdfbackend = backends and backends.registered.pdf or { }
89local codeinjections = pdfbackend.codeinjections
90local nodeinjections = pdfbackend.nodeinjections
91
92lpdf = lpdf or { }
93local lpdf = lpdf
94lpdf.flags = lpdf.flags or { }
95
96table.setmetatableindex(lpdf, function(t,k)
97 report_blocked("function %a is not accessible",k)
98 os.exit()
99end)
100
101local trace_finalizers = false trackers.register("backend.finalizers", function(v) trace_finalizers = v end)
102local trace_resources = false trackers.register("backend.resources", function(v) trace_resources = v end)
103
104
105
106local f_hex_4 = formatters["%04X"]
107local f_hex_2 = formatters["%02X"]
108
109local h_hex_4 = table.setmetatableindex(function(t,k)
110 if k < 0 then
111
112 return "0000"
113 elseif k < 256 then
114
115 for i=0,255 do
116 t[i] = f_hex_4(i)
117 end
118 return t[k]
119 else
120 local v = f_hex_4(k)
121 t[k] = v
122 return v
123 end
124end)
125
126local h_hex_2 = table.setmetatableindex(function(t,k)
127 if type(k) == "string" then
128 local v = f_hex_2(byte(k))
129 t[k] = v
130 return v
131 elseif k < 0 or k > 255 then
132
133 return "00"
134 else
135 local v = f_hex_2(k)
136 t[k] = v
137 return v
138 end
139end)
140
141lpdf.h_hex_2 = h_hex_2
142lpdf.h_hex_4 = h_hex_4
143
144
145do
146
147
148
149
150 local initializers = { }
151
152 function lpdf.registerinitializer(initialize)
153 initializers[#initializers+1] = initialize
154 end
155
156 function lpdf.initialize(f)
157 for i=1,#initializers do
158 initializers[i]()
159 end
160 end
161
162end
163
164local pdfreserveobject
165local pdfimmediateobject
166
167updaters.register("backends.pdf.latebindings",function()
168 pdfreserveobject = lpdf.reserveobject
169 pdfimmediateobject = lpdf.immediateobject
170end)
171
172do
173
174 local pdfgetmatrix, pdfhasmatrix, pdfgetpos
175
176 updaters.register("backends.pdf.latebindings",function()
177 job.positions.registerhandlers {
178 getpos = drivers.getpos,
179 getrpos = drivers.getrpos,
180 gethpos = drivers.gethpos,
181 getvpos = drivers.getvpos,
182 }
183 pdfgetmatrix = lpdf.getmatrix
184 pdfhasmatrix = lpdf.hasmatrix
185 pdfgetpos = drivers.getpos
186 end)
187
188 function lpdf.getpos() return pdfgetpos() end
189
190 local function corners(llx,lly,urx,ury,rx,sx,sy,ry)
191 return
192 llx * rx + lly * sy, llx * sx + lly * ry,
193 urx * rx + lly * sy, urx * sx + lly * ry,
194 urx * rx + ury * sy, urx * sx + ury * ry,
195 llx * rx + ury * sy, llx * sx + ury * ry
196 end
197
198 local function bounds(llx,lly,urx,ury,rx,sx,sy,ry)
199 local x1 = llx * rx + lly * sy
200 local y1 = llx * sx + lly * ry
201 local x2 = urx * rx + lly * sy
202 local y2 = urx * sx + lly * ry
203 local x3 = urx * rx + ury * sy
204 local y3 = urx * sx + ury * ry
205 local x4 = llx * rx + ury * sy
206 local y4 = llx * sx + ury * ry
207 llx = min(x1,x2,x3,x4)
208 lly = min(y1,y2,y3,y4)
209 urx = max(x1,x2,x3,x4)
210 ury = max(y1,y2,y3,y4)
211 return llx, lly, urx, ury
212 end
213
214
215
216
217
218
219
220
221
222
223 function lpdf.rectangle(width,height,depth,offset)
224 local tx, ty = pdfgetpos()
225 if offset then
226 tx = tx - offset
227 ty = ty + offset
228 width = width + 2*offset
229 height = height + offset
230 depth = depth + offset
231 end
232 if pdfhasmatrix() then
233 local rx, sx, sy, ry = pdfgetmatrix()
234 local llx, lly, urx, ury = bounds(0,-depth,width,height,rx,sx,sy,ry)
235 tx = tx + sx * height
236 ty = ty - rx * height + height
237 return
238 factor * (tx + llx),
239 factor * (ty + lly),
240 factor * (tx + urx),
241 factor * (ty + ury)
242 else
243 return
244 factor * tx,
245 factor * (ty - depth),
246 factor * (tx + width),
247 factor * (ty + height)
248 end
249 end
250
251 function lpdf.quads(zero,width,height,depth,offset,noscale)
252 local tx, ty = pdfgetpos()
253 if offset then
254 tx = tx - offset
255 ty = ty + offset
256 width = width + 2*offset
257 height = height + offset
258 depth = depth + offset
259 end
260 if pdfhasmatrix() then
261 local rx, sx, sy, ry = pdfgetmatrix()
262 local x1, y1, x2, y2, x3, y3, x4, y4 = corners(zero,-depth,width,height,rx,sx,sy,ry)
263 tx = tx + sx * height
264 ty = ty - rx * height + height
265 return
266 factor * (tx + x1), factor * (ty + y1),
267 factor * (tx + x2), factor * (ty + y2),
268 factor * (tx + x3), factor * (ty + y3),
269 factor * (tx + x4), factor * (ty + y4),
270 zero
271 else
272 local llx = factor * tx
273 local lly = factor * (ty - depth)
274 local urx = factor * (tx + width)
275 local ury = factor * (ty + height)
276 return llx, lly, urx, lly, urx, ury, llx, ury, 0
277 end
278 end
279
280end
281
282local tosixteen, fromsixteen, topdfdoc, frompdfdoc, toeight, fromeight
283
284do
285
286 local cache = table.setmetatableindex(function(t,k)
287 local v = utfbyte(k)
288 if v < 0x10000 then
289 v = format("%04x",v)
290 else
291 v = v - 0x10000
292 v = format("%04x%04x",(v>>10)+0xD800,v%1024+0xDC00)
293 end
294 t[k] = v
295 return v
296 end)
297
298 local unified = Cs(Cc("<feff") * (lpeg.patterns.utf8character/cache)^1 * Cc(">"))
299
300 tosixteen = function(str)
301 if not str or str == "" then
302 return "<feff>"
303 else
304 return lpegmatch(unified,str)
305 end
306 end
307
308
309
310 local more = 0
311
312 local pattern = C(4) / function(s)
313 local now = hextointeger(s)
314 if more > 0 then
315 now = (more-0xD800)*0x400 + (now-0xDC00) + 0x10000
316 more = 0
317 return utfchar(now)
318 elseif now >= 0xD800 and now <= 0xDBFF then
319 more = now
320 return ""
321 else
322 return utfchar(now)
323 end
324 end
325
326 local pattern = P(true) / function() more = 0 end * Cs(pattern^0)
327
328 fromsixteen = function(str)
329 if not str or str == "" then
330 return ""
331 else
332 return lpegmatch(pattern,str)
333 end
334 end
335
336 local toregime = regimes and regimes.toregime
337 local fromregime = regimes and regimes.fromregime
338 local escaped = Cs(
339 Cc("(")
340 * (
341 S("()\n\r\t\b\f")/"\\%0"
342 + P("\\")/"\\\\"
343 + P(1)
344 )^0
345 * Cc(")")
346 )
347
348 topdfdoc = function(str,default)
349 if not str or str == "" then
350 return ""
351 else
352 return lpegmatch(escaped,toregime("pdfdoc",str,default))
353 end
354 end
355
356 frompdfdoc = function(str)
357 if not str or str == "" then
358 return ""
359 else
360 return fromregime("pdfdoc",str)
361 end
362 end
363
364 if not toregime then topdfdoc = function(s) return s end end
365 if not fromregime then frompdfdoc = function(s) return s end end
366
367 toeight = function(str)
368 if not str or str == "" then
369 return "()"
370 else
371 return lpegmatch(escaped,str)
372 end
373 end
374
375
376
377 local unescape = Cs((
378 P("\\")/"" * (
379 S("()")
380 + S("\n\r")^1 / ""
381 + S("nrtbf") / { n = "\n", r = "\r", t = "\t", b = "\b", f = "\f" }
382 + (lpegpatterns.octdigit * lpegpatterns.octdigit^-2) / function(s) return char(octtointeger(s)) end
383 )
384 + P("\\\\") / "\\"
385 + P(1)
386
387 )^0)
388
389 fromeight = function(str)
390 if not str or str == "" then
391 return ""
392 else
393 return lpegmatch(unescape,str)
394 end
395 end
396
397 lpegpatterns.pdffromeight = unescape
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459 local encodingvalues = pdfe.getencodingvalues()
460
461 encodingvalues = table.swapped(encodingvalues,encodingvalues)
462
463 lpdf.encodingvalues = encodingvalues
464
465 local utf16flags = encodingvalues.utf16be | encodingvalues.utf16le
466 local utf16toutf8 = string.utf16toutf8
467
468 function lpdf.frombytes(s,flags)
469 if not s or s == "" then
470 return ""
471 elseif flags & utf16flags ~= 0 then
472
473
474 return utf16toutf8(s)
475 else
476
477 return s
478 end
479 end
480
481 lpdf.tosixteen = tosixteen
482 lpdf.toeight = toeight
483 lpdf.topdfdoc = topdfdoc
484 lpdf.fromsixteen = fromsixteen
485 lpdf.fromeight = fromeight
486 lpdf.frompdfdoc = frompdfdoc
487
488end
489
490local pdfescaped do
491
492 local replacer = S("\0\t\n\r\f ()[]{}/%%#\\") / {
493 ["\00"]="#00",
494 ["\09"]="#09",
495 ["\10"]="#0a",
496 ["\12"]="#0c",
497 ["\13"]="#0d",
498 [ " " ]="#20",
499 [ "#" ]="#23",
500 [ "%" ]="#25",
501 [ "(" ]="#28",
502 [ ")" ]="#29",
503 [ "/" ]="#2f",
504 [ "[" ]="#5b",
505 [ "\\"]="#5c",
506 [ "]" ]="#5d",
507 [ "{" ]="#7b",
508 [ "}" ]="#7d",
509 } + P(1)
510
511 local p_escaped_1 = Cs(Cc("/") * replacer^0)
512 local p_escaped_2 = Cs( replacer^0)
513
514 pdfescaped = function(str,slash)
515 return lpegmatch(slash and p_escaped_1 or p_escaped_2,str) or str
516 end
517
518 lpdf.escaped = pdfescaped
519
520end
521
522local tostring_a, tostring_d
523
524do
525
526 local f_key_null = formatters["%s null"]
527 local f_key_value = formatters["%s %s"]
528
529
530 local f_key_dictionary = formatters["%s << %s >>"]
531 local f_dictionary = formatters["<< %s >>"]
532
533
534 local f_key_array = formatters["%s [ %s ]"]
535 local f_array = formatters["[ %s ]"]
536 local f_key_number = formatters["%s %N"]
537 local f_tonumber = formatters["%N"]
538
539 tostring_d = function(t,contentonly,key)
540 if next(t) then
541 local r = { }
542 local n = 0
543 local e
544 for k, v in next, t do
545 if k == "__extra__" then
546 e = v
547 elseif k == "__stream__" then
548
549 else
550 n = n + 1
551 r[n] = k
552 end
553 end
554 if n > 1 then
555 sort(r)
556 end
557 for i=1,n do
558 local k = r[i]
559 local v = t[k]
560 local tv = type(v)
561
562
563 k = pdfescaped(k,true)
564
565 if tv == "table" then
566
567
568 if v.__lpdftype__ then
569
570
571
572
573 r[i] = f_key_value(k,tostring(v))
574
575 elseif v[1] then
576 r[i] = f_key_value(k,tostring_a(v))
577 else
578 r[i] = f_key_value(k,tostring_d(v))
579 end
580 elseif tv == "string" then
581 r[i] = f_key_value(k,toeight(v))
582 elseif tv == "number" then
583 r[i] = f_key_number(k,v)
584 else
585 r[i] = f_key_value(k,tostring(v))
586 end
587 end
588 if e then
589 r[n+1] = e
590 end
591 r = concat(r," ")
592 if contentonly then
593 return r
594 elseif key then
595 return f_key_dictionary(pdfescaped(key,true),r)
596 else
597 return f_dictionary(r)
598 end
599 elseif contentonly then
600 return ""
601 else
602 return "<< >>"
603 end
604 end
605
606 tostring_a = function(t,contentonly,key)
607 local tn = #t
608 if tn ~= 0 then
609 local r = { }
610 for k=1,tn do
611 local v = t[k]
612 local tv = type(v)
613
614 if tv == "number" then
615 r[k] = f_tonumber(v)
616 elseif tv == "table" then
617
618
619 if v.__lpdftype__ then
620
621
622
623
624 r[k] = tostring(v)
625
626 elseif v[1] then
627 r[k] = tostring_a(v)
628 else
629 r[k] = tostring_d(v)
630 end
631 elseif tv == "string" then
632 r[k] = toeight(v)
633 else
634 r[k] = tostring(v)
635 end
636 end
637 local e = t.__extra__
638 if e then
639 r[tn+1] = e
640 end
641 r = concat(r," ")
642 if contentonly then
643 return r
644 elseif key then
645 return f_key_array(pdfescaped(key,true),r)
646 else
647 return f_array(r)
648 end
649 elseif contentonly then
650 return ""
651 else
652 return "[ ]"
653 end
654 end
655
656end
657
658local f_tonumber = formatters["%N"]
659
660local tostring_x = function(t) return concat(t," ") end
661local tostring_s = function(t) return toeight(t[1]) end
662local tostring_p = function(t) return topdfdoc(t[1],t[2]) end
663local tostring_u = function(t) return tosixteen(t[1]) end
664
665local tostring_n = function(t) return f_tonumber(t[1]) end
666local tostring_c = function(t) return t[1] end
667local tostring_z = function() return "null" end
668local tostring_t = function() return "true" end
669local tostring_f = function() return "false" end
670local tostring_r = function(t) local n = t[1] return n and n > 0 and (n .. " 0 R") or "null" end
671
672local tostring_v = function(t)
673 local s = t[1]
674 if type(s) == "table" then
675 return concat(s)
676 else
677 return s
678 end
679end
680
681local tostring_l = function(t)
682 local s = t[1]
683 if not s or s == "" then
684 return "()"
685 elseif t[2] then
686 return "<" .. s .. ">"
687 else
688
689 return toeight(s)
690 end
691end
692
693local function value_x(t) return t end
694local function value_s(t) return t[1] end
695local function value_p(t) return t[1] end
696local function value_u(t) return t[1] end
697local function value_n(t) return t[1] end
698local function value_c(t) return sub(t[1],2) end
699local function value_d(t) return tostring_d(t,true) end
700local function value_a(t) return tostring_a(t,true) end
701local function value_z() return nil end
702local function value_t(t) return t.value or true end
703local function value_f(t) return t.value or false end
704local function value_r(t) return t[1] or 0 end
705local function value_v(t) return t[1] end
706local function value_l(t) return t[1] end
707
708local function add_to_d(t,v)
709 local k = type(v)
710 if k == "string" then
711 if t.__extra__ then
712 t.__extra__ = t.__extra__ .. " " .. v
713 else
714 t.__extra__ = v
715 end
716 elseif k == "table" then
717 for kk, vv in next, v do
718 local tkk = rawget(t,kk)
719 if tkk and tostring(tkk) ~= tostring(vv) then
720 report_objects("duplicate key %a with different values",kk)
721 end
722 t[kk] = vv
723 end
724 end
725 return t
726end
727
728local function add_to_a(t,v)
729 local k = type(v)
730 if k == "string" then
731 if t.__extra__ then
732 t.__extra__ = t.__extra__ .. " " .. v
733 else
734 t.__extra__ = v
735 end
736 elseif k == "table" then
737 local n = #t
738 for i=1,#v do
739 n = n + 1
740 t[n] = v[i]
741 end
742 end
743 return t
744end
745
746local function add_x(t,k,v) rawset(t,k,tostring(v)) end
747
748local mt_x = { __index = { __lpdftype__ = "stream" }, __tostring = tostring_x, __call = value_x, __newindex = add_x }
749local mt_d = { __index = { __lpdftype__ = "dictionary" }, __tostring = tostring_d, __call = value_d, __add = add_to_d }
750local mt_a = { __index = { __lpdftype__ = "array" }, __tostring = tostring_a, __call = value_a, __add = add_to_a }
751local mt_u = { __index = { __lpdftype__ = "unicode" }, __tostring = tostring_u, __call = value_u }
752local mt_s = { __index = { __lpdftype__ = "string" }, __tostring = tostring_s, __call = value_s }
753local mt_p = { __index = { __lpdftype__ = "docstring" }, __tostring = tostring_p, __call = value_p }
754local mt_n = { __index = { __lpdftype__ = "number" }, __tostring = tostring_n, __call = value_n }
755local mt_c = { __index = { __lpdftype__ = "constant" }, __tostring = tostring_c, __call = value_c }
756local mt_z = { __index = { __lpdftype__ = "null" }, __tostring = tostring_z, __call = value_z }
757local mt_t = { __index = { __lpdftype__ = "true" }, __tostring = tostring_t, __call = value_t }
758local mt_f = { __index = { __lpdftype__ = "false" }, __tostring = tostring_f, __call = value_f }
759local mt_r = { __index = { __lpdftype__ = "reference" }, __tostring = tostring_r, __call = value_r }
760local mt_v = { __index = { __lpdftype__ = "verbose" }, __tostring = tostring_v, __call = value_v }
761local mt_l = { __index = { __lpdftype__ = "literal" }, __tostring = tostring_l, __call = value_l }
762
763local function pdfstream(t)
764 if t then
765 local tt = type(t)
766 if tt == "table" then
767 for i=1,#t do
768 t[i] = tostring(t[i])
769 end
770 elseif tt == "string" then
771 t = { t }
772 else
773 t = { tostring(t) }
774 end
775 end
776 return setmetatable(t or { },mt_x)
777end
778
779local function pdfdictionary(t)
780 return setmetatable(t or { },mt_d)
781end
782
783local function pdfarray(t)
784 if type(t) == "string" then
785 return setmetatable({ t },mt_a)
786 else
787 return setmetatable(t or { },mt_a)
788 end
789end
790
791local function pdfstring(str,default)
792 return setmetatable({ str or default or "" },mt_s)
793end
794
795local function pdfdocstring(str,default,defaultchar)
796 return setmetatable({ str or default or "", defaultchar or " " },mt_p)
797end
798
799local function pdfunicode(str,default)
800 return setmetatable({ str or default or "" },mt_u)
801end
802
803local function pdfliteral(str,hex)
804 return setmetatable({ str, hex },mt_l)
805end
806
807local pdfnumber, pdfconstant
808
809do
810
811 local cache = { }
812
813 pdfnumber = function(n,default)
814 if not n then
815 n = default
816 end
817 local c = cache[n]
818 if not c then
819 c = setmetatable({ n },mt_n)
820
821 end
822 return c
823 end
824
825 for i=-1,9 do cache[i] = pdfnumber(i) end
826
827 local escaped = lpdf.escaped
828
829 local cache = table.setmetatableindex(function(t,k)
830 local v = setmetatable({ escaped(k,true) }, mt_c)
831 t[k] = v
832 return v
833 end)
834
835 pdfconstant = function(str,default)
836 if not str then
837 str = default or "none"
838 end
839 return cache[str]
840 end
841
842end
843
844local pdfnull, pdfboolean, pdfreference, pdfverbose
845
846do
847
848 local p_null = { } setmetatable(p_null, mt_z)
849 local p_true = { } setmetatable(p_true, mt_t)
850 local p_false = { } setmetatable(p_false,mt_f)
851
852 pdfnull = function()
853 return p_null
854 end
855
856 pdfboolean = function(b,default)
857 if type(b) == "boolean" then
858 return b and p_true or p_false
859 else
860 return default and p_true or p_false
861 end
862 end
863
864
865
866
867
868 local r_zero = setmetatable({ 0 },mt_r)
869
870 pdfreference = function(r)
871 if r and r ~= 0 then
872 return setmetatable({ r },mt_r)
873 else
874 return r_zero
875 end
876 end
877
878 local v_zero = setmetatable({ 0 },mt_v)
879 local v_empty = setmetatable({ "" },mt_v)
880
881 pdfverbose = function(t)
882 if t == 0 then
883 return v_zero
884 elseif t == "" then
885 return v_empty
886 else
887 return setmetatable({ t },mt_v)
888 end
889 end
890
891end
892
893lpdf.stream = pdfstream
894lpdf.dictionary = pdfdictionary
895lpdf.array = pdfarray
896lpdf.docstring = pdfdocstring
897lpdf.string = pdfstring
898lpdf.unicode = pdfunicode
899lpdf.number = pdfnumber
900lpdf.constant = pdfconstant
901lpdf.null = pdfnull
902lpdf.boolean = pdfboolean
903lpdf.reference = pdfreference
904lpdf.verbose = pdfverbose
905lpdf.literal = pdfliteral
906
907
908do
909
910 local options = {
911
912
913 print = 0x0004,
914 modify = 0x0008,
915 extract = 0x0010,
916 add = 0x0020,
917
918
919 fillin = 0x0100,
920 access = 0x0200,
921 assemble = 0x0400,
922 quality = 0x0800,
923
924
925
926
927 }
928
929 lpdf.permissions = options
930
931 function lpdf.topermissions(permissions)
932 if permissions and permissions > 0 then
933 local t = { }
934 for k, v in next, options do
935 if permissions & v ~= 0 then
936 t[k] = true
937 end
938 end
939 return t
940 end
941 end
942
943end
944
945if not callbacks then return lpdf end
946
947
948
949local pagefinalizers = { { }, { }, { } }
950local documentfinalizers = { { }, { }, { } }
951
952local pageresources, pageattributes, pagesattributes
953
954local function resetpageproperties()
955 pageresources = pdfdictionary()
956 pageattributes = pdfdictionary()
957 pagesattributes = pdfdictionary()
958end
959
960function lpdf.getpageproperties()
961 return {
962 pageresources = pageresources,
963 pageattributes = pageattributes,
964 pagesattributes = pagesattributes,
965 }
966end
967
968resetpageproperties()
969
970lpdf.registerinitializer(resetpageproperties)
971
972local function addtopageresources (k,v) pageresources [k] = v end
973local function addtopageattributes (k,v) pageattributes [k] = v end
974local function addtopagesattributes(k,v) pagesattributes[k] = v end
975
976lpdf.addtopageresources = addtopageresources
977lpdf.addtopageattributes = addtopageattributes
978lpdf.addtopagesattributes = addtopagesattributes
979
980local function set(where,what,f,when,comment)
981 if type(when) == "string" then
982 when, comment = 2, when
983 elseif not when then
984 when = 2
985 end
986 local w = where[when]
987 w[#w+1] = { f, comment }
988 if trace_finalizers then
989 report_finalizing("%s set: [%s,%s]",what,when,#w)
990 end
991end
992
993local function run(where,what)
994 if trace_finalizers then
995 report_finalizing("start backend, category %a, n %a",what,#where)
996 end
997 for i=1,#where do
998 local w = where[i]
999 for j=1,#w do
1000 local wj = w[j]
1001 if trace_finalizers then
1002 report_finalizing("%s finalizer: [%s,%s] %s",what,i,j,wj[2] or "")
1003 end
1004 wj[1]()
1005 end
1006 end
1007 if trace_finalizers then
1008 report_finalizing("stop finalizing")
1009 end
1010end
1011
1012local function registerpagefinalizer(f,when,comment)
1013 set(pagefinalizers,"page",f,when,comment)
1014end
1015
1016local function registerdocumentfinalizer(f,when,comment)
1017 set(documentfinalizers,"document",f,when,comment)
1018end
1019
1020lpdf.registerpagefinalizer = registerpagefinalizer
1021lpdf.registerdocumentfinalizer = registerdocumentfinalizer
1022
1023function lpdf.finalizepage(shipout)
1024 if shipout and not environment.initex then
1025
1026 run(pagefinalizers,"page")
1027 resetpageproperties()
1028 end
1029end
1030
1031local finalized = false
1032
1033function lpdf.finalizedocument()
1034 if not environment.initex and not finalized then
1035 run(documentfinalizers,"document")
1036 finalized = true
1037 end
1038end
1039
1040callbacks.register("finish_pdfpage", lpdf.finalizepage)
1041callbacks.register("finish_pdffile", lpdf.finalizedocument)
1042
1043do
1044
1045
1046
1047 local function trace_set(what,key)
1048 if trace_resources then
1049 report_finalizing("setting key %a in %a",key,what)
1050 end
1051 end
1052
1053 local function trace_flush(what)
1054 if trace_resources then
1055 report_finalizing("flushing %a",what)
1056 end
1057 end
1058
1059 lpdf.protectresources = true
1060
1061 local catalog = pdfdictionary { Type = pdfconstant("Catalog") }
1062 local info = pdfdictionary { Type = pdfconstant("Info") }
1063
1064
1065 local function checkcatalog()
1066 if not environment.initex then
1067 trace_flush("catalog")
1068 return true
1069 end
1070 end
1071
1072 local function checkinfo()
1073 if not environment.initex then
1074 trace_flush("info")
1075 if lpdf.majorversion() > 1 then
1076 for k, v in next, info do
1077 if k == "CreationDate" or k == "ModDate" then
1078
1079 else
1080 info[k] = nil
1081 end
1082 end
1083 end
1084 return true
1085 end
1086 end
1087
1088 local function flushcatalog()
1089 if checkcatalog() then
1090 catalog.Type = nil
1091 end
1092 end
1093
1094 local function flushinfo()
1095 if checkinfo() then
1096 info.Type = nil
1097 end
1098 end
1099
1100 local userdata = nil
1101
1102 function lpdf.setuserdata(key,value)
1103 if not userdata then
1104 userdata = pdfdictionary()
1105 end
1106 userdata[key] = pdfunicode(tostring(value))
1107 end
1108
1109 function lpdf.getcatalog()
1110 if checkcatalog() then
1111 catalog.Type = pdfconstant("Catalog")
1112
1113
1114 catalog.Extensions = pdfdictionary {
1115 Type = pdfconstant("Extensions"),
1116 LMTX = pdfdictionary {
1117 BaseVersion = pdfconstant("2.0"),
1118 ExtensionLevel = 1,
1119 }
1120 }
1121 local statistics = pdfdictionary {
1122 FontRegistries = lpdf.noffontregistries(),
1123 }
1124 if next(statistics) then
1125
1126
1127 statistics.Type = pdfconstant("Statistics")
1128 catalog.LMTX_Statistics = pdfreference(pdfimmediateobject(tostring(statistics)))
1129 end
1130
1131 local labels = structures.pages.getlabels()
1132 if labels and next(labels) then
1133 for k, v in next, labels do
1134 if type(v) == "table" then
1135 labels[k] = pdfarray(v)
1136 end
1137 end
1138 labels = pdfdictionary {
1139 Type = pdfconstant("Pages"),
1140 Labels = pdfdictionary(labels),
1141 }
1142 catalog.LMTX_Pages = pdfreference(pdfimmediateobject(tostring(labels)))
1143 end
1144
1145 return pdfreference(pdfimmediateobject(tostring(catalog)))
1146 end
1147 end
1148
1149 function lpdf.getinfo()
1150 if checkinfo() then
1151 return pdfreference(pdfimmediateobject(tostring(info)))
1152 end
1153 end
1154
1155 function lpdf.addtocatalog(k,v)
1156 if not (lpdf.protectresources and catalog[k]) then
1157 trace_set("catalog",k)
1158 catalog[k] = v
1159 end
1160 end
1161
1162 function lpdf.addtoinfo(k,v)
1163 if not (lpdf.protectresources and info[k]) then
1164 trace_set("info",k)
1165 info[k] = v
1166 end
1167 end
1168
1169 local names = pdfdictionary {}
1170
1171 local function flushnames()
1172 if next(names) and not environment.initex then
1173
1174
1175
1176
1177 trace_flush("names")
1178 lpdf.addtocatalog("Names",pdfreference(pdfimmediateobject(tostring(names))))
1179 end
1180 end
1181
1182 function lpdf.addtonames(k,v)
1183 if not (lpdf.protectresources and names[k]) then
1184 trace_set("names", k)
1185 names [k] = v
1186 end
1187 end
1188
1189 local r_extgstates, r_colorspaces, r_patterns, r_shades
1190 local d_extgstates, d_colorspaces, d_patterns, d_shades
1191 local p_extgstates, p_colorspaces, p_patterns, p_shades
1192
1193 lpdf.registerinitializer(function()
1194 r_extgstates = nil ; r_colorspaces = nil ; r_patterns = nil ; r_shades = nil ;
1195 d_extgstates = nil ; d_colorspaces = nil ; d_patterns = nil ; d_shades = nil ;
1196 p_extgstates = nil ; p_colorspaces = nil ; p_patterns = nil ; p_shades = nil ;
1197 end)
1198
1199 local function checkextgstates () if d_extgstates then addtopageresources("ExtGState", p_extgstates ) end end
1200 local function checkcolorspaces() if d_colorspaces then addtopageresources("ColorSpace",p_colorspaces) end end
1201 local function checkpatterns () if d_patterns then addtopageresources("Pattern", p_patterns ) end end
1202 local function checkshades () if d_shades then addtopageresources("Shading", p_shades ) end end
1203
1204 local function flushextgstates () if d_extgstates then trace_flush("extgstates") pdfimmediateobject(r_extgstates, tostring(d_extgstates )) end end
1205 local function flushcolorspaces() if d_colorspaces then trace_flush("colorspaces") pdfimmediateobject(r_colorspaces,tostring(d_colorspaces)) end end
1206 local function flushpatterns () if d_patterns then trace_flush("patterns") pdfimmediateobject(r_patterns, tostring(d_patterns )) end end
1207 local function flushshades () if d_shades then trace_flush("shades") pdfimmediateobject(r_shades, tostring(d_shades )) end end
1208
1209
1210
1211
1212
1213
1214
1215 local pdfgetfontobjectnumber
1216
1217 updaters.register("backends.pdf.latebindings",function()
1218 pdfgetfontobjectnumber = lpdf.getfontobjectnumber
1219 end)
1220
1221 local f_font = formatters["%s%d"]
1222
1223 function lpdf.collectedresources(options)
1224 local ExtGState = d_extgstates and next(d_extgstates ) and p_extgstates
1225 local ColorSpace = d_colorspaces and next(d_colorspaces) and p_colorspaces
1226 local Pattern = d_patterns and next(d_patterns ) and p_patterns
1227 local Shading = d_shades and next(d_shades ) and p_shades
1228 local Font
1229 if options and options.patterns == false then
1230 Pattern = nil
1231 end
1232 local fonts = options and options.fonts
1233 if fonts and next(fonts) then
1234 local prefix = options.fontprefix or "F"
1235 Font = pdfdictionary { }
1236 for k, v in sortedhash(fonts) do
1237 Font[f_font(prefix,v)] = pdfreference(pdfgetfontobjectnumber(k))
1238 end
1239 end
1240 if ExtGState or ColorSpace or Pattern or Shading or Font then
1241 local collected = pdfdictionary {
1242 ExtGState = ExtGState,
1243 ColorSpace = ColorSpace,
1244 Pattern = Pattern,
1245 Shading = Shading,
1246 Font = Font,
1247 }
1248 if options and options.serialize == false then
1249 return collected
1250 else
1251 return collected()
1252 end
1253 elseif options and options.notempty then
1254 return nil
1255 elseif options and options.serialize == false then
1256 return pdfdictionary { }
1257 else
1258 return ""
1259 end
1260 end
1261
1262 function lpdf.adddocumentextgstate (k,v)
1263 if not d_extgstates then
1264 r_extgstates = pdfreserveobject()
1265 d_extgstates = pdfdictionary()
1266 p_extgstates = pdfreference(r_extgstates)
1267 end
1268 d_extgstates[k] = v
1269 end
1270
1271 function lpdf.adddocumentcolorspace(k,v)
1272 if not d_colorspaces then
1273 r_colorspaces = pdfreserveobject()
1274 d_colorspaces = pdfdictionary()
1275 p_colorspaces = pdfreference(r_colorspaces)
1276 end
1277 d_colorspaces[k] = v
1278 end
1279
1280 function lpdf.adddocumentpattern(k,v)
1281 if not d_patterns then
1282 r_patterns = pdfreserveobject()
1283 d_patterns = pdfdictionary()
1284 p_patterns = pdfreference(r_patterns)
1285 end
1286 d_patterns[k] = v
1287 end
1288
1289 function lpdf.adddocumentshade(k,v)
1290 if not d_shades then
1291 r_shades = pdfreserveobject()
1292 d_shades = pdfdictionary()
1293 p_shades = pdfreference(r_shades)
1294 end
1295 d_shades[k] = v
1296 end
1297
1298 registerdocumentfinalizer(flushextgstates,3,"extended graphic states")
1299 registerdocumentfinalizer(flushcolorspaces,3,"color spaces")
1300 registerdocumentfinalizer(flushpatterns,3,"patterns")
1301 registerdocumentfinalizer(flushshades,3,"shades")
1302
1303 registerdocumentfinalizer(flushnames,3,"names")
1304 registerdocumentfinalizer(flushcatalog,3,"catalog")
1305 registerdocumentfinalizer(flushinfo,3,"info")
1306
1307 registerpagefinalizer(checkextgstates,3,"extended graphic states")
1308 registerpagefinalizer(checkcolorspaces,3,"color spaces")
1309 registerpagefinalizer(checkpatterns,3,"patterns")
1310 registerpagefinalizer(checkshades,3,"shades")
1311
1312end
1313
1314
1315
1316function lpdf.rotationcm(a)
1317 local s = sind(a)
1318 local c = cosd(a)
1319 return format("%.6F %.6F %.6F %.6F 0 0 cm",c,s,-s,c)
1320end
1321
1322
1323
1324function lpdf.checkedkey(t,key,variant)
1325 local pn = t and t[key]
1326 if pn ~= nil then
1327 local tn = type(pn)
1328 if tn == variant then
1329 if variant == "string" then
1330 if pn ~= "" then
1331 return pn
1332 end
1333 elseif variant == "table" then
1334 if next(pn) then
1335 return pn
1336 end
1337 else
1338 return pn
1339 end
1340 elseif tn == "string" then
1341 if variant == "number" then
1342 return tonumber(pn)
1343 elseif variant == "boolean" then
1344 return isboolean(pn,nil,true)
1345 end
1346 end
1347 end
1348
1349end
1350
1351function lpdf.checkedvalue(value,variant)
1352 if value ~= nil then
1353 local tv = type(value)
1354 if tv == variant then
1355 if variant == "string" then
1356 if value ~= "" then
1357 return value
1358 end
1359 elseif variant == "table" then
1360 if next(value) then
1361 return value
1362 end
1363 else
1364 return value
1365 end
1366 elseif tv == "string" then
1367 if variant == "number" then
1368 return tonumber(value)
1369 elseif variant == "boolean" then
1370 return isboolean(value,nil,true)
1371 end
1372 end
1373 end
1374end
1375
1376function lpdf.limited(n,min,max,default)
1377 if not n then
1378 return default
1379 else
1380 n = tonumber(n)
1381 if not n then
1382 return default
1383 elseif n > max then
1384 return max
1385 elseif n < min then
1386 return min
1387 else
1388 return n
1389 end
1390 end
1391end
1392
1393
1394
1395
1396
1397
1398
1399
1400if implement then
1401
1402 local f_actual_text_p = formatters["BT /Span << /ActualText <feff%s> >> BDC %s EMC ET"]
1403 local f_actual_text_b = formatters["BT /Span << /ActualText <feff%s> >> BDC"]
1404 local f_actual_text_b_not = formatters["/Span << /ActualText <feff%s> >> BDC"]
1405 local f_actual_text = formatters["/Span <</ActualText %s >> BDC"]
1406
1407 local f_alternative_text = formatters["/Span <</Alt %s >> BDC"]
1408
1409 local s_actual_text_e <const> = "EMC ET"
1410 local s_actual_text_e_not <const> = "EMC"
1411
1412 local context = context
1413 local pdfdirect = nodes.pool.directliteral
1414 local tounicode = fonts.mappings.tounicode
1415
1416
1417
1418 function codeinjections.startactualtext(str)
1419 return f_actual_text(tosixteen(str))
1420 end
1421
1422 function codeinjections.stopactualtext()
1423 return s_actual_text_e_not
1424 end
1425
1426 function codeinjections.startalternativetext(str)
1427 return f_alternative_text(tosixteen(str))
1428 end
1429
1430 function codeinjections.stopalternativetext()
1431 return s_actual_text_e_not
1432 end
1433
1434
1435
1436 function codeinjections.unicodetoactualtext(unicode,pdfcode)
1437 return f_actual_text_p(type(unicode) == "string" and unicode or tounicode(unicode),pdfcode)
1438 end
1439
1440 function codeinjections.startunicodetoactualtext(unicode)
1441 return f_actual_text_b(type(unicode) == "string" and unicode or tounicode(unicode))
1442 end
1443
1444 function codeinjections.stopunicodetoactualtext()
1445 return s_actual_text_e
1446 end
1447
1448 function codeinjections.startunicodetoactualtextdirect(unicode)
1449 return f_actual_text_b_not(type(unicode) == "string" and unicode or tounicode(unicode))
1450 end
1451
1452 function codeinjections.stopunicodetoactualtextdirect()
1453 return s_actual_text_e_not
1454 end
1455
1456 implement {
1457 name = "startactualtext",
1458 arguments = "string",
1459 actions = function(str)
1460 context(pdfdirect(f_actual_text(tosixteen(str))))
1461 end
1462 }
1463
1464 implement {
1465 name = "stopactualtext",
1466 actions = function()
1467 context(pdfdirect("EMC"))
1468 end
1469 }
1470
1471 implement {
1472 name = "startalternativetext",
1473 arguments = "string",
1474 actions = function(str)
1475 context(pdfdirect(f_alternative_text(tosixteen(str))))
1476 end
1477 }
1478
1479 implement {
1480 name = "stopalternativetext",
1481 actions = function()
1482 context(pdfdirect("EMC"))
1483 end
1484 }
1485
1486 local setstate = nodes.nuts.pool.setstate
1487
1488 function nodeinjections.startalternate(str)
1489 return setstate(f_actual_text(tosixteen(str)))
1490 end
1491
1492 function nodeinjections.stopalternate()
1493 return setstate("EMC")
1494 end
1495
1496 implement {
1497 name = "pdfsetuserdata",
1498 arguments = "2 strings",
1499 protected = true,
1500 public = true,
1501 actions = lpdf.setuserdata
1502 }
1503
1504end
1505
1506
1507
1508
1509if implement then
1510
1511 implement { name = "pdfbackendcurrentresources", public = true, untraced = true, actions = { lpdf.collectedresources, context } }
1512 implement { name = "pdfbackendsetcatalog", usage = "value", public = true, protected = true, arguments = "2 arguments", actions = lpdf.addtocatalog }
1513 implement { name = "pdfbackendsetinfo", usage = "value", public = true, protected = true, arguments = "2 arguments", actions = function(a,b,c) lpdf.addtoinfo(a,b,c) end }
1514 implement { name = "pdfbackendsetname", usage = "value", public = true, protected = true, arguments = "2 arguments", actions = lpdf.addtonames }
1515 implement { name = "pdfbackendsetpageattribute", usage = "value", public = true, protected = true, arguments = "2 arguments", actions = lpdf.addtopageattributes }
1516 implement { name = "pdfbackendsetpagesattribute", usage = "value", public = true, protected = true, arguments = "2 arguments", actions = lpdf.addtopagesattributes }
1517 implement { name = "pdfbackendsetpageresource", usage = "value", public = true, protected = true, arguments = "2 arguments", actions = lpdf.addtopageresources }
1518 implement { name = "pdfbackendsetextgstate", usage = "value", public = true, protected = true, arguments = "2 arguments", actions = function(a,b) lpdf.adddocumentextgstate (a,pdfverbose(b)) end }
1519 implement { name = "pdfbackendsetcolorspace", usage = "value", public = true, protected = true, arguments = "2 arguments", actions = function(a,b) lpdf.adddocumentcolorspace(a,pdfverbose(b)) end }
1520 implement { name = "pdfbackendsetpattern", usage = "value", public = true, protected = true, arguments = "2 arguments", actions = function(a,b) lpdf.adddocumentpattern (a,pdfverbose(b)) end }
1521 implement { name = "pdfbackendsetshade", usage = "value", public = true, protected = true, arguments = "2 arguments", actions = function(a,b) lpdf.adddocumentshade (a,pdfverbose(b)) end }
1522
1523end
1524
1525
1526
1527function lpdf.copyconstant(v)
1528 if v ~= nil then
1529 return pdfconstant(v)
1530 end
1531end
1532
1533function lpdf.copyboolean(v)
1534 if v ~= nil then
1535 return pdfboolean(v)
1536 end
1537end
1538
1539function lpdf.copyunicode(v)
1540 if v then
1541 return pdfunicode(v)
1542 end
1543end
1544
1545function lpdf.copyarray(a)
1546 if a then
1547 local t = pdfarray()
1548 for i=1,#a do
1549 t[i] = a(i)
1550 end
1551 return t
1552 end
1553end
1554
1555function lpdf.copydictionary(d)
1556 if d then
1557 local t = pdfdictionary()
1558 for k, v in next, d do
1559 t[k] = d(k)
1560 end
1561 return t
1562 end
1563end
1564
1565function lpdf.copynumber(v)
1566 return v
1567end
1568
1569function lpdf.copyinteger(v)
1570 return v
1571end
1572
1573function lpdf.copyfloat(v)
1574 return v
1575end
1576
1577function lpdf.copystring(v)
1578 if v then
1579 return pdfstring(v)
1580 end
1581end
1582
1583do
1584
1585
1586
1587
1588
1589 local a_procset = nil
1590 local d_procset = nil
1591 local include = false
1592
1593 lpdf.registerinitializer(function()
1594 a_procset = nil
1595 d_procset = nil
1596 end)
1597
1598 function lpdf.setincludeprocset(v)
1599 include = v
1600 end
1601
1602 function lpdf.procset(dict)
1603 if not include or lpdf.majorversion() > 1 then
1604 return nil
1605 else
1606 if not a_procset then
1607 a_procset = pdfarray {
1608 pdfconstant("PDF"),
1609 pdfconstant("Text"),
1610 pdfconstant("ImageB"),
1611 pdfconstant("ImageC"),
1612 pdfconstant("ImageI"),
1613 }
1614 a_procset = pdfreference(pdfimmediateobject(tostring(a_procset)))
1615 end
1616 if dict then
1617 if not d_procset then
1618 d_procset = pdfdictionary {
1619 ProcSet = a_procset
1620 }
1621 d_procset = pdfreference(pdfimmediateobject(tostring(d_procset)))
1622 end
1623 return d_procset
1624 else
1625 return a_procset
1626 end
1627 end
1628 end
1629
1630end
1631 |