node-ext.lmt /size: 18 Kb    last modification: 2025-02-21 11:03
1if not modules then modules = { } end modules ['back-out'] = {
2    version   = 1.001,
3    comment   = "companion to back-ini.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 type, unpack, rawset = type, unpack, type
10local loadstring = loadstring
11local sind, cosd, abs = math.sind, math.cosd, math.abs
12local insert, remove = table.insert, table.remove
13
14local context             = context
15local implement           = interfaces.implement
16
17local allocate            = utilities.storage.allocate
18
19local formatters          = string.formatters
20
21local get                 = tokens.accessors.index
22
23local scanners            = tokens.scanners
24local scaninteger         = scanners.integer
25local scanstring          = scanners.string
26local scankeyword         = scanners.keyword
27local scantokenlist       = scanners.tokenlist
28local scannumber          = scanners.number
29
30local nuts                = nodes.nuts
31local tonode              = nuts.tonode
32local copynut             = nuts.copy
33local nutpool             = nuts.pool
34local nodepool            = nodes.pool
35
36local nodeproperties      = nodes.properties.data
37
38local register            = nutpool.register
39local newnut              = nuts.new
40local tonode              = nodes.tonode
41
42-- Whatsits are a generic node type with very few fields. We use them to implement
43-- whatever we like. Some are good old tex whatsits but many are specific to the
44-- macro package. They are part of the backend.
45
46backends                  = backends or { }
47local backends            = backends
48
49local whatsitcodes        = allocate { }
50nodes.whatsitcodes        = whatsitcodes
51local lastwhatsit         = 0
52
53local whatsit_code        <const> = nodes.nodecodes.whatsit
54
55nodes.subtypes.whatsit       = whatsitcodes
56nodes.subtypes[whatsit_code] = whatsitcodes
57
58local function registerwhatsit(name)
59    lastwhatsit = lastwhatsit + 1
60    whatsitcodes[lastwhatsit] = name
61    whatsitcodes[name] = lastwhatsit
62    return lastwhatsit
63end
64
65local function registerwhatsitnode(name)
66    return register(newnut(whatsit_code,registerwhatsit(name)))
67end
68
69-- We only have a subset of literals. In fact, we try to avoid literals.
70
71do
72
73    local literalvalues = allocate { }
74    nodes.literalvalues = literalvalues
75    local lastliteral   = 0
76    local literalnode   = registerwhatsitnode("literal")
77
78    local function registerliteral(name,alias)
79        lastliteral = lastliteral + 1
80        literalvalues[lastliteral] = name
81        literalvalues[name] = lastliteral
82        if alias then
83            literalvalues[alias] = lastliteral
84        end
85        return lastliteral
86    end
87
88    local originliteral_code = registerliteral("origin")
89    local pageliteral_code   = registerliteral("page")
90    local directliteral_code = registerliteral("always","direct")
91    local rawliteral_code    = registerliteral("raw")
92    local textliteral_code   = registerliteral("text") -- not to be used
93    local fontliteral_code   = registerliteral("font") -- not to be used
94
95    function nutpool.originliteral(str) local t = copynut(literalnode) nodeproperties[t] = { data = str, mode = originliteral_code } return t end
96    function nutpool.pageliteral  (str) local t = copynut(literalnode) nodeproperties[t] = { data = str, mode = pageliteral_code   } return t end
97    function nutpool.directliteral(str) local t = copynut(literalnode) nodeproperties[t] = { data = str, mode = directliteral_code } return t end
98    function nutpool.rawliteral   (str) local t = copynut(literalnode) nodeproperties[t] = { data = str, mode = rawliteral_code    } return t end
99
100    local pdfliterals = {
101        [originliteral_code] = originliteral_code, [literalvalues[originliteral_code]] = originliteral_code,
102        [pageliteral_code]   = pageliteral_code,   [literalvalues[pageliteral_code]]   = pageliteral_code,
103        [directliteral_code] = directliteral_code, [literalvalues[directliteral_code]] = directliteral_code,
104        [rawliteral_code]    = rawliteral_code,    [literalvalues[rawliteral_code]]    = rawliteral_code,
105    }
106
107    function nutpool.literal(mode,str)
108        local t = copynut(literalnode)
109        if str then
110            nodeproperties[t] = { data = str, mode = pdfliterals[mode] or pageliteral_code }
111        else
112            nodeproperties[t] = { data = mode, mode = pageliteral_code }
113        end
114        return t
115    end
116
117end
118
119-- The latelua node is just another whatsit and we handle the \LUA\ code with
120-- other \LUA\ code, contrary to \LUATEX\ where it's a native node.
121
122do
123
124    local getdata     = nuts.getdata
125    local serialize   = token.serialize
126
127    local lateluanode = registerwhatsitnode("latelua")
128    local noflatelua  = 0
129
130    function nutpool.latelua(code)
131        local n = copynut(lateluanode)
132        nodeproperties[n] = { data = code }
133        return n
134    end
135
136    function backends.latelua(current,pos_h,pos_v) -- todo: pass pos_h and pos_v (more efficient in lmtx)
137        local prop = nodeproperties[current]
138        local data = prop and prop.data or getdata(current)
139        local kind = type(data)
140        noflatelua = noflatelua + 1
141        if kind == "table" then
142            data.action(data.specification or data,current,pos_h,pos_v)
143        elseif kind == "function" then
144            data(current,pos_h,pos_v)
145        else
146            if kind ~= "string" then
147                data = serialize(data)
148            end
149            if data and #data ~= "" then
150                local code = loadstring(data)
151                if code then
152                    code(current,pos_h,pos_v)
153                end
154            end
155        end
156    end
157
158    function backends.getcallbackstate()
159        return { count = noflatelua }
160    end
161
162    implement {
163        name      = "latelua",
164        public    = true,
165        protected = true,
166        untraced  = true,
167        actions   = function()
168            local node = copynut(lateluanode)
169            local name = "latelua"
170            if scankeyword("name") then
171                name = scanstring()
172            end
173            local data = scantokenlist()
174            nodeproperties[node] = { name = name, data = data }
175            return context(tonode(node))
176        end,
177    }
178
179end
180
181-- This can be anything. Dealing with these nodes is done via properties, as with
182-- the other whatsits.
183
184do
185
186    local usernode = registerwhatsitnode("userdefined")
187
188    local userids  = allocate()
189    local lastid   = 0
190
191    setmetatable(userids, {
192        __index = function(t,k)
193            if type(k) == "string" then
194                lastid = lastid + 1
195                rawset(userids,lastid,k)
196                rawset(userids,k,lastid)
197                return lastid
198            else
199                rawset(userids,k,k)
200                return k
201            end
202        end,
203        __call = function(t,k)
204            return t[k]
205        end
206    } )
207
208    function nutpool.userdefined(id,data)
209        local n = copynut(usernode)
210        nodeproperties[n] = { id = id, data = data }
211        return n
212    end
213
214    nutpool .usernode = nutpool.userdefined
215    nutpool .userids  = userids
216    nodepool.userids  = userids
217
218end
219
220-- This one is only used by generic packages.
221
222do
223
224    local saveposnode = registerwhatsitnode("savepos")
225
226    function nutpool.savepos()
227        return copynut(saveposnode)
228    end
229
230end
231
232do
233
234    local savenode      = registerwhatsitnode("save")
235    local restorenode   = registerwhatsitnode("restore")
236    local setmatrixnode = registerwhatsitnode("setmatrix")
237
238    local stack         = { }
239    local restore       = true -- false
240
241    -- these are old school nodes
242
243    function nutpool.save()
244        return copynut(savenode)
245    end
246
247    function nutpool.restore()
248        return copynut(restorenode)
249    end
250
251    function nutpool.setmatrix(rx,sx,sy,ry,tx,ty)
252        local t = copynut(setmatrixnode)
253        nodeproperties[t] = { matrix = { rx, sx, sy, ry, tx, ty } }
254        return t
255    end
256
257    implement {
258        name    = "startgraphicgroup",
259        actions = function()
260            context(tonode(copynut(savenode)))
261        end,
262    }
263
264    implement {
265        name    = "stopgraphicgroup",
266        actions = function()
267            context(tonode(copynut(restorenode)))
268        end,
269    }
270
271    -- common
272
273    local function stopcommon(n)
274        local top = remove(stack)
275        if top == false then
276            return -- not wrapped
277        elseif top == true then
278            return copynut(n)
279        elseif top then
280            local t = copynut(n)
281            nodeproperties[t] = { matrix = { unpack(top) } }
282            return t
283        else
284            return -- nesting error
285        end
286    end
287
288    -- matrix
289
290    local startmatrixnode = registerwhatsitnode("startmatrix")
291    local stopmatrixnode  = registerwhatsitnode("stopmatrix")
292
293    local function startmatrix(rx, sx, sy, ry)
294        if rx == 1 and sx == 0 and sy == 0 and ry == 1 then
295            insert(stack,false)
296        else
297            local t = copynut(startmatrixnode)
298            nodeproperties[t] = { matrix = { rx, sx, sy, ry } }
299            insert(stack,store and { -rx, -sx, -sy, -ry } or true)
300            return t
301        end
302    end
303
304    local function stopmatrix()
305        return stopcommon(stopmatrixnode)
306    end
307
308    implement {
309        name    = "startmatrix",
310        actions = function()
311            local rx, sx, sy, ry = 1, 0, 0, 1
312            while true do
313                    if scankeyword("rx") then rx = scannumber()
314                elseif scankeyword("ry") then ry = scannumber()
315                elseif scankeyword("sx") then sx = scannumber()
316                elseif scankeyword("sy") then sy = scannumber()
317                else   break end
318            end
319            local t = startmatrix(rx,sx,sy,ry)
320            if t then
321                context(tonode(t))
322            end
323        end,
324    }
325
326    implement {
327        name    = "stopmatrix",
328        actions = function()
329            local t = stopmatrix(n)
330            if t then
331                context(tonode(t))
332            end
333        end,
334    }
335
336    -- scale
337
338    local startscalingnode = registerwhatsitnode("startscaling")
339    local stopscalingnode  = registerwhatsitnode("stopscaling")
340
341    local function startscaling(rx,ry) -- at the tex end we use sx and sy instead of rx and ry
342        if rx == 1 and ry == 1 then
343            insert(stack,false)
344        else
345            if rx == 0 then
346                rx = 0.0001
347            end
348            if ry == 0 then
349                ry = 0.0001
350            end
351            local t = copynut(startscalingnode)
352            nodeproperties[t] = { matrix = { rx, 0, 0, ry } }
353            insert(stack,restore and { 1/rx, 0, 0, 1/ry } or true)
354            return t
355        end
356    end
357
358    local function stopscaling() -- at the tex end we use sx and sy instead of rx and ry
359        return stopcommon(stopscalingnode)
360    end
361
362    nutpool.startscaling = startscaling
363    nutpool.stopscaling  = stopscaling
364
365    implement {
366        name    = "startscaling",
367        actions = function()
368            local rx, ry = 1, 1
369            while true do
370                if scankeyword("rx") then
371                    rx = scannumber()
372                elseif scankeyword("ry") then
373                    ry = scannumber()
374                else
375                    break
376                end
377            end
378            local t = startscaling(rx,ry)
379            if t then
380                context(tonode(t))
381            end
382        end,
383    }
384
385    implement {
386        name    = "stopscaling",
387        actions = function()
388            local t = stopscaling(n)
389            if t then
390                context(tonode(t))
391            end
392        end,
393    }
394
395    -- rotate
396
397    local startrotationnode = registerwhatsitnode("startrotation")
398    local stoprotationnode  = registerwhatsitnode("stoprotation")
399
400    local function startrotation(a)
401        if a == 0 then
402            insert(stack,false)
403        else
404            local s, c = sind(a), cosd(a)
405            if abs(s) < 0.000001 then
406                s = 0 -- otherwise funny -0.00000
407            end
408            if abs(c) < 0.000001 then
409                c = 0 -- otherwise funny -0.00000
410            end
411            local t = copynut(startrotationnode)
412            nodeproperties[t] = { matrix = { c, s, -s, c } }
413            insert(stack,restore and { c, -s, s, c } or true)
414            return t
415        end
416    end
417
418    local function stoprotation()
419        return stopcommon(stoprotationnode)
420    end
421
422    nutpool.startrotation = startrotation
423    nutpool.stoprotation  = stoprotation
424
425    implement {
426        name    = "startrotation",
427        actions = function()
428            local n = scannumber()
429            local t = startrotation(n)
430            if t then
431                context(tonode(t))
432            end
433        end,
434    }
435
436    implement {
437        name    = "stoprotation",
438        actions = function()
439            local t = stoprotation()
440            if t then
441                context(tonode(t))
442            end
443        end,
444    }
445
446    -- mirror
447
448    local startmirroringnode = registerwhatsitnode("startmirroring")
449    local stopmirroringnode  = registerwhatsitnode("stopmirroring")
450
451    local function startmirroring()
452        local t = copynut(startmirroringnode)
453        nodeproperties[t] = { matrix = { -1, 0, 0, 1 } }
454        return t
455    end
456
457    local function stopmirroring()
458        local t = copynut(stopmirroringnode)
459        nodeproperties[t] = { matrix = { -1, 0, 0, 1 } }
460        return t
461    end
462
463    implement {
464        name    = "startmirroring",
465        actions = function()
466            context(tonode(startmirroring()))
467        end,
468    }
469
470    implement {
471        name    = "stopmirroring",
472        actions = function()
473            context(tonode(stopmirroring()))
474        end,
475    }
476
477    -- clip
478
479    local startclippingnode = registerwhatsitnode("startclipping")
480    local stopclippingnode  = registerwhatsitnode("stopclipping")
481
482    local function startclipping(path)
483        local t = copynut(startclippingnode)
484        nodeproperties[t] = { path = path }
485        return t
486    end
487
488    local function stopclipping()
489        return copynut(stopclippingnode)
490    end
491
492    implement {
493        name    = "startclipping",
494        actions = function()
495            context(tonode(startclipping(scanstring())))
496        end
497    }
498
499    implement {
500        name    = "stopclipping",
501        actions = function()
502            context(tonode(stopclipping()))
503        end,
504    }
505
506end
507
508-- The (delayed and immediate) write operations are emulated in \LUA\ and presented
509-- as primitives at the \TEX\ end.
510
511do
512
513    local logwriter      = logs.writer
514    local openfile       = io.open
515    local flushio        = io.flush
516    local serialize      = token.serialize
517
518    local opennode       = registerwhatsitnode("open")
519    local writenode      = registerwhatsitnode("write")
520    local closenode      = registerwhatsitnode("close")
521    local channels       = { }
522
523    local immediate_code <const> = tex.flagcodes.immediate
524
525    function backends.openout(n)
526        local p = nodeproperties[n]
527        if p then
528            local handle = openfile(p.filename,"wb") or false
529            if handle then
530                channels[p.channel] = handle
531            else
532                -- error
533            end
534        end
535    end
536
537    function backends.writeout(n)
538        local p = nodeproperties[n]
539        if p then
540            local handle  = channels[p.channel]
541            local content = serialize(p.data)
542            if handle then
543                handle:write(content,"\n")
544            else
545               logwriter(content,"\n")
546            end
547        end
548    end
549
550    function backends.closeout(n)
551        local p = nodeproperties[n]
552        if p then
553            local channel = p.channel
554            local handle  = channels[channel]
555            if handle then
556                handle:close()
557                channels[channel] = false
558                flushio()
559            else
560                -- error
561            end
562        end
563    end
564
565    local function immediately(prefix)
566        return prefix and (prefix & immediate_code) ~= 0
567    end
568
569    implement {
570        name    = "openout",
571        public  = true,
572        usage   = "value",
573        actions = function(prefix)
574            local channel = scaninteger()
575            scankeyword("=") -- hack
576            local filename = scanstring()
577            if not immediately(prefix) then
578                local n = copynut(opennode)
579                nodeproperties[n] = { channel = channel, filename = filename } -- action = "open"
580                return context(tonode(n))
581            elseif not channels[channel] then
582                local handle = openfile(filename,"wb") or false
583                if handle then
584                    channels[channel] = handle
585                else
586                    -- error
587                end
588            end
589        end,
590    }
591
592    implement {
593        name    = "write",
594        public  = true,
595        usage   = "value",
596        actions = function(prefix)
597            local channel = scaninteger()
598            if not immediately(prefix) then
599                local t = scantokenlist()
600                local n = copynut(writenode)
601                nodeproperties[n] = { channel = channel, data = t } -- action = "write"
602                return context(tonode(n))
603            else
604                local content = scanstring()
605                local handle  = channels[channel]
606                if handle then
607                    handle:write(content,"\n")
608                else
609                    logwriter(content,"\n")
610                end
611            end
612        end,
613    }
614
615    implement {
616        name    = "closeout",
617        public  = true,
618        usage   = "value",
619        actions = function(prefix)
620            local channel = scaninteger()
621            if not immediately(prefix) then
622                local n = copynut(closenode)
623                nodeproperties[n] = { channel = channel } -- action = "close"
624                return context(tonode(n))
625            else
626                local handle = channels[channel]
627                if handle then
628                    handle:close()
629                    channels[channel] = false
630                    flushio()
631                else
632                    -- error
633                end
634            end
635        end,
636    }
637
638    -- so we have them defined and can use them at the tex end .. still needed?
639
640    local open_command  = get(token.create("openout"))
641    local write_command = get(token.create("write"))
642    local close_command = get(token.create("closeout"))
643
644end
645
646-- states: these get injected independent of grouping
647
648do
649
650    local setstatenode = registerwhatsitnode("setstate")
651
652    function nutpool.setstate(data)
653        local n = copynut(setstatenode)
654        nodeproperties[n] = { data = data }
655        return n
656    end
657
658end
659
660-- We don't support specials so we make them dummies.
661
662do
663    implement {
664        name      = "special",
665        actions   = scanstring,
666        public    = true,
667        protected = true,
668    }
669end
670