node-shp.lmt /size: 6402 b    last modification: 2025-02-21 11:03
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   <const> = nodecodes.hlist
20local vlist_code   <const> = 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        <const> = interfaces.variables.yes
32
33local actions = tasks.actions("shipouts")
34
35handlers.finalizelist = actions
36
37function handlers.finalizebox(box,ispage)
38    actions(getbox(box),ispage) -- nut
39end
40
41function handlers.finalizelist(list,ispage) -- beware of export: block it if needed
42    actions(list,ispage) -- 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    -- we do it in normalize but there we only handle lines, so hboxes still
61    -- have them .. so:
62
63    nodes.handlers.cleanuppage = function(head)
64        local count    = 0 -- see below
65        local found    = 0
66        local replaced = 0
67        if flatten then
68            head, count = flattendiscretionaries(head,true) -- nested
69        end
70        if soften then
71            head, found, replaced = softenhyphens(head) -- could have been a lua loop
72        end
73        if trace then
74            report("%i discretionaries flattened, %i of %i discretionary hyphens softened",count,replaced,found)
75        end
76        return head
77    end
78
79    implement {
80        name      = "setsofthyphens",
81        arguments = "string",
82        actions   = function(v)
83            soften = v == v_yes
84        end
85    }
86
87end
88
89-- interface
90
91implement {
92    name      = "finalizeshipoutbox",
93    arguments = { "integer", true },
94    actions   = handlers.finalizebox,
95}
96
97implement {
98    name      = "finalizeobjectbox",
99    arguments = { "integer", false },
100    actions   = handlers.finalizebox,
101}
102
103-- just in case we want to optimize lookups:
104
105local frequencies = { }
106
107nodes.tracers.frequencies = frequencies
108
109local data = { }
110local done = false
111
112setmetatableindex(data,function(t,k)
113    local v = { }
114    setmetatableindex(v,function(t,k)
115        local v = { }
116        t[k] = v
117        setmetatableindex(v,function(t,k)
118            t[k] = 0
119            return 0
120        end)
121        return v
122    end)
123    t[k] = v
124    return v
125end)
126
127local function count(head,data,subcategory)
128    -- no components, pre, post, replace .. can maybe an option .. but
129    -- we use this for optimization so it makes sense to look the the
130    -- main node only
131    for n, id in nextnode, tonut(head) do
132        local dn = data[nodecodes[id]] -- we could use id and then later convert to nodecodes
133        dn[subcategory] = dn[subcategory] + 1
134        if id == hlist_code or id == vlist_code then
135            count(getlist(n),data,subcategory)
136        end
137    end
138end
139
140local function register(category,subcategory)
141    return function(head)
142        done = true
143        count(head,data[category],subcategory)
144        return head, false
145    end
146end
147
148frequencies.register = register
149frequencies.filename = nil
150
151trackers.register("nodes.frequencies",function(v)
152    if type(v) == "string" then
153        frequencies.filename = v
154    end
155    tracers.frequencies_shipouts_before   = register("shipouts",   "begin")
156    tracers.frequencies_shipouts_after    = register("shipouts",   "end")
157    tracers.frequencies_processors_before = register("processors", "begin")
158    tracers.frequencies_processors_after  = register("processors", "end")
159    tasks.prependaction("shipouts",   "before", "nodes.tracers.frequencies_shipouts_before")
160    tasks.appendaction ("shipouts",   "after",  "nodes.tracers.frequencies_shipouts_after")
161    tasks.prependaction("processors", "before", "nodes.tracers.frequencies_processors_before")
162    tasks.appendaction ("processors", "after",  "nodes.tracers.frequencies_processors_after")
163end)
164
165statistics.register("node frequencies", function()
166    if done then
167        local filename = frequencies.filename or (tex.jobname .. "-frequencies.lua")
168        io.savedata(filename,table.serialize(data,true))
169        return format("saved in %q",filename)
170    end
171end)
172
173do -- no longer used
174
175    local whatsitcodes = nodes.whatsitcodes
176
177    local whatsit_code <const> = nodecodes.whatsit
178
179    local removables   = {
180        [whatsitcodes.open]    = true,
181        [whatsitcodes.close]   = true,
182        [whatsitcodes.write]   = true,
183        [whatsitcodes.savepos] = true,
184        [whatsitcodes.latelua] = true,
185    }
186
187    local setlist = nuts.setlist
188    local getlist = nuts.getlist
189    local remove  = nuts.remove
190
191    local function cleanup(head)
192        for current, id, subtype in nextnode, head do
193            if id == whatsit_code then
194                if removables[subtype] then
195                    head = remove(head,current,true)
196                end
197            elseif id == hlist_code or id == vlist_code then
198                local sl = getlist(current)
199                if sl then
200                    local rl = cleanup(sl)
201                    if rl ~= sl then
202                        setlist(current,rl)
203                    end
204                end
205            end
206        end
207        return head
208    end
209
210    function handlers.cleanupbox(box)
211        cleanup(getbox(box))
212    end
213
214    implement {
215        name      = "cleanupbox",
216        public    = true,
217        protected = true,
218        arguments = "integer",
219        actions   = handlers.cleanupbox,
220    }
221
222end
223