1if not modules then modules = { } end modules ['typo-fln'] = {
2 version = 1.001,
3 comment = "companion to typo-fln.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 trace_firstlines = false trackers.register("typesetters.firstlines", function(v) trace_firstlines = v end)
18local report_firstlines = logs.reporter("nodes","firstlines")
19
20typesetters.firstlines = typesetters.firstlines or { }
21local firstlines = typesetters.firstlines
22
23local nodes = nodes
24
25local tasks = nodes.tasks
26local enableaction = tasks.enableaction
27local disableaction = tasks.disableaction
28
29local context = context
30local implement = interfaces.implement
31
32local nuts = nodes.nuts
33local tonode = nuts.tonode
34
35local getnext = nuts.getnext
36local getprev = nuts.getprev
37local getboth = nuts.getboth
38local setboth = nuts.setboth
39local getid = nuts.getid
40local getwidth = nuts.getwidth
41local getlist = nuts.getlist
42local setlist = nuts.setlist
43local getattr = nuts.getattr
44local getbox = nuts.getbox
45local getdisc = nuts.getdisc
46local setdisc = nuts.setdisc
47local setlink = nuts.setlink
48local setfont = nuts.setfont
49local setglyphdata = nuts.setglyphdata
50local getprop = nuts.getprop
51local setprop = nuts.setprop
52
53local nodecodes = nodes.nodecodes
54
55local glyph_code <const> = nodecodes.glyph
56local disc_code <const> = nodecodes.disc
57local kern_code <const> = nodecodes.kern
58local glue_code <const> = nodecodes.glue
59local par_code <const> = nodecodes.par
60
61local spaceskip_code <const> = nodes.gluecodes.spaceskip
62
63local nextglyph = nuts.traversers.glyph
64local nextdisc = nuts.traversers.disc
65
66local flushnodelist = nuts.flushlist
67local flushnode = nuts.flushnode
68local copy_node_list = nuts.copylist
69local insertnodebefore = nuts.insertbefore
70local insertnodeafter = nuts.insertafter
71local remove_node = nuts.remove
72local getdimensions = nuts.dimensions
73local hpack_node_list = nuts.hpack
74local startofpar = nuts.startofpar
75
76local setcoloring = nuts.colors.set
77
78local nodepool = nuts.pool
79local newpenalty = nodepool.penalty
80local newkern = nodepool.kern
81local tracerrule = nodes.tracers.pool.nuts.rule
82
83local actions = { }
84firstlines.actions = actions
85
86local a_firstline <const> = attributes.private('firstline')
87
88local texget = tex.get
89
90local variables = interfaces.variables
91local v_default <const> = variables.default
92local v_line <const> = variables.line
93local v_word <const> = variables.word
94
95local function set(par,specification)
96 enableaction("processors","typesetters.firstlines.handler")
97 if trace_firstlines then
98 report_firstlines("enabling firstlines")
99 end
100 setprop(par,a_firstline,specification)
101end
102
103function firstlines.set(specification)
104 nuts.setparproperty(set,specification)
105end
106
107implement {
108 name = "setfirstline",
109 actions = firstlines.set,
110 arguments = {
111 {
112 { "alternative" },
113 { "font", "integer" },
114 { "dynamic", "integer" },
115 { "ma", "integer" },
116 { "ca", "integer" },
117 { "ta", "integer" },
118 { "n", "integer" },
119 }
120 }
121}
122
123actions[v_line] = function(head,setting)
124 local dynamic = setting.dynamic
125 local font = setting.font
126 local noflines = setting.n or 1
127 local ma = setting.ma or 0
128 local ca = setting.ca
129 local ta = setting.ta
130 local hangafter = texget("hangafter")
131 local hangindent = texget("hangindent")
132
133 local nofchars = 0
134 local n = 0
135 local temp = copy_node_list(head)
136 local linebreaks = { }
137
138 local set = function(head)
139 for g in nextglyph, head do
140 if dynamic > 0 then
141 setglyphdata(g,dynamic)
142 end
143 setfont(g,font)
144 end
145 end
146
147 set(temp)
148
149 for g in nextdisc, temp do
150 local pre, post, replace = getdisc(g)
151 if pre then
152 set(pre)
153 end
154 if post then
155 set(post)
156 end
157 if replace then
158 set(replace)
159 end
160 end
161
162 local start = temp
163 local list = temp
164 local prev = temp
165 for i=1,noflines do
166 local hsize = texget("hsize") - texget("leftskip",false) - texget("rightskip",false)
167
168
169
170 if i <= - hangafter then
171 hsize = hsize - hangindent
172 end
173
174 local function list_dimensions(list,start)
175 local temp = copy_node_list(list,start)
176 temp = nodes.handlers.characters(temp)
177 temp = nodes.injections.handler(temp)
178
179
180
181
182
183 local width = getdimensions(temp)
184 flushnodelist(temp)
185 return width
186 end
187
188 local function try(extra)
189 local width = list_dimensions(list,start)
190 if extra then
191 width = width + list_dimensions(extra)
192 end
193
194 if width > hsize then
195 list = prev
196 return true
197 else
198 linebreaks[i] = n
199 prev = start
200 nofchars = n
201 end
202 end
203
204 while start do
205 local id = getid(start)
206 if id == glyph_code then
207
208 elseif id == disc_code then
209
210 n = n + 1
211 local pre, post, replace = getdisc(start)
212 if pre and try(pre) then
213 break
214 elseif replace and try(replace) then
215 break
216 end
217 elseif id == kern_code then
218
219 elseif id == glue_code then
220 n = n + 1
221 if try() then
222 break
223 end
224 end
225 start = getnext(start)
226 end
227 if not linebreaks[i] then
228 linebreaks[i] = n
229 end
230 end
231
232 flushnodelist(temp)
233
234 local start = head
235 local n = 0
236
237 local function update(start)
238 if dynamic > 0 then
239 setglyphdata(start,dynamic)
240 end
241 setfont(start,font)
242 setcoloring(start,ma,ca,ta)
243 end
244
245 for i=1,noflines do
246 local linebreak = linebreaks[i]
247 while start and n < nofchars do
248 local id = getid(start)
249 local ok = false
250 if id == glyph_code then
251 update(start)
252 elseif id == disc_code then
253 n = n + 1
254 local disc = start
255 local pre, post, replace, pretail, posttail, replacetail = getdisc(disc,true)
256 if linebreak == n then
257 local p, n = getboth(start)
258 if pre then
259 for current in nextglyph, pre do
260 update(current)
261 end
262 setlink(pretail,n)
263 setlink(p,pre)
264 start = pretail
265 pre = nil
266 else
267 setlink(p,n)
268 start = p
269 end
270 if post then
271 local p, n = getboth(start)
272 setlink(posttail,n)
273 setlink(start,post)
274 post = nil
275 end
276 else
277 local p, n = getboth(start)
278 if replace then
279 for current in nextglyph, replace do
280 update(current)
281 end
282 setlink(replacetail,n)
283 setlink(p,replace)
284 start = replacetail
285 replace = nil
286 else
287 setlink(p,n)
288 start = p
289 end
290 end
291 setdisc(disc,pre,post,replace)
292 flushnode(disc)
293 elseif id == glue_code then
294 n = n + 1
295 if linebreak ~= n then
296 head = insertnodebefore(head,start,newpenalty(10000))
297 end
298 end
299 local next = getnext(start)
300 if linebreak == n then
301 if start ~= head then
302 local where = id == glue_code and getprev(start) or start
303 if trace_firstlines then
304 head, where = insertnodeafter(head,where,newpenalty(10000))
305 head, where = insertnodeafter(head,where,newkern(-65536))
306 head, where = insertnodeafter(head,where,tracerrule(65536,4*65536,2*65536,"darkblue"))
307 end
308 head, where = insertnodeafter(head,where,newpenalty(-10000))
309 end
310 start = next
311 break
312 end
313 start = next
314 end
315 end
316
317 return head
318end
319
320actions[v_word] = function(head,setting)
321
322 local dynamic = setting.dynamic
323 local font = setting.font
324 local words = 0
325 local nofwords = setting.n or 1
326 local start = head
327 local ok = false
328 local ma = setting.ma or 0
329 local ca = setting.ca
330 local ta = setting.ta
331 while start do
332 local id = getid(start)
333
334 if id == glyph_code then
335 if not ok then
336 words = words + 1
337 ok = true
338 end
339 setcoloring(start,ma,ca,ta)
340 if dynamic > 0 then
341 setglyphdata(start,dynamic)
342 end
343 setfont(start,font)
344 elseif id == disc_code then
345
346 elseif id == kern_code then
347
348 else
349 ok = false
350 if words == nofwords then
351 break
352 end
353 end
354 start = getnext(start)
355 end
356 return head
357end
358
359actions[v_default] = actions[v_line]
360
361function firstlines.handler(head)
362 if getid(head) == par_code and startofpar(head) then
363 local settings = getprop(head,a_firstline)
364 if settings then
365 disableaction("processors","typesetters.firstlines.handler")
366 local alternative = settings.alternative or v_default
367 local action = actions[alternative] or actions[v_default]
368 if action then
369 if trace_firstlines then
370 report_firstlines("processing firstlines, alternative %a",alternative)
371 end
372 return action(head,settings)
373 end
374 end
375 end
376 return head
377end
378
379
380
381local function applytofirstcharacter(box,what)
382 local tbox = getbox(box)
383 local list = getlist(tbox)
384 local done = nil
385 for n in nextglyph, list do
386 list = remove_node(list,n)
387 done = n
388 break
389 end
390 if done then
391 setlist(tbox,list)
392 local kind = type(what)
393 if kind == "string" then
394 context[what](tonode(done))
395 elseif kind == "function" then
396 what(done)
397 else
398
399 end
400 end
401end
402
403implement {
404 name = "applytofirstcharacter",
405 actions = applytofirstcharacter,
406 arguments = { "integer", "string" }
407}
408 |