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