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
73
74function spacings.handler(head)
75 local _, start = findattribute(head, a_spacings)
76 if start then
77 local done = false
78
79
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)
162 end
163 if start then
164 start = getnext(start)
165 end
166 end
167 if done then
168
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
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
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
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 |