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
71
72function spacings.handler(head)
73 local start = head
74
75
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)
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
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
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
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 |