if not modules then modules = { } end modules ['typo-rub'] = { version = 1.001, comment = "companion to typo-rub.mkiv", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } -- todo: recycle slots better -- todo: hoffset -- todo: auto-increase line height -- todo: only hpack when start <> stop -- A typical bit of afternoon hackery ... with some breaks for watching -- Ghost-Note on youtube (Robert Searight and Nate Werth) ... which expands -- my to-be-had cd/dvd list again. local lpegmatch = lpeg.match local utfcharacters = utf.characters local setmetatableindex = table.setmetatableindex local variables = interfaces.variables local implement = interfaces.implement local texsetattribute = tex.setattribute local v_flushleft = variables.flushleft local v_middle = variables.middle local v_flushright = variables.flushright local v_yes = variables.yes local v_no = variables.no local v_auto = variables.auto local nuts = nodes.nuts local getid = nuts.getid local getsubtype = nuts.getsubtype local getattr = nuts.getattr local setattr = nuts.setattr local getnext = nuts.getnext local setnext = nuts.setnext local getprev = nuts.getprev local setprev = nuts.setprev local setlink = nuts.setlink local getlist = nuts.getlist local setlist = nuts.setlist local setshift = nuts.setshift local getwidth = nuts.getwidth local setwidth = nuts.setwidth local hpack = nuts.hpack local takebox = nuts.takebox local nextlist = nuts.traversers.list 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 penalty_code = nodecodes.penalty local hlist_code = nodecodes.hlist local vlist_code = nodecodes.vlist local par_code = nodecodes.par local dir_code = nodecodes.dir local kerncodes = nodes.kerncodes local fontkern_code = kerncodes.font local nodepool = nuts.pool local new_kern = nodepool.kern local setprop = nuts.setprop local getprop = nuts.getprop local findattribute = nuts.findattribute local enableaction = nodes.tasks.enableaction local nofrubies = 0 local rubylist = { } local a_ruby = attributes.private("ruby") local rubies = { } typesetters.rubies = rubies local trace_rubies = false trackers.register("typesetters.rubies",function(v) trace_rubies = v end) local report_rubies = logs.reporter("rubies") -- todo: use the more modern lmtx storage model local registervalue = attributes.registervalue local getvalue = attributes.getvalue local texsetattribute = tex.setattribute do local shared = nil local splitter = lpeg.tsplitat("|") local function enable() enableaction("processors","typesetters.rubies.check") enableaction("shipouts", "typesetters.rubies.attach") enable = false end local ctx_setruby = context.core.setruby local function ruby(settings) local base = settings.base local comment = settings.comment shared = settings local c = lpegmatch(splitter,comment) if #c == 1 then ctx_setruby(base,comment) if trace_rubies then report_rubies("- %s -> %s",base,comment) end else local i = 0 for b in utfcharacters(base) do i = i + 1 local r = c[i] if r then ctx_setruby(b,r) if trace_rubies then report_rubies("%i: %s -> %s",i,b,r) end else ctx_setruby(b,"") if trace_rubies then report_rubies("%i: %s",i,b) end end end end if enable then enable() end end local function startruby(settings) shared = settings if enable then enable() end end implement { name = "ruby", actions = ruby, arguments = { { { "align" }, { "stretch" }, { "hoffset", "dimension" }, { "voffset", "dimension" }, { "comment" }, { "base" }, } }, } implement { name = "startruby", actions = startruby, arguments = { { { "align" }, { "stretch" }, { "hoffset", "dimension" }, { "voffset", "dimension" }, } }, } local function setruby(n,m) nofrubies = nofrubies + 1 local r = takebox(n) local t = { text = r, width = getwidth(r), basewidth = 0, start = false, stop = false, } -- rubylist[nofrubies] = setmetatableindex(t,shared) -- texsetattribute(a_ruby,nofrubies) texsetattribute(a_ruby,registervalue(a_ruby,setmetatableindex(t,shared))) end implement { name = "setruby", actions = setruby, arguments = "integer", } end -- function rubies.check(head) -- local current = head -- local start = nil -- local stop = nil -- local found = nil -- -- local function flush(where) -- local r = rubylist[found] -- if r then -- local prev = getprev(start) -- local next = getnext(stop) -- setprev(start) -- setnext(stop) -- local h = hpack(start) -- if start == head then -- head = h -- else -- setlink(prev,h) -- end -- setlink(h,next) -- local bwidth = getwidth(h) -- local rwidth = r.width -- r.basewidth = bwidth -- r.start = start -- r.stop = stop -- setprop(h,"ruby",found) -- if rwidth > bwidth then -- -- ruby is wider -- setwidth(h,rwidth) -- end -- end -- end -- -- while current do -- local nx = getnext(current) -- local id = getid(current) -- if id == glyph_code then -- local a = getattr(current,a_ruby) -- if not a then -- if found then -- flush("flush 1") -- found = nil -- end -- elseif a == found then -- stop = current -- else -- if found then -- flush("flush 2") -- end -- found = a -- start = current -- stop = current -- end -- -- go on -- elseif id == kern_code and getsubtype(current,fontkern_code) then -- -- go on -- elseif found and id == disc_code then -- -- go on (todo: look into disc) -- elseif found then -- flush("flush 3") -- found = nil -- end -- current = nx -- end -- -- if found then -- flush("flush 4") -- end -- return head, true -- no need for true -- end function rubies.check(head) local _, current = findattribute(head,a_ruby) if current then local start = nil local stop = nil local found = nil local function flush(where) -- local r = rubylist[found] local r = getvalue(a_ruby,found) if r then -- can be an option while start ~= stop and getid(start) == glue_code do start = getnext(start) end while stop ~= start and getid(stop) == glue_code do stop = getprev(stop) end -- local prev = getprev(start) local next = getnext(stop) setprev(start) setnext(stop) local h = hpack(start) if start == head then head = h else setlink(prev,h) end setlink(h,next) local bwidth = getwidth(h) local rwidth = r.width r.basewidth = bwidth r.start = start r.stop = stop setprop(h,"ruby",found) if rwidth > bwidth then -- ruby is wider setwidth(h,rwidth) end end end -- while current do -- local nx = getnext(current) -- local a = getattr(current,a_ruby) -- if not a then -- if found then -- flush("flush 1") -- found = nil -- end -- elseif a == found then -- stop = current -- else -- if found then -- flush("flush 2") -- end -- found = a -- start = current -- stop = current -- end -- current = nx -- end -- todo: we can avoid a lookup while current do local nx = getnext(current) local a = getattr(current,a_ruby) if not a then if found then flush("flush 1") found = nil end _, current = findattribute(nx,a_ruby) elseif a == found then stop = current current = nx else if found then flush("flush 2") end found = a start = current stop = current current = nx end end end if found then flush("flush 4") end return head, true -- no need for true end local attach local function whatever(current,list) local a = getprop(current,"ruby") if a then -- local ruby = rubylist[a] local ruby = getvalue(a_ruby,a) local align = ruby.align or v_middle local stretch = ruby.stretch or v_no local hoffset = ruby.hoffset or 0 local voffset = ruby.voffset or 0 local start = ruby.start local stop = ruby.stop local text = ruby.text local rwidth = ruby.width local bwidth = ruby.basewidth local delta = rwidth - bwidth setwidth(text,0) if voffset ~= 0 then setshift(text,voffset) end -- center them if delta > 0 then -- ruby is wider if stretch == v_yes then setlink(text,start) while start and start ~= stop do local s = nodepool.stretch() local n = getnext(start) setlink(start,s,n) start = n end text = hpack(text,rwidth,"exactly") else local left = new_kern(delta/2) local right = new_kern(delta/2) setlink(text,left,start) setlink(stop,right) end setlist(current,text) elseif delta < 0 then -- ruby is narrower if align == v_auto then local l = true local c = getprev(current) while c do local id = getid(c) if id == glue_code or id == penalty_code or id == kern_code then -- go on elseif id == hlist_code and getwidth(c) == 0 then -- go on elseif id == whatsit_code or id == par_code or id == dir_code then -- go on else l = false break end c = getprev(c) end local r = true local c = getnext(current) while c do local id = getid(c) if id == glue_code or id == penalty_code or id == kern_code then -- go on elseif id == hlist_code and getwidth(c) == 0 then -- go on else r = false break end c = getnext(c) end if l and not r then align = v_flushleft elseif r and not l then align = v_flushright else align = v_middle end end if align == v_flushleft then setlink(text,start) setlist(current,text) elseif align == v_flushright then local left = new_kern(-delta) local right = new_kern(delta) setlink(left,text,right,start) setlist(current,left) else local left = new_kern(-delta/2) local right = new_kern(delta/2) setlink(left,text,right,start) setlist(current,left) end else setlink(text,start) setlist(current,text) end setprop(current,"ruby",false) -- rubylist[a] = nil elseif list then attach(list) end end attach = function(head) for current, id, subtype, list in nextlist, head do if id == hlist_code or id == vlist_code then whatever(current,list) end end return head end rubies.attach = attach -- for now there is no need to be compact -- local data = { } -- rubies.data = data -- -- function rubies.define(settings) -- data[#data+1] = settings -- return #data -- end -- -- implement { -- name = "defineruby", -- actions = { rubies.define, context }, -- arguments = { -- { -- { "align" }, -- { "stretch" }, -- } -- } -- }