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