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