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