1if not modules then modules = { } end modules ['typo-brk'] = {
2 version = 1.001,
3 comment = "companion to typo-brk.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
12local next, type, tonumber, tostring = next, type, tonumber, tostring
13local utfbyte, utfchar = utf.byte, utf.char
14local format = string.format
15
16local trace_breakpoints = false trackers.register("typesetters.breakpoints", function(v) trace_breakpoints = v end)
17
18local report_breakpoints = logs.reporter("typesetting","breakpoints")
19
20local nodes, node = nodes, node
21
22local settings_to_array = utilities.parsers.settings_to_array
23
24local nuts = nodes.nuts
25local tonut = nuts.tonut
26
27local getnext = nuts.getnext
28local getprev = nuts.getprev
29local getboth = nuts.getboth
30local getsubtype = nuts.getsubtype
31local getfont = nuts.getfont
32local getid = nuts.getid
33
34local getattrlist = nuts.getattrlist
35local takeattr = nuts.takeattr
36local getlanguage = nuts.getlanguage
37local isglyph = nuts.isglyph
38
39local setattr = nuts.setattr
40local setattrlist = nuts.setattrlist
41local setlink = nuts.setlink
42local setchar = nuts.setchar
43local setdisc = nuts.setdisc
44local setnext = nuts.setnext
45local setprev = nuts.setprev
46local setboth = nuts.setboth
47local setsubtype = nuts.setsubtype
48
49local copy_node = nuts.copy
50local copy_node_list = nuts.copylist
51local flushnode = nuts.flushnode
52local insertnodebefore = nuts.insertbefore
53local insertnodeafter = nuts.insertafter
54local remove_node = nuts.remove
55local endofmath = nuts.endofmath
56
57local tonodes = nuts.tonodes
58
59local texsetattribute = tex.setattribute
60local unsetvalue = attributes.unsetvalue
61
62local nodepool = nuts.pool
63local enableaction = nodes.tasks.enableaction
64
65local v_reset = interfaces.variables.reset
66local v_yes = interfaces.variables.yes
67
68local implement = interfaces.implement
69
70local new_penalty = nodepool.penalty
71local new_glue = nodepool.glue
72local new_disc = nodepool.disc
73local new_wordboundary = nodepool.wordboundary
74
75local nodecodes = nodes.nodecodes
76local kerncodes = nodes.kerncodes
77
78local kern_code = nodecodes.kern
79local math_code = nodecodes.math
80
81local fontkern_code = kerncodes.fontkern
82local italickern_code = kerncodes.italiccorrection
83
84local is_letter = characters.is_letter
85
86local typesetters = typesetters
87
88typesetters.breakpoints = typesetters.breakpoints or {}
89local breakpoints = typesetters.breakpoints
90
91breakpoints.mapping = breakpoints.mapping or { }
92breakpoints.numbers = breakpoints.numbers or { }
93
94breakpoints.methods = breakpoints.methods or { }
95local methods = breakpoints.methods
96
97local a_breakpoints = attributes.private("breakpoint")
98
99storage.register("typesetters/breakpoints/mapping", breakpoints.mapping, "typesetters.breakpoints.mapping")
100
101local mapping = breakpoints.mapping
102local numbers = breakpoints.mapping
103
104for i=1,#mapping do
105 local m = mapping[i]
106 numbers[m.name] = m
107end
108
109
110
111
112
113local function insert_break(head,start,stop,before,after,kern)
114 if not kern then
115 local p = new_penalty(before)
116 local g = new_glue()
117 setattrlist(p,start)
118 setattrlist(g,start)
119 insertnodebefore(head,start,p)
120 insertnodebefore(head,start,g)
121 end
122 local p = new_penalty(after)
123 local g = new_glue()
124 setattrlist(p,start)
125 setattrlist(g,start)
126 insertnodeafter(head,stop,g)
127 insertnodeafter(head,stop,p)
128end
129
130methods[1] = function(head,start,stop,settings,kern)
131 local p, n = getboth(stop)
132 if p and n then
133 insert_break(head,start,stop,10000,0,kern)
134 end
135 return head, stop
136end
137
138methods[6] = function(head,start,stop,settings,kern)
139 local p = getprev(start)
140 local n = getnext(stop)
141 if p and n then
142 if kern then
143 insert_break(head,start,stop,10000,0,kern)
144 else
145 local l = new_wordboundary()
146 local d = new_disc()
147 local r = new_wordboundary()
148 setattrlist(d,start)
149 setlink(p,l,d,r,n)
150 if start == stop then
151 setboth(start)
152 setdisc(d,start,nil,copy_node(start))
153 else
154 setprev(start)
155 setnext(stop)
156 setdisc(d,start,nil,copy_node_list(start))
157 end
158 stop = r
159 end
160 end
161 return head, stop
162end
163
164methods[2] = function(head,start)
165 local p, n = getboth(start)
166 if p and n then
167 local replace
168 head, start, replace = remove_node(head,start)
169 local post = copy_node(replace)
170 local hyphen = copy_node(post)
171 setchar(hyphen,languages.prehyphenchar(getlanguage(post)))
172 setlink(post,hyphen)
173 head, start = insertnodebefore(head,start,new_disc(nil,post,replace))
174 setattrlist(start,replace)
175 insert_break(head,start,start,10000,10000)
176 end
177 return head, start
178end
179
180methods[3] = function(head,start)
181 local p, n = getboth(start)
182 if p and n then
183 local replace
184 head, start, replace = remove_node(head,start)
185 local pre = copy_node(replace)
186 local hyphen = copy_node(pre)
187 setchar(hyphen,languages.prehyphenchar(getlanguage(pre)))
188 setlink(hyphen,pre)
189 head, start = insertnodebefore(head,start,new_disc(hyphen,nil,replace))
190 setattrlist(start,tmp)
191 insert_break(head,start,start,10000,10000)
192 end
193 return head, start
194end
195
196methods[4] = function(head,start)
197 local p, n = getboth(start)
198 if p and n then
199 local tmp
200 head, start, tmp = remove_node(head,start)
201 head, start = insertnodebefore(head,start,new_disc())
202 setattrlist(start,tmp)
203 setdisc(start,copy_node(tmp),copy_node(tmp),tmp)
204 insert_break(head,start,start,10000,10000)
205 end
206 return head, start
207end
208
209methods[5] = function(head,start,stop,settings)
210 local p, n = getboth(start)
211 if p and n then
212 local tmp
213 head, start, tmp = remove_node(head,start)
214 head, start = insertnodebefore(head,start,new_disc())
215 local attr = getattrlist(tmp)
216 local font = getfont(tmp)
217 local left = settings.left
218 local right = settings.right
219 local middle = settings.middle
220 if left then
221 left = tonodes(tostring(left),font,attr)
222 end
223 if right then
224 right = tonodes(tostring(right),font,attr)
225 end
226 if middle then
227 middle = tonodes(tostring(middle),font,attr)
228 end
229 setdisc(start,left,right,middle)
230 setattrlist(start,attr)
231 flushnode(tmp)
232 insert_break(head,start,start,10000,10000)
233 end
234 return head, start
235end
236
237
238
239
240
241function breakpoints.handler(head)
242 local done = false
243 local attr = nil
244 local map = nil
245 local current = head
246 while current do
247 local char, id = isglyph(current)
248 if char then
249
250 local a = takeattr(current,a_breakpoints)
251 if a and a > 0 then
252 if a ~= attr then
253 local data = mapping[a]
254 if data then
255 map = data.characters
256 else
257 map = nil
258 end
259 attr = a
260 end
261 if map then
262 local cmap = map[char]
263 if cmap then
264
265
266
267 local lang = getlanguage(current)
268 local smap = lang and lang >= 0 and lang < 0x7FFF and (cmap[languages.numbers[lang]] or cmap[""])
269 if smap then
270 local skip = smap.skip
271 local start = current
272 local stop = current
273 current = getnext(current)
274 if skip then
275 while current do
276 local c = isglyph(current)
277 if c == char then
278 stop = current
279 current = getnext(current)
280 else
281 break
282 end
283 end
284 end
285 local d = { start, stop, cmap, smap, char }
286 if done then
287 done[#done+1] = d
288 else
289 done = { d }
290 end
291 else
292 current = getnext(current)
293 end
294 else
295 current = getnext(current)
296 end
297 else
298 current = getnext(current)
299 end
300 else
301 current = getnext(current)
302 end
303 elseif id == math_code then
304 attr = nil
305 current = endofmath(current)
306 if current then
307 current = getnext(current)
308 end
309 else
310 current = getnext(current)
311 end
312 end
313 if not done then
314 return head
315 end
316
317
318 for i=1,#done do
319 local data = done[i]
320 local start = data[1]
321 local stop = data[2]
322 local cmap = data[3]
323 local smap = data[4]
324
325
326
327
328 local nleft = smap.nleft
329 local cleft = 0
330 local prev = getprev(start)
331 local kern = nil
332 while prev and nleft ~= cleft do
333 local char, id = isglyph(prev)
334 if char then
335 if not is_letter[char] then
336 cleft = -1
337 break
338 end
339 cleft = cleft + 1
340 prev = getprev(prev)
341 elseif id == kern_code then
342 local s = getsubtype(prev)
343 if s == fontkern_code or s == italickern_code then
344 if cleft == 0 then
345 kern = prev
346 prev = getprev(prev)
347 else
348 break
349 end
350 else
351 break
352 end
353 else
354 break
355 end
356 end
357 if nleft == cleft then
358 local nright = smap.nright
359 local cright = 0
360 local next = getnext(stop)
361 while next and nright ~= cright do
362 local char, id = isglyph(next)
363 if char then
364 if not is_letter[char] then
365 cright = -1
366 break
367 end
368 if cright == 1 and cmap[char] then
369
370 break
371 end
372 cright = cright + 1
373 next = getnext(next)
374 elseif id == kern_code then
375 local s = getsubtype(next)
376 if s == fontkern_code or s == italickern_code then
377 if cleft == 0 then
378 next = getnext(next)
379 else
380 break
381 end
382 else
383 break
384 end
385 else
386 break
387 end
388 end
389 if nright == cright then
390 local method = methods[smap.type]
391 if method then
392 head, start = method(head,start,stop,smap,kern)
393 end
394 end
395
396 end
397 end
398 return head
399end
400
401local enabled = false
402
403function breakpoints.define(name)
404 local data = numbers[name]
405 if data then
406
407 else
408 local number = #mapping + 1
409 local data = {
410 name = name,
411 number = number,
412 characters = { },
413 }
414 mapping[number] = data
415 numbers[name] = data
416 end
417end
418
419function breakpoints.setreplacement(name,char,language,settings)
420 char = utfbyte(char)
421 local data = numbers[name]
422 if data then
423 local characters = data.characters
424 local cmap = characters[char]
425 if not cmap then
426 cmap = { }
427 characters[char] = cmap
428 end
429 local left, right, middle = settings.left, settings.right, settings.middle
430 cmap[language or ""] = {
431 type = tonumber(settings.type) or 1,
432 nleft = tonumber(settings.nleft) or 1,
433 nright = tonumber(settings.nright) or 1,
434 left = left ~= "" and left or nil,
435 right = right ~= "" and right or nil,
436 middle = middle ~= "" and middle or nil,
437 skip = settings.range == v_yes,
438 }
439 end
440end
441
442function breakpoints.set(n)
443 if n == v_reset then
444 n = unsetvalue
445 else
446 n = mapping[n]
447 if not n then
448 n = unsetvalue
449 else
450 if not enabled then
451 if trace_breakpoints then
452 report_breakpoints("enabling breakpoints handler")
453 end
454 enableaction("processors","typesetters.breakpoints.handler")
455 end
456 n = n.number
457 end
458 end
459 texsetattribute(a_breakpoints,n)
460end
461
462
463
464implement {
465 name = "definebreakpoints",
466 actions = breakpoints.define,
467 arguments = "string"
468}
469
470implement {
471 name = "definebreakpoint",
472 actions = breakpoints.setreplacement,
473 arguments = {
474 "string",
475 "string",
476 "string",
477 {
478 { "type", "integer" },
479 { "nleft", "integer" },
480 { "nright", "integer" },
481 { "right" },
482 { "left" },
483 { "middle" },
484 { "range" },
485 }
486 }
487}
488
489implement {
490 name = "setbreakpoints",
491 actions = breakpoints.set,
492 arguments = "string"
493}
494 |