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 local function bothdigit(current)
208 local prev, next = getboth(current)
209 if next and prev and getid(next) == glyph_code and getid(prev) == glyph_code then
210 local pchar = getchar(prev)
211 local nchar = getchar(next)
212 local pdata = fontcharacters[getfont(prev)][pchar]
213 local ndata = fontcharacters[getfont(next)][nchar]
214 local punicode = pdata and pdata.unicode or pchar
215 local nunicode = ndata and ndata.unicode or nchar
216 if punicode and nunicode and categories[punicode] == "nd" and categories[nunicode] == "nd" then
217 return true
218 else
219 return false
220 end
221 end
222 end
223
224 while current do
225 local char, id = isglyph(current)
226 if char then
227 local font = id
228 local data = fontcharacters[font][char]
229 local unicode = data and data.unicode or char
230 if not unicode then
231
232 elseif unicode == separator then
233 c = current
234 if trace_split then
235 setcolor(current,"darkred")
236 end
237 dataset.hasseparator = true
238 elseif categories[unicode] == "nd" or validseparators[unicode] then
239 if c then
240 if not a_start then
241 a_start = current
242 end
243 a_stop = current
244 if trace_split then
245 setcolor(current,validseparators[unicode] and "darkcyan" or "darkblue")
246 end
247 else
248 if not b_start then
249 if sign then
250 b_start = sign
251 local c, f = isglyph(sign)
252 local new = validsigns[c]
253 if char == new or not fontcharacters[f][new] then
254 if trace_split then
255 setcolor(sign,"darkyellow")
256 end
257 else
258 setchar(sign,new)
259 if trace_split then
260 setcolor(sign,"darkmagenta")
261 end
262 end
263 sign = nil
264 b_stop = current
265 else
266 b_start = current
267 b_stop = current
268 end
269 else
270 b_stop = current
271 end
272 if trace_split and current ~= sign then
273 setcolor(current,validseparators[unicode] and "darkcyan" or "darkblue")
274 end
275 end
276 elseif not b_start then
277 sign = validsigns[unicode] and current
278
279
280
281 end
282 elseif (b_start or a_start) and id == glue_code then
283
284
285 if bothdigit(current) then
286 local width = fontcharacters[getfont(b_start or a_start)][separator or period].width
287 setglue(current,width,0,0)
288 setattr(current,a_character,punctuationspace)
289 if a_start then
290 a_stop = current
291 elseif b_start then
292 b_stop = current
293 end
294 end
295 end
296 current = getnext(current)
297 end
298 else
299 while current do
300 local char, id = isglyph(current)
301 if char then
302 local font = id
303
304 local unicode = fontcharacters[font][char].unicode or char
305 if not unicode then
306
307 elseif unicode == separator then
308 c = current
309 if trace_split then
310 setcolor(current,"darkred")
311 end
312 dataset.hasseparator = true
313 else
314 if c then
315 if not a_start then
316 a_start = current
317 end
318 a_stop = current
319 if trace_split then
320 setcolor(current,"darkgreen")
321 end
322 else
323 if not b_start then
324 b_start = current
325 end
326 b_stop = current
327 if trace_split then
328 setcolor(current,"darkblue")
329 end
330 end
331 end
332 end
333 current = getnext(current)
334 end
335 end
336 local predefined = dataset.predefined
337 local before, after
338 if predefined then
339 before = b_start and getdimensions(b_start,getnext(b_stop)) or 0
340 after = a_start and getdimensions(a_start,getnext(a_stop)) or 0
341 else
342 local entry = list[row]
343 if entry then
344 before = entry.before or 0
345 after = entry.after or 0
346 else
347 before = b_start and getdimensions(b_start,getnext(b_stop)) or 0
348 after = a_start and getdimensions(a_start,getnext(a_stop)) or 0
349 list[row] = {
350 before = before,
351 after = after,
352 }
353 return head, true
354 end
355 if not dataset.collected then
356
357 local maxbefore = 0
358 local maxafter = 0
359 for k, v in next, list do
360 local before = v.before
361 local after = v.after
362 if before and before > maxbefore then
363 maxbefore = before
364 end
365 if after and after > maxafter then
366 maxafter = after
367 end
368 end
369 dataset.maxbefore = maxbefore
370 dataset.maxafter = maxafter
371 dataset.collected = true
372 end
373 end
374 local maxbefore = dataset.maxbefore
375 local maxafter = dataset.maxafter
376 local new_kern = trace_split and traced_kern or new_kern
377 if b_start then
378 if before < maxbefore then
379 head = insertnodebefore(head,b_start,new_kern(maxbefore-before))
380 end
381 if not c then
382
383 if dataset.hasseparator then
384 local width = fontcharacters[getfont(b_start)][separator].width
385 insertnodeafter(head,b_stop,new_kern(maxafter+width))
386 end
387 elseif a_start then
388
389 if after < maxafter then
390 insertnodeafter(head,a_stop,new_kern(maxafter-after))
391 end
392 else
393
394 if maxafter > 0 then
395 insertnodeafter(head,c,new_kern(maxafter))
396 end
397 end
398 elseif a_start then
399 if c then
400
401 if maxbefore > 0 then
402 head = insertnodebefore(head,c,new_kern(maxbefore))
403 end
404 else
405
406 local width = fontcharacters[getfont(b_stop)][separator].width
407 head = insertnodebefore(head,a_start,new_kern(maxbefore+width))
408 end
409 if after < maxafter then
410 insertnodeafter(head,a_stop,new_kern(maxafter-after))
411 end
412 elseif c then
413
414 if maxbefore > 0 then
415 head = insertnodebefore(head,c,new_kern(maxbefore))
416 end
417 if maxafter > 0 then
418 insertnodeafter(head,c,new_kern(maxafter))
419 end
420 end
421 return head
422end
423 |