1if not modules then modules = { } end modules ['luatex-mplib'] = {
2 version = 1.001,
3 comment = "companion to luatex-mplib.tex",
4 author = "Hans Hagen & Taco Hoekwater",
5 copyright = "ConTeXt Development Team",
6 license = "public domain",
7}
8
9
10
11
12
13if metapost and metapost.version then
14
15
16
17
18else
19
20 local format, match, gsub = string.format, string.match, string.gsub
21 local concat = table.concat
22 local abs = math.abs
23
24 local mplib = require ('mplib')
25 local kpse = require ('kpse')
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40 metapost = metapost or { }
41 metapost.version = 1.00
42 metapost.showlog = metapost.showlog or false
43 metapost.lastlog = ""
44
45
46
47 local file = file or { }
48
49 function file.replacesuffix(filename, suffix)
50 return (string.gsub(filename,"%.[%a%d]+$","")) .. "." .. suffix
51 end
52
53 function file.stripsuffix(filename)
54 return (string.gsub(filename,"%.[%a%d]+$",""))
55 end
56
57
58
59 local mpkpse = kpse.new("luatex","mpost")
60
61 metapost.finder = metapost.finder or function(name, mode, ftype)
62 if mode == "w" then
63 return name
64 else
65 return mpkpse:find_file(name,ftype)
66 end
67 end
68
69
70
71
72
73 metapost.report = metapost.report or function(...)
74 if logs.report then
75 logs.report("metapost",...)
76 else
77 texio.write(format("<mplib: %s>",format(...)))
78 end
79 end
80
81
82
83
84
85 function metapost.resetlastlog()
86 metapost.lastlog = ""
87 end
88
89 local mplibone = tonumber(mplib.version()) <= 1.50
90
91 if mplibone then
92
93 metapost.make = metapost.make or function(name,mem_name,dump)
94 local t = os.clock()
95 local mpx = mplib.new {
96 ini_version = true,
97 find_file = metapost.finder,
98 job_name = file.stripsuffix(name)
99 }
100 mpx:execute(string.format("input %s ;",name))
101 if dump then
102 mpx:execute("dump ;")
103 metapost.report("format %s made and dumped for %s in %0.3f seconds",mem_name,name,os.clock()-t)
104 else
105 metapost.report("%s read in %0.3f seconds",name,os.clock()-t)
106 end
107 return mpx
108 end
109
110 function metapost.load(name)
111 local mem_name = file.replacesuffix(name,"mem")
112 local mpx = mplib.new {
113 ini_version = false,
114 mem_name = mem_name,
115 find_file = metapost.finder
116 }
117 if not mpx and type(metapost.make) == "function" then
118
119 mpx = metapost.make(name,mem_name)
120 end
121 if mpx then
122 metapost.report("using format %s",mem_name,false)
123 return mpx, nil
124 else
125 return nil, { status = 99, error = "out of memory or invalid format" }
126 end
127 end
128
129 else
130
131 local preamble = [[
132 boolean mplib ; mplib := true ;
133 let dump = endinput ;
134 input %s ;
135 ]]
136
137 metapost.make = metapost.make or function()
138 end
139
140 local template = [[
141 \pdfoutput=1
142 \pdfpkresolution600
143 \pdfcompresslevel=9
144 %s\relax
145 \hsize=100in
146 \vsize=\hsize
147 \hoffset=-1in
148 \voffset=\hoffset
149 \topskip=0pt
150 \setbox0=\hbox{%s}\relax
151 \pageheight=\ht0
152 \pagewidth=\wd0
153 \box0
154 \bye
155 ]]
156
157 metapost.texrunner = "mtxrun --script plain"
158
159 local texruns = 0
160 local texhash = { }
161
162 function metapost.maketext(mpd,str,what)
163
164
165 local verbatimtex = mpd.verbatimtex
166 if not verbatimtex then
167 verbatimtex = { }
168 mpd.verbatimtex = verbatimtex
169 end
170 if what == 1 then
171 table.insert(verbatimtex,str)
172 else
173 local texcode = format(template,concat(verbatimtex,"\n"),str)
174 local texdone = texhash[texcode]
175 local jobname = tex.jobname
176 if not texdone then
177 texruns = texruns + 1
178 texdone = texruns
179 texhash[texcode] = texdone
180 local texname = format("%s-mplib-%s.tmp",jobname,texdone)
181 local logname = format("%s-mplib-%s.log",jobname,texdone)
182 local pdfname = format("%s-mplib-%s.pdf",jobname,texdone)
183 io.savedata(texname,texcode)
184 os.execute(format("%s %s",metapost.texrunner,texname))
185 os.remove(texname)
186 os.remove(logname)
187 end
188 return format('"image::%s-mplib-%s.pdf" infont defaultfont',jobname,texdone)
189 end
190 end
191
192 local function mpprint(buffer,...)
193 for i=1,select("#",...) do
194 local value = select(i,...)
195 if value ~= nil then
196 local t = type(value)
197 if t == "number" then
198 buffer[#buffer+1] = format("%.16f",value)
199 elseif t == "string" then
200 buffer[#buffer+1] = value
201 elseif t == "table" then
202 buffer[#buffer+1] = "(" .. concat(value,",") .. ")"
203 else
204 buffer[#buffer+1] = tostring(value)
205 end
206 end
207 end
208 end
209
210 function metapost.runscript(mpd,code)
211 local code = loadstring(code)
212 if type(code) == "function" then
213 local buffer = { }
214 function metapost.print(...)
215 mpprint(buffer,...)
216 end
217 code()
218
219 return concat(buffer,"")
220 end
221 return ""
222 end
223
224 local modes = {
225 scaled = true,
226 decimal = true,
227 binary = true,
228 double = true,
229 }
230
231 function metapost.load(name,mode)
232 local mpd = {
233 buffer = { },
234 verbatim = { }
235 }
236 local mpx = mplib.new {
237 ini_version = true,
238 find_file = metapost.finder,
239 make_text = function(...) return metapost.maketext (mpd,...) end,
240 run_script = function(...) return metapost.runscript(mpd,...) end,
241 extensions = 1,
242 math_mode = mode and modes[mode] and mode or "scaled",
243 }
244 local result
245 if not mpx then
246 result = { status = 99, error = "out of memory"}
247 else
248 result = mpx:execute(format(preamble, file.replacesuffix(name,"mp")))
249 end
250 metapost.reporterror(result)
251 return mpx, result
252 end
253
254 end
255
256 function metapost.unload(mpx)
257 if mpx then
258 mpx:finish()
259 end
260 end
261
262 function metapost.reporterror(result)
263 if not result then
264 metapost.report("mp error: no result object returned")
265 elseif result.status > 0 then
266 local t, e, l = result.term, result.error, result.log
267 if t then
268 metapost.report("mp terminal: %s",t)
269 end
270 if e then
271 metapost.report("mp error: %s", e)
272 end
273 if not t and not e and l then
274 metapost.lastlog = metapost.lastlog .. "\n " .. l
275 metapost.report("mp log: %s",l)
276 else
277 metapost.report("mp error: unknown, no error, terminal or log messages")
278 end
279 else
280 return false
281 end
282 return true
283 end
284
285 function metapost.process(format,data,mode)
286 local converted, result = false, {}
287 local mpx = metapost.load(format,mode)
288 if mpx and data then
289 local result = mpx:execute(data)
290 if not result then
291 metapost.report("mp error: no result object returned")
292 elseif result.status > 0 then
293 metapost.report("mp error: %s",(result.term or "no-term") .. "\n" .. (result.error or "no-error"))
294 elseif metapost.showlog then
295 metapost.lastlog = metapost.lastlog .. "\n" .. result.term
296 metapost.report("mp info: %s",result.term or "no-term")
297 elseif result.fig then
298 converted = metapost.convert(result)
299 else
300 metapost.report("mp error: unknown error, maybe no beginfig/endfig")
301 end
302
303
304 else
305 metapost.report("mp error: mem file not found")
306 end
307 return converted, result
308 end
309
310 local function getobjects(result,figure,f)
311 return figure:objects()
312 end
313
314 function metapost.convert(result,flusher)
315 metapost.flush(result,flusher)
316 return true
317 end
318
319
320
321
322 local function pdf_startfigure(n,llx,lly,urx,ury)
323 tex.sprint(format("\\startMPLIBtoPDF{%s}{%s}{%s}{%s}",llx,lly,urx,ury))
324 end
325
326 local function pdf_stopfigure()
327 tex.sprint("\\stopMPLIBtoPDF")
328 end
329
330 function pdf_literalcode(fmt,...)
331 tex.sprint(format("\\MPLIBtoPDF{%s}",format(fmt,...)))
332 end
333
334 function pdf_textfigure(font,size,text,width,height,depth)
335 local how, what = match(text,"^(.-)::(.+)$")
336 if how == "image" then
337 tex.sprint(format("\\MPLIBpdftext{%s}{%s}",what,depth))
338 else
339
340
341 tex.sprint(format("\\MPLIBtextext{%s}{%s}{\\hpack{\\detokenize{%s}}}{%s}",font,size,text,depth))
342 end
343 end
344
345 local bend_tolerance = 131/65536
346
347 local rx, sx, sy, ry, tx, ty, divider = 1, 0, 0, 1, 0, 0, 1
348
349 local function pen_characteristics(object)
350 local t = mplib.pen_info(object)
351 rx, ry, sx, sy, tx, ty = t.rx, t.ry, t.sx, t.sy, t.tx, t.ty
352 divider = sx*sy - rx*ry
353 return not (sx==1 and rx==0 and ry==0 and sy==1 and tx==0 and ty==0), t.width
354 end
355
356 local function concatinated(px, py)
357 return (sy*px-ry*py)/divider,(sx*py-rx*px)/divider
358 end
359
360 local function curved(ith,pth)
361 local d = pth.left_x - ith.right_x
362 if abs(ith.right_x - ith.x_coord - d) <= bend_tolerance and abs(pth.x_coord - pth.left_x - d) <= bend_tolerance then
363 d = pth.left_y - ith.right_y
364 if abs(ith.right_y - ith.y_coord - d) <= bend_tolerance and abs(pth.y_coord - pth.left_y - d) <= bend_tolerance then
365 return false
366 end
367 end
368 return true
369 end
370
371 local function flushnormalpath(path,open)
372 local pth, ith
373 for i=1,#path do
374 pth = path[i]
375 if not ith then
376 pdf_literalcode("%f %f m",pth.x_coord,pth.y_coord)
377 elseif curved(ith,pth) then
378 pdf_literalcode("%f %f %f %f %f %f c",ith.right_x,ith.right_y,pth.left_x,pth.left_y,pth.x_coord,pth.y_coord)
379 else
380 pdf_literalcode("%f %f l",pth.x_coord,pth.y_coord)
381 end
382 ith = pth
383 end
384 if not open then
385 local one = path[1]
386 if curved(pth,one) then
387 pdf_literalcode("%f %f %f %f %f %f c",pth.right_x,pth.right_y,one.left_x,one.left_y,one.x_coord,one.y_coord )
388 else
389 pdf_literalcode("%f %f l",one.x_coord,one.y_coord)
390 end
391 elseif #path == 1 then
392
393 local one = path[1]
394 pdf_literalcode("%f %f l",one.x_coord,one.y_coord)
395 end
396 return t
397 end
398
399 local function flushconcatpath(path,open)
400 pdf_literalcode("%f %f %f %f %f %f cm", sx, rx, ry, sy, tx ,ty)
401 local pth, ith
402 for i=1,#path do
403 pth = path[i]
404 if not ith then
405 pdf_literalcode("%f %f m",concatinated(pth.x_coord,pth.y_coord))
406 elseif curved(ith,pth) then
407 local a, b = concatinated(ith.right_x,ith.right_y)
408 local c, d = concatinated(pth.left_x,pth.left_y)
409 pdf_literalcode("%f %f %f %f %f %f c",a,b,c,d,concatinated(pth.x_coord, pth.y_coord))
410 else
411 pdf_literalcode("%f %f l",concatinated(pth.x_coord, pth.y_coord))
412 end
413 ith = pth
414 end
415 if not open then
416 local one = path[1]
417 if curved(pth,one) then
418 local a, b = concatinated(pth.right_x,pth.right_y)
419 local c, d = concatinated(one.left_x,one.left_y)
420 pdf_literalcode("%f %f %f %f %f %f c",a,b,c,d,concatinated(one.x_coord, one.y_coord))
421 else
422 pdf_literalcode("%f %f l",concatinated(one.x_coord,one.y_coord))
423 end
424 elseif #path == 1 then
425
426 local one = path[1]
427 pdf_literalcode("%f %f l",concatinated(one.x_coord,one.y_coord))
428 end
429 return t
430 end
431
432
433
434 function metapost.flush(result,flusher)
435 if result then
436 local figures = result.fig
437 if figures then
438 for f=1, #figures do
439 metapost.report("flushing figure %s",f)
440 local figure = figures[f]
441 local objects = getobjects(result,figure,f)
442 local fignum = tonumber(match(figure:filename(),"([%d]+)$") or figure:charcode() or 0)
443 local miterlimit, linecap, linejoin, dashed = -1, -1, -1, false
444 local bbox = figure:boundingbox()
445 local llx, lly, urx, ury = bbox[1], bbox[2], bbox[3], bbox[4]
446 if urx < llx then
447
448 pdf_startfigure(fignum,0,0,0,0)
449 pdf_stopfigure()
450 else
451 pdf_startfigure(fignum,llx,lly,urx,ury)
452 pdf_literalcode("q")
453 if objects then
454 local savedpath = nil
455 local savedhtap = nil
456 for o=1,#objects do
457 local object = objects[o]
458 local objecttype = object.type
459 if objecttype == "start_bounds" or objecttype == "stop_bounds" then
460
461 elseif objecttype == "start_clip" then
462 local evenodd = not object.istext and object.postscript == "evenodd"
463 pdf_literalcode("q")
464 flushnormalpath(object.path,t,false)
465 pdf_literalcode("W n")
466 pdf_literalcode(evenodd and "W* n" or "W n")
467 elseif objecttype == "stop_clip" then
468 pdf_literalcode("Q")
469 miterlimit, linecap, linejoin, dashed = -1, -1, -1, false
470 elseif objecttype == "special" then
471
472 elseif objecttype == "text" then
473 local ot = object.transform
474 pdf_literalcode("q %f %f %f %f %f %f cm",ot[3],ot[4],ot[5],ot[6],ot[1],ot[2])
475 pdf_textfigure(object.font,object.dsize,object.text,object.width,object.height,object.depth)
476 pdf_literalcode("Q")
477 else
478 local evenodd, collect, both = false, false, false
479 local postscript = object.postscript
480 if not object.istext then
481 if postscript == "evenodd" then
482 evenodd = true
483 elseif postscript == "collect" then
484 collect = true
485 elseif postscript == "both" then
486 both = true
487 elseif postscript == "eoboth" then
488 evenodd = true
489 both = true
490 end
491 end
492 if collect then
493 if not savedpath then
494 savedpath = { object.path or false }
495 savedhtap = { object.htap or false }
496 else
497 savedpath[#savedpath+1] = object.path or false
498 savedhtap[#savedhtap+1] = object.htap or false
499 end
500 else
501 local cs = object.color
502 local cr = false
503 if cs and #cs > 0 then
504 cs, cr = metapost.colorconverter(cs)
505 pdf_literalcode(cs)
506 end
507 local ml = object.miterlimit
508 if ml and ml ~= miterlimit then
509 miterlimit = ml
510 pdf_literalcode("%f M",ml)
511 end
512 local lj = object.linejoin
513 if lj and lj ~= linejoin then
514 linejoin = lj
515 pdf_literalcode("%i j",lj)
516 end
517 local lc = object.linecap
518 if lc and lc ~= linecap then
519 linecap = lc
520 pdf_literalcode("%i J",lc)
521 end
522 local dl = object.dash
523 if dl then
524 local d = format("[%s] %i d",concat(dl.dashes or {}," "),dl.offset)
525 if d ~= dashed then
526 dashed = d
527 pdf_literalcode(dashed)
528 end
529 elseif dashed then
530 pdf_literalcode("[] 0 d")
531 dashed = false
532 end
533 local path = object.path
534 local transformed, penwidth = false, 1
535 local open = path and path[1].left_type and path[#path].right_type
536 local pen = object.pen
537 if pen then
538 if pen.type == 'elliptical' then
539 transformed, penwidth = pen_characteristics(object)
540 pdf_literalcode("%f w",penwidth)
541 if objecttype == 'fill' then
542 objecttype = 'both'
543 end
544 else
545 objecttype = 'fill'
546 end
547 end
548 if transformed then
549 pdf_literalcode("q")
550 end
551 if path then
552 if savedpath then
553 for i=1,#savedpath do
554 local path = savedpath[i]
555 if transformed then
556 flushconcatpath(path,open)
557 else
558 flushnormalpath(path,open)
559 end
560 end
561 savedpath = nil
562 end
563 if transformed then
564 flushconcatpath(path,open)
565 else
566 flushnormalpath(path,open)
567 end
568 if objecttype == "fill" then
569 pdf_literalcode("h f")
570 elseif objecttype == "outline" then
571 if both then
572 pdf_literalcode(evenodd and "h B*" or "h B")
573 else
574 pdf_literalcode(open and "S" or "h S")
575 end
576 elseif objecttype == "both" then
577 pdf_literalcode(evenodd and "h B*" or "h B")
578 end
579 end
580 if transformed then
581 pdf_literalcode("Q")
582 end
583 local path = object.htap
584 if path then
585 if transformed then
586 pdf_literalcode("q")
587 end
588 if savedhtap then
589 for i=1,#savedhtap do
590 local path = savedhtap[i]
591 if transformed then
592 flushconcatpath(path,open)
593 else
594 flushnormalpath(path,open)
595 end
596 end
597 savedhtap = nil
598 evenodd = true
599 end
600 if transformed then
601 flushconcatpath(path,open)
602 else
603 flushnormalpath(path,open)
604 end
605 if objecttype == "fill" then
606 pdf_literalcode("h f")
607 elseif objecttype == "outline" then
608 pdf_literalcode(evenodd and "h f*" or "h f")
609 elseif objecttype == "both" then
610 pdf_literalcode(evenodd and "h B*" or "h B")
611 end
612 if transformed then
613 pdf_literalcode("Q")
614 end
615 end
616 if cr then
617 pdf_literalcode(cr)
618 end
619 end
620 end
621 end
622 end
623 pdf_literalcode("Q")
624 pdf_stopfigure()
625 end
626 end
627 end
628 end
629 end
630
631 function metapost.colorconverter(cr)
632 local n = #cr
633 if n == 4 then
634 local c, m, y, k = cr[1], cr[2], cr[3], cr[4]
635 return format("%.3f %.3f %.3f %.3f k %.3f %.3f %.3f %.3f K",c,m,y,k,c,m,y,k), "0 g 0 G"
636 elseif n == 3 then
637 local r, g, b = cr[1], cr[2], cr[3]
638 return format("%.3f %.3f %.3f rg %.3f %.3f %.3f RG",r,g,b,r,g,b), "0 g 0 G"
639 else
640 local s = cr[1]
641 return format("%.3f g %.3f G",s,s), "0 g 0 G"
642 end
643 end
644
645end
646 |