1if not modules then modules = { } end modules [ ' mlib-pdf ' ] = {
2 version = 1 . 001 ,
3 comment = " companion to mlib-ctx.mkiv " ,
4 author = " Hans Hagen, PRAGMA-ADE, Hasselt NL " ,
5 copyright = " PRAGMA ADE / ConTeXt Development Team " ,
6 license = " see context related readme files " ,
7}
8
9local gsub = string . gsub
10local concat , insert , remove = table . concat , table . insert , table . remove
11local abs , sqrt , round = math . abs , math . sqrt , math . round
12local setmetatable , rawset , tostring , tonumber , type = setmetatable , rawset , tostring , tonumber , type
13local P , S , C , Ct , Cc , Cg , Cf , Carg = lpeg . P , lpeg . S , lpeg . C , lpeg . Ct , lpeg . Cc , lpeg . Cg , lpeg . Cf , lpeg . Carg
14local lpegmatch = lpeg . match
15local formatters = string . formatters
16
17local report_metapost = logs . reporter ( " metapost " )
18
19local trace_variables = false trackers . register ( " metapost.variables " , function ( v ) trace_variables = v end )
20
21local mplib = mplib
22local context = context
23
24local allocate = utilities . storage . allocate
25
26local pen_info = mplib . pen_info
27local getfields = mplib . getfields or mplib . fields
28
29local save_table = false
30local force_stroke = false
31
32metapost = metapost or { }
33local metapost = metapost
34
35metapost . flushers = metapost . flushers or { }
36local pdfflusher = { }
37metapost . flushers . pdf = pdfflusher
38
39metapost . n = 0
40
41local experiment = true
42local savedliterals = nil
43local mpsliteral = nodes . pool . originliteral
44
45local f_f = formatters [ " %.6N " ]
46local f_m = formatters [ " %.6N %.6N m " ]
47local f_c = formatters [ " %.6N %.6N %.6N %.6N %.6N %.6N c " ]
48local f_l = formatters [ " %.6N %.6N l " ]
49local f_cm = formatters [ " %.6N %.6N %.6N %.6N %.6N %.6N cm " ]
50local f_M = formatters [ " %.6N M " ]
51local f_j = formatters [ " %i j " ]
52local f_J = formatters [ " %i J " ]
53local f_d = formatters [ " [%s] %.6N d " ]
54local f_w = formatters [ " %.6N w " ]
55
56directives . register ( " metapost.savetable " , function ( v )
57 if type ( v ) = = " string " then
58 save_table = file . addsuffix ( v , " mpl " )
59 elseif v then
60 save_table = file . addsuffix ( environment . jobname . . " -graphic " , " mpl " )
61 else
62 save_table = false
63 end
64end )
65
66trackers . register ( " metapost.forcestroke " , function ( v )
67 force_stroke = v
68end )
69
70
71
72
73
74
75local function getobjects ( result , figure , index )
76 return figure : objects ( )
77end
78
79function metapost . convert ( specification , result )
80 local flusher = specification . flusher
81 local askedfig = specification . askedfig
82 if save_table then
83 table . save ( save_table , metapost . totable ( result , 1 ) )
84 end
85 metapost . flush ( specification , result )
86 return true
87end
88
89function metapost . flushliteral ( d )
90 if savedliterals then
91 context ( mpsliteral ( savedliterals [ d ] ) )
92 else
93 report_metapost ( " problem flushing literal %a " , d )
94 end
95end
96
97function metapost . flushreset ( )
98 savedliterals = nil
99end
100
101function pdfflusher . comment ( message )
102 if message then
103 message = formatters [ " %% mps graphic %s: %s " ] ( metapost . n , message )
104 if experiment then
105 context ( mpsliteral ( message ) )
106 elseif savedliterals then
107 local last = # savedliterals + 1
108 savedliterals [ last ] = message
109 context . MPLIBtoPDF ( last )
110 else
111 savedliterals = { message }
112 context . MPLIBtoPDF ( 1 )
113 end
114 end
115end
116
117function pdfflusher . startfigure ( n , llx , lly , urx , ury , message )
118 savedliterals = nil
119 metapost . n = metapost . n + 1
120 context . startMPLIBtoPDF ( f_f ( llx ) , f_f ( lly ) , f_f ( urx ) , f_f ( ury ) )
121 if message then pdfflusher . comment ( message ) end
122end
123
124function pdfflusher . stopfigure ( message )
125 if message then pdfflusher . comment ( message ) end
126 context . stopMPLIBtoPDF ( )
127 context . MPLIBflushreset ( )
128end
129
130function pdfflusher . flushfigure ( pdfliterals )
131 if # pdfliterals > 0 then
132 pdfliterals = concat ( pdfliterals , " \n " )
133 if experiment then
134 context ( mpsliteral ( pdfliterals ) )
135 else
136 if savedliterals then
137 local last = # savedliterals + 1
138 savedliterals [ last ] = pdfliterals
139 context . MPLIBtoPDF ( last )
140 else
141 savedliterals = { pdfliterals }
142 context . MPLIBtoPDF ( 1 )
143 end
144 end
145 end
146end
147
148function pdfflusher . textfigure ( font , size , text , width , height , depth )
149 text = gsub ( text , " . " , " \\hbox{%1} " )
150 context . MPtextext ( font , size , text , 0 , - number . dimenfactors . bp * depth )
151end
152
153local bend_tolerance = 131 / 65536
154
155local rx , sx , sy , ry , tx , ty , divider = 1 , 0 , 0 , 1 , 0 , 0 , 1
156
157local function pen_characteristics ( object )
158 local t = pen_info ( object )
159 rx , ry , sx , sy , tx , ty = t . rx , t . ry , t . sx , t . sy , t . tx , t . ty
160 divider = sx * sy - rx * ry
161 return not ( sx = = 1 and rx = = 0 and ry = = 0 and sy = = 1 and tx = = 0 and ty = = 0 ) , t . width
162end
163
164local function mpconcat ( px , py )
165 return ( sy * px - ry * py ) / divider , ( sx * py - rx * px ) / divider
166end
167
168local function curved ( ith , pth )
169 local d = pth . left_x - ith . right_x
170 if abs ( ith . right_x - ith . x_coord - d ) < = bend_tolerance and abs ( pth . x_coord - pth . left_x - d ) < = bend_tolerance then
171 d = pth . left_y - ith . right_y
172 if abs ( ith . right_y - ith . y_coord - d ) < = bend_tolerance and abs ( pth . y_coord - pth . left_y - d ) < = bend_tolerance then
173 return false
174 end
175 end
176 return true
177end
178
179local function flushnormalpath ( path , t , open )
180 local pth , ith , nt
181 local length = # path
182 if t then
183 nt = # t
184 else
185 t = { }
186 nt = 0
187 end
188 for i = 1 , length do
189 nt = nt + 1
190 pth = path [ i ]
191 if not ith then
192 t [ nt ] = f_m ( pth . x_coord , pth . y_coord )
193 elseif curved ( ith , pth ) then
194 t [ nt ] = f_c ( ith . right_x , ith . right_y , pth . left_x , pth . left_y , pth . x_coord , pth . y_coord )
195 else
196 t [ nt ] = f_l ( pth . x_coord , pth . y_coord )
197 end
198 ith = pth
199 end
200 if not open then
201 nt = nt + 1
202 local one = path [ 1 ]
203 if curved ( pth , one ) then
204 t [ nt ] = f_c ( pth . right_x , pth . right_y , one . left_x , one . left_y , one . x_coord , one . y_coord )
205 else
206 t [ nt ] = f_l ( one . x_coord , one . y_coord )
207 end
208 elseif length = = 1 then
209
210 local one = path [ 1 ]
211 nt = nt + 1
212 t [ nt ] = f_l ( one . x_coord , one . y_coord )
213 end
214 return t
215end
216
217local function flushconcatpath ( path , t , open , transform )
218 local pth , ith , nt
219 local length = # path
220 if t then
221 nt = # t
222 else
223 t = { }
224 nt = 0
225 end
226 if transform then
227 nt = nt + 1
228 t [ nt ] = f_cm ( sx , rx , ry , sy , tx , ty )
229 end
230 for i = 1 , length do
231 nt = nt + 1
232 pth = path [ i ]
233 if not ith then
234 t [ nt ] = f_m ( mpconcat ( pth . x_coord , pth . y_coord ) )
235 elseif curved ( ith , pth ) then
236 local a , b = mpconcat ( ith . right_x , ith . right_y )
237 local c , d = mpconcat ( pth . left_x , pth . left_y )
238 t [ nt ] = f_c ( a , b , c , d , mpconcat ( pth . x_coord , pth . y_coord ) )
239 else
240 t [ nt ] = f_l ( mpconcat ( pth . x_coord , pth . y_coord ) )
241 end
242 ith = pth
243 end
244 if not open then
245 nt = nt + 1
246 local one = path [ 1 ]
247 if curved ( pth , one ) then
248 local a , b = mpconcat ( pth . right_x , pth . right_y )
249 local c , d = mpconcat ( one . left_x , one . left_y )
250 t [ nt ] = f_c ( a , b , c , d , mpconcat ( one . x_coord , one . y_coord ) )
251 else
252 t [ nt ] = f_l ( mpconcat ( one . x_coord , one . y_coord ) )
253 end
254 elseif length = = 1 then
255
256 nt = nt + 1
257 local one = path [ 1 ]
258 t [ nt ] = f_l ( mpconcat ( one . x_coord , one . y_coord ) )
259 end
260 return t
261end
262
263local function toboundingbox ( path )
264 local size = # path
265 if size = = 4 then
266 local pth = path [ 1 ]
267 local x = pth . x_coord
268 local y = pth . y_coord
269 local llx , lly , urx , ury = x , y , x , y
270 for i = 2 , size do
271 pth = path [ i ]
272 x = pth . x_coord
273 y = pth . y_coord
274 if x < llx then
275 llx = x
276 elseif x > urx then
277 urx = x
278 end
279 if y < lly then
280 lly = y
281 elseif y > ury then
282 ury = y
283 end
284 end
285 return { llx , lly , urx , ury }
286 else
287 return { 0 , 0 , 0 , 0 }
288 end
289end
290
291metapost . flushnormalpath = flushnormalpath
292
293
294
295
296
297
298
299
300
301
302
303local ignore = function ( ) end
304
305local space = P ( " " )
306local equal = P ( " = " )
307local key = C ( ( 1 - equal ) ^ 1 ) * equal
308local newline = S ( " \n\r " ) ^ 1
309local number = ( ( ( 1 - space - newline ) ^ 1 ) / tonumber ) * ( space ^ 0 )
310
311local p_number = number
312local p_string = C ( ( 1 - newline ) ^ 0 )
313local p_boolean = P ( " false " ) * Cc ( false ) + P ( " true " ) * Cc ( true )
314local p_set = Ct ( number ^ 1 )
315local p_path = Ct ( Ct ( number * number ^ -5 ) ^ 1 )
316
317local variable =
318 P ( " 1: " ) * p_number
319 + P ( " 2: " ) * p_string
320 + P ( " 3: " ) * p_boolean
321 + S ( " 4568 " ) * P ( " : " ) * p_set
322 + P ( " 7: " ) * p_path
323
324local pattern_tab = Cf ( Carg ( 1 ) * ( Cg ( variable * newline ^ 0 ) ^ 0 ) , rawset )
325
326local variable =
327 P ( " 1: " ) * p_number
328 + P ( " 2: " ) * p_string
329 + P ( " 3: " ) * p_boolean
330 + S ( " 4568 " ) * P ( " : " ) * number ^ 1
331 + P ( " 7: " ) * ( number * number ^ -5 ) ^ 1
332
333local pattern_lst = ( variable * newline ^ 0 ) ^ 0
334
335metapost . variables = { }
336metapost . properties = { }
337
338function metapost . untagvariable ( str , variables )
339 if variables = = false then
340 return lpegmatch ( pattern_lst , str )
341 else
342 return lpegmatch ( pattern_tab , str , 1 , variables or { } )
343 end
344end
345
346
347
348
349
350function metapost . processspecial ( str )
351 local code = loadstring ( str )
352 if code then
353 if trace_variables then
354 report_metapost ( " executing special code: %s " , str )
355 end
356 code ( )
357 else
358 report_metapost ( " invalid special code: %s " , str )
359 end
360end
361
362local stack = { }
363
364local function pushproperties ( figure )
365
366 local boundingbox = figure : boundingbox ( )
367 local slot = figure : charcode ( ) or 0
368 local properties = {
369 llx = boundingbox [ 1 ] ,
370 lly = boundingbox [ 2 ] ,
371 urx = boundingbox [ 3 ] ,
372 ury = boundingbox [ 4 ] ,
373 slot = slot ,
374 width = figure : width ( ) ,
375 height = figure : height ( ) ,
376 depth = figure : depth ( ) ,
377 italic = figure : italcorr ( ) ,
378 number = slot ,
379 }
380 insert ( stack , properties )
381 metapost . properties = properties
382 return properties
383end
384
385local function popproperties ( )
386 metapost . properties = remove ( stack )
387end
388
389local function nocomment ( ) end
390
391metapost . comment = nocomment
392
393function metapost . flush ( specification , result )
394 if result then
395 local flusher = specification . flusher
396 local askedfig = specification . askedfig
397 local incontext = specification . incontext
398 local figures = result . fig
399 if figures then
400 flusher = flusher or pdfflusher
401 local resetplugins = metapost . resetplugins or ignore
402 local processplugins = metapost . processplugins or ignore
403 local synchronizeplugins = metapost . synchronizeplugins or ignore
404 local pluginactions = metapost . pluginactions or ignore
405 local startfigure = flusher . startfigure
406 local stopfigure = flusher . stopfigure
407 local flushfigure = flusher . flushfigure
408 local textfigure = flusher . textfigure
409 local processspecial = flusher . processspecial or metapost . processspecial
410 metapost . comment = flusher . comment or nocomment
411 for index = 1 , # figures do
412 local figure = figures [ index ]
413 local properties = pushproperties ( figure )
414 if askedfig = = " direct " or askedfig = = " all " or askedfig = = properties . number then
415 local objects = getobjects ( result , figure , index )
416 local result = { }
417 local miterlimit = -1
418 local linecap = -1
419 local linejoin = -1
420 local dashed = false
421 local linewidth = false
422 local llx = properties . llx
423 local lly = properties . lly
424 local urx = properties . urx
425 local ury = properties . ury
426 if urx < llx then
427
428 startfigure ( properties . number , 0 , 0 , 0 , 0 , " invalid " , figure )
429 stopfigure ( )
430 else
431
432
433
434 local groupstack = { }
435
436 local function processfigure ( )
437 result [ # result + 1 ] = " q "
438 if objects then
439
440 local savedpath = nil
441 local savedhtap = nil
442 for o = 1 , # objects do
443 local object = objects [ o ]
444 local objecttype = object . type
445 if objecttype = = " text " then
446 result [ # result + 1 ] = " q "
447 local ot = object . transform
448 result [ # result + 1 ] = f_cm ( ot [ 3 ] , ot [ 4 ] , ot [ 5 ] , ot [ 6 ] , ot [ 1 ] , ot [ 2 ] )
449 flushfigure ( result )
450 result = { }
451 textfigure ( object . font , object . dsize , object . text , object . width , object . height , object . depth )
452 result [ # result + 1 ] = " Q "
453 elseif objecttype = = " special " then
454 if processspecial then
455 processspecial ( object . prescript )
456 end
457 elseif objecttype = = " start_clip " then
458 local evenodd = not object . istext and object . postscript = = " evenodd "
459 result [ # result + 1 ] = " q "
460 flushnormalpath ( object . path , result , false )
461 result [ # result + 1 ] = evenodd and " W* n " or " W n "
462 elseif objecttype = = " stop_clip " then
463 result [ # result + 1 ] = " Q "
464 miterlimit , linecap , linejoin , dashed , linewidth = -1 , -1 , -1 , " " , false
465 elseif objecttype = = " start_bounds " or objecttype = = " stop_bounds " then
466
467 elseif objecttype = = " start_group " then
468 if lpdf . flushgroup then
469 local before , after = processplugins ( object )
470 if before then
471 result [ # result + 1 ] = " q "
472 result = pluginactions ( before , result , flushfigure )
473 insert ( groupstack , {
474 after = after ,
475 result = result ,
476 bbox = toboundingbox ( object . path ) ,
477 } )
478 result = { }
479 miterlimit , linecap , linejoin , dashed , linewidth = -1 , -1 , -1 , " " , false
480 else
481 insert ( groupstack , false )
482 end
483 else
484 insert ( groupstack , false )
485 end
486 elseif objecttype = = " stop_group " then
487 local data = remove ( groupstack )
488 if data then
489 local reference = lpdf . flushgroup ( concat ( result , " \r " ) , data . bbox )
490 result = data . result
491 result [ # result + 1 ] = reference
492 result = pluginactions ( data . after , result , flushfigure )
493 result [ # result + 1 ] = " Q "
494 miterlimit , linecap , linejoin , dashed , linewidth = -1 , -1 , -1 , " " , false
495 end
496 else
497
498
499
500
501
502 local original = object
503 local object = { }
504 setmetatable ( object , {
505 __index = original
506 } )
507 local before ,
508 after = processplugins ( object )
509 local evenodd = false
510 local collect = false
511 local both = false
512 local flush = false
513 local postscript = object . postscript
514 if not object . istext then
515 if postscript = = " evenodd " then
516 evenodd = true
517 elseif postscript = = " collect " then
518 collect = true
519 elseif postscript = = " flush " then
520 flush = true
521 elseif postscript = = " both " then
522 both = true
523 elseif postscript = = " eoboth " then
524 evenodd = true
525 both = true
526 end
527 end
528
529 if flush and not savedpath then
530
531 elseif collect then
532 if not savedpath then
533 savedpath = { object . path or false }
534 savedhtap = { object . htap or false }
535 else
536 savedpath [ # savedpath + 1 ] = object . path or false
537 savedhtap [ # savedhtap + 1 ] = object . htap or false
538 end
539 else
540 local objecttype = object . type
541 if before then
542 result = pluginactions ( before , result , flushfigure )
543 end
544 local ml = object . miterlimit
545 if ml and ml ~ = miterlimit then
546 miterlimit = ml
547 result [ # result + 1 ] = f_M ( ml )
548 end
549 local lj = object . linejoin
550 if lj and lj ~ = linejoin then
551 linejoin = lj
552 result [ # result + 1 ] = f_j ( lj )
553 end
554 local lc = object . linecap
555 if lc and lc ~ = linecap then
556 linecap = lc
557 result [ # result + 1 ] = f_J ( lc )
558 end
559 if both then
560 if dashed ~ = false then
561 result [ # result + 1 ] = " [] 0 d "
562 dashed = false
563 end
564 else
565 local dl = object . dash
566 if dl then
567 local d = f_d ( concat ( dl . dashes or { } , " " ) , dl . offset )
568 if d ~ = dashed then
569 dashed = d
570 result [ # result + 1 ] = d
571 end
572 elseif dashed ~ = false then
573 result [ # result + 1 ] = " [] 0 d "
574 dashed = false
575 end
576 end
577 local path = object . path
578 local transformed = false
579 local penwidth = 1
580 local open = path and path [ 1 ] . left_type and path [ # path ] . right_type
581 local pen = object . pen
582 if pen then
583 if pen . type = = " elliptical " then
584 transformed , penwidth = pen_characteristics ( original )
585 if penwidth ~ = linewidth then
586 result [ # result + 1 ] = f_w ( penwidth )
587 linewidth = penwidth
588 end
589 if objecttype = = " fill " then
590 objecttype = " both "
591 end
592 else
593 objecttype = " fill "
594 end
595 end
596 if transformed then
597 result [ # result + 1 ] = " q "
598 end
599 if path then
600 if savedpath then
601 for i = 1 , # savedpath do
602 local path = savedpath [ i ]
603 if transformed then
604 flushconcatpath ( path , result , open , i = = 1 )
605 else
606 flushnormalpath ( path , result , open )
607 end
608 end
609 savedpath = nil
610 end
611 if flush then
612
613 elseif transformed then
614 flushconcatpath ( path , result , open , true )
615 else
616 flushnormalpath ( path , result , open )
617 end
618 if force_stroke then
619 result [ # result + 1 ] = open and " S " or " h S "
620 elseif objecttype = = " fill " then
621 result [ # result + 1 ] = evenodd and " h f* " or " h f "
622 elseif objecttype = = " outline " then
623 if both then
624 result [ # result + 1 ] = evenodd and " h B* " or " h B "
625 else
626 result [ # result + 1 ] = open and " S " or " h S "
627 end
628 elseif objecttype = = " both " then
629 result [ # result + 1 ] = evenodd and " h B* " or " h B "
630 end
631 end
632 if transformed then
633 result [ # result + 1 ] = " Q "
634 end
635 local path = object . htap
636 if path then
637 if transformed then
638 result [ # result + 1 ] = " q "
639 end
640 if savedhtap then
641 for i = 1 , # savedhtap do
642 local path = savedhtap [ i ]
643 if transformed then
644 flushconcatpath ( path , result , open , i = = 1 )
645 else
646 flushnormalpath ( path , result , open )
647 end
648 end
649 savedhtap = nil
650 evenodd = true
651 end
652 if transformed then
653 flushconcatpath ( path , result , open , true )
654 else
655 flushnormalpath ( path , result , open )
656 end
657 if force_stroke then
658 result [ # result + 1 ] = open and " S " or " h S "
659 elseif objecttype = = " fill " then
660 result [ # result + 1 ] = evenodd and " h f* " or " h f "
661 elseif objecttype = = " outline " then
662 result [ # result + 1 ] = open and " S " or " h S "
663 elseif objecttype = = " both " then
664 result [ # result + 1 ] = evenodd and " h B* " or " h B "
665 end
666 if transformed then
667 result [ # result + 1 ] = " Q "
668 end
669 end
670 if after then
671 result = pluginactions ( after , result , flushfigure )
672 end
673 end
674 if object . grouped then
675
676 miterlimit , linecap , linejoin , dashed , linewidth = -1 , -1 , -1 , " " , false
677 end
678 end
679 end
680 end
681 result [ # result + 1 ] = " Q "
682 flushfigure ( result )
683 end
684 startfigure ( properties . number , llx , lly , urx , ury , " begin " , figure )
685 if incontext then
686 context ( function ( ) processfigure ( ) end )
687 else
688 processfigure ( )
689 end
690 stopfigure ( " end " )
691
692 end
693 if askedfig ~ = " all " then
694 break
695 end
696 end
697 popproperties ( )
698 end
699 metapost . comment = nocomment
700 resetplugins ( result )
701 end
702 end
703end
704
705
706
707do
708
709 local result = { }
710
711 local flusher = {
712 startfigure = function ( )
713 result = { }
714 context . startnointerference ( )
715 end ,
716 flushfigure = function ( literals )
717 local n = # result
718 for i = 1 , # literals do
719 result [ n + i ] = literals [ i ]
720 end
721 end ,
722 stopfigure = function ( )
723 context . stopnointerference ( )
724 end
725 }
726
727 local specification = {
728 flusher = flusher ,
729
730 }
731
732 function metapost . pdfliterals ( result )
733 metapost . flush ( specification , result )
734 return result
735 end
736
737end
738
739function metapost . totable ( result , askedfig )
740 local askedfig = askedfig or 1
741 local figure = result and result . fig and result . fig [ 1 ]
742 if figure then
743 local results = { }
744 local objects = getobjects ( result , figure , askedfig )
745 for o = 1 , # objects do
746 local object = objects [ o ]
747 local result = { }
748 local fields = getfields ( object )
749 for f = 1 , # fields do
750 local field = fields [ f ]
751 result [ field ] = object [ field ]
752 end
753 results [ o ] = result
754 end
755 local boundingbox = figure : boundingbox ( )
756 return {
757 boundingbox = {
758 llx = boundingbox [ 1 ] ,
759 lly = boundingbox [ 2 ] ,
760 urx = boundingbox [ 3 ] ,
761 ury = boundingbox [ 4 ] ,
762 } ,
763 objects = results
764 }
765 else
766 return nil
767 end
768end
769 |