mlib-pdf.lmt /size: 41 Kb    last modification: 2025-02-21 11:03
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, sortedkeys = table.concat, table.insert, table.remove, table.sortedkeys
11local sqrt, round = 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 peninfo         = mplib.peninfo
27local getfields       = mplib.getfields or mplib.fields -- todo: in lmtx get them once and then use gettype
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 mpsliteral      = nodes.pool.originliteral
42
43local f_f  = formatters["%.6N"]
44local f_m  = formatters["%.6N %.6N m"]
45local f_c  = formatters["%.6N %.6N %.6N %.6N %.6N %.6N c"]
46local f_l  = formatters["%.6N %.6N l"]
47local f_cm = formatters["%.6N %.6N %.6N %.6N %.6N %.6N cm"]
48local f_M  = formatters["%.6N M"]
49local f_j  = formatters["%i j"]
50local f_J  = formatters["%i J"]
51local f_d  = formatters["[%s] %.6N d"]
52local f_w  = formatters["%.6N w"]
53local f_r  = formatters["%.6N %.6N %.6N %.6N re"]
54
55directives.register("metapost.savetable",function(v)
56    if type(v) == "string" then
57        save_table = file.addsuffix(v,"mpl")
58    elseif v then
59        save_table = file.addsuffix(environment.jobname .. "-graphic","mpl")
60    else
61        save_table = false
62    end
63end)
64
65trackers.register("metapost.forcestroke",function(v)
66    force_stroke = v
67end)
68
69-- local function gettolerance(objects)
70--     return objects:tolerance()
71-- end
72
73function metapost.convert(specification,result)
74    local flusher  = specification.flusher
75    local askedfig = specification.askedfig
76    if save_table then
77        table.save(save_table,metapost.totable(result,1)) -- direct
78    end
79    metapost.flush(specification,result)
80    return true -- done
81end
82
83function pdfflusher.tocomment(message)
84    if message then
85        return formatters["%% mps graphic %s: %s"](metapost.n,message)
86    else
87        return formatters["%% mps graphic %s"](metapost.n)
88    end
89end
90
91function pdfflusher.startfigure(n,llx,lly,urx,ury,message,figure,plugmode)
92    metapost.n = metapost.n + 1
93    context.startMPLIBtoPDF(f_f(llx),f_f(lly),f_f(urx),f_f(ury),plugmode and "1" or "0")
94end
95
96function pdfflusher.stopfigure(message)
97    context.stopMPLIBtoPDF()
98end
99
100function pdfflusher.flushfigure(pdfliterals) -- table
101    if #pdfliterals > 0 then
102        pdfliterals = concat(pdfliterals,"\n")
103        context(mpsliteral(pdfliterals))
104    end
105end
106
107function pdfflusher.textfigure(font,size,text,width,height,depth) -- we could save the factor
108    text = gsub(text,".","\\hbox{%1}") -- kerning happens in metapost (i have to check if this is true for mplib)
109    context.MPtextext(font,size,text,0,-number.dimenfactors.bp*depth)
110end
111
112local rx, sx, sy, ry, tx, ty, divider = 1, 0, 0, 1, 0, 0, 1
113
114local function pen_characteristics(object)
115    local t = peninfo(object)
116    rx, ry, sx, sy, tx, ty = t.rx, t.ry, t.sx, t.sy, t.tx, t.ty
117    divider = sx*sy - rx*ry
118    return not (sx == 1 and rx == 0 and ry == 0 and sy == 1 and tx == 0 and ty == 0), t.width
119end
120
121local function mpconcat(px, py) -- no tx, ty here / we can move this one inline if needed
122    return (sy*px-ry*py)/divider,(sx*py-rx*px)/divider
123end
124
125local getbendtolerance = metapost.getbendtolerance
126local curved           = metapost.hascurvature
127
128local function flushnormalpath(path,t,open,tolerance)
129    local pth, ith, nt
130    local length = #path
131    if t then
132        nt = #t
133    else
134        t = { }
135        nt = 0
136    end
137    if length == 4 and path.cycle then
138        local p1 = path[1]
139        local p2 = path[2]
140        local p3 = path[3]
141        local p4 = path[4]
142        if not p1.curved and not p2.curved and not p3.curved and not p4.curved then
143            local p1x = p1.x_coord
144            local p1y = p1.y_coord
145            local p3x = p3.x_coord
146            local p3y = p3.y_coord
147            if p1y == p2.y_coord and p1x == p4.x_coord and p2.x_coord == p3x and p3y == p4.y_coord then
148                nt = nt + 1
149                t[nt] = f_r(p1.x_coord,p1y,p3x-p1x,p3y-p1y)
150                path.rectangle = true
151                return
152            end
153        end
154    end
155    for i=1,length do
156        nt = nt + 1
157        pth = path[i]
158        if not ith or pth.state == 1 then
159            t[nt] = f_m(pth.x_coord,pth.y_coord)
160     -- elseif curved(ith,pth,tolerance) then
161        elseif pth.curved then
162            t[nt] = f_c(ith.right_x,ith.right_y,pth.left_x,pth.left_y,pth.x_coord,pth.y_coord)
163        else
164            t[nt] = f_l(pth.x_coord,pth.y_coord)
165        end
166        ith = pth
167    end
168    if not open then
169        nt = nt + 1
170        local one = path[1]
171        local len = path[length]
172        if len.state == 2 then
173            -- special case (potrace-001.tex)
174        elseif one.state == 1 then
175            t[nt] = f_m(one.x_coord,one.y_coord)
176     -- elseif curved(pth,one,tolerance) then
177        elseif one.curved then
178            t[nt] = f_c(pth.right_x,pth.right_y,one.left_x,one.left_y,one.x_coord,one.y_coord)
179        else
180            t[nt] = f_l(one.x_coord,one.y_coord)
181        end
182    elseif length == 1 then
183        -- special case .. draw point
184        local one = path[1]
185        nt = nt + 1
186        t[nt] = f_l(one.x_coord,one.y_coord)
187    end
188    return t
189end
190
191local function flushconcatpath(path,t,open,tolerance,transform)
192    local pth, ith, nt
193    local length = #path
194    if t then
195        nt = #t
196    else
197        t = { }
198        nt = 0
199    end
200    if transform then
201        nt = nt + 1
202        t[nt] = f_cm(sx,rx,ry,sy,tx,ty)
203    end
204    for i=1,length do
205        nt = nt + 1
206        pth = path[i]
207        if not ith or pth.state == 1 then
208            t[nt] = f_m(mpconcat(pth.x_coord,pth.y_coord))
209     -- elseif curved(ith,pth,tolerance) then
210        elseif pth.curved then
211            local a, b = mpconcat(ith.right_x,ith.right_y)
212            local c, d = mpconcat(pth.left_x,pth.left_y)
213            t[nt] = f_c(a,b,c,d,mpconcat(pth.x_coord,pth.y_coord))
214        else
215           t[nt] = f_l(mpconcat(pth.x_coord, pth.y_coord))
216        end
217        ith = pth
218    end
219    if not open then
220        nt = nt + 1
221        local one = path[1]
222        local len = path[length]
223        if len.state == 2 then
224            -- special case (potrace-001.tex)
225        elseif one.state == 1 then
226            t[nt] = f_m(one.x_coord,one.y_coord)
227     -- elseif curved(pth,one,tolerance) then
228        elseif one.curved then
229            local a, b = mpconcat(pth.right_x,pth.right_y)
230            local c, d = mpconcat(one.left_x,one.left_y)
231            t[nt] = f_c(a,b,c,d,mpconcat(one.x_coord, one.y_coord))
232        else
233            t[nt] = f_l(mpconcat(one.x_coord,one.y_coord))
234        end
235    elseif length == 1 then
236        -- special case .. draw point
237        nt = nt + 1
238        local one = path[1]
239        t[nt] = f_l(mpconcat(one.x_coord,one.y_coord))
240    end
241    return t
242end
243
244local function toboundingbox(path)
245    local size = #path
246    if size == 4 then
247        local pth = path[1]
248        local x = pth.x_coord
249        local y = pth.y_coord
250        local llx, lly, urx, ury = x, y, x, y
251        for i=2,size do
252            pth = path[i]
253            x   = pth.x_coord
254            y   = pth.y_coord
255            if x < llx then
256                llx = x
257            elseif x > urx then
258                urx = x
259            end
260            if y < lly then
261                lly = y
262            elseif y > ury then
263                ury = y
264            end
265        end
266        return { llx, lly, urx, ury }
267    else
268        return { 0, 0, 0, 0 }
269    end
270end
271
272function metapost.flushnormalpath(path, t, open, tolerance)
273    return flushnormalpath(path, t, open, tolerance or getbendtolerance())
274end
275
276-- The flusher is pdf based, if another backend is used, we need to overload the
277-- flusher; this is beta code, the organization will change (already upgraded in
278-- sync with mplib)
279--
280-- We can avoid the before table but I like symmetry. There is of course a small
281-- performance penalty, but so is passing extra arguments (result, flusher, after)
282-- and returning stuff.
283--
284-- This variable stuff will change in lmtx.
285
286local ignore   = function() end
287
288local space    = P(" ")
289local equal    = P("=")
290local key      = C((1-equal)^1) * equal
291local newline  = S("\n\r")^1
292local number   = (((1-space-newline)^1) / tonumber) * (space^0)
293
294local p_number  = number
295local p_string  = C((1-newline)^0)
296local p_boolean = P("false") * Cc(false) + P("true") * Cc(true)
297local p_set     = Ct(number^1)
298local p_path    = Ct(Ct(number * number^-5)^1)
299
300local variable =
301    P("1:")            * p_number
302  + P("2:")            * p_string
303  + P("3:")            * p_boolean
304  + S("4568") * P(":") * p_set
305  + P("7:")            * p_path
306
307local pattern_tab = Cf ( Carg(1) * (Cg(variable * newline^0)^0), rawset)
308
309local variable =
310    P("1:")            * p_number
311  + P("2:")            * p_string
312  + P("3:")            * p_boolean
313  + S("4568") * P(":") * number^1
314  + P("7:")            * (number * number^-5)^1
315
316local pattern_lst = (variable * newline^0)^0
317
318metapost.variables  = { } -- currently across instances
319metapost.properties = { } -- to be stacked
320
321function metapost.untagvariable(str,variables) -- will be redone
322    if variables == false then
323        return lpegmatch(pattern_lst,str)
324    else
325        return lpegmatch(pattern_tab,str,1,variables or { })
326    end
327end
328
329-- function metapost.processspecial(str)
330--     lpegmatch(pattern_key,object.prescript,1,variables)
331-- end
332
333-- function metapost.processspecial(str)
334--     local code = loadstring(str)
335--     if code then
336--         if trace_variables then
337--             report_metapost("executing special code: %s",str)
338--         end
339--         code()
340--     else
341--         report_metapost("invalid special code: %s",str)
342--     end
343-- end
344
345local stack      = { }   -- general stack (not related to stacking)
346local nostacking = { 0 } -- layers in figures
347
348local function pushproperties(figure)
349    -- maybe there will be getters in lmtx
350    local boundingbox = figure:boundingbox()
351    local slot = figure:charcode() or 0
352    local properties = {
353        llx    = boundingbox[1],
354        lly    = boundingbox[2],
355        urx    = boundingbox[3],
356        ury    = boundingbox[4],
357        slot   = slot,
358        width  = figure:width(),
359        height = figure:height(),
360        depth  = figure:depth(),
361        italic = figure:italic(),
362        number = slot,
363    }
364    insert(stack,properties)
365    metapost.properties = properties
366    return properties
367end
368
369local function popproperties()
370    metapost.properties = remove(stack)
371end
372
373local optimizecolor = false
374
375directives.register("metapost.optimizecolor", function(v)
376    optimizecolor = v
377end)
378
379local function enhancedobject(original)
380    return setmetatable({ }, { __index = original })
381end
382
383function metapost.flush(specification,result)
384    if result then
385        local flusher   = specification.flusher
386        local askedfig  = specification.askedfig
387        local incontext = specification.incontext
388        local filtering = specification.filtering
389        local figures   = result.fig
390        if figures then
391            flusher = flusher or pdfflusher
392local plugmode = not metapost.getbackendoption(specification.mpx,"noplugins")
393            local resetplugins       = plugmode and metapost.resetplugins       or ignore -- before figure
394            local processplugins     = plugmode and metapost.processplugins     or ignore -- each object
395            local synchronizeplugins = plugmode and metapost.synchronizeplugins or ignore
396            local pluginactions      = plugmode and metapost.pluginactions      or ignore -- before / after
397            local startfigure        = flusher.startfigure
398            local stopfigure         = flusher.stopfigure
399            local flushfigure        = flusher.flushfigure
400            local textfigure         = flusher.textfigure
401         -- local processspecial     = flusher.processspecial or metapost.processspecial
402            local tocomment          = flusher.tocomment
403            if type(filtering) ~= "table" or not next(filtering) then
404                filtering = false
405            end
406            -- patterns: we always use image 1 and then can use patterns for 2..n (or one number)
407            -- we can then do an intermediate flush
408
409            for index=1,#figures do
410                local figure     = figures[index]
411                local properties = pushproperties(figure)
412                if askedfig == "direct" or askedfig == "all" or askedfig == properties.number then
413                    local stacking   = figure:stacking() -- This has to happen before fetching objects!
414                    local objects    = figure:objects()
415                    local tolerance  = figure:tolerance() or getbendtolerance()
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                        -- invalid
428                        startfigure(properties.number,0,0,0,0,"invalid",figure,plugmode)
429                        if tocomment then
430                            result[#result+1] = tocomment("invalid")
431                        end
432                        stopfigure()
433                    else
434
435                        -- we need to be indirect if we want the one-pass solution
436
437                        local groupstack = { }
438
439                        local function processfigure()
440                            if tocomment then
441                                result[#result+1] = tocomment("begin")
442                            end
443                            result[#result+1] = "q"
444                            if objects then
445    --                             resetplugins(result) -- we should move the colorinitializer here
446                                local savedpath = nil
447                                local savedhtap = nil
448                                if stacking then
449                                    stacking = { }
450                                    for o=1,#objects do
451                                        local stack = objects[o].stacking
452                                        if stack then
453                                            if filtering then
454                                                stacking[stack] = filtering[stack]
455                                            else
456                                                stacking[stack] = true
457                                            end
458                                        end
459                                    end
460                                    stacking = sortedkeys(stacking)
461                                else
462                                    stacking = nostacking
463                                end
464                                for i=1,#stacking do
465                                    local stack = stacking[i]
466                                    for o=1,#objects do
467                                        local object = objects[o]
468                                        if stack == object.stacking then
469                                            local objecttype = object.type
470                                            if objecttype == "fill" or objecttype == "outline" then
471                                                -- we use an indirect table as we want to overload
472                                                -- entries but this is not possible in userdata
473                                                --
474                                                -- can be optimized if no path
475                                                --
476                                                local original = object
477                                                local object   = { }
478                                                setmetatable(object, {
479                                                    __index = original
480                                                })
481                                                local before,
482                                                      after,
483                                                      options    = processplugins(object)
484                                                local evenodd    = false
485                                                local collect    = false
486                                                local both       = false
487                                                local flush      = false
488                                                local outline    = force_outline
489                                                local envelope   = false
490                                                local postscript = object.postscript
491                                                local tolerance  = options and tonumber(options.tolerance) or tolerance
492                                        -- if not object.istext then
493                                                if postscript == "evenodd" then
494                                                    evenodd = true
495                                                elseif postscript == "collect" then
496                                                    collect = true
497                                                elseif postscript == "flush" then
498                                                    flush   = true
499                                                elseif postscript == "both" then
500                                                    both = true
501                                                elseif postscript == "eoboth" then
502                                                    evenodd = true
503                                                    both    = true
504                                                elseif postscript == "envelope" then
505                                                    envelope = true
506                                                end
507                                        --  end
508                                                --
509                                                if flush and not savedpath then
510                                                    -- forget about it
511                                                elseif collect then
512                                                    if not savedpath then
513                                                        savedpath = { object.path or false }
514                                                        savedhtap = { object.htap or false }
515                                                    else
516                                                        savedpath[#savedpath+1] = object.path or false
517                                                        savedhtap[#savedhtap+1] = object.htap or false
518                                                    end
519                                                else
520                                                    local objecttype = object.type -- can have changed
521                                                    if envelope then
522                                                        dashed, linewidth = "", 1 -- to be sure
523                                                    end
524                                                    if before then
525                                                        -- We inject too many color and transparency directives although in practice
526                                                        -- it's not that relevant. However, we can optimize this a bit because we know
527                                                        -- what we can get so we just intercept these two cases:
528                                                        --
529                                                        -- { [1] = transparency, [2] = color }
530                                                        -- { [1] = color }
531                                                        --
532                                                        -- It is anyway a rather ugly hack because we should keep some kind of state
533                                                        -- but as we mix all kind of features that is also tricky. Maybe if I'd write
534                                                        -- this from scratch ... but that doesn't pay off. Also tracking a transstate
535                                                        -- is kind of fragile so maybe it should be optional.
536                                                        --
537                                                        if optimizecolor and after then
538                                                            local r = #result
539                                                            if result[r] == "0 g 0 G" and (after[1] == "0 g 0 G" or after[2] == "0 g 0 G") then
540                                                                result[r] = nil
541                                                                r = r - 1
542                                                            end
543                                                            if result[r] == "/Tr0 gs" and (after[1] == "/Tr0 gs" or after[2] == "/Tr0 gs") then
544                                                                result[r] = nil
545                                                            end
546                                                        end
547                                                        --
548                                                        result = pluginactions(before,result,flushfigure)
549                                                    end
550                                                    local ml = object.miterlimit
551                                                    if ml and ml ~= miterlimit then
552                                                        miterlimit = ml
553                                                        result[#result+1] = f_M(ml)
554                                                    end
555                                                    local lj = object.linejoin
556                                                    if lj and lj ~= linejoin then
557                                                        linejoin = lj
558                                                        result[#result+1] = f_j(lj)
559                                                    end
560                                                    local lc = object.linecap
561                                                    if lc and lc ~= linecap then
562                                                        linecap = lc
563                                                        result[#result+1] = f_J(lc)
564                                                    end
565                                                    if both then
566                                                        if dashed ~= false then -- was just dashed test
567                                                           result[#result+1] = "[] 0 d"
568                                                           dashed = false
569                                                        end
570                                                    else
571                                                        local dl = object.dash
572                                                        if dl then
573                                                            local d = f_d(concat(dl.dashes or {}," "),dl.offset)
574                                                            if d ~= dashed then
575                                                                dashed = d
576                                                                result[#result+1] = d
577                                                            end
578                                                        elseif dashed ~= false then -- was just dashed test
579                                                           result[#result+1] = "[] 0 d"
580                                                           dashed = false
581                                                        end
582                                                    end
583                                                    local path        = object.path -- newpath
584                                                    local transformed = false
585                                                    local penwidth    = 1
586                                                    local open        = path and path[1].left_type and path[#path].right_type -- at this moment only "end_point"
587                                                    local pen         = object.pen
588                                                    if pen then
589                                                       if pen.type == "elliptical" or outline then
590                                                            transformed, penwidth = pen_characteristics(original) -- boolean, value
591                                                            if penwidth ~= linewidth then
592                                                                result[#result+1] = f_w(penwidth)
593                                                                linewidth = penwidth
594                                                            end
595                                                            if objecttype == "fill" then
596                                                                objecttype = "both"
597                                                            end
598                                                       else -- calculated by mplib itself
599                                                            objecttype = "fill"
600                                                       end
601                                                    end
602                                                    if transformed then
603                                                        result[#result+1] = "q"
604                                                    end
605                                                    if path then
606                                                        if savedpath then
607                                                            for i=1,#savedpath do
608                                                                local path = savedpath[i]
609                                                                local open = not path.cycle
610                                                                if transformed then
611                                                                    flushconcatpath(path,result,open,tolerance,i==1)
612                                                                else
613                                                                    flushnormalpath(path,result,open,tolerance)
614                                                                end
615                                                            end
616                                                            savedpath = nil
617                                                        end
618                                                        if flush then
619                                                            -- ignore this path
620                                                        elseif transformed then
621                                                            flushconcatpath(path,result,open,tolerance,true)
622                                                        else
623                                                            flushnormalpath(path,result,open,tolerance)
624                                                        end
625                                                        if path.rectangle then
626                                                            if outline or envelope then
627                                                                result[#result+1] = "S"
628                                                            elseif objecttype == "fill" then
629                                                                result[#result+1] = "f"
630                                                            elseif objecttype == "outline" then
631                                                                result[#result+1] = both and "B" or "S"
632                                                            elseif objecttype == "both" then
633                                                                result[#result+1] = "B"
634                                                            end
635                                                        elseif outline or envelope then
636                                                            result[#result+1] = open and "S" or "h S"
637                                                        elseif objecttype == "fill" then
638                                                            result[#result+1] = evenodd and "h f*" or "h f" -- f* = eo
639                                                        elseif objecttype == "outline" then
640                                                            if both then
641                                                                result[#result+1] = evenodd and "h B*" or "h B" -- B* = eo
642                                                            else
643                                                                result[#result+1] = open and "S" or "h S"
644                                                            end
645                                                        elseif objecttype == "both" then
646                                                            result[#result+1] = evenodd and "h B*" or "h B" -- B* = eo -- b includes closepath
647                                                        end
648                                                    end
649                                                    if transformed then
650                                                        result[#result+1] = "Q"
651                                                    end
652                                                    local path = object.htap
653                                                    if path then
654                                                        if transformed then
655                                                            result[#result+1] = "q"
656                                                        end
657                                                        if savedhtap then
658                                                            for i=1,#savedhtap do
659                                                                local path = savedhtap[i]
660                                                                local open = not path.cycle
661                                                                if transformed then
662                                                                    flushconcatpath(path,result,open,tolerance,i==1)
663                                                                else
664                                                                    flushnormalpath(path,result,open,tolerance)
665                                                                end
666                                                            end
667                                                            savedhtap = nil
668                                                            evenodd   = true
669                                                        end
670                                                        if transformed then
671                                                            flushconcatpath(path,result,open,tolerance,true)
672                                                        else
673                                                            flushnormalpath(path,result,open,tolerance)
674                                                        end
675                                                        if outline or envelope then
676                                                            result[#result+1] = open and "S" or "h S"
677                                                        elseif objecttype == "fill" then
678                                                            result[#result+1] = evenodd and "h f*" or "h f" -- f* = eo
679                                                        elseif objecttype == "outline" then
680                                                            result[#result+1] = open and "S" or "h S"
681                                                        elseif objecttype == "both" then
682                                                            result[#result+1] = evenodd and "h B*" or "h B" -- B* = eo -- b includes closepath
683                                                        end
684                                                        if transformed then
685                                                            result[#result+1] = "Q"
686                                                        end
687                                                    end
688                                                    if after then
689                                                        result = pluginactions(after,result,flushfigure)
690                                                    end
691                                                end
692                                                if object.grouped then
693                                                    -- can be qQ'd so changes can end up in groups
694                                                    miterlimit, linecap, linejoin, dashed, linewidth = -1, -1, -1, "", false
695                                                end
696                                            elseif objecttype == "start_clip" then
697                                             -- local evenodd = not object.istext and object.postscript == "evenodd"
698                                                local evenodd = object.postscript == "evenodd"
699                                                result[#result+1] = "q"
700                                                flushnormalpath(object.path,result,false,tolerance)
701                                                result[#result+1] = evenodd and "W* n" or "W n"
702                                            elseif objecttype == "stop_clip" then
703                                                result[#result+1] = "Q"
704                                                miterlimit, linecap, linejoin, dashed, linewidth = -1, -1, -1, "", false
705                                            elseif objecttype == "start_bounds" or objecttype == "stop_bounds" then
706                                                -- skip
707                                            elseif objecttype == "start_group" then
708                                                if lpdf.flushgroup then
709                                                    local object = enhancedobject(object)
710                                                    local before, after = processplugins(object)
711                                                    local detail = object.detail
712                                                    if detail or before then
713                                                        result[#result+1] = "q"
714                                                        result = pluginactions(before,result,flushfigure)
715                                                        insert(groupstack, {
716                                                            after  = after,
717                                                            result = result,
718                                                            detail = detail,
719                                                            bbox   = toboundingbox(object.path),
720                                                        })
721                                                        result = { }
722                                                        miterlimit, linecap, linejoin, dashed, linewidth = -1, -1, -1, "", false
723                                                    else
724                                                        insert(groupstack,false)
725                                                    end
726                                                else
727                                                    insert(groupstack,false)
728                                                end
729                                            elseif objecttype == "stop_group" then
730                                                local data = remove(groupstack)
731                                                if data then
732                                                    local reference = lpdf.flushgroup(concat(result,"\r"),data.bbox,data.detail)
733-- crashes on after = false
734                                                    result = data.result
735                                                    result[#result+1] = reference
736                                                    result = pluginactions(data.after,result,flushfigure)
737                                                    result[#result+1] = "Q"
738                                                    miterlimit, linecap, linejoin, dashed, linewidth = -1, -1, -1, "", false
739                                                end
740                                         -- if objecttype == "text" then
741                                         --     result[#result+1] = "q"
742                                         --     local ot = object.transform -- 3,4,5,6,1,2
743                                         --     result[#result+1] = f_cm(ot[3],ot[4],ot[5],ot[6],ot[1],ot[2])
744                                         --     flushfigure(result) -- flush accumulated literals
745                                         --     result = { }
746                                         --     textfigure(object.font,object.dsize,object.text,object.width,object.height,object.depth)
747                                         --     result[#result+1] = "Q"
748                                         -- elseif objecttype == "special" then
749                                         --     if processspecial then
750                                         --         processspecial(object.prescript)
751                                         --     end
752                                         -- else
753                                                -- error
754                                            end
755                                        end
756                                    end
757                                end
758                            end
759                            result[#result+1] = "Q"
760                            if tocomment then
761                                result[#result+1] = tocomment("end")
762                            end
763                            flushfigure(result)
764                        end
765                        startfigure(properties.number,llx,lly,urx,ury,"begin",figure,plugmode)
766                        if incontext then
767                            context(function() processfigure() end)
768                        else
769                            processfigure()
770                        end
771                        stopfigure("end")
772                    end
773                    if askedfig ~= "all" then
774                        break
775                    end
776                end
777                popproperties()
778            end
779            resetplugins(result) -- we should move the colorinitializer here
780        end
781    end
782end
783
784-- tracing:
785
786do
787
788    local result = { }
789
790    local flusher = {
791        startfigure = function()
792            result = { }
793            context.startnointerference()
794        end,
795        flushfigure = function(literals)
796            local n = #result
797            for i=1,#literals do
798                result[n+i] = literals[i]
799            end
800        end,
801        stopfigure = function()
802            context.stopnointerference()
803        end
804    }
805
806    local specification = {
807        flusher   = flusher,
808     -- incontext = true,
809    }
810
811    function metapost.pdfliterals(result)
812        metapost.flush(specification,result)
813        return result
814    end
815
816end
817
818function metapost.totable(result,askedfig)
819    local askedfig = askedfig or 1
820    local figure   = result and result.fig and result.fig[1]
821    if figure then
822        local results = { }
823        local objects = figure:objects()
824        for o=1,#objects do
825            local object = objects[o]
826            local result = { }
827            local fields = getfields(object) -- hm, is this the whole list, if so, we can get it once
828            for f=1,#fields do
829                local field   = fields[f]
830                result[field] = object[field]
831            end
832            results[o] = result
833        end
834        local boundingbox = figure:boundingbox()
835        return {
836            boundingbox = {
837                llx = boundingbox[1],
838                lly = boundingbox[2],
839                urx = boundingbox[3],
840                ury = boundingbox[4],
841            },
842            objects = results
843        }
844    else
845        return nil
846    end
847end
848