mlib-lmp.lmt /size: 20 Kb    last modification: 2025-02-21 11:03
1if not modules then modules = { } end modules ['mlib-lmp'] = {
2    version   = 1.001,
3    comment   = "companion to mlib-ctx.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files",
7}
8
9-- path relates stuff ... todo: use a stack (or numeric index to list)
10
11local type, tonumber, tostring = type, tonumber, tostring
12local setmetatable, setmetatableindex = setmetatable, table.setmetatableindex
13local find, match = string.find, string.match
14local insert, remove, sort = table.insert, table.remove, table.sort
15
16local aux            = mp.aux
17local mpnumeric      = aux.numeric
18local mppair         = aux.pair
19
20local registerdirect = metapost.registerdirect
21local registerscript = metapost.registerscript
22
23local scan           = mp.scan
24local skip           = mp.skip
25local get            = mp.get
26local inject         = mp.inject
27
28local scannumber     = scan.number
29local scanstring     = scan.string
30local scaninteger    = scan.integer
31local scannumeric    = scan.numeric
32local scanwhatever   = scan.whatever
33local scanpath       = scan.path
34local scanproperty   = scan.property
35
36local gethashentry   = get.hashentry
37
38local bpfactor       <const> = number.dimenfactors.bp
39
40local injectwhatever = inject.whatever
41local injectboolean  = inject.boolean
42local injectnumeric  = inject.numeric
43local injectstring   = inject.string
44local injectpair     = inject.pair
45local injectpath     = inject.path
46
47local injectwhd      = inject.whd -- scaled
48local injectxy       = inject.xy
49local injectpt       = inject.pt
50
51local report         = logs.reporter("metapost", "log")
52local report_message = logs.reporter("metapost")
53
54local codes          = metapost.codes
55local types          = metapost.types
56local procodes       = mplib.propertycodes
57
58local implement      = interfaces.implement
59
60do
61
62    local function s(a,b)
63        local aa = a[1]
64        local bb = b[1]
65        if aa == bb then
66            aa = a[2]
67            bb = b[2]
68        end
69        return aa < bb
70    end
71
72    registerscript("sortedpath", function()
73        local p = scanpath()
74        for i=1,#p do
75            local pi = p[i]
76            p[i] = { pi[1], pi[2] }
77        end
78        sort(p,s)
79        injectpath(p)
80    end)
81
82    registerscript("uniquepath", function()
83        local p = scanpath()
84        local u = { }
85        local n = 0
86        local xx = nil
87        local yy = nil
88        sort(p,s)
89        for i=1,#p do
90            local pi = p[i]
91            local x = pi[1]
92            local y = pi[2]
93            if x ~= xx or y ~= yy then
94                n = n + 1
95                u[n] = { x, y }
96                xx = x
97                yy = y
98            end
99        end
100        injectpath(u)
101    end)
102
103end
104
105do
106
107    local p = nil
108    local n = 0
109
110    registerscript("pathreset", function()
111        p = nil
112        n = 0
113    end)
114
115    registerdirect("pathlengthof", function()
116        p = scanpath()
117        n = p and #p or 1
118        return n
119    end)
120
121    registerdirect("pathpointof", function()
122        local i = scaninteger()
123        if i > 0 and i <= n then
124            local pi = p[i]
125            injectpair(pi[1],pi[2])
126        end
127    end)
128
129    registerdirect("pathleftof", function()
130        local i = scaninteger()
131        if i > 0 and i <= n then
132            local pi = p[i]
133            injectpair(pi[5],pi[6])
134        end
135    end)
136
137    registerdirect("pathrightof", function()
138        local i = scaninteger()
139        if i > 0 and i <= n then
140            local pn
141            if i == 1 then
142                pn = p[2] or p[1]
143            else
144                pn = p[i+1] or p[1]
145            end
146            injectpair(pn[3],pn[4])
147        end
148    end)
149
150end
151
152registerscript("showproperty", function()
153    local k, s, p, d = scanproperty()
154    if k then
155        report("name %a, property %a, command %a, detail %a",s,procodes[p] or "-",codes[k] or "-",types[d] or "-")
156    end
157end)
158
159registerscript("showhashentry", function()
160    local s = scanstring()
161    if s then
162        local k, p, d = gethashentry(s)
163        if k then
164            report("name %a, property %a, command %a, detail %a",s,procodes[p] or "-",codes[k] or "-",types[d] or "-")
165        end
166    end
167end)
168
169-- local getmacro      = tokens.getters.macro
170-- local mpgnamespace  = getmacro("??graphicvariable")
171
172-- registerscript("mpv_numeric",   function() injectnumeric  (getmacro(mpgnamespace .. getmacro("currentmpcategory") .. ":" .. scanmpstring())) end)
173-- registerscript("mpv_dimension", function() return getmacro(mpgnamespace .. getmacro("currentmpcategory") .. ":" .. scanmpstring()) end)
174-- registerscript("mpv_string",    function() injectstring   (getmacro(mpgnamespace .. getmacro("currentmpcategory") .. ":" .. scanmpstring())) end)
175
176-- registerscript("mpvar", function() return getmacro(mpgnamespace .. getmacro("currentmpcategory") .. ":" .. scanmpstring(), true) end) -- Isn't it already edef'd?
177-- registerscript("mpvar", function() return getmacro(metapost.namespace .. scanmpstring(), true) end) -- Isn't it already edef'd?
178
179do
180
181    local expandtex       = mp.expandtex
182
183    local tokenvalues     = tokens.values
184    local dimension_value <const> = tokenvalues.dimension
185    local integer_value   <const> = tokenvalues.integer
186    local boolean_value   <const> = tokenvalues.boolean
187    local string_value    <const> = tokenvalues.string
188    local unknown_value   <const> = tokenvalues.none
189
190    registerdirect("mpvard", function()
191        if not expandtex(dimension_value,"mpcategoryparameter",true,scanstring()) then
192            injectnumeric(0)
193        end
194    end)
195
196    registerdirect("mpvarn", function()
197        if not expandtex(integer_value,"mpcategoryparameter",true,scanstring()) then
198            injectnumeric(0)
199        end
200    end)
201
202    registerdirect("mpvars", function()
203        if not expandtex(string_value,"mpcategoryparameter",true,scanstring()) then
204            injectstring("")
205        end
206    end)
207
208    registerdirect("mpvarb", function()
209        if not expandtex(boolean_value,"mpcategoryparameter",true,scanstring()) then
210            injectboolean(false)
211        end
212    end)
213
214    registerdirect("mpvar", function()
215        if not expandtex(unknown_value,"mpcategoryparameter",true,scanstring()) then
216            injectnumeric(0)
217        end
218    end)
219
220    -- older:
221
222    registerscript("texvar", function()
223        if not expandtex(unknown_value,"mpcategoryparameter",true,scanstring()) then
224            injectnumeric(0)
225        end
226    end)
227
228    registerscript("texstr", function()
229        if not expandtex(string_value,"mpcategoryparameter",true,scanstring()) then
230            injectstring("")
231        end
232    end)
233
234end
235
236do
237
238    registerscript("textextanchor", function()
239        local x, y = match(scanstring(),"tx_anchor=(%S+) (%S+)") -- todo: make an lpeg
240        if x and y then
241            x = tonumber(x)
242            y = tonumber(y)
243        end
244        injectpair(x or 0,y or 0)
245    end)
246
247end
248
249do
250
251    local mpnamedcolor = attributes.colors.mpnamedcolor
252    local mpprint      = mp.aux.print
253
254    mp.mf_named_color = function(str)
255        mpprint(mpnamedcolor(str))
256    end
257
258    -- todo: we can inject but currently we always get a string back so then
259    -- we need to deal with it upstream in the color module ... not now
260
261    registerscript("namedcolor",function() mpprint(mpnamedcolor(scanstring())) end)
262
263end
264
265do
266
267    local hashes = setmetatableindex("table")
268
269    -- There is no need to 'new' a hash in which case one can use any reasonable
270    -- tag. The registry aproach is mkiv compatible and not used in mkxl (lmtx).
271
272    local registry = { }
273    local count    = 0
274
275    registerdirect("lmt_hash_new", function()
276        local slot = false
277        for i=1,count do
278            if registry[i] then
279                slot = i
280                break
281            end
282        end
283        if not slot then
284            count = count + 1
285            slot  = count
286        end
287        registry[slot] = true
288        hashes[slot] = nil
289        injectwhatever(slot)
290    end)
291
292    registerdirect("lmt_hash_dispose", function()
293        local name = scanwhatever()
294        hashes[name] = nil
295        -- when new'd
296        if registry[name] then
297            registry[name] = false
298        end
299    end)
300
301    registerdirect("lmt_hash_reset", function()
302        local name = scanwhatever()
303        hashes[name] = nil
304    end)
305
306    registerdirect("lmt_hash_in", function()
307        local name = scanwhatever()
308        local key  = scanwhatever()
309        local hash = hashes[name]
310        injectwhatever(hash and hash[key] and true or false)
311    end)
312
313    registerdirect("lmt_hash_to", function()
314        local name  = scanwhatever()
315        local key   = scanwhatever()
316        local value = scanwhatever()
317        local hash  = hashes[name]
318        if hash then
319            hash[key] = value
320        end
321    end)
322
323    registerdirect("lmt_hash_from", function()
324     -- local name  = scanstring()
325        local name  = scanwhatever()
326     -- local key  = scanstring()
327        local key  = scanwhatever()
328        local hash = hashes[name]
329        injectwhatever(hash and hash[key] or false)
330    end)
331
332    interfaces.implement {
333        name      = "MPfromhash",
334        arguments = "2 strings",
335        actions   = function(name,key)
336            local hash = hashes[name] or hashes[tonumber(name)] or hashes[tostring(name)]
337            if hash then
338                local v = hash[key]
339                if v then
340                    context(v)
341                end
342            end
343        end
344    }
345
346end
347
348do
349
350    local bpfactor     = number.dimenfactors.bp
351    local nbdimensions = nodes.boxes.dimensions
352
353    registerdirect("boxdimensions", function()
354        local category = scanstring()
355        local index    = scanwhatever()
356        injectwhd(nbdimensions(category,index))
357    end)
358
359end
360
361do
362
363    local skiptoken      = skip.token
364
365    local comma_code     <const> = codes.comma
366
367    local getmacro       = tokens.getters.macro
368    local setmacro       = tokens.setters.macro
369
370    local getdimen       = tex.getdimen
371    local getcount       = tex.getcount
372    local gettoks        = tex.gettoks
373    local setdimen       = tex.setdimen
374    local setcount       = tex.setcount
375    local settoks        = tex.settoks
376
377    -- more helpers
378
379    registerdirect("getmacro", function() return getmacro(scanstring()) end)
380    registerdirect("getcount", function() return getcount(scanwhatever()) end)
381    registerdirect("gettoks",  function() return gettoks (scanwhatever()) end)
382    registerdirect("getdimen", function() return getdimen(scanwhatever()) * bpfactor end)
383
384    registerscript("setmacro", function() setmacro(scanstring(),scanstring()) end)
385    registerscript("setdimen", function() setdimen(scanwhatever(),scannumeric()/bpfactor) end)
386    registerscript("setcount", function() setcount(scanwhatever(),scannumeric()) end)
387    registerscript("settoks",  function() settoks (scanwhatever(),scanstring()) end)
388
389    registerscript("setglobalmacro", function() setmacro(scanstring(),scanstring(),"global") end)
390    registerscript("setglobaldimen", function() setdimen("global",scanwhatever(),scannumeric()/bpfactor) end)
391    registerscript("setglobalcount", function() setcount("global",scanwhatever(),scaninteger()) end)
392    registerscript("setglobaltoks",  function() settoks ("global",scanwhatever(),scanstring()) end)
393
394    local utfnum = utf.byte
395    local utfchr = utf.char
396    local utflen = utf.len
397    local utfsub = utf.sub
398
399    registerdirect("utfchr", function() return utfchr(scannumeric()) end)
400    registerdirect("utfnum", function() return utfnum(scanstring()) end)
401    registerdirect("utflen", function() return utflen(scanstring()) end)
402
403    registerdirect("utfsub", function() -- we have an optional third argument so we explicitly scan a text argument
404        return utfsub(scanstring(),skiptoken(comma_code) and scannumeric(),skiptoken(comma_code) and scannumeric())
405    end)
406
407    local setlogging = metapost.setlogging
408
409    registerscript("message", function()
410        setlogging(false)
411        local str = scanstring()
412        setlogging(true)
413        report_message("message : %s",str)
414    end)
415
416end
417
418-- position fun
419
420do
421
422    local getcount       = tex.getcount
423
424    local mpprint        = mp.print
425    local mpfprint       = mp.fprint
426
427    local mpscaninteger  = mp.scan.integer
428    local mpscannumber   = mp.scan.number
429
430    local jobpositions   = job.positions
431    local getwhd         = jobpositions.whd
432    local getxy          = jobpositions.xy
433    local getx           = jobpositions.x
434    local gety           = jobpositions.y
435    local getposition    = jobpositions.position
436    local getpage        = jobpositions.page
437    local getparagraph   = jobpositions.paragraph
438    local getregion      = jobpositions.region
439    local getcolumn      = jobpositions.column
440    local getmacro       = tokens.getters.macro
441
442    local columnofpos    = jobpositions.columnofpos
443    local getcolumndata  = jobpositions.getcolumndata
444 -- local overlapping    = jobpositions.overlapping
445 -- local onsamepage     = jobpositions.onsamepage
446 -- local columnofpos    = jobpositions.columnofpos
447
448    -- why not inject path directly
449
450    registerscript("positionpath", function()
451        local w, h, d = getwhd(scanstring())
452        if w then
453            mpfprint("((%p,%p)--(%p,%p)--(%p,%p)--(%p,%p)--cycle)",0,-d,w,-d,w,h,0,h)
454        else
455            mpprint("(origin--cycle)")
456        end
457    end)
458
459    registerscript("positioncurve", function()
460        local w, h, d = getwhd(scanstring())
461        if w then
462            mpfprint("((%p,%p)..(%p,%p)..(%p,%p)..(%p,%p)..cycle)",0,-d,w,-d,w,h,0,h)
463        else
464            mpprint("(origin--cycle)")
465        end
466    end)
467
468    registerscript("positionbox", function()
469        local p, x, y, w, h, d = getposition(scanstring())
470        if p then
471            mpfprint("((%p,%p)--(%p,%p)--(%p,%p)--(%p,%p)--cycle)",x,y-d,x+w,y-d,x+w,y+h,x,y+h)
472        else
473            mpprint("(%p,%p)--cycle",x or 0,y or 0)
474        end
475    end)
476
477    registerscript("positioncolumnbox", function()
478        local column = mpscaninteger()
479        local data   = getcolumndata(getcount("realpageno"),column)
480        if data then
481            local x, y, w, h, d = data.x, data.y, data.w, data.h, data.d
482            mpfprint("((%p,%p)--(%p,%p)--(%p,%p)--(%p,%p)--cycle)",x,y-d,x+w,y-d,x+w,y+h,x,y+h)
483        else
484            mpprint("(0,0)--cycle")
485        end
486    end)
487
488    registerscript("overlaycolumnbox", function()
489        local column = mpscaninteger()
490        local data   = getcolumndata(getcount("realpageno"),column)
491        if data then
492            local w, hd = data.w, data.h + data.d
493            mpfprint("((0,0)--(%p,0)--(%p,%p)--(0,%p)--cycle)",w,w,hd,hd)
494        else
495            mpprint("(0,0)--cycle")
496        end
497    end)
498
499    registerdirect("positionpage",      function() return getpage     (scanstring()) or 0 end)
500    registerdirect("positioncolumn",    function() return getcolumn   (scanstring()) or 0 end)
501    registerdirect("positionparagraph", function() return getparagraph(scanstring()) or 0 end)
502    registerdirect("positionregion",    function() return getregion   (scanstring()) or "unknown" end)
503    registerdirect("positionanchor",    function() return getmacro    ("MPanchorid") end)
504    registerdirect("positionwhd",       function() injectwhd(getwhd(scanstring())) end)
505    registerdirect("positionxy",        function() injectxy (getxy (scanstring())) end)
506    registerdirect("positionx",         function() injectpt (getx  (scanstring())) end)
507    registerdirect("positiony",         function() injectpt (gety  (scanstring())) end)
508
509    registerdirect("positioncolumnatx", function()
510        local realpage  = mpscaninteger()
511        local xposition = mpscannumber()
512        return columnofpos(realpage,xposition)
513    end)
514
515end
516
517do
518
519    local modes       = tex.modes
520    local systemmodes = tex.systemmodes
521
522    registerdirect("mode",       function() injectboolean(modes      [scanstring()] and true or false) end)
523    registerdirect("systemmode", function() injectboolean(systemmodes[scanstring()] and true or false) end)
524
525    -- for compatibility reasons we keep this (metafun manual):
526
527    local modes       = tex.modes
528    local systemmodes = tex.systemmodes
529
530    function mp.mode(s)
531        injectboolean(modes[s] and true or false)
532    end
533
534    function mp.systemmode(s)
535        injectboolean(systemmodes[s] and true or false)
536    end
537
538    mp.processingmode = mp.mode
539
540end
541
542-- for alan's nodes:
543
544do
545
546    local lpegmatch, lpegpatterns, P = lpeg.match, lpeg.patterns, lpeg.P
547
548    -- todo: scansuffix / why no return boolean (first one)
549
550    registerdirect("isarray", function()
551         injectboolean(find(scanstring(),"%d") and true or false)
552    end)
553
554    registerdirect("prefix", function()
555        local str = scanstring()
556        return match(str,"^(.-)[%d%[]") or str
557    end)
558
559    local dimension = lpeg.counter(P("[") * lpegpatterns.integer * P("]") + lpegpatterns.integer)
560
561    registerdirect("dimension", function() return dimension(scanstring()) end)
562
563    -- todo : share with mlib-pps.lua metapost,isobject
564
565    -- registerdirect("isobject", function()
566    --     injectboolean(find(scanstring(),"mf_object="))
567    -- end
568
569    local p1      = P("mf_object=")
570    local p2      = lpegpatterns.eol * p1
571    local pattern = (1-p2)^0 * p2 + p1
572
573    registerdirect("isobject", function()
574        local str = scanstring()
575        injectboolean(pattern and str ~= "" and lpegmatch(pattern,str))
576    end)
577
578end
579
580-- key/values (moved here, old mechanism)
581
582do
583
584    local stack, top = { }, nil
585
586    local function setvariable(k,v)
587        if top then
588            top[k] = v
589        else
590            metapost.variables[k] = v
591        end
592    end
593
594    local function pushvariable(k)
595        local t = { }
596        if top then
597            insert(stack,top)
598            top[k] = t
599        else
600            metapost.variables[k] = t
601        end
602        top = t
603    end
604
605    local function popvariable()
606        top = remove(stack)
607    end
608
609    registerscript("passvariable", function() setvariable (scanstring(), scanwhatever()) end)
610    registerscript("pushvariable", function() pushvariable(scanstring()) end)
611    registerscript("popvariable",  function() popvariable () end)
612
613    local stack = { }
614
615    local function pushvariables()
616        insert(stack,metapost.variables)
617        metapost.variables = { }
618    end
619
620    local function popvariables()
621        metapost.variables = remove(stack) or metapost.variables
622    end
623
624    metapost.setvariable   = setvariable
625    metapost.pushvariable  = pushvariable
626    metapost.popvariable   = popvariable
627    metapost.pushvariables = pushvariables
628    metapost.popvariables  = popvariables
629
630    implement {
631        name      = "mppushvariables",
632        actions   = pushvariables,
633    }
634
635    implement {
636        name      = "mppopvariables",
637        actions   = popvariables,
638    }
639
640end
641
642do
643
644    local repeatable = utilities.randomizer.repeatable
645
646    registerdirect("repeatablerandom", function()
647        return repeatable(scanstring())
648    end)
649
650end
651
652do
653
654    local hascurvature = metapost.hascurvature
655
656    registerdirect("hascurvature",
657        function()
658            local p = scanpath()    -- pathsegment
659            local t = scannumeric() -- tolerance
660            local l = p[1]
661            local r = p[#p]
662            injectboolean(l and r and hascurvature (
663                {
664                    x_coord = l[1], y_coord = l[2],
665                    left_x  = l[3], left_y  = l[4],
666                    right_x = l[5], right_y = l[6],
667                }, {
668                    x_coord = r[1], y_coord = r[2],
669                    left_x  = r[3], left_y  = r[4],
670                    right_x = r[5], right_y = r[6],
671                },
672                t
673            ) or false)
674        end
675    )
676
677end
678
679do
680
681    local defaults = {
682        noplugins = false
683    }
684
685    local backendoptions = setmetatableindex(function(t,k) local v = setmetatable({},defaults) t[k] = v return v end)
686
687    function metapost.resetbackendoptions(mpx)
688        backendoptions[mpx] = setmetatable({},defaults)
689    end
690
691    function metapost.getbackendoption(mpx,name)
692        return backendoptions[mpx][name]
693    end
694
695    registerdirect("setbackendoption",
696        function()
697            backendoptions[metapost.currentmpx()][scanstring()] = true
698        end
699    )
700end
701
702do
703
704    registerdirect("namedstacking", function()
705        injectnumeric(typesetters.stacking.getindex(scanstring()))
706    end)
707
708end
709