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