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