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