if not modules then modules = { } end modules ['typo-fln'] = { version = 1.001, comment = "companion to typo-fln.mkiv", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } -- When I ran into the following experimental code again, I figured that it dated -- from the early days of mkiv, so I updates it a bit to fit into todays context. -- In the process I might have messed up things. For instance we had a diffent -- wrapper then using head and tail. -- todo: only letters (no punctuation) -- todo: nuts local trace_firstlines = false trackers.register("typesetters.firstlines", function(v) trace_firstlines = v end) local report_firstlines = logs.reporter("nodes","firstlines") typesetters.firstlines = typesetters.firstlines or { } local firstlines = typesetters.firstlines local nodes = nodes local tasks = nodes.tasks local enableaction = tasks.enableaction local disableaction = tasks.disableaction local context = context local implement = interfaces.implement local nuts = nodes.nuts local tonode = nuts.tonode local getnext = nuts.getnext local getprev = nuts.getprev local getboth = nuts.getboth local setboth = nuts.setboth local getid = nuts.getid local getwidth = nuts.getwidth local getlist = nuts.getlist local setlist = nuts.setlist local getattr = nuts.getattr local setattr = nuts.setattr local getbox = nuts.getbox local getdisc = nuts.getdisc local setdisc = nuts.setdisc local setlink = nuts.setlink local setfont = nuts.setfont local setglyphdata = nuts.setglyphdata local getprop = nuts.getprop local setprop = nuts.setprop local nodecodes = nodes.nodecodes local glyph_code = nodecodes.glyph local disc_code = nodecodes.disc local kern_code = nodecodes.kern local glue_code = nodecodes.glue local par_code = nodecodes.par local spaceskip_code = nodes.gluecodes.spaceskip local nextglyph = nuts.traversers.glyph local nextdisc = nuts.traversers.disc local flushnodelist = nuts.flushlist local flushnode = nuts.flushnode local copy_node_list = nuts.copylist local insertnodebefore = nuts.insertbefore local insertnodeafter = nuts.insertafter local remove_node = nuts.remove local getdimensions = nuts.dimensions local hpack_node_list = nuts.hpack local startofpar = nuts.startofpar local nodepool = nuts.pool local newpenalty = nodepool.penalty local newkern = nodepool.kern local tracerrule = nodes.tracers.pool.nuts.rule local actions = { } firstlines.actions = actions local a_firstline = attributes.private('firstline') local a_color = attributes.private('color') local a_transparency = attributes.private('transparency') local a_colormodel = attributes.private('colormodel') local texget = tex.get local variables = interfaces.variables local v_default = variables.default local v_line = variables.line local v_word = variables.word local function set(par,specification) enableaction("processors","typesetters.firstlines.handler") if trace_firstlines then report_firstlines("enabling firstlines") end setprop(par,a_firstline,specification) end function firstlines.set(specification) nuts.setparproperty(set,specification) end implement { name = "setfirstline", actions = firstlines.set, arguments = { { { "alternative" }, { "font", "integer" }, { "dynamic", "integer" }, { "ma", "integer" }, { "ca", "integer" }, { "ta", "integer" }, { "n", "integer" }, } } } actions[v_line] = function(head,setting) local dynamic = setting.dynamic local font = setting.font local noflines = setting.n or 1 local ma = setting.ma or 0 local ca = setting.ca local ta = setting.ta local hangafter = texget("hangafter") local hangindent = texget("hangindent") local parindent = texget("parindent") local nofchars = 0 local n = 0 local temp = copy_node_list(head) local linebreaks = { } local set = function(head) for g in nextglyph, head do if dynamic > 0 then setglyphdata(g,dynamic) end setfont(g,font) end end set(temp) for g in nextdisc, temp do local pre, post, replace = getdisc(g) if pre then set(pre) end if post then set(post) end if replace then set(replace) end end local start = temp local list = temp local prev = temp for i=1,noflines do local hsize = texget("hsize") - texget("leftskip",false) - texget("rightskip",false) if i == 1 then hsize = hsize - parindent end if i <= - hangafter then hsize = hsize - hangindent end local function list_dimensions(list,start) local temp = copy_node_list(list,start) temp = nodes.handlers.characters(temp) temp = nodes.injections.handler(temp) -- temp = typesetters.fontkerns.handler(temp) -- maybe when enabled -- nodes.handlers.protectglyphs(temp) -- not needed as we discard -- temp = typesetters.spacings.handler(temp) -- maybe when enabled -- temp = typesetters.kerns.handler(temp) -- maybe when enabled -- temp = typesetters.cases.handler(temp) -- maybe when enabled local width = getdimensions(temp) flushnodelist(temp) return width end local function try(extra) local width = list_dimensions(list,start) if extra then width = width + list_dimensions(extra) end -- report_firstlines("line length: %p, progression: %p, text: %s",hsize,width,nodes.listtoutf(list,nil,nil,start)) if width > hsize then list = prev return true else linebreaks[i] = n prev = start nofchars = n end end while start do local id = getid(start) if id == glyph_code then -- go on elseif id == disc_code then -- this could be an option n = n + 1 local pre, post, replace = getdisc(start) if pre and try(pre) then break elseif replace and try(replace) then break end elseif id == kern_code then -- todo: fontkern -- this could be an option elseif id == glue_code then n = n + 1 if try() then break end end start = getnext(start) end if not linebreaks[i] then linebreaks[i] = n end end flushnodelist(temp) local start = head local n = 0 local function update(start) if dynamic > 0 then setglyphdata(start,dynamic) end setfont(start,font) if ca and ca > 0 then setattr(start,a_colormodel,ma == 0 and 1 or ma) setattr(start,a_color,ca) end if ta and ta > 0 then setattr(start,a_transparency,ta) end end for i=1,noflines do local linebreak = linebreaks[i] while start and n < nofchars do local id = getid(start) local ok = false if id == glyph_code then update(start) elseif id == disc_code then n = n + 1 local disc = start local pre, post, replace, pretail, posttail, replacetail = getdisc(disc,true) if linebreak == n then local p, n = getboth(start) if pre then for current in nextglyph, pre do update(current) end setlink(pretail,n) setlink(p,pre) start = pretail pre = nil else setlink(p,n) start = p end if post then local p, n = getboth(start) setlink(posttail,n) setlink(start,post) post = nil end else local p, n = getboth(start) if replace then for current in nextglyph, replace do update(current) end setlink(replacetail,n) setlink(p,replace) start = replacetail replace = nil else setlink(p,n) start = p end end setdisc(disc,pre,post,replace) flushnode(disc) elseif id == glue_code then n = n + 1 if linebreak ~= n then head = insertnodebefore(head,start,newpenalty(10000)) -- nobreak end end local next = getnext(start) if linebreak == n then if start ~= head then local where = id == glue_code and getprev(start) or start if trace_firstlines then head, where = insertnodeafter(head,where,newpenalty(10000)) -- nobreak head, where = insertnodeafter(head,where,newkern(-65536)) head, where = insertnodeafter(head,where,tracerrule(65536,4*65536,2*65536,"darkblue")) end head, where = insertnodeafter(head,where,newpenalty(-10000)) -- break end start = next break end start = next end end return head end actions[v_word] = function(head,setting) -- local attribute = fonts.specifiers.contextnumber(setting.feature) -- was experimental local dynamic = setting.dynamic local font = setting.font local words = 0 local nofwords = setting.n or 1 local start = head local ok = false local ma = setting.ma or 0 local ca = setting.ca local ta = setting.ta while start do local id = getid(start) -- todo: delete disc nodes if id == glyph_code then if not ok then words = words + 1 ok = true end if ca and ca > 0 then setattr(start,a_colormodel,ma == 0 and 1 or ma) setattr(start,a_color,ca) end if ta and ta > 0 then setattr(start,a_transparency,ta) end if dynamic > 0 then setglyphdata(start,dynamic) end setfont(start,font) elseif id == disc_code then -- continue elseif id == kern_code then -- todo: fontkern -- continue else ok = false if words == nofwords then break end end start = getnext(start) end return head end actions[v_default] = actions[v_line] function firstlines.handler(head) if getid(head) == par_code and startofpar(head) then local settings = getprop(head,a_firstline) if settings then disableaction("processors","typesetters.firstlines.handler") local alternative = settings.alternative or v_default local action = actions[alternative] or actions[v_default] if action then if trace_firstlines then report_firstlines("processing firstlines, alternative %a",alternative) end return action(head,settings) end end end return head end -- goodie local function applytofirstcharacter(box,what) local tbox = getbox(box) -- assumes hlist local list = getlist(tbox) local done = nil for n in nextglyph, list do list = remove_node(list,n) done = n break end if done then setlist(tbox,list) local kind = type(what) if kind == "string" then context[what](tonode(done)) elseif kind == "function" then what(done) else -- error end end end implement { name = "applytofirstcharacter", actions = applytofirstcharacter, arguments = { "integer", "string" } }