1if not modules then modules = { } end modules ['mlib-run'] = {
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
9
10
11
12
13
14
15
16
17
18
19
20local type, tostring, tonumber, next = type, tostring, tonumber, next
21local find, striplines = string.find, utilities.strings.striplines
22local concat, insert, remove, sortedkeys = table.concat, table.insert, table.remove, table.sortedkeys
23local abs = math.abs
24
25local emptystring = string.is_empty
26
27local trace_graphics = false trackers.register("metapost.graphics", function(v) trace_graphics = v end)
28local trace_tracingall = false trackers.register("metapost.tracingall", function(v) trace_tracingall = v end)
29
30local texerrormessage = logs.texerrormessage
31
32local report_metapost = logs.reporter("metapost")
33local report_terminal = logs.reporter("metapost","terminal")
34local report_tracer = logs.reporter("metapost","trace")
35local report_error = logs.reporter("metapost","error")
36
37local starttiming = statistics.starttiming
38local stoptiming = statistics.stoptiming
39
40local formatters = string.formatters
41
42local mplib = mplib
43metapost = metapost or { }
44local metapost = metapost
45
46metapost.showlog = false
47metapost.lastlog = ""
48metapost.texerrors = false
49metapost.exectime = metapost.exectime or { }
50metapost.nofruns = 0
51
52local mpxformats = { }
53local nofformats = 0
54local mpxpreambles = { }
55local mpxterminals = { }
56local mpxextradata = { }
57
58
59
60
61
62
63
64local function flatten(source,target)
65 for i=1,#source do
66 local d = source[i]
67 if type(d) == "table" then
68 flatten(d,target)
69 elseif d and d ~= "" then
70 target[#target+1] = d
71 end
72 end
73 return target
74end
75
76local function prepareddata(data)
77 if data and data ~= "" then
78 if type(data) == "table" then
79 data = flatten(data,{ })
80 data = #data > 1 and concat(data,"\n") or data[1]
81 end
82 return data
83 end
84end
85
86local execute = mplib.execute
87
88local function executempx(mpx,data)
89 local terminal = mpxterminals[mpx]
90 if terminal then
91 terminal.writer(data)
92 data = nil
93 elseif type(data) == "table" then
94 data = prepareddata(data,collapse)
95 end
96 metapost.nofruns = metapost.nofruns + 1
97 local result = execute(mpx,data)
98 return result
99end
100
101directives.register("mplib.texerrors", function(v) metapost.texerrors = v end)
102trackers.register ("metapost.showlog", function(v) metapost.showlog = v end)
103
104function metapost.resetlastlog()
105 metapost.lastlog = ""
106end
107
108local new_instance = mplib.new
109local find_file = mplib.finder
110
111function metapost.reporterror(result)
112 if not result then
113 report_metapost("error: no result object returned")
114 return true
115 elseif result.status == 0 then
116 return false
117 elseif mplib.realtimelogging then
118 return false
119 else
120 local t = result.term
121 local e = result.error
122 local l = result.log
123 local report = metapost.texerrors and texerrormessage or report_metapost
124 if t and t ~= "" then
125 report("mp error: %s",striplines(t))
126 end
127 if e == "" or e == "no-error" then
128 e = nil
129 end
130 if e then
131 report("mp error: %s",striplines(e))
132 end
133 if not t and not e and l then
134 metapost.lastlog = metapost.lastlog .. "\n" .. l
135 report_metapost("log: %s",l)
136 else
137 report_metapost("error: unknown, no error, terminal or log messages")
138 end
139 return true
140 end
141end
142
143local f_preamble = formatters [ [[
144 boolean mplib ; mplib := true ;
145 let dump = endinput ;
146 input "%s" ;
147 randomseed:=%s;
148]] ]
149
150local methods = {
151 double = "double",
152 scaled = "scaled",
153
154 binary = "double",
155 decimal = "decimal",
156 posit = "posit",
157 default = "scaled",
158}
159
160function metapost.runscript(code)
161 return ""
162end
163
164
165
166local seed = nil
167
168local default_tolerance = 131/65536.0
169local bend_tolerance = default_tolerance
170local move_tolerance = default_tolerance
171
172
173
174
175function metapost.setbendtolerance(t)
176 bend_tolerance = t or default_tolerance
177end
178function metapost.setmovetolerance(t)
179 move_tolerance = t or default_tolerance
180end
181function metapost.settolerance(t)
182 bend_tolerance = t or default_tolerance
183 move_tolerance = t or default_tolerance
184end
185
186function metapost.getbendtolerance()
187 return bend_tolerance
188end
189function metapost.getmovetolerance()
190 return move_tolerance
191end
192function metapost.gettolerance(t)
193 return bend_tolerance, move_tolerance
194end
195
196function metapost.load(name,method)
197 starttiming(mplib)
198 if not seed then
199 seed = job.getrandomseed()
200 if seed <= 1 then
201 seed = seed % 1000
202 elseif seed > 4095 then
203 seed = seed % 4096
204 end
205 end
206 method = method and methods[method] or "scaled"
207 local mpx, terminal = new_instance {
208 bendtolerance = bend_tolerance,
209 movetolerance = move_tolerance,
210 mathmode = method,
211 runscript = metapost.runscript,
212 runinternal = metapost.runinternal,
213 maketext = metapost.maketext,
214 handlers = {
215 log = metapost.newlogger(),
216
217
218 },
219 }
220 report_metapost("initializing number mode %a",method)
221 local result
222 if not mpx then
223 result = { status = 99, error = "out of memory"}
224 else
225 mpxterminals[mpx] = terminal
226
227 metapost.pushscriptrunner(mpx)
228 result = executempx(mpx,f_preamble(file.addsuffix(name,"mp"),seed))
229 metapost.popscriptrunner()
230 end
231 stoptiming(mplib)
232 metapost.reporterror(result)
233 return mpx, result
234end
235
236function metapost.checkformat(mpsinput,method)
237 local mpsinput = mpsinput or "metafun"
238 local foundfile = ""
239 if file.suffix(mpsinput) ~= "" then
240 foundfile = find_file(mpsinput) or ""
241 end
242
243
244
245 if foundfile == "" then
246 foundfile = find_file(file.replacesuffix(mpsinput,"mpxl")) or ""
247 end
248 if foundfile == "" then
249 foundfile = find_file(file.replacesuffix(mpsinput,"mpiv")) or ""
250 end
251 if foundfile == "" then
252 foundfile = find_file(file.replacesuffix(mpsinput,"mp")) or ""
253 end
254 if foundfile == "" then
255 report_metapost("loading %a fails, format not found",mpsinput)
256 else
257 report_metapost("loading %a as %a using method %a",mpsinput,foundfile,method or "default")
258 local mpx, result = metapost.load(foundfile,method)
259 if mpx then
260 return mpx
261 else
262 report_metapost("error in loading %a",mpsinput)
263 metapost.reporterror(result)
264 end
265 end
266end
267
268function metapost.unload(mpx)
269 starttiming(mplib)
270 if mpx then
271 mpx:finish()
272 end
273 stoptiming(mplib)
274end
275
276metapost.defaultformat = "metafun"
277metapost.defaultinstance = "metafun"
278metapost.defaultmethod = "default"
279
280function metapost.getextradata(mpx)
281 return mpxextradata[mpx]
282end
283
284function metapost.pushformat(specification,f,m)
285 if type(specification) ~= "table" then
286 specification = {
287 instance = specification,
288 format = f,
289 method = m,
290 }
291 end
292 local instance = specification.instance
293 local format = specification.format
294 local method = specification.method
295 local definitions = specification.definitions
296 local extensions = specification.extensions
297 local preamble = nil
298 if not instance or instance == "" then
299 instance = metapost.defaultinstance
300 specification.instance = instance
301 end
302 if not format or format == "" then
303 format = metapost.defaultformat
304 specification.format = format
305 end
306 if not method or method == "" then
307 method = metapost.defaultmethod
308 specification.method = method
309 end
310 if definitions and definitions ~= "" then
311 preamble = definitions
312 end
313 if extensions and extensions ~= "" then
314 if preamble then
315 preamble = preamble .. "\n" .. extensions
316 else
317 preamble = extensions
318 end
319 end
320 nofformats = nofformats + 1
321 local usedinstance = instance .. ":" .. nofformats
322 local mpx = mpxformats [usedinstance]
323 local mpp = mpxpreambles[instance] or ""
324
325 if preamble then
326 preamble = prepareddata(preamble)
327 mpp = mpp .. "\n" .. preamble
328 mpxpreambles[instance] = mpp
329 end
330 if not mpx then
331 report_metapost("initializing instance %a using format %a and method %a",usedinstance,format,method)
332 mpx = metapost.checkformat(format,method)
333 mpxformats [usedinstance] = mpx
334 mpxextradata[mpx] = { }
335 if mpp ~= "" then
336 preamble = mpp
337 end
338 end
339 metapost.resetbackendoptions(mpx)
340 if preamble then
341 metapost.pushscriptrunner(mpx)
342 executempx(mpx,preamble)
343 metapost.popscriptrunner()
344 end
345 specification.mpx = mpx
346 return mpx
347end
348
349
350
351
352
353
354
355function metapost.popformat()
356 nofformats = nofformats - 1
357end
358
359function metapost.reset(mpx)
360 if not mpx then
361
362 elseif type(mpx) == "string" then
363 if mpxformats[mpx] then
364 local instance = mpxformats[mpx]
365 instance:finish()
366 mpxterminals[mpx] = nil
367 mpxextradata[mpx] = nil
368 mpxformats [mpx] = nil
369 end
370 else
371 for name, instance in next, mpxformats do
372 if instance == mpx then
373 mpx:finish()
374 mpxterminals[mpx] = nil
375 mpxextradata[mpx] = nil
376 mpxformats [mpx] = nil
377 break
378 end
379 end
380 end
381end
382
383if not metapost.process then
384
385 function metapost.process(specification)
386 metapost.run(specification)
387 end
388
389end
390
391
392
393
394
395
396
397
398
399
400
401do
402
403 local function makebeginbanner(specification)
404 return formatters["%% begin graphic: n=%s\n\n"](metapost.n)
405 end
406
407 local function makeendbanner(specification)
408 return "\n% end graphic\n\n"
409 end
410
411
412
413
414
415
416
417
418
419
420 local mp_tra = { }
421 local mp_tag = 0
422
423 local stack = { }
424 local logger = false
425 local logging = true
426
427 local function pushlogger(mpx,tra)
428 insert(stack,logger)
429 logger = tra or false
430 end
431
432 local function poplogger(mpx)
433 logger = remove(stack) or false
434 end
435
436 function metapost.checktracingonline(n)
437
438 end
439
440 function metapost.setlogging(state)
441 logging = state
442 end
443
444 function metapost.newlogger()
445
446
447
448
449
450
451
452
453 local l, nl, dl = { }, 0, false
454
455 return function(target,str)
456 if not logging then
457 return
458 elseif target == 4 then
459 report_error(str)
460 else
461 if logger and (target == 2 or target == 3) then
462 logger:write(str)
463 end
464 if target == 1 or target == 3 then
465 if str == "\n" then
466 mplib.realtimelogging = true
467 if nl > 0 then
468 report_tracer(concat(l,"",1,nl))
469 nl, dl = 0, false
470 elseif not dl then
471 report_tracer("")
472 dl = true
473 end
474 else
475 nl = nl + 1
476 l[nl] = str
477 end
478 end
479 end
480 end
481
482 end
483
484 function metapost.run(specification)
485 local mpx = specification.mpx
486 local data = specification.data
487 local converted = false
488 local result = { }
489 local mpxdone = type(mpx) == "string"
490 if mpxdone then
491 mpx = metapost.pushformat { instance = mpx, format = mpx }
492 end
493 if mpx and data then
494 local tra = false
495 starttiming(metapost)
496 metapost.variables = { }
497 metapost.pushscriptrunner(mpx)
498 if trace_graphics then
499 tra = mp_tra[mpx]
500 if not tra then
501 mp_tag = mp_tag + 1
502 local jobname = tex.jobname
503 tra = {
504 inp = io.open(formatters["%s-mplib-run-%03i.mp"] (jobname,mp_tag),"w"),
505 log = io.open(formatters["%s-mplib-run-%03i.log"](jobname,mp_tag),"w"),
506 }
507 mp_tra[mpx] = tra
508 end
509 local banner = makebeginbanner(specification)
510 tra.inp:write(banner)
511 tra.log:write(banner)
512 pushlogger(mpx,tra and tra.log)
513 else
514 pushlogger(mpx,false)
515 end
516 if trace_tracingall then
517 executempx(mpx,"tracingall;")
518 end
519
520 if data then
521 if trace_graphics then
522 if type(data) == "table" then
523 for i=1,#data do
524 tra.inp:write(data[i])
525 end
526 else
527 tra.inp:write(data)
528 end
529 end
530 starttiming(metapost.exectime)
531 result = executempx(mpx,data)
532 stoptiming(metapost.exectime)
533 if not metapost.reporterror(result) and result.fig then
534 converted = metapost.convert(specification,result)
535 end
536 else
537 report_metapost("error: invalid graphic")
538 end
539
540 if trace_graphics then
541 local banner = makeendbanner(specification)
542 tra.inp:write(banner)
543 tra.log:write(banner)
544 end
545 stoptiming(metapost)
546 poplogger()
547 metapost.popscriptrunner()
548 end
549 if mpxdone then
550 metapost.popformat()
551 end
552 return converted, result
553 end
554
555end
556
557if not metapost.convert then
558
559 function metapost.convert()
560 report_metapost('warning: no converter set')
561 end
562
563end
564
565function metapost.directrun(formatname,filename,outputformat,astable,mpdata)
566 report_metapost("producing postscript and svg is no longer supported")
567end
568
569do
570
571 local result = { }
572 local width = 0
573 local height = 0
574 local depth = 0
575 local bbox = { 0, 0, 0, 0 }
576
577 local flusher = {
578 startfigure = function(n,llx,lly,urx,ury)
579 result = { }
580 width = urx - llx
581 height = ury
582 depth = -lly
583 bbox = { llx, lly, urx, ury }
584 end,
585 flushfigure = function(t)
586 local r = #result
587 for i=1,#t do
588 r = r + 1
589 result[r] = t[i]
590 end
591 end,
592 stopfigure = function()
593 end,
594 }
595
596
597
598 function metapost.simple(instance,code,useextensions,dontwrap)
599
600 local mpx = metapost.pushformat {
601 instance = instance or "simplefun",
602 format = "metafun",
603 method = "double",
604 }
605 metapost.process {
606 mpx = mpx,
607 flusher = flusher,
608 askedfig = 1,
609 useplugins = useextensions,
610 data = dontwrap and { code } or { "beginfig(1);", code, "endfig;" },
611 incontext = false,
612 }
613 metapost.popformat()
614 if result then
615 local stream = concat(result," ")
616 result = { }
617 return stream, width, height, depth, bbox
618 else
619 return "", 0, 0, 0, { 0, 0, 0, 0 }
620 end
621 end
622
623end
624
625local getstatistics = mplib.getstatistics
626
627function metapost.getstatistics(memonly)
628 if memonly then
629 local n, m = 0, 0
630 for name, mpx in next, mpxformats do
631 n = n + 1
632 m = m + getstatistics(mpx).memory
633 end
634 return n, m
635 else
636 local t = { }
637 for name, mpx in next, mpxformats do
638 t[name] = getstatistics(mpx)
639 end
640 return t
641 end
642end
643
644local gethashentries = mplib.gethashentries
645
646function metapost.gethashentries(name,full)
647 if name then
648 local mpx = mpxformats[name] or mpxformats[name .. ":1"]
649 if mpx then
650 return gethashentries(mpx,full)
651 end
652 else
653 local t = { }
654 for name, mpx in next, mpxformats do
655 t[name] = gethashtokens(mpx,full)
656 end
657 return t
658 end
659end
660
661local gethashtokens = mplib.gethashtokens
662
663function metapost.getinstancenames()
664 return sortedkeys(mpxformats)
665end
666
667function metapost.getinstancebyname(name)
668 return mpxformats[name]
669end
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703local a1 = math.abs
704local a2 = xmath.fabs
705
706function metapost.hascurvature(ith,pth,tolerance)
707
708
709 local v1x = ith.right_x - ith.x_coord
710 local v1y = ith.right_y - ith.y_coord
711 local v2x = pth.left_x - pth.x_coord
712 local v2y = pth.left_y - pth.y_coord
713 local eps = abs(v1x * v2y - v2x * v1y)
714
715 if eps > tolerance then
716
717 return true
718 end
719 local v3x = pth.x_coord - ith.x_coord
720 local v3y = pth.y_coord - ith.y_coord
721 eps = abs(v3x * v2y - v2x * v3y)
722
723 if eps > tolerance then
724
725 return true
726 end
727 eps = abs(v3x * v1y - v1x * v3y)
728
729 if eps > tolerance then
730
731 return true
732 end
733
734 eps = v1x * v3x + v1y * v3y
735 if eps < 0 then
736
737 return true
738 end
739 eps = v2x * v3x + v2y * v3y
740 if eps > 0 then
741
742 return true
743 end
744 eps = (v1x * v1x + v1y * v1y) - (v3x * v3x + v3y * v3y)
745 if eps > 0 then
746
747 return true
748 end
749 eps = (v2x * v2x + v2y * v2y) - (v3x * v3x + v3y * v3y)
750 if eps > 0 then
751
752 return true
753 end
754 return false
755end
756 |