1if not modules then modules = { } end modules ['lpdf-lmt'] = {
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
36local type, next, unpack, tonumber, rawget = type, next, unpack, tonumber, rawget
37local char, rep, find, gsub = string.char, string.rep, string.find, string.gsub
38local formatters = string.formatters
39local concat, sortedhash = table.concat, table.sortedhash
40local setmetatableindex = table.setmetatableindex
41local loaddata = io.loaddata
42
43local bpfactor <const> = number.dimenfactors.bp
44
45local osuuid = os.uuid
46local zlibcompresssize = xzip.compresssize
47
48local nuts = nodes.nuts
49
50local pdfreference = lpdf.reference
51local pdfdictionary = lpdf.dictionary
52local pdfarray = lpdf.array
53local pdfconstant = lpdf.constant
54local pdfliteral = lpdf.literal
55local pdfunicode = lpdf.unicode
56
57local pdfreserveobject
58local pdfpagereference
59local pdfgetpagereference
60local pdfsharedobject
61local pdfflushobject
62local pdfflushstreamobject
63local pdfdeferredobject
64local pdfimmediateobject
65
66local pdfincludeimage
67
68local pdf_pages = pdfconstant("Pages")
69local pdf_page = pdfconstant("Page")
70local pdf_xobject = pdfconstant("XObject")
71local pdf_form = pdfconstant("Form")
72local pdf_pattern = pdfconstant("Pattern")
73
74local fonthashes = fonts.hashes
75local characters = fonthashes.characters
76local descriptions = fonthashes.descriptions
77local parameters = fonthashes.parameters
78local properties = fonthashes.properties
79
80local report = logs.reporter("backend")
81local report_objects = logs.reporter("backend","objects")
82local report_fonts = logs.reporter("backend","fonts")
83local report_encryption = logs.reporter("backend","encryption")
84
85local trace_objects = false trackers.register("backend.objects", function(v) trace_objects = v end)
86local trace_details = false trackers.register("backend.details", function(v) trace_details = v end)
87local trace_indices = false trackers.register("backend.fonts.details", function(v) trace_indices = v end)
88
89
90
91local usedfontnames = { }
92local usedfontobjects = { }
93
94lpdf.usedfontnames = usedfontnames
95lpdf.usedfontobjects = usedfontobjects
96
97
98
99local function compressdata(data,size)
100 local guess = ((size // 4096) + 1) * 2048
101 local comp = zlibcompresssize(data,guess,3)
102
103
104
105
106
107 return comp
108end
109
110
111
112
113
114
115
116local flushers = { }
117
118
119
120local pdf_h = 0
121local pdf_v = 0
122
123local need_tm
124local need_tf
125local need_font
126
127local need_width
128local need_mode
129local done_width
130local done_mode
131local mode
132local current_pdf
133
134local current_font
135local current_effect
136local current_slant
137local current_weight
138local current_sx
139local current_sy
140local current_factor
141local f_x_scale
142local f_y_scale
143local tj_scale
144local tj_delta
145local tj_position
146
147local tmrx, tmry, tmsy, tmtx, tmty
148
149local cmrx, cmry, cmtx, cmty
150local tmef
151local tmef_f_x_scale
152local tmef_f_w_scale
153
154local usedfonts, usedxforms, usedximages, usedxgroups
155local getxformname, getximagename
156local boundingbox, shippingmode, objectnumber
157
158local function usefont(t,k)
159
160 local v = usedfontnames[k]
161 t[k] = v
162 return v
163end
164
165local function reset_variables(specification)
166 pdf_h, pdf_v = 0, 0
167 cmrx, cmry = 1.0, 1.0
168
169 cmtx, cmty = 0.0, 0.0
170 tmrx, tmry = 1.0, 1.0
171
172 tmsy = 0.0
173 tmtx, tmty = 0.0, 0.0
174 tmef = 1.0
175 need_tm = false
176 need_tf = false
177 need_font = true
178 need_width = 0
179 need_mode = 0
180 done_width = false
181 done_mode = false
182 mode = "page"
183 shippingmode = specification.shippingmode
184 objectnumber = specification.objectnumber
185
186
187 tj_scale = 1
188 tj_delta = 0.0
189 tj_position = 0.0
190 current_font = 0
191 current_pdf = 0
192 current_effect = nil
193 current_slant = 0
194 current_weight = 0
195 current_factor = 0
196 current_sx = 1
197 current_sy = 1
198 f_x_scale = 1.0
199 f_y_scale = 1.0
200 tmef_f_x_scale = 1
201 tmef_f_w_scale = 1
202 usedfonts = setmetatableindex(usefont)
203 usedxforms = { }
204 usedximages = { }
205
206 boundingbox = specification.boundingbox
207end
208
209
210
211local buffer = lua.newtable(1024,0)
212local b = 0
213
214local function reset_buffer()
215 b = 0
216end
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263local fontcharacters
264
265local fontparameters
266local fontproperties
267local pdfcharacters
268
269local getstreamhash = fonts.handlers.otf.getstreamhash
270
271local usedfontstreams = utilities.storage.allocate()
272
273local usedindices = setmetatableindex(function(t,k)
274 local n = 0
275
276 local v = setmetatableindex(function(tt,kk)
277 if n >= 0xFFFF then
278 report_fonts("registering character index: overflow in hash %a, todo: use overflow font")
279 else
280 n = n + 1
281 end
282 if trace_indices then
283 report_fonts("registering character index: hash %a, charindex 0x%05X, slotindex 0x%04X",k,kk,n)
284 end
285 local vv = n
286 tt[kk] = vv
287 return vv
288 end)
289 t[k] = v
290 return v
291end)
292
293local usedcharacters = setmetatableindex(function(t,k)
294 local h, d = getstreamhash(k)
295 if trace_indices then
296 report_fonts("registering index table: hash %a, fontid %i",h,k)
297 end
298 usedfontstreams[h] = d
299 local v = usedindices[h]
300 t[k] = v
301 return v
302end)
303
304lpdf.usedfontstreams = usedfontstreams
305lpdf.usedcharacters = usedcharacters
306lpdf.usedindices = usedindices
307
308local horizontalmode = true
309local scalefactor = 1
310local threshold = 655360
311local tjfactor = 100 / 65536
312
313function flushers.updatefontstate(font)
314
315 fontcharacters = characters[font]
316
317 fontparameters = parameters[font]
318 fontproperties = properties[font]
319 local size = fontparameters.size
320 local designsize = fontparameters.designsize or size
321 pdfcharacters = usedcharacters[font]
322 horizontalmode = fontparameters.writingmode ~= "vertical"
323 scalefactor = (designsize/size) * tjfactor
324end
325
326local f_cm = formatters["%.6N %.6N %.6N %.6N %.6N %.6N cm"]
327local f_cz = formatters["%.6N 0 0 %.6N %.6N %.6N cm"]
328
329local f_tm = formatters["%.6N 0 %.6N %.6N %.6N %.6N Tm"]
330
331directives.register("backend.pdf.accurate", function()
332 f_cm = formatters["%.9N %.9N %.9N %.9N %.9N %.9N cm"]
333 f_cz = formatters["%.9N 0 0 %.9N %.9N %.9N cm"]
334
335 f_tm = formatters["%.9N 0 %.9N %.9N %.9N %.9N Tm"]
336end)
337
338local saved_text_pos_v = 0
339local saved_text_pos_h = 0
340
341local function begin_text()
342 saved_text_pos_h = pdf_h
343 saved_text_pos_v = pdf_v
344 b = b + 1 ; buffer[b] = "BT"
345 need_tf = true
346 need_font = true
347 need_width = 0
348 need_mode = 0
349 current_effect = nil
350 current_slant = 0
351 current_weight = 0
352 mode = "text"
353end
354
355local function end_text()
356 if done_width then
357 b = b + 1 ; buffer[b] = "0 w"
358 done_width = false
359 end
360 if done_mode then
361 b = b + 1 ; buffer[b] = "0 Tr"
362 done_mode = false
363 end
364 b = b + 1 ; buffer[b] = "ET"
365 pdf_h = saved_text_pos_h
366 pdf_v = saved_text_pos_v
367 mode = "page"
368end
369
370local begin_chararray, end_chararray do
371
372 local saved_chararray_pos_h
373 local saved_chararray_pos_v
374
375 local saved_b = 0
376
377 begin_chararray = function()
378 saved_chararray_pos_h = pdf_h
379 saved_chararray_pos_v = pdf_v
380 tj_position = horizontalmode and saved_chararray_pos_h or - saved_chararray_pos_v
381 tj_delta = 0
382 saved_b = b
383 b = b + 1 ; buffer[b] = " ["
384 mode = "chararray"
385 end
386
387 end_chararray = function()
388 b = b + 1 ; buffer[b] = "] TJ"
389 buffer[saved_b] = concat(buffer,"",saved_b,b)
390 b = saved_b
391 pdf_h = saved_chararray_pos_h
392 pdf_v = saved_chararray_pos_v
393 mode = "text"
394 end
395
396end
397
398local function begin_charmode()
399 b = b + 1 ; buffer[b] = "<"
400 mode = "char"
401end
402
403local function end_charmode()
404 b = b + 1 ; buffer[b] = ">"
405 mode = "chararray"
406end
407
408local function calc_pdfpos(h,v)
409
410 if mode == "page" then
411 cmtx = h - pdf_h
412 cmty = v - pdf_v
413 return h ~= pdf_h or v ~= pdf_v
414 elseif mode == "text" then
415 tmtx = h - saved_text_pos_h
416 tmty = v - saved_text_pos_v
417 return h ~= pdf_h or v ~= pdf_v
418 elseif horizontalmode then
419 tmty = v - saved_text_pos_v
420 tj_delta = tj_position - h
421 return tj_delta ~= 0 or v ~= pdf_v
422 else
423 tmtx = h - saved_text_pos_h
424 tj_delta = tj_position + v
425 return tj_delta ~= 0 or h ~= pdf_h
426 end
427end
428
429local function pdf_set_pos(h,v)
430 local move = calc_pdfpos(h,v)
431 if move then
432
433 b = b + 1 ; buffer[b] = f_cz(cmrx, cmry, cmtx*bpfactor, cmty*bpfactor)
434 pdf_h = pdf_h + cmtx
435 pdf_v = pdf_v + cmty
436 end
437end
438
439local function pdf_reset_pos()
440 if mode == "page" then
441 cmtx = - pdf_h
442 cmty = - pdf_v
443 if pdf_h == 0 and pdf_v == 0 then
444 return
445 end
446 elseif mode == "text" then
447 tmtx = - saved_text_pos_h
448 tmty = - saved_text_pos_v
449 if pdf_h == 0 and pdf_v == 0 then
450 return
451 end
452 elseif horizontalmode then
453 tmty = - saved_text_pos_v
454 tj_delta = tj_position
455 if tj_delta == 0 and pdf_v == 0 then
456 return
457 end
458 else
459 tmtx = - saved_text_pos_h
460 tj_delta = tj_position
461 if tj_delta == 0 and pdf_h == 0 then
462 return
463 end
464 end
465
466 b = b + 1 ; buffer[b] = f_cz(cmrx, cmry, cmtx*bpfactor, cmty*bpfactor)
467 pdf_h = pdf_h + cmtx
468 pdf_v = pdf_v + cmty
469end
470
471local function pdf_set_pos_temp(h,v)
472 local move = calc_pdfpos(h,v)
473 if move then
474
475 b = b + 1 ; buffer[b] = f_cz(cmrx, cmry, cmtx*bpfactor, cmty*bpfactor)
476 end
477end
478
479
480
481local function pdf_end_string_nl()
482 if mode == "char" then
483 end_charmode()
484 return end_chararray()
485 elseif mode == "chararray" then
486 return end_chararray()
487 end
488end
489
490local function pdf_goto_textmode()
491 if mode == "page" then
492 pdf_reset_pos()
493 return begin_text()
494 elseif mode ~= "text" then
495 if mode == "char" then
496 end_charmode()
497 return end_chararray()
498 else
499 return end_chararray()
500 end
501 end
502end
503
504local function pdf_goto_pagemode()
505 if mode ~= "page" then
506 if mode == "char" then
507 end_charmode()
508 end_chararray()
509 return end_text()
510 elseif mode == "chararray" then
511 end_chararray()
512 return end_text()
513 elseif mode == "text" then
514 return end_text()
515 end
516 end
517end
518
519local function pdf_goto_fontmode()
520 if mode == "char" then
521 end_charmode()
522 end_chararray()
523 end_text()
524 elseif mode == "chararray" then
525 end_chararray()
526 end_text()
527 elseif mode == "text" then
528 end_text()
529 end
530 pdf_reset_pos()
531 mode = "page"
532end
533
534
535
536
537
538
539
540
541
542do
543
544 local round = math.round
545
546
547
548
549
550 local characterwidth = nil
551
552 local hshift = false
553 local vshift = false
554
555 local dupx = 100 / bpfactor
556 local dumx = - dupx
557 local dupy = dupx
558 local dumy = dumx
559
560 directives.register("backend.pdf.drift", function(v)
561 dupx = tonumber(v) or 100
562 if dupx < 10 then
563 dupx = 10
564 elseif dupx > 100 then
565 dupx = 100
566 end
567 dupx = dupx / bpfactor
568 end)
569
570 local h_hex_2 = lpdf.h_hex_2
571 local h_hex_4 = lpdf.h_hex_4
572
573
574
575
576
577
578
579 local characterwidths = setmetatableindex(function(t,font)
580 local d = descriptions[font]
581 local c = characters[font]
582 local f = parameters[font].hfactor or parameters[font].factor
583 local v = setmetatableindex(function(t,char)
584 local w
585 local e = c and c[char]
586 if e then
587 local a = e.advance
588 if a then
589 w = a
590 else
591 w = e.width or 0
592 end
593 end
594 if not w then
595 e = d and d[char]
596 if e then
597 w = e.width
598 if w then
599 w = w * f
600 end
601 end
602 end
603 if not w then
604 w = 0
605 end
606 t[char] = w
607 return w
608 end)
609 t[font] = v
610 return v
611 end)
612
613
614
615 local extend = 1
616 local squeeze = 1
617 local tohex = h_hex_4
618
619 local function setup_fontparameters(font,factor,sx,sy,slant,weight,effect)
620
621 current_sx = sx
622 current_sy = sy
623
624 if fontproperties.bitmapped then
625 tohex = h_hex_2
626 elseif font < 0 then
627 tohex = h_hex_2
628 else
629 tohex = h_hex_4
630 end
631
632 local format = fontproperties.format
633 local expand = 1 + factor / 1000000
634 if effect then
635
636
637 if effect ~= current_effect then
638 current_effect = effect
639 tmrx = 1
640 tmry = 1
641 tmsy = effect.slant or fontparameters.slantfactor or slant or 0
642 extend = effect.extend or fontparameters.extendfactor or 1
643 squeeze = effect.squeeze or fontparameters.squeezefactor or 1
644 need_mode = effect.mode or fontparameters.mode or 0
645 need_width = effect.weight or fontparameters.weight or 0
646 sx = extend * sx
647 sy = squeeze * sy
648 else
649
650
651tmrx = 1
652tmry = 1
653
654 end
655 else
656
657
658 current_effect = nil
659 tmrx = 1
660 tmry = 1
661
662 local e = fontproperties.effect
663 if e then
664 tmsy = e.slant or slant or 0
665 extend = e.extend or 1
666 squeeze = e.squeeze or 1
667 need_mode = e.mode or 0
668 need_width = e.weight or 0
669 sx = extend * sx
670 sy = squeeze * sy
671 else
672 tmsy = slant or 0
673 extend = 1
674 squeeze = 1
675 need_mode = 0
676 need_width = weight
677 end
678 end
679
680 tmef = expand
681 tmrx = expand * tmrx
682
683 current_font = font
684 current_pdf = usedfonts[font]
685 current_factor = factor
686 current_slant = tmsy
687 current_weight = weight
688
689 local sc = fontparameters.size * bpfactor / 10
690
691 if format == "opentype" or format == "type1" then
692 sc = sc * 1000 / fontparameters.units
693 tj_scale = fontparameters.units / 1000
694 else
695 tj_scale = 1
696 end
697 tj_delta = 0
698 tj_position = 0
699
700 tmsy = tmsy * sc
701 tmrx = tmrx * sc
702 tmry = tmry * sc
703
704 f_x_scale = sx
705 if f_x_scale ~= 1.0 then
706 tmrx = tmrx * f_x_scale
707 end
708 f_y_scale = sy
709 if f_y_scale ~= 1.0 then
710 tmsy = tmsy * f_y_scale
711 tmry = tmry * f_y_scale
712 end
713
714 tmef_f_x_scale = tmef * f_x_scale
715
716 if tmef_f_x_scale == 1.0 then
717 tmef_f_x_scale = 1
718 end
719
720 tj_scale = tj_scale * scalefactor / tmef_f_x_scale
721
722 tmef_f_w_scale = tmef_f_x_scale / extend
723
724 characterwidth = characterwidths[font]
725
726
727 hshift = fontparameters.hshift
728 vshift = fontparameters.vshift
729
730 if need_width > 0 then
731 if not need_mode or need_mode == 0 then
732 need_mode = 2
733 end
734 need_width = need_width * 2
735 need_width = need_width * tmrx
736 end
737
738 end
739
740 local f_width = formatters["%.6N w"]
741 local f_mode = formatters["%i Tr"]
742 local f_font = formatters["/F%i 10 Tf"]
743 local f_skip = formatters["%.2N"]
744
745 local s_width = "0 w"
746 local s_mode = "0 Tr"
747
748 local width_factor = 72.27 / 72.0
749
750 local last_fpdf
751
752 local function set_font()
753
754 if need_width ~= 0 then
755 b = b + 1 ; buffer[b] = f_width(width_factor*need_width)
756 done_width = true
757 elseif done_width then
758 b = b + 1 ; buffer[b] = s_width
759 done_width = false
760 end
761
762 if need_mode ~= 0 then
763 b = b + 1 ; buffer[b] = f_mode(need_mode)
764 done_mode = true
765 elseif done_mode then
766 b = b + 1 ; buffer[b] = s_mode
767 done_mode = false
768 end
769
770 if need_font or last_pdf ~= current_pdf then
771 b = b + 1 ; buffer[b] = f_font(current_pdf)
772 last_pdf = current_pdf
773 need_font = false
774 end
775
776 need_tf = false
777 need_tm = true
778 end
779
780 local function set_textmatrix(h,v)
781 local move = calc_pdfpos(h,v)
782 if need_tm or move then
783
784 b = b + 1 ; buffer[b] = f_tm(tmrx, tmsy, tmry, tmtx*bpfactor, tmty*bpfactor)
785 pdf_h = saved_text_pos_h + tmtx
786 pdf_v = saved_text_pos_v + tmty
787 need_tm = false
788 end
789
790 end
791
792
793
794
795
796
797
798 flushers.character = function(current,pos_h,pos_v,pos_r,font,char,data,csx,csy,factor,sx,sy,slant,weight)
799
800 local s = data.scale
801 local x = data.xoffset
802 local y = data.yoffset
803 local effect = data.effect
804
805 if s then
806 sx = s * sx
807 sy = s * sy
808 end
809 if csx then
810 sx = sx * csx
811 csx = 1
812 end
813 if csy then
814 sy = sy * csy
815 csy = 1
816 end
817 if sx ~= current_sx
818 or sy ~= current_sy
819 or need_tf
820 or font ~= current_font
821
822 or mode == "page"
823 or slant ~= current_slant
824 or weight ~= current_weight
825 or effect ~= current_effect
826 then
827 pdf_goto_textmode()
828 setup_fontparameters(font,factor,sx,sy,slant,weight,effect)
829 set_font()
830
831
832
833 elseif current_factor ~= factor then
834
835 setup_fontparameters(font,factor,sx,sy,slant,weight,effect)
836 need_tm = true
837 end
838
839 if x then
840 pos_h = pos_h + x * tmef_f_x_scale
841 end
842 if y then
843 pos_v = pos_v + y * f_y_scale
844 end
845
846 local move = calc_pdfpos(pos_h,pos_v)
847
848 if move or need_tm then
849 if not need_tm then
850 if horizontalmode then
851 if (saved_text_pos_v + tmty) ~= pdf_v then
852 need_tm = true
853 elseif tj_delta >= dupx or tj_delta <= dumx then
854 need_tm = true
855 end
856 else
857 if (saved_text_pos_h + tmtx) ~= pdf_h then
858 need_tm = true
859 elseif tj_delta >= dupy or tj_delta <= dumy then
860 need_tm = true
861 end
862 end
863 end
864 if hshift then pos_h = pos_h + hshift end
865 if vshift then pos_v = pos_v - vshift end
866 if need_tm then
867 pdf_goto_textmode()
868 set_textmatrix(pos_h,pos_v)
869 begin_chararray()
870 move = calc_pdfpos(pos_h,pos_v)
871 end
872 if move then
873 local d = tj_delta * tj_scale
874 if d <= -0.5 or d >= 0.5 then
875 if mode == "char" then
876 end_charmode()
877 end
878
879
880
881 b = b + 1 ; buffer[b] = f_skip(d)
882 end
883 tj_position = tj_position - tj_delta
884
885 end
886 end
887
888 if mode == "chararray" then
889 begin_charmode()
890 end
891
892
893
894
895 tj_position = tj_position + characterwidth[char] * tmef_f_w_scale
896
897 local slot = pdfcharacters[data.index or char]
898
899
900 b = b + 1 ; buffer[b] = tohex[slot]
901
902 end
903
904 do
905
906
907
908 local spaces = setmetatableindex(function(t,font)
909 local bytes = false
910 local data = characters[font]
911 if data then
912 data = data[32]
913 if data then
914 local index = data.index
915 if index then
916 local dummy = usedfonts[font]
917 local slot = usedcharacters[font][index]
918 if slot then
919
920 bytes = tohex[slot]
921 end
922 end
923 end
924 end
925 t[font] = bytes
926 return bytes
927 end)
928
929
930
931 flushers.space = function(font)
932 if mode == "char" and font == current_font then
933 local slot = spaces[font]
934 if slot then
935 tj_position = tj_position + characterwidth[32] * tmef_f_w_scale
936 b = b + 1 ; buffer[b] = slot
937 end
938 end
939 end
940
941 end
942
943 flushers.fontchar = function(font,char,data)
944 local dummy = usedfonts[font]
945 local slot = pdfcharacters[data.index or char]
946 return dummy, slot
947 end
948
949end
950
951
952
953local flushliteral do
954
955 local nodeproperties = nodes.properties.data
956 local literalvalues = nodes.literalvalues
957
958 local originliteral_code <const> = literalvalues.origin
959 local pageliteral_code <const> = literalvalues.page
960 local alwaysliteral_code <const> = literalvalues.always
961 local rawliteral_code <const> = literalvalues.raw
962 local textliteral_code <const> = literalvalues.text
963 local fontliteral_code <const> = literalvalues.font
964
965 flushliteral = function(current,pos_h,pos_v)
966 local p = nodeproperties[current]
967 if p then
968 local str = p.data
969 if str and str ~= "" then
970 local mode = p.mode
971 if mode == originliteral_code then
972 pdf_goto_pagemode()
973 pdf_set_pos(pos_h,pos_v)
974 elseif mode == pageliteral_code then
975 pdf_goto_pagemode()
976 elseif mode == textliteral_code then
977 pdf_goto_textmode()
978 elseif mode == fontliteral_code then
979 pdf_goto_fontmode()
980 elseif mode == alwaysliteral_code then
981 pdf_end_string_nl()
982 need_tm = true
983 elseif mode == rawliteral_code then
984 pdf_end_string_nl()
985 else
986 report("invalid literal mode %a when flushing %a",mode,str)
987 return
988 end
989 b = b + 1 ; buffer[b] = str
990 end
991 end
992 end
993
994 flushers.literal = flushliteral
995
996 function lpdf.print(mode,str)
997
998
999 if str then
1000 mode = literalvalues[mode]
1001 else
1002 mode, str = originliteral_code, mode
1003 end
1004 if str and str ~= "" then
1005 if mode == originliteral_code then
1006 pdf_goto_pagemode()
1007
1008 elseif mode == pageliteral_code then
1009 pdf_goto_pagemode()
1010 elseif mode == textliteral_code then
1011 pdf_goto_textmode()
1012 elseif mode == fontliteral_code then
1013 pdf_goto_fontmode()
1014 elseif mode == alwaysliteral_code then
1015 pdf_end_string_nl()
1016 need_tm = true
1017 elseif mode == rawliteral_code then
1018 pdf_end_string_nl()
1019 else
1020 report("invalid literal mode %a when flushing %a",mode,str)
1021 return
1022 end
1023 b = b + 1 ; buffer[b] = str
1024 end
1025 end
1026
1027end
1028
1029
1030
1031do
1032
1033 local matrices = { }
1034 local positions = { }
1035 local nofpositions = 0
1036 local nofmatrices = 0
1037
1038 local flushsave = function(current,pos_h,pos_v)
1039 nofpositions = nofpositions + 1
1040 positions[nofpositions] = { pos_h, pos_v, nofmatrices }
1041 pdf_goto_pagemode()
1042 pdf_set_pos(pos_h,pos_v)
1043 b = b + 1 ; buffer[b] = "q"
1044 end
1045
1046 local flushrestore = function(current,pos_h,pos_v)
1047 if nofpositions < 1 then
1048 return
1049 end
1050 local t = positions[nofpositions]
1051
1052
1053 if shippingmode == "page" then
1054 nofmatrices = t[3]
1055 end
1056 pdf_goto_pagemode()
1057 pdf_set_pos(pos_h,pos_v)
1058 b = b + 1 ; buffer[b] = "Q"
1059 nofpositions = nofpositions - 1
1060 end
1061
1062 local nodeproperties = nodes.properties.data
1063
1064 local s_matrix_0 <const> = "1 0 0 1 0 0 cm"
1065 local f_matrix_2 = formatters["%.6N 0 0 %.6N 0 0 cm"]
1066 local f_matrix_4 = formatters["%.6N %.6N %.6N %.6N 0 0 cm"]
1067
1068 local flushsetmatrix = function(current,pos_h,pos_v)
1069 local p = nodeproperties[current]
1070 if p then
1071 local m = p.matrix
1072 if m then
1073 local rx, sx, sy, ry = unpack(m)
1074 local s
1075 if not rx then
1076 rx = 1
1077 elseif rx == 0 then
1078 rx = 0.0001
1079 end
1080 if not ry then
1081 ry = 1
1082 elseif ry == 0 then
1083 ry = 0.0001
1084 end
1085 if not sx then
1086 sx = 0
1087 end
1088 if not sy then
1089 sy = 0
1090 end
1091
1092 if sx == 0 and sy == 0 then
1093 if rx == 1 and ry == 1 then
1094 s = s_matrix_0
1095 else
1096 s = f_matrix_2(rx,ry)
1097 end
1098 else
1099 s = f_matrix_4(rx,sx,sy,ry)
1100 end
1101
1102 if shippingmode == "page" then
1103 local tx = pos_h * (1 - rx) - pos_v * sy
1104 local ty = pos_v * (1 - ry) - pos_h * sx
1105 if nofmatrices > 0 then
1106 local t = matrices[nofmatrices]
1107 local r_x, s_x, s_y, r_y, te, tf = t[1], t[2], t[3], t[4], t[5], t[6]
1108 rx, sx = rx * r_x + sx * s_y, rx * s_x + sx * r_y
1109 sy, ry = sy * r_x + ry * s_y, sy * s_x + ry * r_y
1110 tx, ty = tx * r_x + ty * s_y, tx * s_x + ty * r_y
1111 end
1112 nofmatrices = nofmatrices + 1
1113 matrices[nofmatrices] = { rx, sx, sy, ry, tx, ty }
1114 end
1115
1116 pdf_goto_pagemode()
1117 pdf_set_pos(pos_h,pos_v)
1118
1119 b = b + 1
1120 buffer[b] = s
1121 end
1122 end
1123 end
1124
1125 flushers.setmatrix = flushsetmatrix
1126 flushers.save = flushsave
1127 flushers.restore = flushrestore
1128
1129 function lpdf.hasmatrix()
1130 return nofmatrices > 0
1131 end
1132
1133 function lpdf.getmatrix()
1134 if nofmatrices > 0 then
1135 return unpack(matrices[nofmatrices])
1136 else
1137 return 1, 0, 0, 1, 0, 0
1138 end
1139 end
1140
1141 flushers.pushorientation = function(orientation,pos_h,pos_v,pos_r)
1142 pdf_goto_pagemode()
1143 pdf_set_pos(pos_h,pos_v)
1144 b = b + 1 ; buffer[b] = "q"
1145 if orientation == 1 then
1146 b = b + 1 ; buffer[b] = "0 -1 1 0 0 0 cm"
1147 elseif orientation == 2 then
1148 b = b + 1 ; buffer[b] = "-1 0 0 -1 0 0 cm"
1149 elseif orientation == 3 then
1150 b = b + 1 ; buffer[b] = "0 1 -1 0 0 0 cm"
1151 end
1152 end
1153
1154 flushers.poporientation = function(orientation,pos_h,pos_v,pos_r)
1155 pdf_goto_pagemode()
1156 pdf_set_pos(pos_h,pos_v)
1157 b = b + 1 ; buffer[b] = "Q"
1158 end
1159
1160
1161
1162 flushers.startmatrix = function(current,pos_h,pos_v)
1163 flushsave(current,pos_h,pos_v)
1164 flushsetmatrix(current,pos_h,pos_v)
1165 end
1166
1167 flushers.stopmatrix = function(current,pos_h,pos_v)
1168 flushrestore(current,pos_h,pos_v)
1169 end
1170
1171 flushers.startscaling = function(current,pos_h,pos_v)
1172 flushsave(current,pos_h,pos_v)
1173 flushsetmatrix(current,pos_h,pos_v)
1174 end
1175
1176 flushers.stopscaling = function(current,pos_h,pos_v)
1177 flushrestore(current,pos_h,pos_v)
1178 end
1179
1180 flushers.startrotation = function(current,pos_h,pos_v)
1181 flushsave(current,pos_h,pos_v)
1182 flushsetmatrix(current,pos_h,pos_v)
1183 end
1184
1185 flushers.stoprotation = function(current,pos_h,pos_v)
1186 flushrestore(current,pos_h,pos_v)
1187 end
1188
1189 flushers.startmirroring = function(current,pos_h,pos_v)
1190 flushsave(current,pos_h,pos_v)
1191 flushsetmatrix(current,pos_h,pos_v)
1192 end
1193
1194 flushers.stopmirroring = function(current,pos_h,pos_v)
1195 flushrestore(current,pos_h,pos_v)
1196 end
1197
1198 flushers.startclipping = function(current,pos_h,pos_v)
1199 flushsave(current,pos_h,pos_v)
1200
1201 pdf_goto_pagemode()
1202 b = b + 1 ; buffer[b] = formatters["0 w %s W n"](nodeproperties[current].path)
1203 end
1204
1205 flushers.stopclipping = function(current,pos_h,pos_v)
1206 flushrestore(current,pos_h,pos_v)
1207 end
1208
1209end
1210
1211do
1212
1213 local nodeproperties = nodes.properties.data
1214
1215 flushers.setstate = function(current,pos_h,pos_v)
1216 local p = nodeproperties[current]
1217 if p then
1218 local d = p.data
1219 if d and d ~= "" then
1220 pdf_goto_pagemode()
1221 b = b + 1 ; buffer[b] = d
1222 end
1223 end
1224 end
1225
1226end
1227
1228
1229
1230local flushedxforms = { }
1231local localconverter = nil
1232
1233do
1234
1235 local tonut = nodes.tonut
1236 local tonode = nuts.tonode
1237
1238 local pdfbackend = backends.registered.pdf
1239 local nodeinjections = pdfbackend.nodeinjections
1240 local codeinjections = pdfbackend.codeinjections
1241
1242 local newimagerule = nuts.pool.imagerule
1243 local newboxrule = nuts.pool.boxrule
1244
1245 local setprop = nuts.setprop
1246 local getprop = nuts.getprop
1247 local setattrlist = nuts.setattrlist
1248 local getoptions = nuts.getoptions
1249
1250 local getwhd = nuts.getwhd
1251 local flushlist = nuts.flushlist
1252 local getdata = nuts.getdata
1253
1254 local rulecodes = nodes.rulecodes
1255 local normalrule_code <const> = rulecodes.normal
1256 local boxrule_code <const> = rulecodes.box
1257 local imagerule_code <const> = rulecodes.image
1258 local emptyrule_code <const> = rulecodes.empty
1259 local userrule_code <const> = rulecodes.user
1260 local overrule_code <const> = rulecodes.over
1261 local underrule_code <const> = rulecodes.under
1262 local fractionrule_code <const> = rulecodes.fraction
1263 local radicalrule_code <const> = rulecodes.radical
1264 local outlinerule_code <const> = rulecodes.outline
1265
1266
1267 local vertical_rule <const> = tex.ruleoptioncodes.vertical
1268 local horizontal_rule <const> = tex.ruleoptioncodes.horizontal
1269
1270 local processrule = nodes.rules.process
1271
1272 local f_fm = formatters["/Fm%d Do"]
1273 local f_im = formatters["/Im%d Do"]
1274 local f_gr = formatters["/Gp%d Do"]
1275 local f_st = formatters["/Gs%d gs"]
1276
1277 local s_b <const> = "q"
1278 local s_e <const> = "Q"
1279
1280 local f_v = formatters["[] 0 d 0 J %.6N w 0 0 m %.6N 0 l S"]
1281 local f_h = formatters["[] 0 d 0 J %.6N w 0 0 m 0 %.6N l S"]
1282
1283 local f_f = formatters["0 0 %.6N %.6N re f"]
1284 local f_o = formatters["[] 0 d 0 J 0 0 %.6N %.6N re S"]
1285 local f_w = formatters["[] 0 d 0 J %.6N w 0 0 %.6N %.6N re S"]
1286
1287 local f_b = formatters["%.6N w 0 %.6N %.6N %.6N re f"]
1288 local f_x = formatters["[] 0 d 0 J %.6N w %.6N %.6N %.6N %.6N re S"]
1289 local f_y = formatters["[] 0 d 0 J %.6N w %.6N %.6N %.6N %.6N re S %.6N 0 m %.6N 0 l S"]
1290
1291
1292
1293
1294 local f_d_h = formatters["[%.6N %.6N] 0 d 0 J %.6N w 0 %.6N m %.6N %.6N l S"]
1295 local f_d_v = formatters["[%.6N %.6N] 0 d 0 J %.6N w %.6N 0 m %.6N %.6N l S"]
1296
1297
1298
1299 local boxresources, n = { }, 0
1300
1301 getxformname = function(index)
1302 local l = boxresources[index]
1303 if l then
1304 return l.name
1305 else
1306 report("no box resource %S",index)
1307 end
1308 end
1309
1310 lpdf.getxformname = getxformname
1311
1312 local pdfcollectedresources = lpdf.collectedresources
1313
1314 function codeinjections.saveboxresource(box,attributes,resources,immediate,kind,margin,onum)
1315 n = n + 1
1316 local immediate = true
1317 local margin = margin or 0
1318 local objnum = onum or pdfreserveobject()
1319 local list = tonut(type(box) == "number" and tex.takebox(box) or box)
1320
1321 if resources == true then
1322 resources = pdfcollectedresources {
1323 serialize = false,
1324 }
1325 end
1326
1327 local width, height, depth = getwhd(list)
1328
1329 local l = {
1330 width = width,
1331 height = height,
1332 depth = depth,
1333 margin = margin,
1334 attributes = attributes,
1335 resources = resources,
1336 list = nil,
1337 type = kind,
1338 name = n,
1339 index = objnum,
1340 objnum = objnum,
1341 }
1342 local r = boxresources[objnum]
1343 if r then
1344 flushlist(l.list)
1345 l.list = nil
1346 end
1347 boxresources[objnum] = l
1348 if immediate then
1349 localconverter(list,"xform",objnum,l)
1350 flushedxforms[objnum] = { true , objnum }
1351 flushlist(list)
1352 else
1353 l.list = list
1354 end
1355 return objnum
1356 end
1357
1358 function nodeinjections.useboxresource(index,wd,ht,dp)
1359 local l = boxresources[index]
1360 if l then
1361 if wd or ht or dp then
1362 wd, ht, dp = wd or 0, ht or 0, dp or 0
1363 else
1364 wd, ht, dp = l.width, l.height, l.depth
1365 end
1366 local rule = newboxrule(wd,ht,dp)
1367 setattrlist(rule,true)
1368 setprop(rule,"index",index)
1369 return tonode(rule), wd, ht, dp
1370 else
1371 report("no box resource %S",index)
1372 end
1373 end
1374
1375 local function getboxresourcedimensions(index)
1376 local l = boxresources[index]
1377 if l then
1378 return l.width, l.height, l.depth, l.margin
1379 else
1380 report("no box resource %S",index)
1381 end
1382 end
1383
1384 nodeinjections.getboxresourcedimensions = getboxresourcedimensions
1385
1386 function codeinjections.getboxresourcebox(index)
1387 local l = boxresources[index]
1388 if l then
1389 return l.list
1390 end
1391 end
1392
1393
1394
1395
1396 local function flushpdfxform(current,pos_h,pos_v,pos_r,size_h,size_v)
1397
1398 local objnum = getprop(current,"index")
1399 local name = getxformname(objnum)
1400 local info = flushedxforms[objnum]
1401 local r = boxresources[objnum]
1402 if not info then
1403 info = { false , objnum }
1404 flushedxforms[objnum] = info
1405 end
1406 local wd, ht, dp = getboxresourcedimensions(objnum)
1407
1408
1409 local htdp = ht + dp
1410 if wd == 0 or size_h == 0 or htdp == 0 or size_v == 0 then
1411 return
1412 end
1413
1414 local rx, ry = 1, 1
1415 if wd ~= size_h or htdp ~= size_v then
1416 rx = size_h / wd
1417 ry = size_v / htdp
1418 end
1419
1420 usedxforms[objnum] = true
1421 pdf_goto_pagemode()
1422 calc_pdfpos(pos_h,pos_v)
1423 local tx = cmtx * bpfactor
1424 local ty = cmty * bpfactor
1425 b = b + 1 ; buffer[b] = s_b
1426
1427 b = b + 1 ; buffer[b] = f_cz(rx, ry,tx,ty)
1428 b = b + 1 ; buffer[b] = f_fm(name)
1429 b = b + 1 ; buffer[b] = s_e
1430 end
1431
1432
1433
1434 local imagetypes = images.types
1435 local img_none = imagetypes.none
1436 local img_pdf = imagetypes.pdf
1437 local img_stream = imagetypes.stream
1438
1439 local one_bp <const> = 65536 * bpfactor
1440
1441 local imageresources, n = { }, 0
1442
1443 getximagename = function(index)
1444 local l = imageresources[index]
1445 if l then
1446 return l.name
1447 else
1448 report("no image resource %S",index)
1449 end
1450 end
1451
1452
1453
1454
1455
1456 usedxgroups = { }
1457
1458 local flushgroup do
1459
1460 local groups = 0
1461 local group = nil
1462 local states = 0
1463 local names = { }
1464
1465 flushgroup = function(content,bbox,detail)
1466 if not group then
1467 group = pdfdictionary {
1468 Type = pdfconstant("Group"),
1469 S = pdfconstant("Transparency"),
1470 }
1471 end
1472 local wrapper = pdfdictionary {
1473 Type = pdf_xobject,
1474 Subtype = pdf_form,
1475 FormType = 1,
1476 Group = group,
1477 BBox = pdfarray(bbox),
1478 Resources = lpdf.collectedresources {
1479 serialize = false
1480 },
1481 }
1482 local objnum = pdfflushstreamobject(content,wrapper,false)
1483 groups = groups + 1
1484 usedxgroups[groups] = objnum
1485 local kind = detail and detail.type == "luminosity"
1486 local result = f_gr(groups)
1487 if not detail then
1488
1489 elseif detail.type == "luminosity" then
1490 local action = detail.action
1491 if action == "register" then
1492 local state = pdfdictionary {
1493 Type = pdfconstant("ExtGState"),
1494 AIS = false,
1495 CA = 1,
1496 ca = 1,
1497 SMask = pdfdictionary {
1498 G = pdfreference(objnum),
1499 S = pdfconstant("Luminosity"),
1500 Type = pdfconstant("Mask"),
1501 }
1502 }
1503 states = states + 1
1504 lpdf.adddocumentextgstate("Gs" .. states,pdfreference(pdfflushobject(state)))
1505 names[detail.name or "default"] = f_st(states)
1506 return
1507 elseif action == "apply" then
1508 local name = detail.name or "default"
1509 local delay = names[name]
1510 if delay then
1511 result = delay .. " " .. result
1512 names[name] = nil
1513 end
1514 else
1515
1516 end
1517 end
1518 return result
1519 end
1520
1521 end
1522
1523 flushers.group = flushgroup
1524 lpdf.flushgroup = flushgroup
1525
1526
1527
1528 local function flushpdfximage(current,pos_h,pos_v,pos_r,size_h,size_v)
1529
1530 local width,
1531 height,
1532 depth = getwhd(current)
1533 local total = height + depth
1534 local transform = getprop(current,"transform") or 0
1535 local index = getprop(current,"index") or 0
1536 local kind,
1537 xorigin,
1538 yorigin,
1539 xsize,
1540 ysize,
1541 rotation,
1542 objnum,
1543 groupref = pdfincludeimage(index)
1544
1545 if not kind then
1546 report("invalid image %S",index)
1547 return
1548 end
1549
1550 local rx, sx, sy, ry, tx, ty = 1, 0, 0, 1, 0, 0
1551
1552
1553
1554 if kind == img_pdf or kind == img_stream then
1555 rx, ry, tx, ty = 1/xsize, 1/ysize, xorigin/xsize, yorigin/ysize
1556 else
1557
1558
1559
1560
1561
1562 rx, ry = bpfactor, bpfactor
1563 end
1564
1565 if (transform & 7) > 3 then
1566
1567 rx, tx = -rx, -tx
1568 end
1569 local t = (transform + rotation) & 3
1570 if t == 0 then
1571
1572 elseif t == 1 then
1573
1574 rx, sx, sy, ry, tx, ty = 0, rx, -ry, 0, -ty, tx
1575 elseif t == 2 then
1576
1577 rx, ry, tx, ty = -rx, -ry, -tx, -ty
1578 elseif t == 3 then
1579
1580 rx, sx, sy, ry, tx, ty = 0, -rx, ry, 0, ty, -tx
1581 end
1582
1583 rx = rx * width
1584 sx = sx * total
1585 sy = sy * width
1586 ry = ry * total
1587 tx = pos_h - tx * width
1588 ty = pos_v - ty * total
1589
1590 local t = transform + rotation
1591
1592 if (transform & 7) > 3 then
1593 t = t + 1
1594 end
1595
1596 t = t & 3
1597
1598 if t == 0 then
1599
1600 elseif t == 1 then
1601
1602 tx = tx + width
1603 elseif t == 2 then
1604
1605 tx = tx + width
1606 ty = ty + total
1607 elseif t == 3 then
1608
1609 ty = ty + total
1610 end
1611
1612
1613
1614
1615
1616
1617
1618 usedximages[index] = objnum
1619
1620 pdf_goto_pagemode()
1621
1622 calc_pdfpos(tx,ty)
1623
1624 tx = cmtx * bpfactor
1625 ty = cmty * bpfactor
1626
1627 b = b + 1 ; buffer[b] = s_b
1628 b = b + 1 ; buffer[b] = f_cm(rx,sx,sy,ry,tx,ty)
1629 b = b + 1 ; buffer[b] = f_im(index)
1630 b = b + 1 ; buffer[b] = s_e
1631 end
1632
1633 flushers.flushimage = function(index,width,height,depth,pos_h,pos_v)
1634
1635
1636
1637 local total = height + depth
1638 local kind,
1639 xorigin, yorigin,
1640 xsize, ysize,
1641 rotation,
1642 objnum,
1643 groupref = pdfincludeimage(index)
1644
1645 local rx = width / xsize
1646 local sx = 0
1647 local sy = 0
1648 local ry = total / ysize
1649 local tx = pos_h
1650
1651
1652 local ty = pos_v
1653 usedximages[index] = objnum
1654
1655 pdf_goto_pagemode()
1656
1657 calc_pdfpos(tx,ty)
1658
1659 tx = cmtx * bpfactor
1660 ty = cmty * bpfactor
1661
1662 b = b + 1 ; buffer[b] = s_b
1663 b = b + 1 ; buffer[b] = f_cm(rx,sx,sy,ry,tx,ty)
1664 b = b + 1 ; buffer[b] = f_im(index)
1665 b = b + 1 ; buffer[b] = s_e
1666 end
1667
1668
1669
1670
1671
1672
1673
1674
1675 local function onoff(on,off,dim)
1676 on = on * bpfactor
1677 off = off * bpfactor
1678 if on + off > dim then
1679 return dim, 0
1680 else
1681 local n = ((dim -on) // (on + off)) + 1
1682 if n > 1 then
1683 off = (dim - n * on)/(n - 1)
1684 end
1685 return on, off
1686 end
1687 end
1688
1689 flushers.rule = function(current,pos_h,pos_v,pos_r,size_h,size_v,subtype,on,off,running)
1690 if subtype == emptyrule_code then
1691 return
1692 elseif subtype == boxrule_code then
1693 return flushpdfxform(current,pos_h,pos_v,pos_r,size_h,size_v)
1694 elseif subtype == imagerule_code then
1695 return flushpdfximage(current,pos_h,pos_v,pos_r,size_h,size_v)
1696 elseif subtype == userrule_code or (subtype >= overrule_code and subtype <= radicalrule_code) then
1697 pdf_goto_pagemode()
1698 b = b + 1 ; buffer[b] = s_b
1699 pdf_set_pos_temp(pos_h,pos_v)
1700 processrule(current,size_h,size_v,pos_r)
1701 b = b + 1 ; buffer[b] = s_e
1702 return
1703 end
1704
1705 pdf_goto_pagemode()
1706
1707 b = b + 1 ; buffer[b] = s_b
1708
1709 local dim_h = size_h * bpfactor
1710 local dim_v = size_v * bpfactor
1711 local rule
1712
1713
1714
1715
1716 if subtype == outlinerule_code then
1717 local linewidth = getdata(current)
1718 pdf_set_pos_temp(pos_h,pos_v)
1719 if linewidth > 0 then
1720 rule = f_w(linewidth * bpfactor,dim_h,dim_v)
1721 else
1722 rule = f_o(dim_h,dim_v)
1723 end
1724 elseif on ~= 0 and off ~= 0 then
1725 local options = getoptions(current)
1726 local horizontal = (options & horizontal_rule) ~= 0
1727 local vertical = (options & vertical_rule ) ~= 0
1728 if not horizontal and not vertical then
1729 horizontal = true
1730 elseif not running then
1731 horizontal = not horizontal
1732 end
1733 if horizontal then
1734 local offset = dim_v/2
1735 on, off = onoff(on,off,dim_h)
1736 pdf_set_pos_temp(pos_h,pos_v)
1737 rule = f_d_h(on,off,dim_v,offset,dim_h,offset)
1738 else
1739 local offset = dim_h/2
1740 on, off = onoff(on,off,dim_v)
1741 pdf_set_pos_temp(pos_h,pos_v)
1742 rule = f_d_v(on,off,dim_h,offset,offset,dim_v)
1743 end
1744 elseif dim_v <= one_bp then
1745 pdf_set_pos_temp(pos_h,pos_v + 0.5 * size_v)
1746 rule = f_v(dim_v,dim_h)
1747 elseif dim_h <= one_bp then
1748 pdf_set_pos_temp(pos_h + 0.5 * size_h,pos_v)
1749 rule = f_h(dim_h,dim_v)
1750 else
1751 pdf_set_pos_temp(pos_h,pos_v)
1752 rule = f_f(dim_h,dim_v)
1753 end
1754
1755 b = b + 1 ; buffer[b] = rule
1756 b = b + 1 ; buffer[b] = s_e
1757
1758 end
1759
1760 flushers.simplerule = function(pos_h,pos_v,pos_r,size_h,size_v)
1761 pdf_goto_pagemode()
1762
1763 b = b + 1 ; buffer[b] = s_b
1764
1765 local dim_h = size_h * bpfactor
1766 local dim_v = size_v * bpfactor
1767 local rule
1768
1769 if dim_v <= one_bp then
1770 pdf_set_pos_temp(pos_h,pos_v + 0.5 * size_v)
1771 rule = f_v(dim_v,dim_h)
1772 elseif dim_h <= one_bp then
1773 pdf_set_pos_temp(pos_h + 0.5 * size_h,pos_v)
1774 rule = f_h(dim_h,dim_v)
1775 else
1776 pdf_set_pos_temp(pos_h,pos_v)
1777 rule = f_f(dim_h,dim_v)
1778 end
1779
1780 b = b + 1 ; buffer[b] = rule
1781 b = b + 1 ; buffer[b] = s_e
1782 end
1783
1784 flushers.specialrule = function(pos_h,pos_v,pos_r,width,height,depth,line,outline,baseline)
1785 pdf_goto_pagemode()
1786
1787 b = b + 1 ; buffer[b] = s_b
1788
1789 local width = bpfactor * width
1790 local height = bpfactor * height
1791 local depth = bpfactor * depth
1792 local total = height + depth
1793 local line = bpfactor * line
1794 local half = line / 2
1795 local rule
1796
1797 if outline then
1798 local d = -depth + half
1799 local w = width - line
1800 local t = total - line
1801 if baseline and w > 0 then
1802 rule = f_y(line,half,d,w,t,half,w)
1803 else
1804 rule = f_x(line,half,d,w,t)
1805 end
1806 else
1807 rule = f_b(line,-depth,width,total)
1808 end
1809 pdf_set_pos_temp(pos_h,pos_v)
1810
1811 b = b + 1 ; buffer[b] = rule
1812 b = b + 1 ; buffer[b] = s_e
1813 end
1814
1815end
1816
1817
1818
1819local wrapupdocument, registerpage do
1820
1821 local pages = { }
1822 local maxkids = 10
1823 local nofpages = 0
1824 local pagetag = "unset"
1825
1826 registerpage = function(object)
1827 nofpages = nofpages + 1
1828 local objnum = pdfpagereference(nofpages)
1829 pages[nofpages] = {
1830 page = nofpages,
1831 objnum = objnum,
1832 object = object,
1833 tag = pagetag,
1834 }
1835 end
1836
1837 function lpdf.setpagetag(tag)
1838 pagetag = tag or "unset"
1839 end
1840
1841 function lpdf.getnofpages()
1842 return nofpages
1843 end
1844
1845 function lpdf.getpagetags()
1846 local list = { }
1847 for i=1,nofpages do
1848 list[i] = pages[i].tag
1849 end
1850 return list
1851 end
1852
1853 function lpdf.setpageorder(mapping,p)
1854
1855 local list = table.sortedkeys(mapping)
1856 local n = #list
1857 local nop = p or nofpages
1858 if n == nop then
1859 local done = { }
1860 local hash = { }
1861 for i=1,n do
1862 local order = mapping[list[i]]
1863 if hash[order] then
1864 report("invalid page order, duplicate entry %i",order)
1865 return
1866 elseif order < 1 or order > nofpages then
1867 report("invalid page order, no page %i",order)
1868 return
1869 else
1870 done[i] = pages[order]
1871 hash[order] = true
1872 end
1873 end
1874 pages = done
1875 else
1876 report("invalid page order, %i entries expected",nop)
1877 end
1878 end
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895 wrapupdocument = function(driver)
1896
1897
1898 local pagetree = { }
1899 local parent = nil
1900 local minimum = 0
1901 local maximum = 0
1902 local current = 0
1903 if #pages > 1.5 * maxkids then
1904 repeat
1905 local plist, pnode
1906 if current == 0 then
1907 plist, minimum = pages, 1
1908 elseif current == 1 then
1909 plist, minimum = pagetree, 1
1910 else
1911 plist, minimum = pagetree, maximum + 1
1912 end
1913 maximum = #plist
1914 if maximum > minimum then
1915 local kids
1916 for i=minimum,maximum do
1917 local p = plist[i]
1918 if not pnode or #kids == maxkids then
1919 kids = pdfarray()
1920 parent = pdfreserveobject()
1921 pnode = pdfdictionary {
1922 objnum = parent,
1923 Type = pdf_pages,
1924 Kids = kids,
1925 Count = 0,
1926 }
1927 pagetree[#pagetree+1] = pnode
1928 end
1929 kids[#kids+1] = pdfreference(p.objnum)
1930 pnode.Count = pnode.Count + (p.Count or 1)
1931 p.Parent = pdfreference(parent)
1932 end
1933 end
1934 current = current + 1
1935 until maximum == minimum
1936
1937 for i=1,#pagetree do
1938 local entry = pagetree[i]
1939 local objnum = entry.objnum
1940 entry.objnum = nil
1941 pdfflushobject(objnum,entry)
1942 end
1943 else
1944
1945 local kids = pdfarray()
1946 local list = pdfdictionary {
1947 Type = pdf_pages,
1948 Kids = kids,
1949 Count = nofpages,
1950 }
1951 parent = pdfreserveobject()
1952 for i=1,nofpages do
1953 local page = pages[i]
1954 kids[#kids+1] = pdfreference(page.objnum)
1955 page.Parent = pdfreference(parent)
1956 end
1957 pdfflushobject(parent,list)
1958 end
1959 for i=1,nofpages do
1960 local page = pages[i]
1961 local object = page.object
1962 object.Parent = page.Parent
1963 pdfflushobject(page.objnum,object)
1964 end
1965 lpdf.addtocatalog("Pages",pdfreference(parent))
1966 end
1967
1968end
1969
1970local function initialize(driver,details)
1971 reset_variables(details)
1972 reset_buffer()
1973end
1974
1975
1976
1977
1978
1979
1980local compact = false
1981local encryptstream = false
1982local encryptobject = false
1983local encdict = nil
1984local majorversion = 1
1985local minorversion = 7
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995do
1996
1997
1998
1999 local byte, sub, bytes, tohex, tobytes = string.byte, string.sub, string.bytes, string.tohex, string.tobytes
2000 local P, S, V, Cs, lpegmatch, patterns = lpeg.P, lpeg.S, lpeg.V, lpeg.Cs, lpeg.match, lpeg.patterns
2001
2002 local digest256 = sha2.digest256
2003 local digest384 = sha2.digest384
2004 local digest512 = sha2.digest512
2005
2006 local aesencode = aes.encode
2007 local aesdecode = aes.decode
2008 local aesrandom = aes.random
2009
2010
2011
2012 local function validpassword(str)
2013 return #str > 127 and sub(str,1,127) or str
2014 end
2015
2016 local encryptionkey = false
2017 local objectparser = false
2018
2019 do
2020
2021 local function ps_encrypt(str)
2022
2023 str = aesencode(str,encryptionkey,true,true,true)
2024 return "<" .. tohex(str) .. ">"
2025 end
2026
2027 local function hex_encrypt(str)
2028
2029 str = tobytes(str)
2030 str = aesencode(str,encryptionkey,true,true,true)
2031 return "<" .. tohex(str) .. ">"
2032 end
2033
2034 local whitespace = S("\000\009\010\012\013\032")^1
2035 local anything = patterns.anything
2036 local space = patterns.space
2037 local spacing = whitespace^0
2038 local newline = patterns.eol
2039 local cardinal = patterns.cardinal
2040
2041 local p_psstring = (
2042 P("(")
2043 * Cs(P { ( P("\\")/"" * anything + P("(") * V(1) * P(")") + (1 - P(")")) )^0 })
2044 * P(")")
2045 ) / ps_encrypt
2046
2047 local p_hexstring = (
2048 P("<")
2049 * Cs((1-P(">"))^1)
2050 * P(">")
2051 ) / hex_encrypt
2052
2053 local p_comment = P("%") * (1-newline)^1 * newline^1
2054 local p_name = P("/") * (1 - whitespace - S("<>/[]()"))^1
2055 local p_number = patterns.number
2056 local p_boolean = P("true") + P("false")
2057 local p_null = P("null")
2058 local p_reference = cardinal * spacing * cardinal * spacing * P("R")
2059
2060 local p_other = p_name + p_reference + p_psstring + p_hexstring + p_number
2061 + p_boolean + p_null + p_comment
2062
2063 local p_dictionary = { "dictionary",
2064 dictionary = (
2065 P("<<")
2066 * (spacing * p_name * spacing * V("whatever"))^0
2067 * spacing
2068 * P(">>")
2069 ),
2070 array = (
2071 P("[")
2072 * (spacing * V("whatever"))^0
2073 * spacing
2074 * P("]")
2075 ),
2076 whatever = (
2077 V("dictionary")
2078 + V("array")
2079 + p_other
2080 ),
2081 }
2082
2083 local p_object = P { "object",
2084 dictionary = p_dictionary.dictionary,
2085 array = p_dictionary.array,
2086 whatever = p_dictionary.whatever,
2087 object = spacing * (V("dictionary") + V("array") + p_other)
2088 }
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100 objectparser = Cs(p_object^1)
2101
2102 end
2103
2104 local function makehash(password,salt,userkey)
2105 local k = digest256(password .. salt .. (userkey or ""))
2106 local n = 0
2107 while true do
2108 local k1 = rep(password .. k .. (userkey or ""),64)
2109 local k2 = sub(k,1,16)
2110 local iv = sub(k,17,32)
2111 local e = aesencode(k1,k2,iv)
2112 local m = 0
2113 local i = 1
2114 for b in bytes(e) do
2115 m = m + b
2116 if i == 16 then
2117 break
2118 else
2119 i = i + 1
2120 end
2121 end
2122 m = m % 3
2123 if m == 0 then
2124 k = digest256(e)
2125 elseif m == 1 then
2126 k = digest384(e)
2127 else
2128 k = digest512(e)
2129 end
2130 n = n + 1
2131 if n >= 64 and byte(sub(e,-1)) <= (n - 32) then
2132 break
2133 end
2134 end
2135 return sub(k,1,32)
2136 end
2137
2138 local options = lpdf.permissions
2139
2140
2141
2142 local mandate = 0x0200
2143 local defaults = options.print | options.extract | options.quality
2144
2145
2146
2147
2148 function lpdf.setencryption(specification)
2149 if not encryptstream then
2150 local ownerpassword = specification.ownerpassword
2151 local userpassword = specification.userpassword
2152 local optionlist = specification.permissions
2153 if type(ownerpassword) == "string" and ownerpassword ~= "" then
2154
2155 if type(userpassword) ~= "string" then
2156 userpassword = ""
2157 end
2158 userpassword = validpassword(userpassword)
2159 ownerpassword = validpassword(ownerpassword)
2160
2161 encryptionkey = aesrandom(32)
2162
2163 local permissions = mandate
2164 if optionlist then
2165 optionlist = utilities.parsers.settings_to_array(optionlist)
2166 for i=1,#optionlist do
2167 local p = options[optionlist[i]]
2168 if p then
2169 permissions = permissions | p
2170 end
2171 end
2172 else
2173 permissions = permissions | defaults
2174 end
2175
2176 permissions = permissions | 0xF0C3
2177
2178 optionlist = { }
2179 for k, v in sortedhash(options) do
2180 if permissions & v == v then
2181 optionlist[#optionlist+1] = k
2182 end
2183 end
2184
2185 local uservalidationsalt = aesrandom(8)
2186 local userkeysalt = aesrandom(8)
2187 local userhash = makehash(userpassword,uservalidationsalt)
2188 local userkey = userhash .. uservalidationsalt .. userkeysalt
2189 local userintermediate = makehash(userpassword,userkeysalt)
2190 local useraes = aesencode(encryptionkey,userintermediate)
2191
2192 local ownervalidationsalt = aesrandom(8)
2193 local ownerkeysalt = aesrandom(8)
2194 local ownerhash = makehash(ownerpassword,ownervalidationsalt,userkey)
2195 local ownerkey = ownerhash .. ownervalidationsalt .. ownerkeysalt
2196 local ownerintermediate = makehash(ownerpassword,ownerkeysalt,userkey)
2197 local owneraes = aesencode(encryptionkey,ownerintermediate)
2198
2199
2200
2201 local permissionsstring = sio.tocardinal4(0xFFFFFFFF)
2202 .. sio.tocardinal4(permissions)
2203 .. "T"
2204 .. "adb"
2205 .. aesrandom(4)
2206 local permissionsaes = aesencode(permissionsstring,encryptionkey)
2207
2208 permissionsaes = tohex(permissionsaes)
2209 userkey = tohex(userkey)
2210 ownerkey = tohex(ownerkey)
2211 useraes = tohex(useraes)
2212 owneraes = tohex(owneraes)
2213
2214 encdict = pdfdictionary {
2215 Filter = pdfconstant("Standard"),
2216 V = 5,
2217 R = 6,
2218 Length = 256,
2219 StmF = pdfconstant("StdCF"),
2220 StrF = pdfconstant("StdCF"),
2221 P = permissions,
2222 Perms = pdfliteral(permissionsaes,true),
2223 U = pdfliteral(userkey, true),
2224 O = pdfliteral(ownerkey, true),
2225 UE = pdfliteral(useraes, true),
2226 OE = pdfliteral(owneraes, true),
2227 CF = {
2228 StdCF = {
2229 AuthEvent = pdfconstant("DocOpen"),
2230 CFM = pdfconstant("AESV3"),
2231 Length = 32,
2232 }
2233 },
2234
2235 EncryptMetadata = true,
2236 }
2237
2238 encryptstream = function(str)
2239 return aesencode(str,encryptionkey,true,true,true)
2240 end
2241 encryptobject = function(obj)
2242 if obj then
2243 if type(obj) == "table" then
2244
2245
2246
2247 obj = obj()
2248 end
2249 return lpegmatch(objectparser,obj) or obj
2250 end
2251 end
2252
2253 report_encryption("stream objects get encrypted")
2254 if not objectstream then
2255 report_encryption("strings are not encrypted, enable object streams")
2256 end
2257 report_encryption("permissions: % t",optionlist)
2258 if userpassword == "" then
2259 report_encryption("no user password")
2260 end
2261
2262 end
2263 end
2264 end
2265
2266 backends.registered.pdf.codeinjections.setencryption = lpdf.setencryption
2267
2268end
2269
2270do
2271
2272
2273
2274
2275
2276
2277 local P, R, S, Cs, lpegmatch = lpeg.P, lpeg.R, lpeg.S, lpeg.Cs, lpeg.match
2278
2279 local p_ds = (R("09") + S(" ."))^1
2280
2281 local p_nl = S("\n")^1
2282 local p_eg = P("Q")
2283
2284 local p_cl = p_ds * (P("rg") + P("g") + P("k")) * p_ds * (P("RG") + P("G") + P("K"))
2285
2286 local p_tr = P("/Tr") * p_ds * P("gs")
2287
2288 local p_no_cl = (p_cl * p_nl) / ""
2289 local p_no_tr = (p_tr * p_nl) / ""
2290 local p_no_nl = 1 - p_nl
2291
2292 local p_do_cl = p_cl * p_nl
2293 local p_do_tr = p_tr * p_nl
2294
2295 local p_do_eg = p_eg * p_nl
2296
2297 local pattern = Cs( (
2298 (p_no_cl + p_no_tr)^0 * p_do_eg
2299 + p_no_tr * p_no_cl * p_do_tr * p_do_cl
2300 + p_no_cl * p_do_cl
2301 + p_no_tr * p_do_tr
2302 + p_no_nl^1
2303 + 1
2304 )^1 )
2305
2306 local oldsize = 0
2307 local newsize = 0
2308
2309 directives.register("backend.pdf.compact", function(v)
2310 compact = v and function(s)
2311 oldsize = oldsize + #s
2312 s = lpegmatch(pattern,s) or s
2313 newsize = newsize + #s
2314 return s
2315 end
2316 end)
2317
2318 statistics.register("pdf pagestream",function()
2319 if oldsize ~= newsize then
2320 return string.format("old size: %i, new size %i",oldsize,newsize)
2321 end
2322 end)
2323
2324end
2325
2326local flushdeferred
2327
2328local level = 0
2329local state = true
2330
2331function lpdf.setpagestate(s)
2332 state = s
2333end
2334
2335local finalize do
2336
2337 local ceil = math.ceil
2338 local floor = math.floor
2339
2340 local f_font = formatters["F%d"]
2341
2342 local f_form = formatters["Fm%d"]
2343 local f_group = formatters["Gp%d"]
2344 local f_image = formatters["Im%d"]
2345 local f_state = formatters["Gs%d"]
2346
2347 local function checkedbox(mediabox,otherbox,what)
2348 if otherbox and #mediabox == 4 and #otherbox == 4 then
2349 local done = false
2350 if otherbox[1] < mediabox[1] then done = true ; otherbox[1] = mediabox[1] end
2351 if otherbox[2] < mediabox[2] then done = true ; otherbox[2] = mediabox[2] end
2352 if otherbox[3] > mediabox[3] then done = true ; otherbox[3] = mediabox[3] end
2353 if otherbox[4] > mediabox[4] then done = true ; otherbox[4] = mediabox[4] end
2354 if done then
2355 report("limiting %a to 'MediaBox'",what)
2356 end
2357 end
2358 return otherbox
2359 end
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371 local dummy = false
2372
2373 local function checkedresources()
2374 if dummy == false then
2375
2376 dummy = pdfsharedobject(pdfdictionary {
2377
2378 LMTX_Comment = pdfunicode("mandate resource dictionary cf 'latest' specification")
2379 } )
2380
2381
2382
2383 end
2384 return dummy
2385 end
2386
2387 lpdf.checkedresources = checkedresources
2388
2389 finalize = function(driver,details)
2390
2391 if not details then
2392 report("something is wrong, no details in 'finalize'")
2393 end
2394
2395 level = level + 1
2396
2397 pdf_goto_pagemode()
2398
2399 local objnum = details.objnum
2400 local specification = details.specification or { }
2401
2402 local content = concat(buffer,"\n",1,b)
2403
2404 if compact then
2405 content = compact(content)
2406 end
2407
2408 local fonts = nil
2409 local xforms = nil
2410
2411 if next(usedfonts) then
2412 fonts = pdfdictionary { }
2413 for k, v in next, usedfonts do
2414 fonts[f_font(v)] = pdfreference(usedfontobjects[k])
2415 end
2416 end
2417
2418
2419
2420
2421 if next(usedxforms) or next(usedximages) or next(usedxgroups) then
2422 xforms = pdfdictionary { }
2423 for k in sortedhash(usedxforms) do
2424 xforms[f_form(getxformname(k))] = pdfreference(k)
2425 end
2426 for k, v in sortedhash(usedximages) do
2427 xforms[f_image(k)] = pdfreference(v)
2428 end
2429 for k, v in sortedhash(usedxgroups) do
2430 xforms[f_group(k)] = pdfreference(v)
2431 end
2432 end
2433
2434 reset_buffer()
2435
2436
2437
2438 if shippingmode == "page" then
2439
2440 local pageproperties = lpdf.getpageproperties()
2441
2442 local pageresources = pageproperties.pageresources
2443 local pageattributes = pageproperties.pageattributes
2444 local pagesattributes = pageproperties.pagesattributes
2445
2446 pageresources.Font = fonts
2447 pageresources.XObject = xforms
2448
2449
2450 local bbox = pdfarray {
2451 boundingbox[1] * bpfactor,
2452 boundingbox[2] * bpfactor,
2453 boundingbox[3] * bpfactor,
2454 boundingbox[4] * bpfactor,
2455 }
2456
2457 local contentsobj = content ~= "" and pdfflushstreamobject(content,false,true) or false
2458
2459 pageattributes.Type = pdf_page
2460 pageattributes.Contents = contentsobj and pdfreference(contentsobj) or nil
2461 pageattributes.MediaBox = pdfsharedobject(bbox)
2462 pageattributes.Parent = nil
2463 pageattributes.Group = nil
2464
2465
2466
2467 if state == "ignore" or state == false then
2468
2469 else
2470
2471 registerpage(pageattributes)
2472
2473 lpdf.finalizepage(true)
2474
2475 local TrimBox = pageattributes.TrimBox
2476 local CropBox = pageattributes.CropBox
2477 local BleedBox = pageattributes.BleedBox
2478
2479
2480
2481 if TrimBox then pageattributes.TrimBox = pdfsharedobject(checkedbox(bbox,TrimBox,"TrimBox")) end
2482 if CropBox then pageattributes.CropBox = pdfsharedobject(checkedbox(bbox,CropBox,"CropBox")) end
2483 if BleedBox then pageattributes.BleedBox = pdfsharedobject(checkedbox(bbox,BleedBox,"BleedBox")) end
2484
2485 end
2486
2487 pageattributes.Resources = next(pageresources) and pdfsharedobject(pageresources) or checkedresources()
2488
2489 else
2490
2491 local xformtype = specification.type or 0
2492 local margin = specification.margin or 0
2493 local attributes = specification.attributes or ""
2494 local resources = specification.resources or ""
2495 local wrapper = nil
2496
2497 if xformtype == 0 then
2498 wrapper = pdfdictionary {
2499 Type = pdf_xobject,
2500 Subtype = pdf_form,
2501 FormType = 1,
2502 BBox = nil,
2503 Matrix = nil,
2504 Resources = nil,
2505 }
2506 else
2507 wrapper = pdfdictionary {
2508 BBox = nil,
2509 Matrix = nil,
2510 Resources = nil,
2511 }
2512 end
2513 if xformtype == 0 or xformtype == 1 or xformtype == 3 then
2514 margin = 1
2515 wrapper.BBox = pdfarray {
2516 floor(boundingbox[1] * bpfactor) - margin,
2517 floor(boundingbox[2] * bpfactor) - margin,
2518 ceil (boundingbox[3] * bpfactor) + margin,
2519 ceil (boundingbox[4] * bpfactor) + margin,
2520 }
2521 end
2522 if xformtype == 0 or xformtype == 2 or xformtype == 3 then
2523
2524 wrapper.Matrix = pdfarray { 1, 0, 0, 1, 0, 0 }
2525 end
2526
2527 local patterns = true
2528
2529 if attributes.Type and attributes.Type == pdf_pattern then
2530 patterns = false
2531 end
2532
2533 local boxresources = lpdf.collectedresources {
2534 patterns = patterns,
2535 serialize = false,
2536 }
2537 boxresources.Font = fonts
2538 boxresources.XObject = xforms
2539
2540
2541
2542
2543 if resources ~= "" then
2544 boxresources = boxresources + resources
2545 end
2546 if attributes ~= "" then
2547 wrapper = wrapper + attributes
2548 end
2549
2550
2551 wrapper.Resources = next(boxresources) and boxresources or checkedresources()
2552
2553
2554 pdfflushstreamobject(content,wrapper,true,specification.objnum)
2555
2556 end
2557
2558 for objnum in sortedhash(usedxforms) do
2559 local f = flushedxforms[objnum]
2560 if f[1] == false then
2561 f[1] = true
2562 local objnum = f[2]
2563 local specification = boxresources[objnum]
2564 local list = specification.list
2565 localconverter(list,"xform",f[2],specification)
2566 end
2567 end
2568
2569 pdf_h, pdf_v = 0, 0
2570
2571 if level == 1 then
2572 flushdeferred()
2573 end
2574 level = level - 1
2575
2576 end
2577
2578end
2579
2580
2581
2582local objects = { }
2583local streams = { }
2584local nofobjects = 0
2585local offset = 0
2586local f = false
2587local flush = false
2588local objectstream = true
2589local compress = true
2590local cache = false
2591local info = ""
2592local catalog = ""
2593local lastdeferred = false
2594
2595local f_object = formatters["%i 0 obj\010%s\010endobj\010"]
2596local f_stream_n_u = formatters["%i 0 obj\010<< /Length %i >>\010stream\010%s\010endstream\010endobj\010"]
2597local f_stream_n_c = formatters["%i 0 obj\010<< /Filter /FlateDecode /Length %i >>\010stream\010%s\010endstream\010endobj\010"]
2598local f_stream_d_u = formatters["%i 0 obj\010<< %s /Length %i >>\010stream\010%s\010endstream\010endobj\010"]
2599local f_stream_d_c = formatters["%i 0 obj\010<< %s /Filter /FlateDecode /Length %i >>\010stream\010%s\010endstream\010endobj\010"]
2600local f_stream_d_r = formatters["%i 0 obj\010<< %s >>\010stream\010%s\010endstream\010endobj\010"]
2601
2602
2603local f_stream_b_n_u = formatters["%i 0 obj\010<< /Length %i >>\010stream\010"]
2604local f_stream_b_n_c = formatters["%i 0 obj\010<< /Filter /FlateDecode /Length %i >>\010stream\010"]
2605local f_stream_b_d_u = formatters["%i 0 obj\010<< %s /Length %i >>\010stream\010"]
2606local f_stream_b_d_c = formatters["%i 0 obj\010<< %s /Filter /FlateDecode /Length %i >>\010stream\010"]
2607local f_stream_b_d_r = formatters["%i 0 obj\010<< %s >>\010stream\010"]
2608
2609
2610local s_stream_e <const> = "\010endstream\010endobj\010"
2611
2612do
2613
2614
2615
2616
2617 function lpdf.setversion(major,minor)
2618 majorversion = tonumber(major) or majorversion
2619 minorversion = tonumber(minor) or minorversion
2620 end
2621
2622 function lpdf.getversion(major,minor)
2623 return majorversion, minorversion
2624 end
2625
2626 function lpdf.majorversion() return majorversion end
2627 function lpdf.minorversion() return minorversion end
2628
2629
2630
2631
2632 local frozen = false
2633 local clevel = 3
2634 local olevel = 1
2635
2636 function lpdf.setcompression(level,objectlevel,freeze)
2637 if not frozen then
2638 compress = level and level ~= 0 and true or false
2639
2640 if next(streams) then
2641
2642 else
2643 objectstream = objectlevel and objectlevel ~= 0 and true or false
2644 end
2645 frozen = freeze
2646 end
2647 end
2648
2649 function lpdf.getcompression()
2650 return compress and olevel or 0, objectstream and clevel or 0
2651 end
2652
2653 function lpdf.compresslevel()
2654 return compress and olevel or 0
2655 end
2656
2657 function lpdf.objectcompresslevel()
2658 return objectstream and clevel or 0
2659 end
2660
2661 if environment.arguments.nocompression then
2662 lpdf.setcompression(0,0,true)
2663 end
2664
2665end
2666
2667local addtocache, flushcache, cache do
2668
2669 local data, d = { }, 0
2670 local list, l = { }, 0
2671 local coffset = 0
2672 local indices = { }
2673
2674 local maxsize = 32 * 1024
2675 local maxcount = 0xFF
2676
2677 addtocache = function(n,str)
2678 local size = #str
2679 if size == 0 then
2680
2681 return
2682 end
2683 if coffset + size > maxsize or d == maxcount then
2684 flushcache()
2685 end
2686 if d == 0 then
2687 nofobjects = nofobjects + 1
2688 objects[nofobjects] = false
2689 streams[nofobjects] = indices
2690 cache = nofobjects
2691 end
2692 objects[n] = - cache
2693 indices[n] = d
2694 d = d + 1
2695
2696 data[d] = str
2697 l = l + 1 ; list[l] = n
2698 l = l + 1 ; list[l] = coffset
2699 coffset = coffset + size + 1
2700 end
2701
2702 local p_ObjStm = pdfconstant("ObjStm")
2703
2704 flushcache = function()
2705 if l > 0 then
2706 list = concat(list," ")
2707 data[0] = list
2708 data = concat(data,"\010",0,d)
2709 local strobj = pdfdictionary {
2710 Type = p_ObjStm,
2711 N = d,
2712 First = #list + 1,
2713 }
2714 objects[cache] = offset
2715 local fb
2716 if compress then
2717 local size = #data
2718 local comp = compressdata(data,size)
2719 if comp and #comp < size then
2720 data = comp
2721 fb = f_stream_b_d_c
2722 else
2723 fb = f_stream_b_d_u
2724 end
2725 else
2726 fb = f_stream_b_d_u
2727 end
2728 local size = #data
2729 if encryptstream then
2730 data = encryptstream(data)
2731 size = #data
2732 end
2733 local b = fb(cache,strobj(),size)
2734 local e = s_stream_e
2735 flush(f,b)
2736 flush(f,data)
2737 flush(f,e)
2738 offset = offset + #b + size + #e
2739 data, d = { }, 0
2740 list, l = { }, 0
2741 coffset = 0
2742 indices = { }
2743 end
2744 end
2745
2746end
2747
2748do
2749
2750 local names = { }
2751 local cache = { }
2752 local nofpages = 0
2753
2754 local texgetcount = tex.getcount
2755
2756 local c_realpageno <const> = tex.iscount("realpageno")
2757
2758 pdfreserveobject = function(name)
2759 nofobjects = nofobjects + 1
2760 objects[nofobjects] = false
2761 if name then
2762 names[name] = nofobjects
2763 if trace_objects then
2764 report_objects("reserving number %a under name %a",nofobjects,name)
2765 end
2766 elseif trace_objects then
2767 report_objects("reserving number %a",nofobjects)
2768 end
2769 return nofobjects
2770 end
2771
2772 pdfpagereference = function(n,complete)
2773 if n == true or not n then
2774 complete = n
2775 n = texgetcount(c_realpageno)
2776 end
2777 if n > nofpages then
2778 nofpages = n
2779 end
2780 local r = pdfgetpagereference(n)
2781 return complete and pdfreference(r) or r
2782 end
2783
2784 lpdf.reserveobject = pdfreserveobject
2785 lpdf.pagereference = pdfpagereference
2786
2787 function lpdf.lastreferredpage()
2788 return nofpages
2789 end
2790
2791 function lpdf.nofpages()
2792 return structures.pages.nofpages
2793 end
2794
2795 function lpdf.object(...)
2796 pdfdeferredobject(...)
2797 end
2798
2799 function lpdf.delayedobject(data,n)
2800 if n then
2801 pdfdeferredobject(n,data)
2802 else
2803 n = pdfdeferredobject(data)
2804 end
2805
2806 return n
2807 end
2808
2809 pdfflushobject = function(name,data)
2810 if data then
2811 local named = names[name]
2812 if named then
2813 if not trace_objects then
2814 elseif trace_details then
2815 report_objects("flushing data to reserved object with name %a, data: %S",name,data)
2816 else
2817 report_objects("flushing data to reserved object with name %a",name)
2818 end
2819 return pdfimmediateobject(named,tostring(data))
2820 else
2821 if not trace_objects then
2822 elseif trace_details then
2823 report_objects("flushing data to reserved object with number %s, data: %S",name,data)
2824 else
2825 report_objects("flushing data to reserved object with number %s",name)
2826 end
2827 return pdfimmediateobject(name,tostring(data))
2828 end
2829 else
2830 if trace_objects and trace_details then
2831 report_objects("flushing data: %S",name)
2832 end
2833 return pdfimmediateobject(tostring(name))
2834 end
2835 end
2836
2837 pdfflushstreamobject = function(data,dict,compressed,objnum)
2838 if trace_objects then
2839 report_objects("flushing stream object of %s bytes",#data)
2840 end
2841 local dtype = type(dict)
2842 local kind = compressed == "raw" and "raw" or "stream"
2843 local nolength = nil
2844 if compressed == "raw" then
2845 compressed = false
2846 nolength = true
2847
2848 end
2849
2850 return pdfdeferredobject {
2851 objnum = objnum,
2852 immediate = true,
2853 nolength = nolength,
2854 compresslevel = compressed,
2855 type = "stream",
2856 string = data,
2857 attr = (dtype == "string" and dict) or (dtype == "table" and dict()) or nil,
2858 }
2859 end
2860
2861 function lpdf.flushstreamfileobject(filename,dict,compressed,objnum)
2862 if trace_objects then
2863 report_objects("flushing stream file object %a",filename)
2864 end
2865 local dtype = type(dict)
2866 return pdfdeferredobject {
2867 objnum = objnum,
2868 immediate = true,
2869 compresslevel = compressed,
2870 type = "stream",
2871 file = filename,
2872 attr = (dtype == "string" and dict) or (dtype == "table" and dict()) or nil,
2873 }
2874 end
2875
2876 local shareobjectcache, shareobjectreferencecache = { }, { }
2877
2878 function lpdf.shareobject(content)
2879 if content == nil then
2880
2881 else
2882 content = tostring(content)
2883 local o = shareobjectcache[content]
2884 if not o then
2885 o = pdfimmediateobject(content)
2886 shareobjectcache[content] = o
2887 end
2888 return o
2889 end
2890 end
2891
2892 pdfsharedobject = function(content)
2893 if content == nil then
2894
2895 else
2896 content = tostring(content)
2897 local r = shareobjectreferencecache[content]
2898 if not r then
2899 local o = shareobjectcache[content]
2900 if not o then
2901 o = pdfimmediateobject(content)
2902 shareobjectcache[content] = o
2903 end
2904 r = pdfreference(o)
2905 shareobjectreferencecache[content] = r
2906 end
2907 return r
2908 end
2909 end
2910
2911 lpdf.flushobject = pdfflushobject
2912 lpdf.flushstreamobject = pdfflushstreamobject
2913 lpdf.shareobjectreference = pdfsharedobject
2914 lpdf.sharedobject = pdfsharedobject
2915
2916end
2917
2918local preallocated = false
2919
2920local pages do
2921
2922 local texgetcount = tex.getcount
2923
2924 pages = table.setmetatableindex(function(t,k)
2925 if not preallocated then
2926 local n = structures.counters.record("realpage")["last"]
2927 for i=1,n do
2928 t[i] = pdfreserveobject()
2929 end
2930 preallocated = true
2931 if k <= n then
2932 return t[k]
2933 end
2934 end
2935 local v = pdfreserveobject()
2936 t[k] = v
2937 return v
2938 end)
2939
2940 pdfgetpagereference = function(n)
2941 return pages[n]
2942 end
2943
2944 lpdf.getpagereference = pdfgetpagereference
2945
2946end
2947
2948
2949local function flushnormalobj(data,n)
2950 if not n then
2951 nofobjects = nofobjects + 1
2952 n = nofobjects
2953 end
2954 if encryptobject then
2955 data = encryptobject(data)
2956 end
2957 data = f_object(n,data)
2958 if level == 0 then
2959 objects[n] = offset
2960 offset = offset + #data
2961 flush(f,data)
2962 else
2963 if not lastdeferred then
2964 lastdeferred = n
2965 elseif n < lastdeferred then
2966 lastdeferred = n
2967 end
2968 objects[n] = data
2969 end
2970 return n
2971end
2972
2973local flushstreamobj, streamstatus do
2974
2975 local uncompressed = 0
2976 local compressed = 0
2977 local notcompressed = 0
2978
2979 local threshold = 40
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989 streamstatus = function()
2990 return {
2991 nofstreams = uncompressed + compressed + notcompressed,
2992 uncompressed = uncompressed,
2993 compressed = compressed,
2994 notcompressed = notcompressed,
2995 threshold = threshold,
2996 compresslevel = lpdf.compresslevel(),
2997 objectcompresslevel = lpdf.objectcompresslevel(),
2998
2999 }
3000 end
3001
3002 local function updatelength(d,size)
3003
3004 d = gsub(d,"/Length %s*%d+%s*", "/Length " .. size .. " ")
3005
3006 d = gsub(d,"(/Length %d+ )%d+ R%s*", "%1")
3007 return d
3008 end
3009
3010 flushstreamobj = function(data,n,dict,comp,nolength)
3011 if not data then
3012 report("no data for %S",dict)
3013 return
3014 end
3015 if not n then
3016 nofobjects = nofobjects + 1
3017 n = nofobjects
3018 end
3019 local size = #data
3020 if comp ~= false then
3021 comp = compress and size > threshold
3022 end
3023 if encryptobject then
3024 dict = encryptobject(dict)
3025 end
3026 if dict == "" then
3027 dict = nil
3028 end
3029
3030 if level == 0 then
3031 local b = nil
3032 local e = s_stream_e
3033 if nolength then
3034 b = f_stream_b_d_r(n,dict)
3035 if encryptstream then
3036
3037 data = encryptstream(data)
3038 size = #data
3039 end
3040 b = updatelength(b,size)
3041 uncompressed = uncompressed + 1
3042 else
3043 if comp then
3044 local compdata = compressdata(data,size)
3045 if compdata then
3046 local compsize = #compdata
3047 if compsize <= size - threshold then
3048 data = compdata
3049 size = compsize
3050 else
3051 comp = false
3052 end
3053 else
3054 comp = false
3055 end
3056 end
3057 if encryptstream then
3058 data = encryptstream(data)
3059 size = #data
3060 end
3061 if comp then
3062 b = dict and f_stream_b_d_c(n,dict,size) or f_stream_b_n_c(n,size)
3063 compressed = compressed + 1
3064 else
3065 b = dict and f_stream_b_d_u(n,dict,size) or f_stream_b_n_u(n,size)
3066 notcompressed = notcompressed + 1
3067 end
3068 end
3069 flush(f,b)
3070 flush(f,data)
3071 flush(f,e)
3072 objects[n] = offset
3073 offset = offset + #b + size + #e
3074 else
3075 if nolength then
3076 if encryptstream then
3077
3078 data = encryptstream(data)
3079 size = #data
3080 end
3081 dict = updatelength(dict,size)
3082 data = f_stream_d_r(n,dict,data)
3083 uncompressed = uncompressed + 1
3084 else
3085 if comp then
3086 local compdata = compressdata(data,size)
3087 if compdata then
3088 local compsize = #compdata
3089 if compsize <= size - threshold then
3090 data = compdata
3091 size = compsize
3092 else
3093 comp = false
3094 end
3095 else
3096 comp = false
3097 end
3098 end
3099 if encryptstream then
3100 data = encryptstream(data)
3101 size = #data
3102 end
3103 if comp then
3104 data = dict and f_stream_d_c(n,dict,size,data) or f_stream_n_c(n,size,data)
3105 compressed = compressed + 1
3106 else
3107 data = dict and f_stream_d_u(n,dict,size,data) or f_stream_n_u(n,size,data)
3108 notcompressed = notcompressed + 1
3109 end
3110 end
3111 if not lastdeferred then
3112 lastdeferred = n
3113 elseif n < lastdeferred then
3114 lastdeferred = n
3115 end
3116 objects[n] = data
3117 end
3118 return n
3119 end
3120
3121end
3122
3123flushdeferred = function()
3124 if lastdeferred then
3125 for n=lastdeferred,nofobjects do
3126 local o = objects[n]
3127 if type(o) == "string" then
3128 objects[n] = offset
3129 offset = offset + #o
3130 flush(f,o)
3131 end
3132 end
3133 lastdeferred = false
3134 end
3135end
3136
3137pdfimmediateobject = function(a,b,c,d)
3138 local kind
3139 local objnum, data, attr, filename
3140 local compresslevel, objcompression, nolength
3141 local argtype = type(a)
3142 if argtype == "table" then
3143 kind = a.type
3144
3145 objnum = a.objnum
3146 attr = a.attr
3147 compresslevel = a.compresslevel
3148 objcompression = a.objcompression
3149 filename = a.file
3150 data = a.string or a.stream or ""
3151 nolength = a.nolength
3152 if kind == "stream" then
3153 if filename then
3154 data = loaddata(filename) or ""
3155 end
3156 elseif kind == "raw" then
3157 if filename then
3158 data = loaddata(filename) or ""
3159 end
3160 elseif kind == "file"then
3161 kind = "raw"
3162 data = filename and loaddata(filename) or ""
3163 elseif kind == "streamfile" then
3164 kind = "stream"
3165 data = filename and loaddata(filename) or ""
3166 end
3167 else
3168 if argtype == "number" then
3169 objnum = a
3170 a, b, c = b, c, d
3171 else
3172 nofobjects = nofobjects + 1
3173 objnum = nofobjects
3174 end
3175 if b then
3176 if a == "stream" then
3177 kind = "stream"
3178 data = b
3179 elseif a == "file" then
3180
3181 data = loaddata(b)
3182 elseif a == "streamfile" then
3183 kind = "stream"
3184 data = loaddata(b)
3185 else
3186 data = ""
3187 end
3188 attr = c
3189 else
3190
3191 data = a
3192 end
3193 end
3194 if not objnum then
3195 nofobjects = nofobjects + 1
3196 objnum = nofobjects
3197 end
3198
3199 if kind == "stream" then
3200 flushstreamobj(data,objnum,attr,compresslevel,nolength)
3201 elseif objectstream and objcompression ~= false then
3202 addtocache(objnum,data)
3203 else
3204 flushnormalobj(data,objnum)
3205 end
3206 return objnum
3207end
3208
3209pdfdeferredobject = pdfimmediateobject
3210
3211lpdf.deferredobject = pdfimmediateobject
3212lpdf.immediateobject = pdfimmediateobject
3213
3214
3215
3216
3217
3218local openfile, closefile do
3219
3220
3221
3222
3223
3224
3225 local f_used = formatters["%010i 00000 n\013\010"]
3226 local f_link = formatters["%010i 00000 f\013\010"]
3227 local f_first = formatters["%010i 65535 f\013\010"]
3228
3229 local f_pdf_tag = formatters["%%PDF-%i.%i\010"]
3230 local f_xref = formatters["xref\0100 %i\010"]
3231 local f_trailer_id = formatters["trailer\010<< %s /ID [ <%s> <%s> ] >>\010startxref\010%i\010%%%%EOF"]
3232 local f_trailer_no = formatters["trailer\010<< %s >>\010startxref\010%i\010%%%%EOF"]
3233 local f_startxref = formatters["startxref\010%i\010%%%%EOF"]
3234
3235 local inmemory = false
3236 local close = false
3237 local update = false
3238 local usedname = false
3239 local usedsize = false
3240
3241 directives.enable("backend.pdf.inmemory", function(v) inmemory = true end)
3242
3243
3244 local banner <const> = "%\xC3\xCF\xCE\xD4\xC5\xD8\xD4\xD0\xC4\xC6\010"
3245
3246 openfile = function(filename)
3247
3248 local arguments = environment.arguments
3249 if arguments.ownerpassword then
3250 lpdf.setencryption {
3251 ownerpassword = arguments.ownerpassword,
3252 userpassword = arguments.userpassword,
3253 permissions = arguments.permissions,
3254 }
3255 end
3256
3257 if not preallocated then
3258 local dummy = pages[1]
3259 end
3260
3261 if inmemory then
3262 local n = 0
3263 f = { }
3264 flush = function(f,s)
3265 n = n + 1 f[n] = s
3266
3267 end
3268 close = function(f)
3269 f = concat(f)
3270 usedsize = #f
3271 io.savedata(filename,f)
3272 f = false
3273 end
3274 update = function(f,s)
3275 f[1] = s
3276 end
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288 else
3289 f = io.open(filename,"wb")
3290 if not f then
3291 report()
3292 report("quitting because file %a cannot be opened for writing",filename)
3293 report()
3294 os.exit()
3295 end
3296
3297 local m = getmetatable(f)
3298 flush = m.write or m.__index.write
3299 close = m.close or m.__index.close
3300 update = function(f,s)
3301 f:seek("set",0)
3302 f:write(s)
3303 end
3304 end
3305 local version = f_pdf_tag(majorversion,minorversion)
3306 flush(f,version)
3307 flush(f,banner)
3308 offset = offset + #version + #banner
3309 usedname = filename
3310 end
3311
3312 closefile = function(abort)
3313 if abort then
3314 close(f)
3315 if not environment.arguments.nodummy then
3316 f = io.open(abort,"wb")
3317 if f then
3318 local name = resolvers.findfile("context-lmtx-error.pdf")
3319 if name then
3320 local data = io.loaddata(name)
3321 if data then
3322 f:write(data)
3323 f:close()
3324 return
3325 end
3326 end
3327 f:close()
3328 end
3329 end
3330 os.remove(abort)
3331 else
3332 local xrefoffset = offset
3333 local lastfree = 0
3334 local noffree = 0
3335
3336 local os = objectstream
3337 if encryptstream then
3338 objectstream = false
3339 end
3340 local catalog = lpdf.getcatalog()
3341 objectstream = os
3342
3343 local info = lpdf.getinfo()
3344 local trailerid = lpdf.gettrailerid()
3345
3346 if objectstream then
3347 flushdeferred()
3348 flushcache()
3349
3350 offset = lpdf.preparesignature and lpdf.preparesignature(flush,f,offset,objects) or offset
3351
3352 xrefoffset = offset
3353
3354 nofobjects = nofobjects + 1
3355 objects[nofobjects] = offset
3356
3357
3358
3359
3360 local nofbytes = 4
3361 local c1, c2, c3, c4
3362 if offset <= 0xFFFF then
3363 nofbytes = 2
3364 for i=1,nofobjects do
3365 local o = objects[i]
3366 if not o then
3367 noffree = noffree + 1
3368 else
3369 local strm = o < 0
3370 if strm then
3371 o = -o
3372 end
3373 c1 = (o>>8)&0xFF
3374 c2 = (o>>0)&0xFF
3375 if strm then
3376 objects[i] = char(2,c1,c2,streams[o][i])
3377 else
3378 objects[i] = char(1,c1,c2,0)
3379 end
3380 end
3381 end
3382 if noffree > 0 then
3383 for i=nofobjects,1,-1 do
3384 local o = objects[i]
3385 if not o then
3386 local f1 = (lastfree>>8)&0xFF
3387 local f2 = (lastfree>>0)&0xFF
3388 objects[i] = char(0,f1,f2,0)
3389 lastfree = i
3390 end
3391 end
3392 end
3393 elseif offset <= 0xFFFFFF then
3394 nofbytes = 3
3395 for i=1,nofobjects do
3396 local o = objects[i]
3397 if not o then
3398 noffree = noffree + 1
3399 else
3400 local strm = o < 0
3401 if strm then
3402 o = -o
3403 end
3404 c1 = (o>>16)&0xFF
3405 c2 = (o>> 8)&0xFF
3406 c3 = (o>> 0)&0xFF
3407 if strm then
3408 objects[i] = char(2,c1,c2,c3,streams[o][i])
3409 else
3410 objects[i] = char(1,c1,c2,c3,0)
3411 end
3412 end
3413 end
3414 if noffree > 0 then
3415 for i=nofobjects,1,-1 do
3416 local o = objects[i]
3417 if not o then
3418 local f1 = (lastfree>>16)&0xFF
3419 local f2 = (lastfree>> 8)&0xFF
3420 local f3 = (lastfree>> 0)&0xFF
3421 objects[i] = char(0,f1,f2,f3,0)
3422 lastfree = i
3423 end
3424 end
3425 end
3426 else
3427 nofbytes = 4
3428 for i=1,nofobjects do
3429 local o = objects[i]
3430 if not o then
3431 noffree = noffree + 1
3432 else
3433 local strm = o < 0
3434 if strm then
3435 o = -o
3436 end
3437 c1 = (o>>24)&0xFF
3438 c2 = (o>>16)&0xFF
3439 c3 = (o>> 8)&0xFF
3440 c4 = (o>> 0)&0xFF
3441 if strm then
3442 objects[i] = char(2,c1,c2,c3,c4,streams[o][i])
3443 else
3444 objects[i] = char(1,c1,c2,c3,c4,0)
3445 end
3446 end
3447 end
3448 if noffree > 0 then
3449 for i=nofobjects,1,-1 do
3450 local o = objects[i]
3451 if not o then
3452 local f1 = (lastfree>>24)&0xFF
3453 local f2 = (lastfree>>16)&0xFF
3454 local f3 = (lastfree>> 8)&0xFF
3455 local f4 = (lastfree>> 0)&0xFF
3456 objects[i] = char(0,f1,f2,f3,f4,0)
3457 lastfree = i
3458 end
3459 end
3460 end
3461 end
3462 objects[0] = rep("\0",1+nofbytes+1)
3463 local data = concat(objects,"",0,nofobjects)
3464 local size = #data
3465 local xref = pdfdictionary {
3466 Type = pdfconstant("XRef"),
3467 Size = nofobjects + 1,
3468 W = pdfarray { 1, nofbytes, 1 },
3469 Root = catalog,
3470 Info = info,
3471 ID = trailerid and pdfarray { pdfliteral(trailerid,true), pdfliteral(trailerid,true) } or nil,
3472 Encrypt = encdict or nil,
3473 }
3474 local fb
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
3493
3494 if compress then
3495 local comp = compressdata(data,size)
3496 if comp then
3497 data = comp
3498 size = #data
3499 fb = f_stream_b_d_c
3500 else
3501 fb = f_stream_b_d_u
3502 end
3503 else
3504 fb = f_stream_b_d_u
3505 end
3506
3507
3508 flush(f,fb(nofobjects,xref(),size))
3509 flush(f,data)
3510 flush(f,s_stream_e)
3511 flush(f,f_startxref(xrefoffset))
3512 else
3513 flushdeferred()
3514
3515 offset = lpdf.preparesignature and lpdf.preparesignature(flush,f,offset,objects) or offset
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525 xrefoffset = offset
3526 flush(f,f_xref(nofobjects+1))
3527 local trailer = pdfdictionary {
3528 Size = nofobjects + 1,
3529 Root = catalog,
3530 Info = info,
3531 Encrypt = encdict or nil,
3532 }
3533 for i=1,nofobjects do
3534 local o = objects[i]
3535 if o then
3536
3537
3538
3539 objects[i] = f_used(o)
3540 end
3541 end
3542 for i=nofobjects,1,-1 do
3543 local o = objects[i]
3544 if not o then
3545 objects[i] = f_link(lastfree)
3546 lastfree = i
3547 end
3548 end
3549 objects[0] = f_first(lastfree)
3550 flush(f,concat(objects,"",0,nofobjects))
3551 trailer.Size = nofobjects + 1
3552 if trailerid then
3553 flush(f,f_trailer_id(trailer(),trailerid,trailerid,xrefoffset))
3554 else
3555 flush(f,f_trailer_no(trailer(),xrefoffset))
3556 end
3557 end
3558 update(f,f_pdf_tag(majorversion,minorversion))
3559 usedsize = f:seek("end")
3560 close(f)
3561 io.flush()
3562 if lpdf.finalizesignature then lpdf.finalizesignature(usedname,usedsize) end
3563 end
3564 io.flush()
3565 closefile = function() end
3566 end
3567
3568end
3569
3570
3571
3572
3573do
3574
3575
3576
3577
3578
3579 local pdfbackend = backends.registered.pdf
3580 local nodeinjections = pdfbackend.nodeinjections
3581 local codeinjections = pdfbackend.codeinjections
3582
3583 local imagetypes = images.types
3584 local img_none = imagetypes.none
3585
3586 local tonode = nuts.tonode
3587 local newimagerule = nuts.pool.imagerule
3588 local setattrlist = nuts.setattrlist
3589 local setprop = nuts.setprop
3590
3591 local report_images = logs.reporter("backend","images")
3592
3593 local lastindex = 0
3594 local indices = { }
3595
3596 local bpfactor = number.dimenfactors.bp
3597
3598 function codeinjections.newimage(specification)
3599 return specification
3600 end
3601
3602 function codeinjections.copyimage(original)
3603 return setmetatableindex(original)
3604 end
3605
3606 function codeinjections.scanimage(specification)
3607
3608 return specification
3609 end
3610
3611 local function embedimage(specification)
3612 if specification then
3613 lastindex = lastindex + 1
3614 index = lastindex
3615 specification.index = index
3616 local xobject = pdfdictionary { }
3617 if not specification.notype then
3618 xobject.Type = pdf_xobject
3619 xobject.Subtype = pdf_form
3620 xobject.FormType = 1
3621 end
3622 local bbox = specification.bbox
3623 if bbox and not specification.nobbox then
3624 xobject.BBox = pdfarray {
3625 bbox[1] * bpfactor,
3626 bbox[2] * bpfactor,
3627 bbox[3] * bpfactor,
3628 bbox[4] * bpfactor,
3629 }
3630 end
3631 xobject = xobject + specification.attr
3632 if bbox and not specification.width then
3633 specification.width = bbox[3]
3634 end
3635 if bbox and not specification.height then
3636 specification.height = bbox[4]
3637 end
3638 local dict = xobject()
3639
3640 nofobjects = nofobjects + 1
3641 local objnum = nofobjects
3642 local nolength = specification.nolength
3643 local stream = specification.stream or specification.string
3644
3645
3646
3647
3648
3649
3650 if not specification.type then
3651 local kind = specification.kind
3652 if kind then
3653
3654 elseif attr and find(attr,"BBox") then
3655 kind = img_stream
3656 else
3657
3658 kind = img_none
3659 end
3660 specification.type = kind
3661 specification.kind = kind
3662 end
3663 flushstreamobj(stream,objnum,dict,compresslevel,nolength)
3664 specification.objnum = objnum
3665 specification.rotation = specification.rotation or 0
3666 specification.orientation = specification.orientation or 0
3667 specification.transform = specification.transform or 0
3668 specification.stream = nil
3669 specification.attr = nil
3670 specification.type = specification.kind or specification.type or img_none
3671 indices[index] = specification
3672 return specification
3673 end
3674 end
3675
3676 codeinjections.embedimage = embedimage
3677
3678 function codeinjections.wrapimage(specification)
3679
3680 local index = specification.index
3681 if not index then
3682 embedimage(specification)
3683 end
3684
3685 local n = newimagerule(
3686 specification.width or 0,
3687 specification.height or 0,
3688 specification.depth or 0
3689 )
3690 setattrlist(n,true)
3691 setprop(n,"index",specification.index)
3692 return tonode(n)
3693 end
3694
3695 pdfincludeimage = function(index)
3696 local specification = indices[index]
3697 if specification then
3698 local bbox = specification.bbox
3699 local xorigin = bbox[1]
3700 local yorigin = bbox[2]
3701 local xsize = bbox[3] - xorigin
3702 local ysize = bbox[4] - yorigin
3703 local transform = specification.transform or 0
3704 local objnum = specification.objnum or pdfreserveobject()
3705 local groupref = nil
3706 local kind = specification.kind or specification.type or img_none
3707 return
3708 kind,
3709 xorigin, yorigin,
3710 xsize, ysize,
3711 transform,
3712 objnum,
3713 groupref
3714 end
3715 end
3716
3717 lpdf.includeimage = pdfincludeimage
3718
3719end
3720
3721
3722
3723do
3724
3725
3726 local texgetbox = tex.getbox
3727
3728 local pdfname = nil
3729 local converter = nil
3730 local useddriver = nil
3731
3732 local function outputfilename(driver)
3733 return pdfname
3734 end
3735
3736
3737
3738
3739
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749 local function prepare(driver)
3750 if not environment.initex then
3751
3752 backends.initialize("pdf")
3753
3754 pdfname = tex.jobname .. ".pdf"
3755 openfile(pdfname)
3756
3757 luatex.registerstopactions(1,function()
3758 if pdfname then
3759 lpdf.finalizedocument()
3760 closefile()
3761 pdfname = nil
3762 end
3763 end)
3764
3765 luatex.registerpageactions(1,function()
3766 if pdfname then
3767 lpdf.finalizepage(true)
3768 end
3769 end)
3770
3771 lpdf.registerdocumentfinalizer(wrapupdocument,nil,"wrapping up")
3772
3773 statistics.register("result saved in file", function()
3774 local status = streamstatus()
3775 local outputfilename = environment.outputfilename or environment.jobname or tex.jobname or "<unset>"
3776 outputfilename = gsub(outputfilename,"^%./+","")
3777 return string.format(
3778 "%s.pdf, compresslevel %s, objectcompresslevel %s, %i streams, %i uncompressed, %i compressed, %i not compressed, threshold %i",
3779 outputfilename,
3780 status.compresslevel,
3781 status.objectcompresslevel,
3782 status.nofstreams or 0,
3783 status.uncompressed or 0,
3784 status.compressed or 0,
3785 status.notcompressed or 0,
3786 status.threshold or 0
3787 )
3788 end)
3789
3790 luatex.registerstopactions(function()
3791 if pdfname then
3792 local r = lpdf.lastreferredpage()
3793 local s = lpdf.getnofpages()
3794 local t = lpdf.nofpages()
3795 if r > s then
3796 report()
3797 report("referred pages: %i, saved pages %i, pages from tuc file: %i, possible corrupt file",r,s,t)
3798 report()
3799 end
3800 end
3801 end)
3802 end
3803 converter = drivers.converters.lmtx
3804 useddriver = driver
3805 end
3806
3807 local function wrapup(driver)
3808 if pdfname then
3809 closefile()
3810 pdfname = nil
3811 end
3812 end
3813
3814 local function cleanup(driver)
3815 if pdfname then
3816 closefile(pdfname)
3817 pdfname = nil
3818 end
3819 end
3820
3821 local function convert(driver,boxnumber)
3822 converter(driver,texgetbox(boxnumber),"page")
3823 end
3824
3825
3826
3827
3828
3829
3830 localconverter = function(a,b,c,d)
3831 converter(useddriver,a,b,c,d)
3832 end
3833
3834 drivers.install {
3835 name = "pdf",
3836 flushers = flushers,
3837 actions = {
3838 prepare = prepare,
3839 wrapup = wrapup,
3840 cleanup = cleanup,
3841
3842 initialize = initialize,
3843 convert = convert,
3844 finalize = finalize,
3845
3846 outputfilename = outputfilename,
3847 },
3848 }
3849
3850end
3851 |