typo-spa.lmt /size: 9069 b    last modification: 2021-10-28 13:51
1if not modules then modules = { } end modules ['typo-spa'] = {
2    version   = 1.001,
3    comment   = "companion to typo-spa.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 next, type = next, type
10
11local trace_spacing = false  trackers.register("typesetters.spacing", function(v) trace_spacing = v end)
12
13local report_spacing = logs.reporter("typesetting","spacing")
14
15local nodes, fonts, node = nodes, fonts, node
16
17local fonthashes         = fonts.hashes
18local quaddata           = fonthashes.quads
19
20local texsetattribute    = tex.setattribute
21local unsetvalue         = attributes.unsetvalue
22
23local v_reset            = interfaces.variables.reset
24
25local nuts               = nodes.nuts
26
27local getnext            = nuts.getnext
28local getprev            = nuts.getprev
29local getattr            = nuts.getattr
30local isglyph            = nuts.isglyph
31
32local insertnodebefore   = nuts.insertbefore
33local insertnodeafter    = nuts.insertafter
34local remove_node        = nuts.remove
35local endofmath          = nuts.endofmath
36local unsetattributes    = nuts.unsetattributes
37local findattribute      = nuts.findattribute
38
39local nodepool           = nuts.pool
40local new_penalty        = nodepool.penalty
41local new_glue           = nodepool.glue
42
43local nodecodes          = nodes.nodecodes
44local math_code          = nodecodes.math
45
46local somespace          = nodes.somespace
47local somepenalty        = nodes.somepenalty
48
49local enableaction       = nodes.tasks.enableaction
50
51typesetters              = typesetters or { }
52local typesetters        = typesetters
53
54typesetters.spacings     = typesetters.spacings or { }
55local spacings           = typesetters.spacings
56
57spacings.mapping         = spacings.mapping or { }
58spacings.numbers         = spacings.numbers or { }
59
60local a_spacings         = attributes.private("spacing")
61
62storage.register("typesetters/spacings/mapping", spacings.mapping, "typesetters.spacings.mapping")
63
64local mapping = spacings.mapping
65local numbers = spacings.numbers
66
67for i=1,#mapping do
68    local m = mapping[i]
69    numbers[m.name] = m
70end
71
72-- todo cache lastattr
73
74function spacings.handler(head)
75    local _, start = findattribute(head, a_spacings)
76    if start then
77        local done = false
78        -- head is always begin of par (whatsit), so we have at least two prev nodes
79        -- penalty followed by glue
80        while start do
81            local char, id = isglyph(start)
82            if char then
83                local attr = getattr(start,a_spacings)
84                if attr and attr > 0 then
85                    local data = mapping[attr]
86                    if data then
87                        local map = data.characters[char]
88                        if map then
89                            local font = id
90                            local left = map.left
91                            local right = map.right
92                            local alternative = map.alternative
93                            local quad = quaddata[font]
94                            local prev = getprev(start)
95                            if left and left ~= 0 and prev then
96                                local ok = false
97                                local prevprev = getprev(prev)
98                                if alternative == 1 then
99                                    if somespace(prev,true) then
100                                        if somepenalty(prevprev,10000) then
101                                            if trace_spacing then
102                                                report_spacing("removing penalty and space before %C (left)",char)
103                                            end
104                                            head = remove_node(head,prev,true)
105                                            head = remove_node(head,prevprev,true)
106                                        else
107                                            if trace_spacing then
108                                                report_spacing("removing space before %C (left)",char)
109                                            end
110                                            head = remove_node(head,prev,true)
111                                        end
112                                    end
113                                    ok = true
114                                else
115                                    ok = not (somespace(prev,true) and somepenalty(prevprev,true)) or somespace(prev,true)
116                                end
117                                if ok then
118                                    if trace_spacing then
119                                        report_spacing("inserting penalty and space before %C (left)",char)
120                                    end
121                                    insertnodebefore(head,start,new_penalty(10000))
122                                    insertnodebefore(head,start,new_glue(left*quad))
123                                end
124                            end
125                            local next = getnext(start)
126                            if right and right ~= 0 and next then
127                                local ok = false
128                                local nextnext = getnext(next)
129                                if alternative == 1 then
130                                    if somepenalty(next,10000) then
131                                        if somespace(nextnext,true) then
132                                            if trace_spacing then
133                                                report_spacing("removing penalty and space after %C right",char)
134                                            end
135                                            head = remove_node(head,next,true)
136                                            head = remove_node(head,nextnext,true)
137                                        end
138                                    elseif somespace(next,true) then
139                                        if trace_spacing then
140                                            report_spacing("removing space after %C (right)", char)
141                                        end
142                                        head = remove_node(head,next,true)
143                                    end
144                                    ok = true
145                                else
146                                    ok = not (somepenalty(next,10000) and somespace(nextnext,true)) or somespace(next,true)
147                                end
148                                if ok then
149                                    if trace_spacing then
150                                        report_spacing("inserting penalty and space after %C (right)",char)
151                                    end
152                                    insertnodeafter(head,start,new_glue(right*quad))
153                                    insertnodeafter(head,start,new_penalty(10000))
154                                end
155                            end
156                        end
157                    end
158                    done = true
159                end
160            elseif id == math_code then
161                start = endofmath(start) -- weird, can return nil .. no math end?
162            end
163            if start then
164                start = getnext(start)
165            end
166        end
167        if done then
168    --         unsetattributes(a_spacings,head)
169        end
170    end
171    return head
172end
173
174local enabled = false
175
176function spacings.define(name)
177    local data = numbers[name]
178    if data then
179        -- error
180    else
181        local number = #mapping + 1
182        local data = {
183            name       = name,
184            number     = number,
185            characters = { },
186        }
187        mapping[number] = data
188        numbers[name]   = data
189    end
190end
191
192function spacings.setup(name,char,settings)
193    local data = numbers[name]
194    if not data then
195        -- error
196    else
197        data.characters[char] = settings
198    end
199end
200
201function spacings.set(name)
202    local n = unsetvalue
203    if name ~= v_reset then
204        local data = numbers[name]
205        if data then
206            if not enabled then
207                enableaction("processors","typesetters.spacings.handler")
208                enabled = true
209            end
210            n = data.number or unsetvalue
211        end
212    end
213    texsetattribute(a_spacings,n)
214end
215
216function spacings.reset()
217    texsetattribute(a_spacings,unsetvalue)
218end
219
220-- interface
221
222local implement = interfaces.implement
223
224implement {
225    name      = "definecharacterspacing",
226    actions   = spacings.define,
227    arguments = "string"
228}
229
230implement {
231    name      = "setupcharacterspacing",
232    actions   = spacings.setup,
233    arguments = {
234        "string",
235        "integer",
236        {
237            { "left",        "number" },
238            { "right",       "number" },
239            { "alternative", "integer" },
240        }
241    }
242}
243
244implement {
245    name      = "setcharacterspacing",
246    actions   = spacings.set,
247    arguments = "string"
248}
249