1if not modules then modules = { } end modules ['typo-tal'] = {
2 version = 1.001,
3 comment = "companion to typo-tal.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
9
10
11
12
13
14
15
16
17local next, type, tonumber = next, type, tonumber
18local div = math.div
19local utfbyte = utf.byte
20
21local splitmethod = utilities.parsers.splitmethod
22
23local glyph_code <const> = nodes.nodecodes.glyph
24local glue_code <const> = nodes.nodecodes.glue
25
26local fontcharacters = fonts.hashes.characters
27
28local categories = characters.categories
29
30local v_text <const> = interfaces.variables.text
31local v_number <const> = interfaces.variables.number
32
33local nuts = nodes.nuts
34local tonut = nuts.tonut
35
36local getnext = nuts.getnext
37local getprev = nuts.getprev
38local getboth = nuts.getboth
39local getid = nuts.getid
40local getfont = nuts.getfont
41local getchar = nuts.getchar
42local getattr = nuts.getattr
43local isglyph = nuts.isglyph
44
45local setattr = nuts.setattr
46local setchar = nuts.setchar
47
48local insertnodebefore = nuts.insertbefore
49local insertnodeafter = nuts.insertafter
50local nextglyph = nuts.traversers.glyph
51local getdimensions = nuts.dimensions
52
53local setglue = nuts.setglue
54
55local nodepool = nuts.pool
56local new_kern = nodepool.kern
57
58local tracers = nodes.tracers
59local setcolor = tracers.colors.set
60local tracedrule = tracers.pool.nuts.rule
61
62local enableaction = nodes.tasks.enableaction
63
64local characteralign = { }
65typesetters.characteralign = characteralign
66
67local trace_split = false trackers.register("typesetters.characteralign", function(v) trace_split = true end)
68local report = logs.reporter("aligning")
69
70local a_characteralign <const> = attributes.private("characteralign")
71local a_character <const> = attributes.private("characters")
72
73local enabled = false
74
75local datasets = false
76
77local implement = interfaces.implement
78
79local comma <const> = 0x002C
80local period <const> = 0x002E
81local punctuationspace <const> = 0x2008
82
83local validseparators = {
84 [comma] = true,
85 [period] = true,
86 [punctuationspace] = true,
87}
88
89local validsigns = {
90 [0x002B] = 0x002B,
91 [0x002D] = 0x2212,
92 [0x00B1] = 0x00B1,
93 [0x2212] = 0x2212,
94 [0x2213] = 0x2213,
95}
96
97
98
99
100local function setcharacteralign(column,separator,before,after)
101 if not enabled then
102 enableaction("processors","typesetters.characteralign.handler")
103 enabled = true
104 end
105 if not datasets then
106 datasets = { }
107 end
108 local dataset = datasets[column]
109 if not dataset then
110 local method, token
111 if separator then
112 method, token = splitmethod(separator)
113 if method and token then
114 separator = utfbyte(token) or comma
115 else
116 separator = utfbyte(separator) or comma
117 method = validseparators[separator] and v_number or v_text
118 end
119 else
120 separator = comma
121 method = v_number
122 end
123 local before = tonumber(before) or 0
124 local after = tonumber(after) or 0
125 dataset = {
126 separator = separator,
127 list = { },
128 maxbefore = before,
129 maxafter = after,
130 predefined = before > 0 or after > 0,
131 collected = false,
132 method = method,
133 separators = validseparators,
134 signs = validsigns,
135 }
136 datasets[column] = dataset
137 used = true
138 end
139 return dataset
140end
141
142local function resetcharacteralign()
143 datasets = false
144end
145
146characteralign.setcharacteralign = setcharacteralign
147characteralign.resetcharacteralign = resetcharacteralign
148
149implement {
150 name = "setcharacteralign",
151 actions = setcharacteralign,
152 arguments = { "integer", "string" }
153}
154
155implement {
156 name = "setcharacteraligndetail",
157 actions = setcharacteralign,
158 arguments = { "integer", "string", "dimension", "dimension" }
159}
160
161implement {
162 name = "resetcharacteralign",
163 actions = resetcharacteralign
164}
165
166local function traced_kern(w)
167 return tracedrule(w,nil,nil,"darkgray")
168end
169
170function characteralign.handler(head,where)
171 if not datasets then
172 return head
173 end
174 local first
175 for n in nextglyph, head do
176 first = n
177 break
178 end
179 if not first then
180 return head
181 end
182 local a = getattr(first,a_characteralign)
183 if not a or a == 0 then
184 return head
185 end
186 local column = div(a,0xFFFF)
187 local row = a % 0xFFFF
188 local dataset = datasets and datasets[column] or setcharacteralign(column)
189 local separator = dataset.separator
190 local list = dataset.list
191 local b_start = nil
192 local b_stop = nil
193 local a_start = nil
194 local a_stop = nil
195 local c = nil
196 local current = first
197 local sign = nil
198
199 local validseparators = dataset.separators
200 local validsigns = dataset.signs
201 local method = dataset.method
202
203 if method == v_number then
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220 local function bothdigit(current)
221 local prev, next = getboth(current)
222 if next and prev then
223 local char, font = isglyph(prev)
224 if char then
225 local data = fontcharacters[font][char]
226 if data and categories[data.unicode or char] == "nd" then
227 char, font = isglyph(next)
228 if char then
229 data = fontcharacters[font][char]
230 if data and categories[data.unicode or char] == "nd" then
231 return true
232 end
233 end
234 end
235 end
236 end
237 end
238
239 while current do
240 local char, id = isglyph(current)
241 if char then
242 local font = id
243 local data = fontcharacters[font][char]
244 local unicode = data and data.unicode or char
245 if not unicode then
246
247 elseif unicode == separator then
248 c = current
249 if trace_split then
250 setcolor(current,"darkred")
251 end
252 dataset.hasseparator = true
253 elseif categories[unicode] == "nd" or validseparators[unicode] then
254 if c then
255 if not a_start then
256 a_start = current
257 end
258 a_stop = current
259 if trace_split then
260 setcolor(current,validseparators[unicode] and "darkcyan" or "darkblue")
261 end
262 else
263 if not b_start then
264 if sign then
265 b_start = sign
266 local c, f = isglyph(sign)
267 local new = validsigns[c]
268 if char == new or not fontcharacters[f][new] then
269 if trace_split then
270 setcolor(sign,"darkyellow")
271 end
272 else
273 setchar(sign,new)
274 if trace_split then
275 setcolor(sign,"darkmagenta")
276 end
277 end
278 sign = nil
279 b_stop = current
280 else
281 b_start = current
282 b_stop = current
283 end
284 else
285 b_stop = current
286 end
287 if trace_split and current ~= sign then
288 setcolor(current,validseparators[unicode] and "darkcyan" or "darkblue")
289 end
290 end
291 elseif not b_start then
292 sign = validsigns[unicode] and current
293
294
295
296 end
297 elseif (b_start or a_start) and id == glue_code then
298
299
300 if bothdigit(current) then
301 local width = fontcharacters[getfont(b_start or a_start)][separator or period].width
302 setglue(current,width,0,0)
303 setattr(current,a_character,punctuationspace)
304 if a_start then
305 a_stop = current
306 elseif b_start then
307 b_stop = current
308 end
309 end
310 end
311 current = getnext(current)
312 end
313 else
314 while current do
315 local char, id = isglyph(current)
316 if char then
317 local font = id
318
319 local unicode = fontcharacters[font][char].unicode or char
320 if not unicode then
321
322 elseif unicode == separator then
323 c = current
324 if trace_split then
325 setcolor(current,"darkred")
326 end
327 dataset.hasseparator = true
328 else
329 if c then
330 if not a_start then
331 a_start = current
332 end
333 a_stop = current
334 if trace_split then
335 setcolor(current,"darkgreen")
336 end
337 else
338 if not b_start then
339 b_start = current
340 end
341 b_stop = current
342 if trace_split then
343 setcolor(current,"darkblue")
344 end
345 end
346 end
347 end
348 current = getnext(current)
349 end
350 end
351 local predefined = dataset.predefined
352 local before, after
353 if predefined then
354 before = b_start and getdimensions(b_start,getnext(b_stop)) or 0
355 after = a_start and getdimensions(a_start,getnext(a_stop)) or 0
356 else
357 local entry = list[row]
358 if entry then
359 before = entry.before or 0
360 after = entry.after or 0
361 else
362 before = b_start and getdimensions(b_start,getnext(b_stop)) or 0
363 after = a_start and getdimensions(a_start,getnext(a_stop)) or 0
364 list[row] = {
365 before = before,
366 after = after,
367 }
368 return head, true
369 end
370 if not dataset.collected then
371
372 local maxbefore = 0
373 local maxafter = 0
374 for k, v in next, list do
375 local before = v.before
376 local after = v.after
377 if before and before > maxbefore then
378 maxbefore = before
379 end
380 if after and after > maxafter then
381 maxafter = after
382 end
383 end
384 dataset.maxbefore = maxbefore
385 dataset.maxafter = maxafter
386 dataset.collected = true
387 end
388 end
389 local maxbefore = dataset.maxbefore
390 local maxafter = dataset.maxafter
391 local new_kern = trace_split and traced_kern or new_kern
392 if b_start then
393 if before < maxbefore then
394 head = insertnodebefore(head,b_start,new_kern(maxbefore-before))
395 end
396 if not c then
397
398 if dataset.hasseparator then
399 local width = fontcharacters[getfont(b_start)][separator].width
400 insertnodeafter(head,b_stop,new_kern(maxafter+width))
401 end
402 elseif a_start then
403
404 if after < maxafter then
405 insertnodeafter(head,a_stop,new_kern(maxafter-after))
406 end
407 else
408
409 if maxafter > 0 then
410 insertnodeafter(head,c,new_kern(maxafter))
411 end
412 end
413 elseif a_start then
414 if c then
415
416 if maxbefore > 0 then
417 head = insertnodebefore(head,c,new_kern(maxbefore))
418 end
419 else
420
421 local width = fontcharacters[getfont(b_stop)][separator].width
422 head = insertnodebefore(head,a_start,new_kern(maxbefore+width))
423 end
424 if after < maxafter then
425 insertnodeafter(head,a_stop,new_kern(maxafter-after))
426 end
427 elseif c then
428
429 if maxbefore > 0 then
430 head = insertnodebefore(head,c,new_kern(maxbefore))
431 end
432 if maxafter > 0 then
433 insertnodeafter(head,c,new_kern(maxafter))
434 end
435 end
436 return head
437end
438 |