typo-rub.lmt /size: 14 Kb    last modification: 2025-02-21 11:03
1if not modules then modules = { } end modules ['typo-rub'] = {
2    version   = 1.001,
3    comment   = "companion to typo-rub.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-- todo: recycle slots better
10-- todo: hoffset
11-- todo: auto-increase line height
12-- todo: only hpack when start <> stop
13
14-- A typical bit of afternoon hackery ... with some breaks for watching
15-- Ghost-Note on youtube (Robert Searight and Nate Werth) ... which expands
16-- my to-be-had cd/dvd list again.
17
18local lpegmatch         = lpeg.match
19local utfcharacters     = utf.characters
20local setmetatableindex = table.setmetatableindex
21
22local variables       = interfaces.variables
23local implement       = interfaces.implement
24
25local texsetattribute = tex.setattribute
26
27local v_flushleft     <const> = variables.flushleft
28local v_middle        <const> = variables.middle
29local v_flushright    <const> = variables.flushright
30local v_yes           <const> = variables.yes
31local v_no            <const> = variables.no
32local v_auto          <const> = variables.auto
33
34local nuts            = nodes.nuts
35
36local getid           = nuts.getid
37local getsubtype      = nuts.getsubtype
38local getattr         = nuts.getattr
39local setattr         = nuts.setattr
40local getnext         = nuts.getnext
41local setnext         = nuts.setnext
42local getprev         = nuts.getprev
43local setprev         = nuts.setprev
44local setlink         = nuts.setlink
45local getlist         = nuts.getlist
46local setlist         = nuts.setlist
47local setshift        = nuts.setshift
48local getwidth        = nuts.getwidth
49local setwidth        = nuts.setwidth
50
51local hpack           = nuts.hpack
52local takebox         = nuts.takebox
53
54local nextlist        = nuts.traversers.list
55
56local nodecodes       = nodes.nodecodes
57local kerncodes       = nodes.kerncodes
58
59local glyph_code      <const> = nodecodes.glyph
60local disc_code       <const> = nodecodes.disc
61local kern_code       <const> = nodecodes.kern
62local glue_code       <const> = nodecodes.glue
63local penalty_code    <const> = nodecodes.penalty
64local hlist_code      <const> = nodecodes.hlist
65local vlist_code      <const> = nodecodes.vlist
66local par_code        <const> = nodecodes.par
67local dir_code        <const> = nodecodes.dir
68
69local fontkern_code   <const> = kerncodes.fontkern
70
71local nodepool        = nuts.pool
72local new_kern        = nodepool.kern
73
74local setprop         = nuts.setprop
75local getprop         = nuts.getprop
76
77local findattribute   = nuts.findattribute
78
79local enableaction    = nodes.tasks.enableaction
80
81local nofrubies       = 0
82local rubylist        = { }
83
84local a_ruby          <const> = attributes.private("ruby")
85
86local rubies          = { }
87typesetters.rubies    = rubies
88
89local trace_rubies    = false  trackers.register("typesetters.rubies",function(v) trace_rubies = v end)
90local report_rubies   = logs.reporter("rubies")
91
92-- todo: use the more modern lmtx storage model
93
94local registervalue   = attributes.registervalue
95local getvalue        = attributes.getvalue
96local texsetattribute = tex.setattribute
97
98do
99
100    local shared   = nil
101    local splitter = lpeg.tsplitat("|")
102
103    local function enable()
104        enableaction("processors","typesetters.rubies.check")
105        enableaction("shipouts",  "typesetters.rubies.attach")
106        enable = false
107    end
108
109    local ctx_setruby = context.core.setruby
110
111    local function ruby(settings)
112        local base    = settings.base
113        local comment = settings.comment
114        shared = settings
115        local c = lpegmatch(splitter,comment)
116        if #c == 1 then
117            ctx_setruby(base,comment)
118            if trace_rubies then
119                report_rubies("- %s -> %s",base,comment)
120            end
121        else
122            local i = 0
123            for b in utfcharacters(base) do
124                i = i + 1
125                local r = c[i]
126                if r then
127                    ctx_setruby(b,r)
128                    if trace_rubies then
129                        report_rubies("%i: %s -> %s",i,b,r)
130                    end
131                else
132                    ctx_setruby(b,"")
133                    if trace_rubies then
134                        report_rubies("%i: %s",i,b)
135                    end
136                end
137            end
138        end
139        if enable then
140            enable()
141        end
142    end
143
144    local function startruby(settings)
145        shared = settings
146        if enable then
147            enable()
148        end
149    end
150
151    implement {
152        name      = "ruby",
153        actions   = ruby,
154        arguments = {
155            {
156                { "align" },
157                { "stretch" },
158                { "hoffset", "dimension" },
159                { "voffset", "dimension" },
160                { "comment" },
161                { "base" },
162            }
163        },
164    }
165
166    implement {
167        name      = "startruby",
168        actions   = startruby,
169        arguments = {
170            {
171                { "align" },
172                { "stretch" },
173                { "hoffset", "dimension" },
174                { "voffset", "dimension" },
175            }
176        },
177    }
178
179    local function setruby(n,m)
180        nofrubies = nofrubies + 1
181        local r = takebox(n)
182        local t = {
183            text      = r,
184            width     = getwidth(r),
185            basewidth = 0,
186            start     = false,
187            stop      = false,
188        }
189     -- rubylist[nofrubies] = setmetatableindex(t,shared)
190     -- texsetattribute(a_ruby,nofrubies)
191        texsetattribute(a_ruby,registervalue(a_ruby,setmetatableindex(t,shared)))
192    end
193
194    implement {
195        name      = "setruby",
196        actions   = setruby,
197        arguments = "integer",
198    }
199
200end
201
202-- function rubies.check(head)
203--     local current = head
204--     local start   = nil
205--     local stop    = nil
206--     local found   = nil
207--
208--     local function flush(where)
209--         local r = rubylist[found]
210--         if r then
211--             local prev = getprev(start)
212--             local next = getnext(stop)
213--             setprev(start)
214--             setnext(stop)
215--             local h = hpack(start)
216--             if start == head then
217--                 head = h
218--             else
219--                 setlink(prev,h)
220--             end
221--             setlink(h,next)
222--             local bwidth = getwidth(h)
223--             local rwidth = r.width
224--             r.basewidth  = bwidth
225--             r.start      = start
226--             r.stop       = stop
227--             setprop(h,"ruby",found)
228--             if rwidth > bwidth then
229--                 -- ruby is wider
230--                 setwidth(h,rwidth)
231--             end
232--         end
233--     end
234--
235--     while current do
236--         local nx = getnext(current)
237--         local id = getid(current)
238--         if id == glyph_code then
239--             local a  = getattr(current,a_ruby)
240--             if not a then
241--                 if found then
242--                     flush("flush 1")
243--                     found = nil
244--                 end
245--             elseif a == found then
246--                 stop = current
247--             else
248--                 if found then
249--                     flush("flush 2")
250--                 end
251--                 found = a
252--                 start = current
253--                 stop  = current
254--             end
255--             -- go on
256--         elseif id == kern_code and getsubtype(current,fontkern_code) then
257--             -- go on
258--         elseif found and id == disc_code then
259--             -- go on (todo: look into disc)
260--         elseif found then
261--             flush("flush 3")
262--             found = nil
263--         end
264--         current = nx
265--     end
266--
267--     if found then
268--         flush("flush 4")
269--     end
270--     return head, true -- no need for true
271-- end
272
273function rubies.check(head)
274    local _, current = findattribute(head,a_ruby)
275    if current then
276
277        local start   = nil
278        local stop    = nil
279        local found   = nil
280
281        local function flush(where)
282--            local r = rubylist[found]
283local r = getvalue(a_ruby,found)
284            if r then
285                -- can be an option
286                while start ~= stop and getid(start) == glue_code do
287                    start = getnext(start)
288                end
289                while stop ~= start and getid(stop) == glue_code do
290                    stop = getprev(stop)
291                end
292                --
293                local prev = getprev(start)
294                local next = getnext(stop)
295                setprev(start)
296                setnext(stop)
297                local h = hpack(start)
298                if start == head then
299                    head = h
300                else
301                    setlink(prev,h)
302                end
303                setlink(h,next)
304                local bwidth = getwidth(h)
305                local rwidth = r.width
306                r.basewidth  = bwidth
307                r.start      = start
308                r.stop       = stop
309                setprop(h,"ruby",found)
310                if rwidth > bwidth then
311                    -- ruby is wider
312                    setwidth(h,rwidth)
313                end
314            end
315        end
316
317--         while current do
318--             local nx = getnext(current)
319--             local a  = getattr(current,a_ruby)
320--             if not a then
321--                 if found then
322--                     flush("flush 1")
323--                     found = nil
324--                 end
325--             elseif a == found then
326--                 stop = current
327--             else
328--                 if found then
329--                     flush("flush 2")
330--                 end
331--                 found = a
332--                 start = current
333--                 stop  = current
334--             end
335--             current = nx
336--         end
337
338        -- todo: we can avoid a lookup
339
340        while current do
341            local nx = getnext(current)
342            local a  = getattr(current,a_ruby)
343            if not a then
344                if found then
345                    flush("flush 1")
346                    found = nil
347                end
348_, current = findattribute(nx,a_ruby)
349            elseif a == found then
350                stop = current
351current = nx
352            else
353                if found then
354                    flush("flush 2")
355                end
356                found = a
357                start = current
358                stop  = current
359current = nx
360            end
361        end
362
363    end
364
365    if found then
366        flush("flush 4")
367    end
368    return head, true -- no need for true
369end
370
371
372local attach
373
374local function whatever(current,list)
375    local a = getprop(current,"ruby")
376    if a then
377--         local ruby    = rubylist[a]
378local ruby = getvalue(a_ruby,a)
379        local align   = ruby.align   or v_middle
380        local stretch = ruby.stretch or v_no
381        local hoffset = ruby.hoffset or 0
382        local voffset = ruby.voffset or 0
383        local start   = ruby.start
384        local stop    = ruby.stop
385        local text    = ruby.text
386        local rwidth  = ruby.width
387        local bwidth  = ruby.basewidth
388        local delta   = rwidth - bwidth
389        setwidth(text,0)
390        if voffset ~= 0 then
391            setshift(text,voffset)
392        end
393        -- center them
394        if delta > 0 then
395            -- ruby is wider
396            if stretch == v_yes then
397                setlink(text,start)
398                while start and start ~= stop do
399                    local s = nodepool.stretch()
400                    local n = getnext(start)
401                    setlink(start,s,n)
402                    start = n
403                end
404                text = hpack(text,rwidth,"exactly")
405            else
406                local left  = new_kern(delta/2)
407                local right = new_kern(delta/2)
408                setlink(text,left,start)
409                setlink(stop,right)
410            end
411            setlist(current,text)
412        elseif delta < 0 then
413            -- ruby is narrower
414            if align == v_auto then
415                local l = true
416                local c = getprev(current)
417                while c do
418                    local id = getid(c)
419                    if id == glue_code or id == penalty_code or id == kern_code then
420                        -- go on
421                    elseif id == hlist_code and getwidth(c) == 0 then
422                        -- go on
423                    elseif id == whatsit_code or id == par_code or id == dir_code then
424                        -- go on
425                    else
426                        l = false
427                        break
428                    end
429                    c = getprev(c)
430                end
431                local r = true
432                local c = getnext(current)
433                while c do
434                    local id = getid(c)
435                    if id == glue_code or id == penalty_code or id == kern_code then
436                        -- go on
437                    elseif id == hlist_code and getwidth(c) == 0 then
438                        -- go on
439                    else
440                        r = false
441                        break
442                    end
443                    c = getnext(c)
444                end
445                if l and not r then
446                    align = v_flushleft
447                elseif r and not l then
448                    align = v_flushright
449                else
450                    align = v_middle
451                end
452            end
453            if align == v_flushleft then
454                setlink(text,start)
455                setlist(current,text)
456            elseif align == v_flushright then
457                local left  = new_kern(-delta)
458                local right = new_kern(delta)
459                setlink(left,text,right,start)
460                setlist(current,left)
461            else
462                local left  = new_kern(-delta/2)
463                local right = new_kern(delta/2)
464                setlink(left,text,right,start)
465                setlist(current,left)
466            end
467        else
468            setlink(text,start)
469            setlist(current,text)
470        end
471        setprop(current,"ruby",false)
472--         rubylist[a] = nil
473    elseif list then
474        attach(list)
475    end
476end
477
478attach = function(head)
479    for current, id, subtype, list in nextlist, head do
480        if id == hlist_code or id == vlist_code then
481            whatever(current,list)
482        end
483    end
484    return head
485end
486
487rubies.attach = attach
488
489-- for now there is no need to be compact
490
491-- local data          = { }
492-- rubies.data         = data
493--
494-- function rubies.define(settings)
495--     data[#data+1] = settings
496--     return #data
497-- end
498--
499-- implement {
500--     name      = "defineruby",
501--     actions   = { rubies.define, context },
502--     arguments = {
503--         {
504--             { "align" },
505--             { "stretch" },
506--         }
507--     }
508-- }
509