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
35local type, next, unpack, tonumber, rawget = type, next, unpack, tonumber, rawget
36local char, rep, find = string.char, string.rep, string.find
37local formatters, splitupstring = string.formatters, string.splitup
38local concat, sortedhash = table.concat, table.sortedhash
39local setmetatableindex = table.setmetatableindex
40local loaddata = io.loaddata
41
42local bpfactor = number.dimenfactors.bp
43
44local osuuid = os.uuid
45local zlibcompresssize = xzip.compresssize
46
47local nuts = nodes.nuts
48local tonut = nodes.tonut
49local tonode = nuts.tonode
50
51local pdfreference = lpdf.reference
52local pdfdictionary = lpdf.dictionary
53local pdfarray = lpdf.array
54local pdfconstant = lpdf.constant
55local pdfliteral = lpdf.literal
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")
83
84local trace_objects = false trackers.register("backend.objects", function(v) trace_objects = v end)
85local trace_details = false trackers.register("backend.details", function(v) trace_details = v end)
86local trace_indices = false trackers.register("backend.fonts.details", function(v) trace_indices = v end)
87
88
89
90local usedfontnames = { }
91local usedfontobjects = { }
92
93lpdf.usedfontnames = usedfontnames
94lpdf.usedfontobjects = usedfontobjects
95
96
97
98local function compressdata(data,size)
99 local guess = ((size // 4096) + 1) * 2048
100 local comp = zlibcompresssize(data,guess,3)
101
102
103
104
105
106 return comp
107end
108
109
110
111
112
113
114
115local flushers = { }
116
117
118
119local pdf_h, pdf_v
120local need_tm, need_tf, cur_tmrx, cur_factor, cur_f, cur_e
121local need_width, need_mode, done_width, done_mode
122local mode
123local f_pdf_cur, f_pdf, fs_cur, fs, f_cur, f_x_scale, f_y_scale
124local tj_delta, cw
125local usedfonts, usedxforms, usedximages, usedxgroups
126local getxformname, getximagename
127local boundingbox, shippingmode, objectnumber
128local tmrx, tmry, tmsx, tmsy, tmtx, tmty
129local cmrx, cmry, cmsx, cmsy, cmtx, cmty
130local tmef
131
132local function usefont(t,k)
133
134 local v = usedfontnames[k]
135 t[k] = v
136 return v
137end
138
139local function reset_variables(specification)
140 pdf_h, pdf_v = 0, 0
141 cmrx, cmry = 1.0, 1.0
142 cmsx, cmsy = 0.0, 0.0
143 cmtx, cmty = 0.0, 0.0
144 tmrx, tmry = 1.0, 1.0
145 tmsx, tmsy = 0.0, 0.0
146 tmtx, tmty = 0.0, 0.0
147 tmef = 1.0
148 need_tm = false
149 need_tf = false
150 need_width = 0
151 need_mode = 0
152 done_width = false
153 done_mode = false
154 mode = "page"
155 shippingmode = specification.shippingmode
156 objectnumber = specification.objectnumber
157 cur_tmrx = 0.0
158 f_cur = 0
159 f_pdf_cur = 0
160 f_pdf = 0
161 fs_cur = 0
162 fs = 0
163 f_x_scale = 1.0
164 f_y_scale = 1.0
165 cur_factor = 0
166 cur_f = false
167 cur_e = false
168 tj_delta = 0.0
169 cw = 0.0
170 usedfonts = setmetatableindex(usefont)
171 usedxforms = { }
172 usedximages = { }
173
174 boundingbox = specification.boundingbox
175end
176
177
178
179local buffer = lua.newtable(1024,0)
180local b = 0
181
182local function reset_buffer()
183 b = 0
184end
185
186
187
188local fontcharacters
189local fontdescriptions
190local fontparameters
191local fontproperties
192local pdfcharacters
193
194local getstreamhash = fonts.handlers.otf.getstreamhash
195
196local usedfontstreams = { }
197
198local usedindices = setmetatableindex(function(t,k)
199 local n = 0
200 local v = setmetatableindex(function(tt,kk)
201 if n >= 0xFFFF then
202 report_fonts("registering character index: overflow in hash %a, todo: use overflow font")
203 else
204 n = n + 1
205 end
206 if trace_indices then
207 report_fonts("registering character index: hash %a, charindex 0x%05X, slotindex 0x%04X",k,kk,n)
208 end
209 local vv = n
210 tt[kk] = vv
211 return vv
212 end)
213 t[k] = v
214 return v
215end)
216
217local usedcharacters = setmetatableindex(function(t,k)
218 local h, d = getstreamhash(k)
219 if trace_indices then
220 report_fonts("registering index table: hash %a, fontid %i",h,k)
221 end
222 usedfontstreams[h] = d
223 local v = usedindices[h]
224 t[k] = v
225 return v
226end)
227
228lpdf.usedfontstreams = usedfontstreams
229lpdf.usedcharacters = usedcharacters
230lpdf.usedindices = usedindices
231
232local horizontalmode = true
233local scalefactor = 1
234local threshold = 655360
235local thresfactor = 100
236local tjfactor = 100 / 65536
237
238function flushers.updatefontstate(font)
239 fontcharacters = characters[font]
240 fontdescriptions = descriptions[font]
241 fontparameters = parameters[font]
242 fontproperties = properties[font]
243 local size = fontparameters.size
244 local designsize = fontparameters.designsize or size
245 pdfcharacters = usedcharacters[font]
246 horizontalmode = fontparameters.writingmode ~= "vertical"
247 scalefactor = (designsize/size) * tjfactor
248
249 local fthreshold = fontproperties.threshold
250 threshold = (fthreshold and (size * fthreshold / 100)) or 655360
251
252 if (fontparameters.extendfactor or 1) == 1 then
253
254 elseif fontparameters.hshift or fontparameters.vshift then
255
256 else
257
258 threshold = threshold / 5
259 end
260end
261
262
263
264local f_cm = formatters["%.6N %.6N %.6N %.6N %.6N %.6N cm"]
265local f_tm = formatters["%.6N %.6N %.6N %.6N %.6N %.6N Tm"]
266
267local saved_text_pos_v = 0
268local saved_text_pos_h = 0
269
270local function begin_text()
271 saved_text_pos_h = pdf_h
272 saved_text_pos_v = pdf_v
273 b = b + 1 ; buffer[b] = "BT"
274 need_tf = true
275 need_width = 0
276 need_mode = 0
277 mode = "text"
278end
279
280local function end_text()
281 if done_width then
282 b = b + 1 ; buffer[b] = "0 w"
283 done_width = false
284 end
285 if done_mode then
286 b = b + 1 ; buffer[b] = "0 Tr"
287 done_mode = false
288 end
289 b = b + 1 ; buffer[b] = "ET"
290 pdf_h = saved_text_pos_h
291 pdf_v = saved_text_pos_v
292 mode = "page"
293end
294
295local saved_chararray_pos_h
296local saved_chararray_pos_v
297
298local saved_b = 0
299
300local function begin_chararray()
301 saved_chararray_pos_h = pdf_h
302 saved_chararray_pos_v = pdf_v
303 cw = horizontalmode and saved_chararray_pos_h or - saved_chararray_pos_v
304 tj_delta = 0
305 saved_b = b
306 b = b + 1 ; buffer[b] = " ["
307 mode = "chararray"
308end
309
310local function end_chararray()
311 b = b + 1 ; buffer[b] = "] TJ"
312 buffer[saved_b] = concat(buffer,"",saved_b,b)
313 b = saved_b
314 pdf_h = saved_chararray_pos_h
315 pdf_v = saved_chararray_pos_v
316 mode = "text"
317end
318
319local function begin_charmode()
320 b = b + 1 ; buffer[b] = "<"
321 mode = "char"
322end
323
324local function end_charmode()
325 b = b + 1 ; buffer[b] = ">"
326 mode = "chararray"
327end
328
329local function calc_pdfpos(h,v)
330
331 if mode == "page" then
332 cmtx = h - pdf_h
333 cmty = v - pdf_v
334 return h ~= pdf_h or v ~= pdf_v
335 elseif mode == "text" then
336 tmtx = h - saved_text_pos_h
337 tmty = v - saved_text_pos_v
338 return h ~= pdf_h or v ~= pdf_v
339 elseif horizontalmode then
340 tmty = v - saved_text_pos_v
341 tj_delta = cw - h
342 return tj_delta ~= 0 or v ~= pdf_v
343 else
344 tmtx = h - saved_text_pos_h
345 tj_delta = cw + v
346 return tj_delta ~= 0 or h ~= pdf_h
347 end
348end
349
350local function pdf_set_pos(h,v)
351 local move = calc_pdfpos(h,v)
352 if move then
353 b = b + 1 ; buffer[b] = f_cm(cmrx, cmsx, cmsy, cmry, cmtx*bpfactor, cmty*bpfactor)
354 pdf_h = pdf_h + cmtx
355 pdf_v = pdf_v + cmty
356 end
357end
358
359local function pdf_reset_pos()
360 if mode == "page" then
361 cmtx = - pdf_h
362 cmty = - pdf_v
363 if pdf_h == 0 and pdf_v == 0 then
364 return
365 end
366 elseif mode == "text" then
367 tmtx = - saved_text_pos_h
368 tmty = - saved_text_pos_v
369 if pdf_h == 0 and pdf_v == 0 then
370 return
371 end
372 elseif horizontalmode then
373 tmty = - saved_text_pos_v
374 tj_delta = cw
375 if tj_delta == 0 and pdf_v == 0 then
376 return
377 end
378 else
379 tmtx = - saved_text_pos_h
380 tj_delta = cw
381 if tj_delta == 0 and pdf_h == 0 then
382 return
383 end
384 end
385 b = b + 1 ; buffer[b] = f_cm(cmrx, cmsx, cmsy, cmry, cmtx*bpfactor, cmty*bpfactor)
386 pdf_h = pdf_h + cmtx
387 pdf_v = pdf_v + cmty
388end
389
390local function pdf_set_pos_temp(h,v)
391 local move = calc_pdfpos(h,v)
392 if move then
393 b = b + 1 ; buffer[b] = f_cm(cmrx, cmsx, cmsy, cmry, cmtx*bpfactor, cmty*bpfactor)
394 end
395end
396
397
398
399local function pdf_end_string_nl()
400 if mode == "char" then
401 end_charmode()
402 return end_chararray()
403 elseif mode == "chararray" then
404 return end_chararray()
405 end
406end
407
408local function pdf_goto_textmode()
409 if mode == "page" then
410 pdf_reset_pos()
411 return begin_text()
412 elseif mode ~= "text" then
413 if mode == "char" then
414 end_charmode()
415 return end_chararray()
416 else
417 return end_chararray()
418 end
419 end
420end
421
422local function pdf_goto_pagemode()
423 if mode ~= "page" then
424 if mode == "char" then
425 end_charmode()
426 end_chararray()
427 return end_text()
428 elseif mode == "chararray" then
429 end_chararray()
430 return end_text()
431 elseif mode == "text" then
432 return end_text()
433 end
434 end
435end
436
437local function pdf_goto_fontmode()
438 if mode == "char" then
439 end_charmode()
440 end_chararray()
441 end_text()
442 elseif mode == "chararray" then
443 end_chararray()
444 end_text()
445 elseif mode == "text" then
446 end_text()
447 end
448 pdf_reset_pos()
449 mode = "page"
450end
451
452
453do
454
455 local round = math.round
456
457
458
459
460
461 local naturalwidth = nil
462 local hshift = false
463 local vshift = false
464
465
466
467
468
469
470
471 local naturalwidths = setmetatableindex(function(t,font)
472 local d = descriptions[font]
473 local c = characters[font]
474 local f = parameters[font].hfactor or parameters[font].factor
475 local v = setmetatableindex(function(t,char)
476 local w
477 local e = c and c[char]
478 if e then
479 w = e.width or 0
480 end
481 if not w then
482 e = d and d[char]
483 if e then
484 w = e.width
485 if w then
486 w = w * f
487 end
488 end
489 end
490 if not w then
491 w = 0
492 end
493 t[char] = w
494 return w
495 end)
496 t[font] = v
497 return v
498 end)
499
500 local function setup_fontparameters(font,factor,f,e,sx,sy)
501 local slant = fontparameters.slantfactor or 0
502 local extend = fontparameters.extendfactor or 1
503 local squeeze = fontparameters.squeezefactor or 1
504 local expand = 1 + factor / 1000000
505 local format = fontproperties.format
506 if e then
507 extend = extend * e
508 end
509 tmef = expand
510 tmrx = expand * extend
511 tmsy = slant
512 tmry = squeeze
513 need_width = fontparameters.width or 0
514 need_mode = fontparameters.mode or 0
515 f_cur = font
516 f_pdf = usedfonts[font]
517 cur_factor = factor
518 cur_f = f
519 cur_e = e
520 tj_delta = 0
521 cw = 0
522 f_x_scale = 1.0
523 f_y_scale = 1.0
524 fs = fontparameters.size * bpfactor
525 if f then
526 fs = fs * f
527 end
528
529 if format == "opentype" or format == "type1" then
530 fs = fs * 1000 / fontparameters.units
531 end
532
533 f_x_scale = sx
534 if f_x_scale ~= 1.0 then
535 tmrx = tmrx * f_x_scale
536 end
537 f_y_scale = sy
538 if f_y_scale ~= 1.0 then
539 tmry = tmry * f_y_scale
540 end
541
542 naturalwidth = naturalwidths[font]
543
544 hshift = fontparameters.hshift
545 vshift = fontparameters.vshift
546 end
547
548 local f_width = formatters["%.6N w"]
549 local f_mode = formatters["%i Tr"]
550 local f_font = formatters["/F%i %.6N Tf"]
551
552 local s_width = "0 w"
553 local s_mode = "0 Tr"
554
555 local width_factor = 72.27 / 72000.0
556
557 local function set_font()
558
559 if need_width ~= 0 then
560 b = b + 1 ; buffer[b] = f_width(width_factor*need_width)
561 done_width = true
562 elseif done_width then
563 b = b + 1 ; buffer[b] = s_width
564 done_width = false
565 end
566
567 if need_mode ~= 0 then
568 b = b + 1 ; buffer[b] = f_mode(need_mode)
569 done_mode = true
570 elseif done_mode then
571 b = b + 1 ; buffer[b] = s_mode
572 done_mode = false
573 end
574 b = b + 1 ; buffer[b] = f_font(f_pdf,fs)
575 f_pdf_cur = f_pdf
576 fs_cur = fs
577 need_tf = false
578 need_tm = true
579 end
580
581 local function set_textmatrix(h,v)
582 local move = calc_pdfpos(h,v)
583 if need_tm or move then
584 b = b + 1 ; buffer[b] = f_tm(tmrx, tmsx, tmsy, tmry, tmtx*bpfactor, tmty*bpfactor)
585 pdf_h = saved_text_pos_h + tmtx
586 pdf_v = saved_text_pos_v + tmty
587 need_tm = false
588 end
589 cur_tmrx = tmrx
590 end
591
592 local f_hex_4 = formatters["%04X"]
593 local f_hex_2 = formatters["%02X"]
594
595 local h_hex_4 = setmetatableindex(function(t,k)
596 if k < 256 then
597
598 for i=0,255 do
599 t[i] = f_hex_4(i)
600 end
601 return t[k]
602 else
603 local v = f_hex_4(k)
604 t[k] = v
605 return v
606 end
607 end)
608 local h_hex_2 = setmetatableindex(function(t,k)
609 local v = k < 256 and f_hex_2(k) or "00"
610 t[k] = v
611 return v
612 end)
613
614
615
616
617
618
619
620
621
622 flushers.character = function(current,pos_h,pos_v,pos_r,font,char,data,f,e,factor,sx,sy)
623 if sx ~= f_x_scale or sy ~= f_y_scale or need_tf or font ~= f_cur or f_pdf ~= f_pdf_cur or fs ~= fs_cur or mode == "page" then
624 pdf_goto_textmode()
625 setup_fontparameters(font,factor,f,e,sx,sy)
626 set_font()
627 elseif cur_f ~= f then
628 pdf_goto_textmode()
629 setup_fontparameters(font,factor,f,e,sx,sy)
630 set_font()
631
632 elseif cur_tmrx ~= tmrx or cur_factor ~= factor or cur_e ~= e then
633 setup_fontparameters(font,factor,f,e,sx,sy)
634 need_tm = true
635 end
636
637 local move = calc_pdfpos(pos_h,pos_v)
638
639 if trace_threshold then
640 report_fonts(
641 "before: font %i, char %C, factor %i, naturalwidth %p, move %l, tm %l, hpos %p, delta %p, threshold %p, cw %p",
642 font,char,factor,naturalwidth[char],move,need_tm,pos_h,tj_delta,threshold,cw
643 )
644 end
645
646 if move or need_tm then
647 if not need_tm then
648 if horizontalmode then
649 if (saved_text_pos_v + tmty) ~= pdf_v then
650 need_tm = true
651 elseif tj_delta >= threshold or tj_delta <= -threshold then
652 need_tm = true
653 end
654 else
655 if (saved_text_pos_h + tmtx) ~= pdf_h then
656 need_tm = true
657 elseif tj_delta >= threshold or tj_delta <= -threshold then
658 need_tm = true
659 end
660 end
661 end
662
663 if hshift then pos_h = pos_h + hshift end
664 if vshift then pos_v = pos_v - vshift end
665
666 if need_tm then
667 pdf_goto_textmode()
668 set_textmatrix(pos_h,pos_v)
669 begin_chararray()
670 move = calc_pdfpos(pos_h,pos_v)
671 end
672 if move then
673 local d = tj_delta * scalefactor / f_x_scale
674 if d <= -0.5 or d >= 0.5 then
675 if mode == "char" then
676 end_charmode()
677 end
678 b = b + 1 ; buffer[b] = round(d)
679 end
680 cw = cw - tj_delta
681 end
682 end
683
684 if trace_threshold then
685 report_fonts(
686 "after : font %i, char %C, factor %i, naturalwidth %p, move %l, tm %l, hpos %p, delta %p, threshold %p, cw %p",
687 font,char,factor,naturalwidth[char],move,need_tm,pos_h,tj_delta,threshold,cw
688 )
689 end
690
691 if mode == "chararray" then
692 begin_charmode()
693 end
694
695 cw = cw + naturalwidth[char] * tmef * f_x_scale
696
697 local slot = pdfcharacters[data.index or char]
698
699 b = b + 1 ; buffer[b] = font > 0 and h_hex_4[slot] or h_hex_2[slot]
700
701 end
702
703 flushers.fontchar = function(font,char,data)
704 local dummy = usedfonts[font]
705 local slot = pdfcharacters[data.index or char]
706 return dummy
707 end
708
709end
710
711
712
713local flushliteral do
714
715 local nodeproperties = nodes.properties.data
716 local literalvalues = nodes.literalvalues
717
718 local originliteral_code = literalvalues.origin
719 local pageliteral_code = literalvalues.page
720 local alwaysliteral_code = literalvalues.always
721 local rawliteral_code = literalvalues.raw
722 local textliteral_code = literalvalues.text
723 local fontliteral_code = literalvalues.font
724
725 flushliteral = function(current,pos_h,pos_v)
726 local p = nodeproperties[current]
727 if p then
728 local str = p.data
729 if str and str ~= "" then
730 local mode = p.mode
731 if mode == originliteral_code then
732 pdf_goto_pagemode()
733 pdf_set_pos(pos_h,pos_v)
734 elseif mode == pageliteral_code then
735 pdf_goto_pagemode()
736 elseif mode == textliteral_code then
737 pdf_goto_textmode()
738 elseif mode == fontliteral_code then
739 pdf_goto_fontmode()
740 elseif mode == alwaysliteral_code then
741 pdf_end_string_nl()
742 need_tm = true
743 elseif mode == rawliteral_code then
744 pdf_end_string_nl()
745 else
746 report("invalid literal mode %a when flushing %a",mode,str)
747 return
748 end
749 b = b + 1 ; buffer[b] = str
750 end
751 end
752 end
753
754 flushers.literal = flushliteral
755
756 function lpdf.print(mode,str)
757
758
759 if str then
760 mode = literalvalues[mode]
761 else
762 mode, str = originliteral_code, mode
763 end
764 if str and str ~= "" then
765 if mode == originliteral_code then
766 pdf_goto_pagemode()
767
768 elseif mode == pageliteral_code then
769 pdf_goto_pagemode()
770 elseif mode == textliteral_code then
771 pdf_goto_textmode()
772 elseif mode == fontliteral_code then
773 pdf_goto_fontmode()
774 elseif mode == alwaysliteral_code then
775 pdf_end_string_nl()
776 need_tm = true
777 elseif mode == rawliteral_code then
778 pdf_end_string_nl()
779 else
780 report("invalid literal mode %a when flushing %a",mode,str)
781 return
782 end
783 b = b + 1 ; buffer[b] = str
784 end
785 end
786
787end
788
789
790
791do
792
793 local matrices = { }
794 local positions = { }
795 local nofpositions = 0
796 local nofmatrices = 0
797
798 local flushsave = function(current,pos_h,pos_v)
799 nofpositions = nofpositions + 1
800 positions[nofpositions] = { pos_h, pos_v, nofmatrices }
801 pdf_goto_pagemode()
802 pdf_set_pos(pos_h,pos_v)
803 b = b + 1 ; buffer[b] = "q"
804 end
805
806 local flushrestore = function(current,pos_h,pos_v)
807 if nofpositions < 1 then
808 return
809 end
810 local t = positions[nofpositions]
811
812
813 if shippingmode == "page" then
814 nofmatrices = t[3]
815 end
816 pdf_goto_pagemode()
817 pdf_set_pos(pos_h,pos_v)
818 b = b + 1 ; buffer[b] = "Q"
819 nofpositions = nofpositions - 1
820 end
821
822 local nodeproperties = nodes.properties.data
823
824 local s_matrix_0 <const> = "1 0 0 1 0 0 cm"
825 local f_matrix_2 = formatters["%.6N 0 0 %.6N 0 0 cm"]
826 local f_matrix_4 = formatters["%.6N %.6N %.6N %.6N 0 0 cm"]
827
828 local flushsetmatrix = function(current,pos_h,pos_v)
829 local p = nodeproperties[current]
830 if p then
831 local m = p.matrix
832 if m then
833 local rx, sx, sy, ry = unpack(m)
834 local s
835 if not rx then
836 rx = 1
837 elseif rx == 0 then
838 rx = 0.0001
839 end
840 if not ry then
841 ry = 1
842 elseif ry == 0 then
843 ry = 0.0001
844 end
845 if not sx then
846 sx = 0
847 end
848 if not sy then
849 sy = 0
850 end
851
852 if sx == 0 and sy == 0 then
853 if rx == 1 and ry == 1 then
854 s = s_matrix_0
855 else
856 s = f_matrix_2(rx,ry)
857 end
858 else
859 s = f_matrix_4(rx,sx,sy,ry)
860 end
861
862 if shippingmode == "page" then
863 local tx = pos_h * (1 - rx) - pos_v * sy
864 local ty = pos_v * (1 - ry) - pos_h * sx
865 if nofmatrices > 0 then
866 local t = matrices[nofmatrices]
867 local r_x, s_x, s_y, r_y, te, tf = t[1], t[2], t[3], t[4], t[5], t[6]
868 rx, sx = rx * r_x + sx * s_y, rx * s_x + sx * r_y
869 sy, ry = sy * r_x + ry * s_y, sy * s_x + ry * r_y
870 tx, ty = tx * r_x + ty * s_y, tx * s_x + ty * r_y
871 end
872 nofmatrices = nofmatrices + 1
873 matrices[nofmatrices] = { rx, sx, sy, ry, tx, ty }
874 end
875
876 pdf_goto_pagemode()
877 pdf_set_pos(pos_h,pos_v)
878
879 b = b + 1
880 buffer[b] = s
881 end
882 end
883 end
884
885 flushers.setmatrix = flushsetmatrix
886 flushers.save = flushsave
887 flushers.restore = flushrestore
888
889 function lpdf.hasmatrix()
890 return nofmatrices > 0
891 end
892
893 function lpdf.getmatrix()
894 if nofmatrices > 0 then
895 return unpack(matrices[nofmatrices])
896 else
897 return 1, 0, 0, 1, 0, 0
898 end
899 end
900
901 flushers.pushorientation = function(orientation,pos_h,pos_v,pos_r)
902 pdf_goto_pagemode()
903 pdf_set_pos(pos_h,pos_v)
904 b = b + 1 ; buffer[b] = "q"
905 if orientation == 1 then
906 b = b + 1 ; buffer[b] = "0 -1 1 0 0 0 cm"
907 elseif orientation == 2 then
908 b = b + 1 ; buffer[b] = "-1 0 0 -1 0 0 cm"
909 elseif orientation == 3 then
910 b = b + 1 ; buffer[b] = "0 1 -1 0 0 0 cm"
911 end
912 end
913
914 flushers.poporientation = function(orientation,pos_h,pos_v,pos_r)
915 pdf_goto_pagemode()
916 pdf_set_pos(pos_h,pos_v)
917 b = b + 1 ; buffer[b] = "Q"
918 end
919
920
921
922 flushers.startmatrix = function(current,pos_h,pos_v)
923 flushsave(current,pos_h,pos_v)
924 flushsetmatrix(current,pos_h,pos_v)
925 end
926
927 flushers.stopmatrix = function(current,pos_h,pos_v)
928 flushrestore(current,pos_h,pos_v)
929 end
930
931 flushers.startscaling = function(current,pos_h,pos_v)
932 flushsave(current,pos_h,pos_v)
933 flushsetmatrix(current,pos_h,pos_v)
934 end
935
936 flushers.stopscaling = function(current,pos_h,pos_v)
937 flushrestore(current,pos_h,pos_v)
938 end
939
940 flushers.startrotation = function(current,pos_h,pos_v)
941 flushsave(current,pos_h,pos_v)
942 flushsetmatrix(current,pos_h,pos_v)
943 end
944
945 flushers.stoprotation = function(current,pos_h,pos_v)
946 flushrestore(current,pos_h,pos_v)
947 end
948
949 flushers.startmirroring = function(current,pos_h,pos_v)
950 flushsave(current,pos_h,pos_v)
951 flushsetmatrix(current,pos_h,pos_v)
952 end
953
954 flushers.stopmirroring = function(current,pos_h,pos_v)
955 flushrestore(current,pos_h,pos_v)
956 end
957
958 flushers.startclipping = function(current,pos_h,pos_v)
959 flushsave(current,pos_h,pos_v)
960
961 pdf_goto_pagemode()
962 b = b + 1 ; buffer[b] = formatters["0 w %s W n"](nodeproperties[current].path)
963 end
964
965 flushers.stopclipping = function(current,pos_h,pos_v)
966 flushrestore(current,pos_h,pos_v)
967 end
968
969end
970
971do
972
973 local nodeproperties = nodes.properties.data
974
975 flushers.setstate = function(current,pos_h,pos_v)
976 local p = nodeproperties[current]
977 if p then
978 local d = p.data
979 if d and d ~= "" then
980 pdf_goto_pagemode()
981 b = b + 1 ; buffer[b] = d
982 end
983 end
984 end
985
986end
987
988
989
990local flushedxforms = { }
991local localconverter = nil
992
993local flushimage do
994
995 local pdfbackend = backends.registered.pdf
996 local nodeinjections = pdfbackend.nodeinjections
997 local codeinjections = pdfbackend.codeinjections
998
999 local newimagerule = nuts.pool.imagerule
1000 local newboxrule = nuts.pool.boxrule
1001
1002 local setprop = nuts.setprop
1003 local getprop = nuts.getprop
1004 local setattrlist = nuts.setattrlist
1005
1006 local getwhd = nuts.getwhd
1007 local flushlist = nuts.flushlist
1008 local getdata = nuts.getdata
1009
1010 local rulecodes = nodes.rulecodes
1011 local normalrule_code = rulecodes.normal
1012 local boxrule_code = rulecodes.box
1013 local imagerule_code = rulecodes.image
1014 local emptyrule_code = rulecodes.empty
1015 local userrule_code = rulecodes.user
1016 local overrule_code = rulecodes.over
1017 local underrule_code = rulecodes.under
1018 local fractionrule_code = rulecodes.fraction
1019 local radicalrule_code = rulecodes.radical
1020 local outlinerule_code = rulecodes.outline
1021
1022 local processrule = nodes.rules.process
1023
1024 local f_fm = formatters["/Fm%d Do"]
1025 local f_im = formatters["/Im%d Do"]
1026 local f_gr = formatters["/Gp%d Do"]
1027
1028 local s_b <const> = "q"
1029 local s_e <const> = "Q"
1030
1031 local f_v = formatters["[] 0 d 0 J %.6N w 0 0 m %.6N 0 l S"]
1032 local f_h = formatters["[] 0 d 0 J %.6N w 0 0 m 0 %.6N l S"]
1033
1034 local f_f = formatters["0 0 %.6N %.6N re f"]
1035 local f_o = formatters["[] 0 d 0 J 0 0 %.6N %.6N re S"]
1036 local f_w = formatters["[] 0 d 0 J %.6N w 0 0 %.6N %.6N re S"]
1037
1038 local f_b = formatters["%.6N w 0 %.6N %.6N %.6N re f"]
1039 local f_x = formatters["[] 0 d 0 J %.6N w %.6N %.6N %.6N %.6N re S"]
1040 local f_y = formatters["[] 0 d 0 J %.6N w %.6N %.6N %.6N %.6N re S %.6N 0 m %.6N 0 l S"]
1041
1042
1043
1044 local boxresources, n = { }, 0
1045
1046 getxformname = function(index)
1047 local l = boxresources[index]
1048 if l then
1049 return l.name
1050 else
1051 report("no box resource %S",index)
1052 end
1053 end
1054
1055 lpdf.getxformname = getxformname
1056
1057 local pdfcollectedresources = lpdf.collectedresources
1058
1059 function codeinjections.saveboxresource(box,attributes,resources,immediate,kind,margin)
1060 n = n + 1
1061 local immediate = true
1062 local margin = margin or 0
1063 local objnum = pdfreserveobject()
1064 local list = tonut(type(box) == "number" and tex.takebox(box) or box)
1065
1066 if resources == true then
1067 resources = pdfcollectedresources()
1068 end
1069
1070 local width, height, depth = getwhd(list)
1071
1072 local l = {
1073 width = width,
1074 height = height,
1075 depth = depth,
1076 margin = margin,
1077 attributes = attributes,
1078 resources = resources,
1079 list = nil,
1080 type = kind,
1081 name = n,
1082 index = objnum,
1083 objnum = objnum,
1084 }
1085 boxresources[objnum] = l
1086 if immediate then
1087 localconverter(list,"xform",objnum,l)
1088 flushedxforms[objnum] = { true , objnum }
1089 flushlist(list)
1090 else
1091 l.list = list
1092 end
1093 return objnum
1094 end
1095
1096 function nodeinjections.useboxresource(index,wd,ht,dp)
1097 local l = boxresources[index]
1098 if l then
1099 if wd or ht or dp then
1100 wd, ht, dp = wd or 0, ht or 0, dp or 0
1101 else
1102 wd, ht, dp = l.width, l.height, l.depth
1103 end
1104 local rule = newboxrule(wd,ht,dp)
1105 setattrlist(rule,true)
1106 setprop(rule,"index",index)
1107 return tonode(rule), wd, ht, dp
1108 else
1109 report("no box resource %S",index)
1110 end
1111 end
1112
1113 local function getboxresourcedimensions(index)
1114 local l = boxresources[index]
1115 if l then
1116 return l.width, l.height, l.depth, l.margin
1117 else
1118 report("no box resource %S",index)
1119 end
1120 end
1121
1122 nodeinjections.getboxresourcedimensions = getboxresourcedimensions
1123
1124 function codeinjections.getboxresourcebox(index)
1125 local l = boxresources[index]
1126 if l then
1127 return l.list
1128 end
1129 end
1130
1131
1132
1133
1134 local function flushpdfxform(current,pos_h,pos_v,pos_r,size_h,size_v)
1135
1136 local objnum = getprop(current,"index")
1137 local name = getxformname(objnum)
1138 local info = flushedxforms[objnum]
1139 local r = boxresources[objnum]
1140 if not info then
1141 info = { false , objnum }
1142 flushedxforms[objnum] = info
1143 end
1144 local wd, ht, dp = getboxresourcedimensions(objnum)
1145
1146
1147 local htdp = ht + dp
1148 if wd == 0 or size_h == 0 or htdp == 0 or size_v == 0 then
1149 return
1150 end
1151
1152 local rx, ry = 1, 1
1153 if wd ~= size_h or htdp ~= size_v then
1154 rx = size_h / wd
1155 ry = size_v / htdp
1156 end
1157
1158 usedxforms[objnum] = true
1159 pdf_goto_pagemode()
1160 calc_pdfpos(pos_h,pos_v)
1161 tx = cmtx * bpfactor
1162 ty = cmty * bpfactor
1163 b = b + 1 ; buffer[b] = s_b
1164 b = b + 1 ; buffer[b] = f_cm(rx,0,0,ry,tx,ty)
1165 b = b + 1 ; buffer[b] = f_fm(name)
1166 b = b + 1 ; buffer[b] = s_e
1167 end
1168
1169
1170
1171 local imagetypes = images.types
1172 local img_none = imagetypes.none
1173 local img_pdf = imagetypes.pdf
1174 local img_stream = imagetypes.stream
1175
1176 local one_bp = 65536 * bpfactor
1177
1178 local imageresources, n = { }, 0
1179
1180 getximagename = function(index)
1181 local l = imageresources[index]
1182 if l then
1183 return l.name
1184 else
1185 report("no image resource %S",index)
1186 end
1187 end
1188
1189
1190
1191
1192
1193 usedxgroups = { }
1194 local groups = 0
1195 local group = nil
1196
1197 local flushgroup = function(content,bbox)
1198 if not group then
1199 group = pdfdictionary {
1200 Type = pdfconstant("Group"),
1201 S = pdfconstant("Transparency"),
1202 }
1203 end
1204 local wrapper = pdfdictionary {
1205 Type = pdf_xobject,
1206 Subtype = pdf_form,
1207 FormType = 1,
1208 Group = group,
1209 BBox = pdfarray(bbox),
1210 Resources = lpdf.collectedresources { serialize = false },
1211 }
1212 local objnum = pdfflushstreamobject(content,wrapper,false)
1213 groups = groups + 1
1214 usedxgroups[groups] = objnum
1215 return f_gr(groups)
1216 end
1217
1218 flushers.group = flushgroup
1219 lpdf.flushgroup = flushgroup
1220
1221
1222
1223 local function flushpdfximage(current,pos_h,pos_v,pos_r,size_h,size_v)
1224
1225 local width,
1226 height,
1227 depth = getwhd(current)
1228 local total = height + depth
1229 local transform = getprop(current,"transform") or 0
1230 local index = getprop(current,"index") or 0
1231 local kind,
1232 xorigin,
1233 yorigin,
1234 xsize,
1235 ysize,
1236 rotation,
1237 objnum,
1238 groupref = pdfincludeimage(index)
1239
1240 if not kind then
1241 report("invalid image %S",index)
1242 return
1243 end
1244
1245 local rx, sx, sy, ry, tx, ty = 1, 0, 0, 1, 0, 0
1246
1247
1248
1249 if kind == img_pdf or kind == img_stream then
1250 rx, ry, tx, ty = 1/xsize, 1/ysize, xorigin/xsize, yorigin/ysize
1251 else
1252
1253
1254
1255
1256
1257 rx, ry = bpfactor, bpfactor
1258 end
1259
1260 if (transform & 7) > 3 then
1261
1262 rx, tx = -rx, -tx
1263 end
1264 local t = (transform + rotation) & 3
1265 if t == 0 then
1266
1267 elseif t == 1 then
1268
1269 rx, sx, sy, ry, tx, ty = 0, rx, -ry, 0, -ty, tx
1270 elseif t == 2 then
1271
1272 rx, ry, tx, ty = -rx, -ry, -tx, -ty
1273 elseif t == 3 then
1274
1275 rx, sx, sy, ry, tx, ty = 0, -rx, ry, 0, ty, -tx
1276 end
1277
1278 rx = rx * width
1279 sx = sx * total
1280 sy = sy * width
1281 ry = ry * total
1282 tx = pos_h - tx * width
1283 ty = pos_v - ty * total
1284
1285 local t = transform + rotation
1286
1287 if (transform & 7) > 3 then
1288 t = t + 1
1289 end
1290
1291 t = t & 3
1292
1293 if t == 0 then
1294
1295 elseif t == 1 then
1296
1297 tx = tx + width
1298 elseif t == 2 then
1299
1300 tx = tx + width
1301 ty = ty + total
1302 elseif t == 3 then
1303
1304 ty = ty + total
1305 end
1306
1307
1308
1309
1310
1311
1312
1313 usedximages[index] = objnum
1314
1315 pdf_goto_pagemode()
1316
1317 calc_pdfpos(tx,ty)
1318
1319 tx = cmtx * bpfactor
1320 ty = cmty * bpfactor
1321
1322 b = b + 1 ; buffer[b] = s_b
1323 b = b + 1 ; buffer[b] = f_cm(rx,sx,sy,ry,tx,ty)
1324 b = b + 1 ; buffer[b] = f_im(index)
1325 b = b + 1 ; buffer[b] = s_e
1326 end
1327
1328 flushimage = function(index,width,height,depth,pos_h,pos_v)
1329
1330
1331
1332 local total = height + depth
1333 local kind,
1334 xorigin, yorigin,
1335 xsize, ysize,
1336 rotation,
1337 objnum,
1338 groupref = pdfincludeimage(index)
1339
1340 local rx = width / xsize
1341 local sx = 0
1342 local sy = 0
1343 local ry = total / ysize
1344 local tx = pos_h
1345
1346
1347 local ty = pos_v
1348
1349 usedximages[index] = objnum
1350
1351 pdf_goto_pagemode()
1352
1353 calc_pdfpos(tx,ty)
1354
1355 tx = cmtx * bpfactor
1356 ty = cmty * bpfactor
1357
1358 b = b + 1 ; buffer[b] = s_b
1359 b = b + 1 ; buffer[b] = f_cm(rx,sx,sy,ry,tx,ty)
1360 b = b + 1 ; buffer[b] = f_im(index)
1361 b = b + 1 ; buffer[b] = s_e
1362 end
1363
1364 flushers.image = flushimage
1365
1366
1367
1368
1369
1370
1371
1372
1373 flushers.rule = function(current,pos_h,pos_v,pos_r,size_h,size_v,subtype)
1374
1375 if subtype == emptyrule_code then
1376 return
1377 elseif subtype == boxrule_code then
1378 return flushpdfxform(current,pos_h,pos_v,pos_r,size_h,size_v)
1379 elseif subtype == imagerule_code then
1380 return flushpdfximage(current,pos_h,pos_v,pos_r,size_h,size_v)
1381 end
1382 if subtype == userrule_code or subtype >= overrule_code and subtype <= radicalrule_code then
1383 pdf_goto_pagemode()
1384 b = b + 1 ; buffer[b] = s_b
1385 pdf_set_pos_temp(pos_h,pos_v)
1386 processrule(current,size_h,size_v,pos_r)
1387 b = b + 1 ; buffer[b] = s_e
1388 return
1389 end
1390
1391 pdf_goto_pagemode()
1392
1393
1394
1395 b = b + 1 ; buffer[b] = s_b
1396
1397 local dim_h = size_h * bpfactor
1398 local dim_v = size_v * bpfactor
1399 local rule
1400
1401 if dim_v <= one_bp then
1402 pdf_set_pos_temp(pos_h,pos_v + 0.5 * size_v)
1403 rule = f_v(dim_v,dim_h)
1404 elseif dim_h <= one_bp then
1405 pdf_set_pos_temp(pos_h + 0.5 * size_h,pos_v)
1406 rule = f_h(dim_h,dim_v)
1407 else
1408 pdf_set_pos_temp(pos_h,pos_v)
1409 if subtype == outlinerule_code then
1410 local linewidth = getdata(current)
1411 if linewidth > 0 then
1412 rule = f_w(linewidth * bpfactor,dim_h,dim_v)
1413 else
1414 rule = f_o(dim_h,dim_v)
1415 end
1416 else
1417 rule = f_f(dim_h,dim_v)
1418 end
1419 end
1420
1421 b = b + 1 ; buffer[b] = rule
1422 b = b + 1 ; buffer[b] = s_e
1423
1424
1425
1426
1427 end
1428
1429 flushers.simplerule = function(pos_h,pos_v,pos_r,size_h,size_v)
1430 pdf_goto_pagemode()
1431
1432 b = b + 1 ; buffer[b] = s_b
1433
1434 local dim_h = size_h * bpfactor
1435 local dim_v = size_v * bpfactor
1436 local rule
1437
1438 if dim_v <= one_bp then
1439 pdf_set_pos_temp(pos_h,pos_v + 0.5 * size_v)
1440 rule = f_v(dim_v,dim_h)
1441 elseif dim_h <= one_bp then
1442 pdf_set_pos_temp(pos_h + 0.5 * size_h,pos_v)
1443 rule = f_h(dim_h,dim_v)
1444 else
1445 pdf_set_pos_temp(pos_h,pos_v)
1446 rule = f_f(dim_h,dim_v)
1447 end
1448
1449 b = b + 1 ; buffer[b] = rule
1450 b = b + 1 ; buffer[b] = s_e
1451 end
1452
1453 flushers.specialrule = function(pos_h,pos_v,pos_r,width,height,depth,line,outline,baseline)
1454 pdf_goto_pagemode()
1455
1456 b = b + 1 ; buffer[b] = s_b
1457
1458 local width = bpfactor * width
1459 local height = bpfactor * height
1460 local depth = bpfactor * depth
1461 local total = height + depth
1462 local line = bpfactor * line
1463 local half = line / 2
1464 local rule
1465
1466 if outline then
1467 local d = -depth + half
1468 local w = width - line
1469 local t = total - line
1470 if baseline then
1471 rule = f_y(line,half,d,w,t,half,w)
1472 else
1473 rule = f_x(line,half,d,w,t)
1474 end
1475 else
1476 rule = f_b(line,-depth,width,total)
1477 end
1478 pdf_set_pos_temp(pos_h,pos_v)
1479
1480 b = b + 1 ; buffer[b] = rule
1481 b = b + 1 ; buffer[b] = s_e
1482 end
1483
1484end
1485
1486
1487
1488local wrapupdocument, registerpage do
1489
1490 local pages = { }
1491 local maxkids = 10
1492 local nofpages = 0
1493 local pagetag = "unset"
1494
1495 registerpage = function(object)
1496 nofpages = nofpages + 1
1497 local objnum = pdfpagereference(nofpages)
1498 pages[nofpages] = {
1499 page = nofpages,
1500 objnum = objnum,
1501 object = object,
1502 tag = pagetag,
1503 }
1504 end
1505
1506 function lpdf.setpagetag(tag)
1507 pagetag = tag or "unset"
1508 end
1509
1510 function lpdf.getnofpages()
1511 return nofpages
1512 end
1513
1514 function lpdf.getpagetags()
1515 local list = { }
1516 for i=1,nofpages do
1517 list[i] = pages[i].tag
1518 end
1519 return list
1520 end
1521
1522 function lpdf.setpageorder(mapping)
1523
1524 local list = table.sortedkeys(mapping)
1525 local n = #list
1526 if n == nofpages then
1527 local done = { }
1528 local hash = { }
1529 for i=1,n do
1530 local order = mapping[list[i]]
1531 if hash[order] then
1532 report("invalid page order, duplicate entry %i",order)
1533 return
1534 elseif order < 1 or order > nofpages then
1535 report("invalid page order, no page %i",order)
1536 return
1537 else
1538 done[i] = pages[order]
1539 hash[order] = true
1540 end
1541 end
1542 pages = done
1543 else
1544 report("invalid page order, %i entries expected",nofpages)
1545 end
1546 end
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563 wrapupdocument = function(driver)
1564
1565
1566 local pagetree = { }
1567 local parent = nil
1568 local minimum = 0
1569 local maximum = 0
1570 local current = 0
1571 if #pages > 1.5 * maxkids then
1572 repeat
1573 local plist, pnode
1574 if current == 0 then
1575 plist, minimum = pages, 1
1576 elseif current == 1 then
1577 plist, minimum = pagetree, 1
1578 else
1579 plist, minimum = pagetree, maximum + 1
1580 end
1581 maximum = #plist
1582 if maximum > minimum then
1583 local kids
1584 for i=minimum,maximum do
1585 local p = plist[i]
1586 if not pnode or #kids == maxkids then
1587 kids = pdfarray()
1588 parent = pdfreserveobject()
1589 pnode = pdfdictionary {
1590 objnum = parent,
1591 Type = pdf_pages,
1592 Kids = kids,
1593 Count = 0,
1594 }
1595 pagetree[#pagetree+1] = pnode
1596 end
1597 kids[#kids+1] = pdfreference(p.objnum)
1598 pnode.Count = pnode.Count + (p.Count or 1)
1599 p.Parent = pdfreference(parent)
1600 end
1601 end
1602 current = current + 1
1603 until maximum == minimum
1604
1605 for i=1,#pagetree do
1606 local entry = pagetree[i]
1607 local objnum = entry.objnum
1608 entry.objnum = nil
1609 pdfflushobject(objnum,entry)
1610 end
1611 else
1612
1613 local kids = pdfarray()
1614 local list = pdfdictionary {
1615 Type = pdf_pages,
1616 Kids = kids,
1617 Count = nofpages,
1618 }
1619 parent = pdfreserveobject()
1620 for i=1,nofpages do
1621 local page = pages[i]
1622 kids[#kids+1] = pdfreference(page.objnum)
1623 page.Parent = pdfreference(parent)
1624 end
1625 pdfflushobject(parent,list)
1626 end
1627 for i=1,nofpages do
1628 local page = pages[i]
1629 local object = page.object
1630 object.Parent = page.Parent
1631 pdfflushobject(page.objnum,object)
1632 end
1633 lpdf.addtocatalog("Pages",pdfreference(parent))
1634
1635 end
1636
1637end
1638
1639pdf_h, pdf_v = 0, 0
1640
1641local function initialize(driver,details)
1642 reset_variables(details)
1643 reset_buffer()
1644end
1645
1646
1647
1648
1649
1650
1651local compact = false
1652
1653do
1654
1655
1656
1657
1658
1659
1660 local P, R, S, Cs, lpegmatch = lpeg.P, lpeg.R, lpeg.S, lpeg.Cs, lpeg.match
1661
1662 local p_ds = (R("09") + S(" ."))^1
1663
1664 local p_nl = S("\n")^1
1665 local p_eg = P("Q")
1666
1667 local p_cl = p_ds * (P("rg") + P("g") + P("k")) * p_ds * (P("RG") + P("G") + P("K"))
1668
1669 local p_tr = P("/Tr") * p_ds * P("gs")
1670
1671 local p_no_cl = (p_cl * p_nl) / ""
1672 local p_no_tr = (p_tr * p_nl) / ""
1673 local p_no_nl = 1 - p_nl
1674
1675 local p_do_cl = p_cl * p_nl
1676 local p_do_tr = p_tr * p_nl
1677
1678 local p_do_eg = p_eg * p_nl
1679
1680 local pattern = Cs( (
1681 (p_no_cl + p_no_tr)^0 * p_do_eg
1682 + p_no_tr * p_no_cl * p_do_tr * p_do_cl
1683 + p_no_cl * p_do_cl
1684 + p_no_tr * p_do_tr
1685 + p_no_nl^1
1686 + 1
1687 )^1 )
1688
1689 local oldsize = 0
1690 local newsize = 0
1691
1692 directives.register("pdf.compact", function(v)
1693 compact = v and function(s)
1694 oldsize = oldsize + #s
1695 s = lpegmatch(pattern,s) or s
1696 newsize = newsize + #s
1697 return s
1698 end
1699 end)
1700
1701 statistics.register("pdf pagestream",function()
1702 if oldsize ~= newsize then
1703 return string.format("old size: %i, new size %i",oldsize,newsize)
1704 end
1705 end)
1706
1707
1708end
1709
1710local flushdeferred
1711
1712local level = 0
1713
1714local finalize do
1715
1716 local f_font = formatters["F%d"]
1717
1718 local f_form = formatters["Fm%d"]
1719 local f_group = formatters["Gp%d"]
1720 local f_image = formatters["Im%d"]
1721
1722 finalize = function(driver,details)
1723
1724 level = level + 1
1725
1726 pdf_goto_pagemode()
1727
1728 local objnum = details.objnum
1729 local specification = details.specification
1730
1731 local content = concat(buffer,"\n",1,b)
1732
1733 if compact then
1734 content = compact(content)
1735 end
1736
1737 local fonts = nil
1738 local xforms = nil
1739
1740 if next(usedfonts) then
1741 fonts = pdfdictionary { }
1742 for k, v in next, usedfonts do
1743
1744 fonts[f_font(v)] = pdfreference(usedfontobjects[k])
1745 end
1746 end
1747
1748
1749
1750
1751 if next(usedxforms) or next(usedximages) or next(usedxgroups) then
1752 xforms = pdfdictionary { }
1753 for k in sortedhash(usedxforms) do
1754 xforms[f_form(getxformname(k))] = pdfreference(k)
1755 end
1756 for k, v in sortedhash(usedximages) do
1757 xforms[f_image(k)] = pdfreference(v)
1758 end
1759 for k, v in sortedhash(usedxgroups) do
1760 xforms[f_group(k)] = pdfreference(v)
1761 end
1762 end
1763
1764 reset_buffer()
1765
1766
1767
1768 if shippingmode == "page" then
1769
1770 local pageproperties = lpdf.getpageproperties()
1771
1772 local pageresources = pageproperties.pageresources
1773 local pageattributes = pageproperties.pageattributes
1774 local pagesattributes = pageproperties.pagesattributes
1775
1776 pageresources.Font = fonts
1777 pageresources.XObject = xforms
1778 pageresources.ProcSet = lpdf.procset()
1779
1780 local bbox = pdfarray {
1781 boundingbox[1] * bpfactor,
1782 boundingbox[2] * bpfactor,
1783 boundingbox[3] * bpfactor,
1784 boundingbox[4] * bpfactor,
1785 }
1786
1787 local contentsobj = pdfflushstreamobject(content,false,true)
1788
1789 pageattributes.Type = pdf_page
1790 pageattributes.Contents = pdfreference(contentsobj)
1791 pageattributes.Resources = pageresources
1792
1793
1794 pageattributes.MediaBox = pdfsharedobject(bbox)
1795 pageattributes.Parent = nil
1796 pageattributes.Group = nil
1797
1798
1799
1800 registerpage(pageattributes)
1801
1802 lpdf.finalizepage(true)
1803
1804 local TrimBox = pageattributes.TrimBox
1805 local CropBox = pageattributes.CropBox
1806 local BleedBox = pageattributes.BleedBox
1807
1808
1809
1810 if TrimBox then pageattributes.TrimBox = pdfsharedobject(TrimBox ) end
1811 if CropBox then pageattributes.CropBox = pdfsharedobject(CropBox ) end
1812 if BleedBox then pageattributes.BleedBox = pdfsharedobject(BleedBox) end
1813
1814 else
1815
1816 local xformtype = specification.type or 0
1817 local margin = specification.margin or 0
1818 local attributes = specification.attributes or ""
1819 local resources = specification.resources or ""
1820
1821 local wrapper = nil
1822
1823 if xformtype == 0 then
1824 wrapper = pdfdictionary {
1825 Type = pdf_xobject,
1826 Subtype = pdf_form,
1827 FormType = 1,
1828 BBox = nil,
1829 Matrix = nil,
1830 Resources = nil,
1831 }
1832 else
1833 wrapper = pdfdictionary {
1834 BBox = nil,
1835 Matrix = nil,
1836 Resources = nil,
1837 }
1838 end
1839 if xformtype == 0 or xformtype == 1 or xformtype == 3 then
1840 wrapper.BBox = pdfarray {
1841 -margin * bpfactor,
1842 -margin * bpfactor,
1843 (boundingbox[3] + margin) * bpfactor,
1844 (boundingbox[4] + margin) * bpfactor,
1845 }
1846 end
1847 if xformtype == 0 or xformtype == 2 or xformtype == 3 then
1848
1849 wrapper.Matrix = pdfarray { 1, 0, 0, 1, 0, 0 }
1850 end
1851
1852 local patterns = true
1853
1854 if attributes.Type and attributes.Type == pdf_pattern then
1855 patterns = false
1856 end
1857
1858 local boxresources = lpdf.collectedresources {
1859 patterns = patterns,
1860 serialize = false,
1861 }
1862 boxresources.Font = fonts
1863 boxresources.XObject = xforms
1864
1865
1866
1867
1868 if resources ~= "" then
1869 boxresources = boxresources + resources
1870 end
1871 if attributes ~= "" then
1872 wrapper = wrapper + attributes
1873 end
1874
1875 wrapper.Resources = next(boxresources) and boxresources or nil
1876 wrapper.ProcSet = lpdf.procset()
1877
1878 pdfflushstreamobject(content,wrapper,true,specification.objnum)
1879
1880 end
1881
1882 for objnum in sortedhash(usedxforms) do
1883 local f = flushedxforms[objnum]
1884 if f[1] == false then
1885 f[1] = true
1886 local objnum = f[2]
1887 local specification = boxresources[objnum]
1888 local list = specification.list
1889 localconverter(list,"xform",f[2],specification)
1890 end
1891 end
1892
1893 pdf_h, pdf_v = 0, 0
1894
1895 if level == 1 then
1896 flushdeferred()
1897 end
1898 level = level - 1
1899
1900 end
1901
1902end
1903
1904
1905
1906local objects = { }
1907local streams = { }
1908local nofobjects = 0
1909local offset = 0
1910local f = false
1911local flush = false
1912local threshold = 40
1913local objectstream = true
1914local compress = true
1915local cache = false
1916local info = ""
1917local catalog = ""
1918local lastdeferred = false
1919local majorversion = 1
1920local minorversion = 7
1921
1922directives.register("backend.pdf.threshold",function(v)
1923 if v then
1924 threshold = tonumber(v) or 40
1925 else
1926 threshold = -1000
1927 end
1928end)
1929
1930local f_object = formatters["%i 0 obj\010%s\010endobj\010"]
1931local f_stream_n_u = formatters["%i 0 obj\010<< /Length %i >>\010stream\010%s\010endstream\010endobj\010"]
1932local f_stream_n_c = formatters["%i 0 obj\010<< /Filter /FlateDecode /Length %i >>\010stream\010%s\010endstream\010endobj\010"]
1933local f_stream_d_u = formatters["%i 0 obj\010<< %s /Length %i >>\010stream\010%s\010endstream\010endobj\010"]
1934local f_stream_d_c = formatters["%i 0 obj\010<< %s /Filter /FlateDecode /Length %i >>\010stream\010%s\010endstream\010endobj\010"]
1935local f_stream_d_r = formatters["%i 0 obj\010<< %s >>\010stream\010%s\010endstream\010endobj\010"]
1936
1937
1938local f_stream_b_n_u = formatters["%i 0 obj\010<< /Length %i >>\010stream\010"]
1939local f_stream_b_n_c = formatters["%i 0 obj\010<< /Filter /FlateDecode /Length %i >>\010stream\010"]
1940local f_stream_b_d_u = formatters["%i 0 obj\010<< %s /Length %i >>\010stream\010"]
1941local f_stream_b_d_c = formatters["%i 0 obj\010<< %s /Filter /FlateDecode /Length %i >>\010stream\010"]
1942local f_stream_b_d_r = formatters["%i 0 obj\010<< %s >>\010stream\010"]
1943
1944
1945local s_stream_e <const> = "\010endstream\010endobj\010"
1946
1947do
1948
1949
1950
1951
1952 function lpdf.setversion(major,minor)
1953 majorversion = tonumber(major) or majorversion
1954 minorversion = tonumber(minor) or minorversion
1955 end
1956
1957 function lpdf.getversion(major,minor)
1958 return majorversion, minorversion
1959 end
1960
1961 function lpdf.majorversion() return majorversion end
1962 function lpdf.minorversion() return minorversion end
1963
1964
1965
1966
1967 local frozen = false
1968 local clevel = 3
1969 local olevel = 1
1970
1971 function lpdf.setcompression(level,objectlevel,freeze)
1972 if not frozen then
1973 compress = level and level ~= 0 and true or false
1974 objectstream = objectlevel and objectlevel ~= 0 and true or false
1975 frozen = freeze
1976 end
1977 end
1978
1979 function lpdf.getcompression()
1980 return compress and olevel or 0, objectstream and clevel or 0
1981 end
1982
1983 function lpdf.compresslevel()
1984 return compress and olevel or 0
1985 end
1986
1987 function lpdf.objectcompresslevel()
1988 return objectstream and clevel or 0
1989 end
1990
1991 if environment.arguments.nocompression then
1992 lpdf.setcompression(0,0,true)
1993 end
1994
1995end
1996
1997local addtocache, flushcache, cache do
1998
1999 local data, d = { }, 0
2000 local list, l = { }, 0
2001 local coffset = 0
2002 local indices = { }
2003
2004 local maxsize = 32 * 1024
2005 local maxcount = 0xFF
2006
2007 addtocache = function(n,str)
2008 local size = #str
2009 if size == 0 then
2010
2011 return
2012 end
2013 if coffset + size > maxsize or d == maxcount then
2014 flushcache()
2015 end
2016 if d == 0 then
2017 nofobjects = nofobjects + 1
2018 objects[nofobjects] = false
2019 streams[nofobjects] = indices
2020 cache = nofobjects
2021 end
2022 objects[n] = - cache
2023 indices[n] = d
2024 d = d + 1
2025
2026 data[d] = str
2027 l = l + 1 ; list[l] = n
2028 l = l + 1 ; list[l] = coffset
2029 coffset = coffset + size + 1
2030 end
2031
2032 local p_ObjStm = pdfconstant("ObjStm")
2033
2034 flushcache = function()
2035 if l > 0 then
2036 list = concat(list," ")
2037 data[0] = list
2038 data = concat(data,"\010",0,d)
2039 local strobj = pdfdictionary {
2040 Type = p_ObjStm,
2041 N = d,
2042 First = #list + 1,
2043 }
2044 objects[cache] = offset
2045 local fb
2046 if compress then
2047 local size = #data
2048 local comp = compressdata(data,size)
2049 if comp and #comp < size then
2050 data = comp
2051 fb = f_stream_b_d_c
2052 else
2053 fb = f_stream_b_d_u
2054 end
2055 else
2056 fb = f_stream_b_d_u
2057 end
2058 local s = #data
2059 local b = fb(cache,strobj(),s)
2060 local e = s_stream_e
2061 flush(f,b)
2062 flush(f,data)
2063 flush(f,e)
2064 offset = offset + #b + s + #e
2065 data, d = { }, 0
2066 list, l = { }, 0
2067 coffset = 0
2068 indices = { }
2069 end
2070 end
2071
2072end
2073
2074do
2075
2076 local names = { }
2077 local cache = { }
2078 local nofpages = 0
2079
2080 local texgetcount = tex.getcount
2081
2082 pdfreserveobject = function(name)
2083 nofobjects = nofobjects + 1
2084 objects[nofobjects] = false
2085 if name then
2086 names[name] = nofobjects
2087 if trace_objects then
2088 report_objects("reserving number %a under name %a",nofobjects,name)
2089 end
2090 elseif trace_objects then
2091 report_objects("reserving number %a",nofobjects)
2092 end
2093 return nofobjects
2094 end
2095
2096 pdfpagereference = function(n,complete)
2097 if n == true or not n then
2098 complete = n
2099 n = texgetcount("realpageno")
2100 end
2101 if n > nofpages then
2102 nofpages = n
2103 end
2104 local r = pdfgetpagereference(n)
2105 return complete and pdfreference(r) or r
2106 end
2107
2108 lpdf.reserveobject = pdfreserveobject
2109 lpdf.pagereference = pdfpagereference
2110
2111 function lpdf.lastreferredpage()
2112 return nofpages
2113 end
2114
2115 function lpdf.nofpages()
2116 return structures.pages.nofpages
2117 end
2118
2119 function lpdf.object(...)
2120 pdfdeferredobject(...)
2121 end
2122
2123 function lpdf.delayedobject(data,n)
2124 if n then
2125 pdfdeferredobject(n,data)
2126 else
2127 n = pdfdeferredobject(data)
2128 end
2129
2130 return n
2131 end
2132
2133 pdfflushobject = function(name,data)
2134 if data then
2135 local named = names[name]
2136 if named then
2137 if not trace_objects then
2138 elseif trace_details then
2139 report_objects("flushing data to reserved object with name %a, data: %S",name,data)
2140 else
2141 report_objects("flushing data to reserved object with name %a",name)
2142 end
2143 return pdfimmediateobject(named,tostring(data))
2144 else
2145 if not trace_objects then
2146 elseif trace_details then
2147 report_objects("flushing data to reserved object with number %s, data: %S",name,data)
2148 else
2149 report_objects("flushing data to reserved object with number %s",name)
2150 end
2151 return pdfimmediateobject(name,tostring(data))
2152 end
2153 else
2154 if trace_objects and trace_details then
2155 report_objects("flushing data: %S",name)
2156 end
2157 return pdfimmediateobject(tostring(name))
2158 end
2159 end
2160
2161 pdfflushstreamobject = function(data,dict,compressed,objnum)
2162 if trace_objects then
2163 report_objects("flushing stream object of %s bytes",#data)
2164 end
2165 local dtype = type(dict)
2166 local kind = compressed == "raw" and "raw" or "stream"
2167 local nolength = nil
2168 if compressed == "raw" then
2169 compressed = false
2170 nolength = true
2171
2172 end
2173 return pdfdeferredobject {
2174 objnum = objnum,
2175 immediate = true,
2176 nolength = nolength,
2177
2178 compresslevel = compressed,
2179 type = "stream",
2180 string = data,
2181 attr = (dtype == "string" and dict) or (dtype == "table" and dict()) or nil,
2182 }
2183 end
2184
2185 function lpdf.flushstreamfileobject(filename,dict,compressed,objnum)
2186 if trace_objects then
2187 report_objects("flushing stream file object %a",filename)
2188 end
2189 local dtype = type(dict)
2190 return pdfdeferredobject {
2191 objnum = objnum,
2192 immediate = true,
2193
2194 compresslevel = compressed,
2195 type = "stream",
2196 file = filename,
2197 attr = (dtype == "string" and dict) or (dtype == "table" and dict()) or nil,
2198 }
2199 end
2200
2201 local shareobjectcache, shareobjectreferencecache = { }, { }
2202
2203 function lpdf.shareobject(content)
2204 if content == nil then
2205
2206 else
2207 content = tostring(content)
2208 local o = shareobjectcache[content]
2209 if not o then
2210 o = pdfimmediateobject(content)
2211 shareobjectcache[content] = o
2212 end
2213 return o
2214 end
2215 end
2216
2217 pdfsharedobject = function(content)
2218 if content == nil then
2219
2220 else
2221 content = tostring(content)
2222 local r = shareobjectreferencecache[content]
2223 if not r then
2224 local o = shareobjectcache[content]
2225 if not o then
2226 o = pdfimmediateobject(content)
2227 shareobjectcache[content] = o
2228 end
2229 r = pdfreference(o)
2230 shareobjectreferencecache[content] = r
2231 end
2232 return r
2233 end
2234 end
2235
2236 lpdf.flushobject = pdfflushobject
2237 lpdf.flushstreamobject = pdfflushstreamobject
2238 lpdf.shareobjectreference = pdfsharedobject
2239 lpdf.sharedobject = pdfsharedobject
2240
2241end
2242
2243local pages = table.setmetatableindex(function(t,k)
2244 local v = pdfreserveobject()
2245 t[k] = v
2246 return v
2247end)
2248
2249pdfgetpagereference = function(n)
2250 return pages[n]
2251end
2252
2253lpdf.getpagereference = pdfgetpagereference
2254
2255local function flushnormalobj(data,n)
2256 if not n then
2257 nofobjects = nofobjects + 1
2258 n = nofobjects
2259 end
2260 data = f_object(n,data)
2261 if level == 0 then
2262 objects[n] = offset
2263 offset = offset + #data
2264 flush(f,data)
2265 else
2266 if not lastdeferred then
2267 lastdeferred = n
2268 elseif n < lastdeferred then
2269 lastdeferred = n
2270 end
2271 objects[n] = data
2272 end
2273 return n
2274end
2275
2276local function flushstreamobj(data,n,dict,comp,nolength)
2277 if not data then
2278 report("no data for %S",dict)
2279 return
2280 end
2281 if not n then
2282 nofobjects = nofobjects + 1
2283 n = nofobjects
2284 end
2285 local size = #data
2286 if comp ~= false then
2287 comp = compress and size > threshold
2288 end
2289 if level == 0 then
2290 local b = nil
2291 local e = s_stream_e
2292 if nolength then
2293 b = f_stream_b_d_r(n,dict)
2294 else
2295 if comp then
2296 local compdata = compressdata(data,size)
2297 if compdata then
2298 local compsize = #compdata
2299 if compsize <= size - threshold then
2300 data = compdata
2301 size = compsize
2302 else
2303 comp = false
2304 end
2305 else
2306 comp = false
2307 end
2308 end
2309 if comp then
2310 b = dict and f_stream_b_d_c(n,dict,size) or f_stream_b_n_c(n,size)
2311 else
2312 b = dict and f_stream_b_d_u(n,dict,size) or f_stream_b_n_u(n,size)
2313 end
2314 end
2315 flush(f,b)
2316 flush(f,data)
2317 flush(f,e)
2318 objects[n] = offset
2319 offset = offset + #b + size + #e
2320 else
2321 if nolength then
2322 data = f_stream_d_r(n,dict,data)
2323 else
2324 if comp then
2325 local compdata = compressdata(data,size)
2326 if compdata then
2327 local compsize = #compdata
2328 if compsize <= size - threshold then
2329 data = compdata
2330 size = compsize
2331 else
2332 comp = false
2333 end
2334 else
2335 comp = false
2336 end
2337 end
2338 if comp then
2339 data = dict and f_stream_d_c(n,dict,size,data) or f_stream_n_c(n,size,data)
2340 else
2341 data = dict and f_stream_d_u(n,dict,size,data) or f_stream_n_u(n,size,data)
2342 end
2343 end
2344 if not lastdeferred then
2345 lastdeferred = n
2346 elseif n < lastdeferred then
2347 lastdeferred = n
2348 end
2349 objects[n] = data
2350 end
2351 return n
2352end
2353
2354flushdeferred = function()
2355 if lastdeferred then
2356 for n=lastdeferred,nofobjects do
2357 local o = objects[n]
2358 if type(o) == "string" then
2359 objects[n] = offset
2360 offset = offset + #o
2361 flush(f,o)
2362 end
2363 end
2364 lastdeferred = false
2365 end
2366end
2367
2368pdfimmediateobject = function(a,b,c,d)
2369 local kind
2370 local objnum, data, attr, filename
2371 local compresslevel, objcompression, nolength
2372 local argtype = type(a)
2373 if argtype == "table" then
2374 kind = a.type
2375
2376 objnum = a.objnum
2377 attr = a.attr
2378 compresslevel = a.compresslevel
2379 objcompression = a.objcompression
2380 filename = a.file
2381 data = a.string or a.stream or ""
2382 nolength = a.nolength
2383 if kind == "stream" then
2384 if filename then
2385 data = loaddata(filename) or ""
2386 end
2387 elseif kind == "raw"then
2388 if filename then
2389 data = loaddata(filename) or ""
2390 end
2391 elseif kind == "file"then
2392 kind = "raw"
2393 data = filename and loaddata(filename) or ""
2394 elseif kind == "streamfile" then
2395 kind = "stream"
2396 data = filename and loaddata(filename) or ""
2397 end
2398 else
2399 if argtype == "number" then
2400 objnum = a
2401 a, b, c = b, c, d
2402 else
2403 nofobjects = nofobjects + 1
2404 objnum = nofobjects
2405 end
2406 if b then
2407 if a == "stream" then
2408 kind = "stream"
2409 data = b
2410 elseif a == "file" then
2411
2412 data = loaddata(b)
2413 elseif a == "streamfile" then
2414 kind = "stream"
2415 data = loaddata(b)
2416 else
2417 data = ""
2418 end
2419 attr = c
2420 else
2421
2422 data = a
2423 end
2424 end
2425 if not objnum then
2426 nofobjects = nofobjects + 1
2427 objnum = nofobjects
2428 end
2429
2430 if kind == "stream" then
2431 flushstreamobj(data,objnum,attr,compresslevel,nolength)
2432 elseif objectstream and objcompression ~= false then
2433 addtocache(objnum,data)
2434 else
2435 flushnormalobj(data,objnum)
2436 end
2437 return objnum
2438end
2439
2440pdfdeferredobject = pdfimmediateobject
2441
2442lpdf.deferredobject = pdfimmediateobject
2443lpdf.immediateobject = pdfimmediateobject
2444
2445
2446
2447
2448
2449local openfile, closefile do
2450
2451
2452
2453
2454
2455
2456 local f_used = formatters["%010i 00000 n\013\010"]
2457 local f_link = formatters["%010i 00000 f\013\010"]
2458 local f_first = formatters["%010i 65535 f\013\010"]
2459
2460 local f_pdf_tag = formatters["%%PDF-%i.%i\010"]
2461 local f_xref = formatters["xref\0100 %i\010"]
2462 local f_trailer_id = formatters["trailer\010<< %s /ID [ <%s> <%s> ] >>\010startxref\010%i\010%%%%EOF"]
2463 local f_trailer_no = formatters["trailer\010<< %s >>\010startxref\010%i\010%%%%EOF"]
2464 local f_startxref = formatters["startxref\010%i\010%%%%EOF"]
2465
2466 local inmemory = false
2467 local close = false
2468 local update = false
2469
2470
2471 local banner <const> = "%\xC3\xCF\xCE\xD4\xC5\xD8\xD4\xD0\xC4\xC6\010"
2472
2473
2474
2475
2476 openfile = function(filename)
2477 if inmemory then
2478 local n = 0
2479 f = { }
2480 flush = function(f,s)
2481 n = n + 1 f[n] = s
2482
2483 end
2484 close = function(f)
2485 f = concat(f)
2486 io.savedata(filename,f)
2487 f = false
2488 end
2489 update = function(f,s)
2490 f[1] = s
2491 end
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503 else
2504 f = io.open(filename,"wb")
2505 if not f then
2506 report()
2507 report("quitting because file %a cannot be opened for writing",filename)
2508 report()
2509 os.exit()
2510 end
2511
2512 local m = getmetatable(f)
2513 flush = m.write or m.__index.write
2514 close = m.close or m.__index.close
2515 update = function(f,s)
2516 f:seek("set",0)
2517 f:write(s)
2518 end
2519 end
2520 local version = f_pdf_tag(majorversion,minorversion)
2521 flush(f,version)
2522 flush(f,banner)
2523 offset = offset + #version + #banner
2524 end
2525
2526 closefile = function(abort)
2527 if abort then
2528 close(f)
2529 if not environment.arguments.nodummy then
2530 f = io.open(abort,"wb")
2531 if f then
2532 local name = resolvers.findfile("context-lmtx-error.pdf")
2533 if name then
2534 local data = io.loaddata(name)
2535 if data then
2536 f:write(data)
2537 f:close()
2538 return
2539 end
2540 end
2541 f:close()
2542 end
2543 end
2544 os.remove(abort)
2545 else
2546 local xrefoffset = offset
2547 local lastfree = 0
2548 local noffree = 0
2549 local catalog = lpdf.getcatalog()
2550 local info = lpdf.getinfo()
2551 local trailerid = lpdf.gettrailerid()
2552 if objectstream then
2553 flushdeferred()
2554 flushcache()
2555
2556 xrefoffset = offset
2557
2558 nofobjects = nofobjects + 1
2559 objects[nofobjects] = offset
2560
2561
2562
2563
2564 local nofbytes = 4
2565 local c1, c2, c3, c4
2566 if offset <= 0xFFFF then
2567 nofbytes = 2
2568 for i=1,nofobjects do
2569 local o = objects[i]
2570 if not o then
2571 noffree = noffree + 1
2572 else
2573 local strm = o < 0
2574 if strm then
2575 o = -o
2576 end
2577 c1 = (o>>8)&0xFF
2578 c2 = (o>>0)&0xFF
2579 if strm then
2580 objects[i] = char(2,c1,c2,streams[o][i])
2581 else
2582 objects[i] = char(1,c1,c2,0)
2583 end
2584 end
2585 end
2586 if noffree > 0 then
2587 for i=nofobjects,1,-1 do
2588 local o = objects[i]
2589 if not o then
2590 local f1 = (lastfree>>8)&0xFF
2591 local f2 = (lastfree>>0)&0xFF
2592 objects[i] = char(0,f1,f2,0)
2593 lastfree = i
2594 end
2595 end
2596 end
2597 elseif offset <= 0xFFFFFF then
2598 nofbytes = 3
2599 for i=1,nofobjects do
2600 local o = objects[i]
2601 if not o then
2602 noffree = noffree + 1
2603 else
2604 local strm = o < 0
2605 if strm then
2606 o = -o
2607 end
2608 c1 = (o>>16)&0xFF
2609 c2 = (o>> 8)&0xFF
2610 c3 = (o>> 0)&0xFF
2611 if strm then
2612 objects[i] = char(2,c1,c2,c3,streams[o][i])
2613 else
2614 objects[i] = char(1,c1,c2,c3,0)
2615 end
2616 end
2617 end
2618 if noffree > 0 then
2619 for i=nofobjects,1,-1 do
2620 local o = objects[i]
2621 if not o then
2622 local f1 = (lastfree>>16)&0xFF
2623 local f2 = (lastfree>> 8)&0xFF
2624 local f3 = (lastfree>> 0)&0xFF
2625 objects[i] = char(0,f1,f2,f3,0)
2626 lastfree = i
2627 end
2628 end
2629 end
2630 else
2631 nofbytes = 4
2632 for i=1,nofobjects do
2633 local o = objects[i]
2634 if not o then
2635 noffree = noffree + 1
2636 else
2637 local strm = o < 0
2638 if strm then
2639 o = -o
2640 end
2641 c1 = (o>>24)&0xFF
2642 c2 = (o>>16)&0xFF
2643 c3 = (o>> 8)&0xFF
2644 c4 = (o>> 0)&0xFF
2645 if strm then
2646 objects[i] = char(2,c1,c2,c3,c4,streams[o][i])
2647 else
2648 objects[i] = char(1,c1,c2,c3,c4,0)
2649 end
2650 end
2651 end
2652 if noffree > 0 then
2653 for i=nofobjects,1,-1 do
2654 local o = objects[i]
2655 if not o then
2656 local f1 = (lastfree>>24)&0xFF
2657 local f2 = (lastfree>>16)&0xFF
2658 local f3 = (lastfree>> 8)&0xFF
2659 local f4 = (lastfree>> 0)&0xFF
2660 objects[i] = char(0,f1,f2,f3,f4,0)
2661 lastfree = i
2662 end
2663 end
2664 end
2665 end
2666 objects[0] = rep("\0",1+nofbytes+1)
2667 local data = concat(objects,"",0,nofobjects)
2668 local size = #data
2669 local xref = pdfdictionary {
2670 Type = pdfconstant("XRef"),
2671 Size = nofobjects + 1,
2672 W = pdfarray { 1, nofbytes, 1 },
2673 Root = catalog,
2674 Info = info,
2675 ID = trailerid and pdfarray { pdfliteral(trailerid,true), pdfliteral(trailerid,true) } or nil,
2676 }
2677 local fb
2678 if compress then
2679 local comp = compressdata(data,size)
2680 if comp then
2681 data = comp
2682 size = #data
2683 fb = f_stream_b_d_c
2684 else
2685 fb = f_stream_b_d_u
2686 end
2687 else
2688 fb = f_stream_b_d_u
2689 end
2690 flush(f,fb(nofobjects,xref(),size))
2691 flush(f,data)
2692 flush(f,s_stream_e)
2693 flush(f,f_startxref(xrefoffset))
2694 else
2695 flushdeferred()
2696 xrefoffset = offset
2697 flush(f,f_xref(nofobjects+1))
2698 local trailer = pdfdictionary {
2699 Size = nofobjects+1,
2700 Root = catalog,
2701 Info = info,
2702 }
2703 for i=1,nofobjects do
2704 local o = objects[i]
2705 if o then
2706 objects[i] = f_used(o)
2707 end
2708 end
2709 for i=nofobjects,1,-1 do
2710 local o = objects[i]
2711 if not o then
2712 objects[i] = f_link(lastfree)
2713 lastfree = i
2714 end
2715 end
2716 objects[0] = f_first(lastfree)
2717 flush(f,concat(objects,"",0,nofobjects))
2718 trailer.Size = nofobjects + 1
2719 if trailerid then
2720 flush(f,f_trailer_id(trailer(),trailerid,trailerid,xrefoffset))
2721 else
2722 flush(f,f_trailer_no(trailer(),xrefoffset))
2723 end
2724 end
2725 update(f,f_pdf_tag(majorversion,minorversion))
2726 close(f)
2727 end
2728 io.flush()
2729 closefile = function() end
2730 end
2731
2732end
2733
2734
2735
2736
2737do
2738
2739
2740
2741
2742
2743 local pdfbackend = backends.registered.pdf
2744 local nodeinjections = pdfbackend.nodeinjections
2745 local codeinjections = pdfbackend.codeinjections
2746
2747 local imagetypes = images.types
2748 local img_none = imagetypes.none
2749
2750 local newimagerule = nuts.pool.imagerule
2751 local setattrlist = nuts.setattrlist
2752 local setprop = nuts.setprop
2753
2754 local report_images = logs.reporter("backend","images")
2755
2756 local lastindex = 0
2757 local indices = { }
2758
2759 local bpfactor = number.dimenfactors.bp
2760
2761 function codeinjections.newimage(specification)
2762 return specification
2763 end
2764
2765 function codeinjections.copyimage(original)
2766 return setmetatableindex(original)
2767 end
2768
2769 function codeinjections.scanimage(specification)
2770
2771 return specification
2772 end
2773
2774 local function embedimage(specification)
2775 if specification then
2776 lastindex = lastindex + 1
2777 index = lastindex
2778 specification.index = index
2779 local xobject = pdfdictionary { }
2780 if not specification.notype then
2781 xobject.Type = pdf_xobject
2782 xobject.Subtype = pdf_form
2783 xobject.FormType = 1
2784 end
2785 local bbox = specification.bbox
2786 if bbox and not specification.nobbox then
2787 xobject.BBox = pdfarray {
2788 bbox[1] * bpfactor,
2789 bbox[2] * bpfactor,
2790 bbox[3] * bpfactor,
2791 bbox[4] * bpfactor,
2792 }
2793 end
2794 xobject = xobject + specification.attr
2795 if bbox and not specification.width then
2796 specification.width = bbox[3]
2797 end
2798 if bbox and not specification.height then
2799 specification.height = bbox[4]
2800 end
2801 local dict = xobject()
2802
2803 nofobjects = nofobjects + 1
2804 local objnum = nofobjects
2805 local nolength = specification.nolength
2806 local stream = specification.stream or specification.string
2807
2808
2809
2810
2811
2812
2813 if not specification.type then
2814 local kind = specification.kind
2815 if kind then
2816
2817 elseif attr and find(attr,"BBox") then
2818 kind = img_stream
2819 else
2820
2821 kind = img_none
2822 end
2823 specification.type = kind
2824 specification.kind = kind
2825 end
2826 flushstreamobj(stream,objnum,dict,compresslevel,nolength)
2827 specification.objnum = objnum
2828 specification.rotation = specification.rotation or 0
2829 specification.orientation = specification.orientation or 0
2830 specification.transform = specification.transform or 0
2831 specification.stream = nil
2832 specification.attr = nil
2833 specification.type = specification.kind or specification.type or img_none
2834 indices[index] = specification
2835 return specification
2836 end
2837 end
2838
2839 codeinjections.embedimage = embedimage
2840
2841 function codeinjections.wrapimage(specification)
2842
2843 local index = specification.index
2844 if not index then
2845 embedimage(specification)
2846 end
2847
2848 local n = newimagerule(
2849 specification.width or 0,
2850 specification.height or 0,
2851 specification.depth or 0
2852 )
2853 setattrlist(n,true)
2854 setprop(n,"index",specification.index)
2855 return tonode(n)
2856 end
2857
2858 pdfincludeimage = function(index)
2859 local specification = indices[index]
2860 if specification then
2861 local bbox = specification.bbox
2862 local xorigin = bbox[1]
2863 local yorigin = bbox[2]
2864 local xsize = bbox[3] - xorigin
2865 local ysize = bbox[4] - yorigin
2866 local transform = specification.transform or 0
2867 local objnum = specification.objnum or pdfreserveobject()
2868 local groupref = nil
2869 local kind = specification.kind or specification.type or img_none
2870 return
2871 kind,
2872 xorigin, yorigin,
2873 xsize, ysize,
2874 transform,
2875 objnum,
2876 groupref
2877 end
2878 end
2879
2880 lpdf.includeimage = pdfincludeimage
2881
2882end
2883
2884
2885
2886do
2887
2888
2889 local texgetbox = tex.getbox
2890
2891 local pdfname = nil
2892 local converter = nil
2893 local useddriver = nil
2894
2895 local function outputfilename(driver)
2896 return pdfname
2897 end
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913 local function prepare(driver)
2914 if not environment.initex then
2915
2916 backends.initialize("pdf")
2917
2918 pdfname = tex.jobname .. ".pdf"
2919 openfile(pdfname)
2920
2921 luatex.registerstopactions(1,function()
2922 if pdfname then
2923 lpdf.finalizedocument()
2924 closefile()
2925 pdfname = nil
2926 end
2927 end)
2928
2929 luatex.registerpageactions(1,function()
2930 if pdfname then
2931 lpdf.finalizepage(true)
2932 end
2933 end)
2934
2935 lpdf.registerdocumentfinalizer(wrapupdocument,nil,"wrapping up")
2936
2937 statistics.register("result saved in file", function()
2938 local outputfilename = environment.outputfilename or environment.jobname or tex.jobname or "<unset>"
2939 outputfilename = string.gsub(outputfilename,"^%./+","")
2940 return string.format("%s.%s, compresslevel %s, objectcompresslevel %s",outputfilename,"pdf",lpdf.getcompression())
2941 end)
2942
2943 luatex.registerstopactions(function()
2944 if pdfname then
2945 local r = lpdf.lastreferredpage()
2946 local s = lpdf.getnofpages()
2947 local t = lpdf.nofpages()
2948 if r > s then
2949 report()
2950 report("referred pages: %i, saved pages %i, pages from tuc file: %i, possible corrupt file",r,s,t)
2951 report()
2952 end
2953 end
2954 end)
2955 end
2956 converter = drivers.converters.lmtx
2957 useddriver = driver
2958 end
2959
2960 local function wrapup(driver)
2961 if pdfname then
2962 closefile()
2963 pdfname = nil
2964 end
2965 end
2966
2967 local function cleanup(driver)
2968 if pdfname then
2969 closefile(pdfname)
2970 pdfname = nil
2971 end
2972 end
2973
2974 local function convert(driver,boxnumber)
2975 converter(driver,texgetbox(boxnumber),"page")
2976 end
2977
2978 localconverter = function(...)
2979 converter(useddriver,...)
2980 end
2981
2982 drivers.install {
2983 name = "pdf",
2984 flushers = flushers,
2985 actions = {
2986 prepare = prepare,
2987 wrapup = wrapup,
2988 cleanup = cleanup,
2989
2990 initialize = initialize,
2991 convert = convert,
2992 finalize = finalize,
2993
2994 outputfilename = outputfilename,
2995 },
2996 }
2997
2998end
2999 |