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