node-shp.lmt /size: 6097 b    last modification: 2023-12-21 09:44
1if not modules then modules = { } end modules ['node-shp'] = {
2    version   = 1.001,
3    optimize  = true,
4    comment   = "companion to node-ini.mkiv",
5    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
6    copyright = "PRAGMA ADE / ConTeXt Development Team",
7    license   = "see context related readme files"
8}
9
10local next, type = next, type
11local format = string.format
12local setmetatableindex = table.setmetatableindex
13
14local nodes        = nodes
15local tasks        = nodes.tasks
16local handlers     = nodes.handlers
17
18local nodecodes    = nodes.nodecodes
19local hlist_code   = nodecodes.hlist
20local vlist_code   = nodecodes.vlist
21
22local nuts         = nodes.nuts
23local tonut        = nuts.tonut
24
25local nextnode     = nuts.traversers.node
26local getlist      = nuts.getlist
27local getbox       = nuts.getbox
28
29local implement    = interfaces.implement
30
31local v_yes        = interfaces.variables.yes
32
33local actions = tasks.actions("shipouts")
34
35handlers.finalizelist = actions
36
37function handlers.finalizebox(box)
38    actions(getbox(box)) -- nut
39end
40
41function handlers.finalizelist(list) -- beware of export: block it if needed
42    actions(list) -- nut
43end
44
45do
46
47    local flattendiscretionaries = nuts.flattendiscretionaries
48    local softenhyphens          = nuts.softenhyphens
49
50    local report  = logs.reporter("shipout")
51
52    local trace   = false
53    local flatten = true -- also done as line option
54    local soften  = true -- true by default
55
56    trackers  .register("backend.cleanup",         function(v) trace   = v end)
57    directives.register("backend.cleanup.flatten", function(v) flatten = v end)
58    directives.register("backend.cleanup.soften",  function(v) soften  = v end)
59
60    nodes.handlers.cleanuppage = function(head)
61     -- local count    = 0
62        local found    = 0
63        local replaced = 0
64        if flatten then
65            head, count = flattendiscretionaries(head,true) -- nested
66        end
67        if soften then
68            head, found, replaced = softenhyphens(head) -- could have been a lua loop
69        end
70        if trace then
71            report("%i discretionaries flattened, %i of %i discretionary hyphens softened",count,replaced,found)
72        end
73        return head
74    end
75
76    implement {
77        name      = "setsofthyphens",
78        arguments = "string",
79        actions   = function(v)
80            soften = v == v_yes
81        end
82    }
83
84end
85
86-- interface
87
88implement {
89    name      = "finalizebox",
90    arguments = "integer",
91    actions   = handlers.finalizebox,
92}
93
94-- just in case we want to optimize lookups:
95
96local frequencies = { }
97
98nodes.tracers.frequencies = frequencies
99
100local data = { }
101local done = false
102
103setmetatableindex(data,function(t,k)
104    local v = { }
105    setmetatableindex(v,function(t,k)
106        local v = { }
107        t[k] = v
108        setmetatableindex(v,function(t,k)
109            t[k] = 0
110            return 0
111        end)
112        return v
113    end)
114    t[k] = v
115    return v
116end)
117
118local function count(head,data,subcategory)
119    -- no components, pre, post, replace .. can maybe an option .. but
120    -- we use this for optimization so it makes sense to look the the
121    -- main node only
122    for n, id in nextnode, tonut(head) do
123        local dn = data[nodecodes[id]] -- we could use id and then later convert to nodecodes
124        dn[subcategory] = dn[subcategory] + 1
125        if id == hlist_code or id == vlist_code then
126            count(getlist(n),data,subcategory)
127        end
128    end
129end
130
131local function register(category,subcategory)
132    return function(head)
133        done = true
134        count(head,data[category],subcategory)
135        return head, false
136    end
137end
138
139frequencies.register = register
140frequencies.filename = nil
141
142trackers.register("nodes.frequencies",function(v)
143    if type(v) == "string" then
144        frequencies.filename = v
145    end
146    handlers.frequencies_shipouts_before   = register("shipouts",   "begin")
147    handlers.frequencies_shipouts_after    = register("shipouts",   "end")
148    handlers.frequencies_processors_before = register("processors", "begin")
149    handlers.frequencies_processors_after  = register("processors", "end")
150    tasks.prependaction("shipouts",   "before", "nodes.handlers.frequencies_shipouts_before")
151    tasks.appendaction ("shipouts",   "after",  "nodes.handlers.frequencies_shipouts_after")
152    tasks.prependaction("processors", "before", "nodes.handlers.frequencies_processors_before")
153    tasks.appendaction ("processors", "after",  "nodes.handlers.frequencies_processors_after")
154end)
155
156statistics.register("node frequencies", function()
157    if done then
158        local filename = frequencies.filename or (tex.jobname .. "-frequencies.lua")
159        io.savedata(filename,table.serialize(data,true))
160        return format("saved in %q",filename)
161    end
162end)
163
164do -- for the moment:
165
166    local whatsitcodes = nodes.whatsitcodes
167    local whatsit_code = nodecodes.whatsit
168
169    local removables   = {
170        [whatsitcodes.open]    = true,
171        [whatsitcodes.close]   = true,
172        [whatsitcodes.write]   = true,
173        [whatsitcodes.savepos] = true,
174        [whatsitcodes.latelua] = true,
175    }
176
177    local setlist = nuts.setlist
178    local getlist = nuts.getlist
179    local remove  = nuts.remove
180
181    local function cleanup(head)
182        for current, id, subtype in nextnode, head do
183            if id == whatsit_code then
184                if removables[subtype] then
185                    head = remove(head,current,true)
186                end
187            elseif id == hlist_code or id == vlist_code then
188                local sl = getlist(current)
189                if sl then
190                    local rl = cleanup(sl)
191                    if rl ~= sl then
192                        setlist(current,rl)
193                    end
194                end
195            end
196        end
197        return head
198    end
199
200    function handlers.cleanupbox(box)
201        cleanup(getbox(box))
202    end
203
204    implement {
205        name      = "cleanupbox",
206        public    = true,
207        protected = true,
208        actions   = handlers.cleanupbox,
209        arguments = "integerargument"
210    }
211
212end
213