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