OSDN Git Service

fix \ltjruby in non-yoko direction
[luatex-ja/luatexja.git] / src / ltj-ruby.lua
1 --
2 -- ltj-ruby.lua
3 --
4 luatexbase.provides_module({
5   name = 'luatexja.ruby',
6   date = '2018/02/18',
7   description = 'Ruby annotation',
8 })
9 luatexja.ruby = {}
10 luatexja.load_module('stack');     local ltjs = luatexja.stack
11 luatexja.load_module('base');      local ltjb = luatexja.base
12
13 local to_node =  node.direct.tonode
14 local to_direct =  node.direct.todirect
15
16 local setfield =  node.direct.setfield
17 local setglue = luatexja.setglue
18 local getfield =  node.direct.getfield
19 local getid =  node.direct.getid
20 local getfont =  node.direct.getfont
21 local getlist =  node.direct.getlist
22 local getchar =  node.direct.getchar
23 local getsubtype =  node.direct.getsubtype
24
25 local node_new = node.direct.new
26 local node_remove = node.direct.remove
27 local node_next =  node.direct.getnext
28 local node_copy, node_free, node_tail = node.direct.copy, node.direct.free, node.direct.tail
29 local has_attr, set_attr = node.direct.has_attribute, node.direct.set_attribute
30 local insert_before, insert_after = node.direct.insert_before, node.direct.insert_after
31
32 local id_hlist = node.id('hlist')
33 local id_vlist = node.id('vlist')
34 local id_rule = node.id('rule')
35 local id_whatsit = node.id('whatsit')
36 local id_glue = node.id('glue')
37 local id_kern = node.id('kern')
38 local id_penalty = node.id('penalty')
39 local sid_user = node.subtype('user_defined')
40 local ltjs_get_stack_table = luatexja.stack.get_stack_table
41 local id_pbox_w = 258 -- cluster which consists of a whatsit
42
43 local attr_icflag = luatexbase.attributes['ltj@icflag']
44 -- ルビ処理用の attribute は他のやつの流用なので注意!
45 -- 進入許容量 (sp)
46 local attr_ruby_maxprep = luatexbase.attributes['ltj@charclass']
47 local attr_ruby_maxpostp = luatexbase.attributes['ltj@kcat0']
48 local attr_ruby_maxmargin = luatexbase.attributes['ltj@kcat1']
49 local attr_ruby_stretch = luatexbase.attributes['ltj@kcat2']
50 local attr_ruby_mode = luatexbase.attributes['ltj@kcat3']
51 local attr_ruby_id = luatexbase.attributes['ltj@kcat4'] -- uniq id
52 local attr_ruby_intergap = luatexbase.attributes['ltj@kcat5']
53 local attr_ruby = luatexbase.attributes['ltj@rubyattr']
54 -- ルビ内部処理用,以下のようにノードによって使われ方が異なる
55 -- * (whatsit) では JAglue 処理時に,
56 --     「2つ前のクラスタもルビ」 ==> そのルビクラスタの id
57 --   otherwise ==> unset
58 -- * (whatsit).value node ではルビ全角の値(sp単位)
59 -- * 行分割で whatsit の前後に並ぶノードでは,「何番目のルビ関連ノード」か
60 -- * (whatsit).value に続く整形済み vbox たちでは post_intrusion の値
61 local cat_lp = luatexbase.catcodetables['latex-package']
62
63 local round, floor = tex.round, math.floor
64 local min, max = math.min, math.max
65
66 luatexja.userid_table.RUBY_PRE = luatexbase.newuserwhatsitid('ruby_pre',  'luatexja')
67 luatexja.userid_table.RUBY_POST = luatexbase.newuserwhatsitid('ruby_post',  'luatexja')
68 local RUBY_PRE  = luatexja.userid_table.RUBY_PRE
69 local RUBY_POST = luatexja.userid_table.RUBY_POST
70
71 ----------------------------------------------------------------
72 -- TeX interface 0
73 ----------------------------------------------------------------
74 do
75    local getbox = node.direct.getbox
76    function luatexja.ruby.cpbox() return node_copy(getbox(0)) end
77 end
78
79 ----------------------------------------------------------------
80 -- 補助関数群 1
81 ----------------------------------------------------------------
82
83 local function gauss(coef)
84    -- #coef 式,#coef 変数の連立1次方程式系を掃きだし法で解く.
85    local deg = #coef
86    for i = 1, deg do
87       if coef[i][i]==0 then
88          for j = i+1, deg do
89             if coef[j][i]~=0 then
90                coef[i], coef[j] = coef[j], coef[i]; break
91             end
92          end
93       end
94       for j = 1,deg do
95          local d = coef[i][i];
96          if j~=i then
97             local e = coef[j][i]
98             for k = 1, deg+1 do coef[j][k] = coef[j][k] - e*coef[i][k]/d end
99          else
100             for k = 1, deg+1 do coef[i][k] = coef[i][k]/d end
101          end
102       end
103    end
104 end
105
106 local function solve_1(coef)
107    local a, b, c = coef[1][4], coef[2][4], coef[3][4]
108    coef[1][4], coef[2][4], coef[3][4] = c-b, a+b-c, c-a
109    return coef
110 end
111
112 local function solve_2(coef)
113    local a, b, c, d, e = coef[1][6], coef[2][6], coef[3][6], coef[4][6], coef[5][6]
114    coef[1][6], coef[2][6], coef[3][6], coef[4][6], coef[5][6]
115       = e-c, a+c-e, e-a-d, b+d-e, e-b
116    return coef
117 end
118
119
120 -- 実行回数 + ルビ中身 から uniq_id を作る関数
121 luatexja.ruby.old_break_info = {} -- public, 前 run 時の分割情報
122 local old_break_info = luatexja.ruby.old_break_info
123 local cache_handle
124 function luatexja.ruby.read_old_break_info()
125    if  tex.jobname then
126       local fname = tex.jobname .. '.ltjruby'
127       local real_file = kpse.find_file(fname)
128       if real_file then dofile(real_file) end
129       cache_handle = io.open(fname, 'w')
130    end
131 end
132 local make_uniq_id
133 do
134    local exec_count = 0
135    make_uniq_id = function (w)
136       exec_count = exec_count + 1
137       return exec_count
138    end
139 end
140
141 -- concatenation of boxes: reusing nodes
142 -- ルビ組版が行われている段落/hboxでの設定が使われる.
143 -- ルビ文字を格納しているボックスでの設定ではない!
144 local concat
145 do
146    local node_prev = node.direct.getprev
147    function concat(f, b)
148       if f then
149          if b then
150             local h, nh = getlist(f), getlist(b)
151             if getid(nh)==id_whatsit and getsubtype(nh)==sid_user then
152                nh=node_next(nh); node_free(node_prev(nh))
153             end
154             setfield(node_tail(h), 'next', nh)
155             setfield(f, 'head', nil); node_free(f)
156             setfield(b, 'head', nil); node_free(b)
157             local g = luatexja.jfmglue.main(h,false)
158             return node.direct.hpack(g)
159          else
160             return f
161          end
162       elseif b then
163          return b
164       else
165          local h = node_new(id_hlist)
166          setfield(h, 'subtype', 0)
167          setfield(h, 'width', 0)
168          setfield(h, 'height', 0)
169          setfield(h, 'depth', 0)
170          setfield(h, 'glue_set', 0)
171          setfield(h, 'glue_order', 0)
172          setfield(h, 'head', nil)
173          return h
174       end
175    end
176 end
177
178 local function expand_3bits(num)
179    local t = {}; local a = num
180    for i = 1, 10 do
181       t[i] = a%8; a = floor(a/8)
182    end
183    return t
184 end
185 ----------------------------------------------------------------
186 -- 補助関数群 2
187 ----------------------------------------------------------------
188
189 -- box の中身のノードは再利用される
190 local enlarge
191 do
192    local FROM_JFM       = luatexja.icflag_table.FROM_JFM
193    local PROCESSED      = luatexja.icflag_table.PROCESSED
194    local KANJI_SKIP     = luatexja.icflag_table.KANJI_SKIP
195    local KANJI_SKIP_JFM = luatexja.icflag_table.KANJI_SKIP_JFM
196    local XKANJI_SKIP    = luatexja.icflag_table.XKANJI_SKIP
197    local XKANJI_SKIP_JFM= luatexja.icflag_table.XKANJI_SKIP_JFM
198    enlarge = function (box, new_width, pre, middle, post, prenw, postnw)
199       -- pre, middle, post: 伸縮比率
200       -- prenw, postnw: 前後の自然長 (sp)
201       local h = getlist(box);
202       local hh, hd = getfield(box, 'height'), getfield(box, 'depth')
203       local hx = h
204       while hx do
205          local hic = has_attr(hx, attr_icflag)
206          if (hic == KANJI_SKIP) or (hic == KANJI_SKIP_JFM)
207             or (hic == XKANJI_SKIP) or (hic == XKANJI_SKIP_JFM)
208             or ((hic<=FROM_JFM+63) and (hic>=FROM_JFM)) then
209             -- この 5 種類の空白をのばす
210                if getid(hx) == id_kern then
211                   local k = node_new(id_glue)
212                   setglue(k, getfield(hx, 'kern'), round(middle*65536), 0,
213                              2, 0)
214                   setfield(k, 'subtype', 0);
215                   h = insert_after(h, hx, k);
216                   h = node_remove(h, hx); node_free(hx); hx = k
217                else -- glue
218                   setglue(hx, getfield(hx, 'width'), round(middle*65536), 0,
219                              2, 0)
220                end
221          end
222          hx = node_next(hx)
223       end
224       -- 先頭の空白を挿入
225       local k = node_new(id_glue);
226       setglue(k, prenw, round(pre*65536), 0, 2, 0)
227       h = insert_before(h, h, k);
228       -- 末尾の空白を挿入
229       local k = node_new(id_glue);
230       setglue(k, postnw, round(post*65536), 0, 2, 0)
231       insert_after(h, node_tail(h), k);
232       -- hpack
233       setfield(box, 'head', nil); node_free(box)
234       box = node.direct.hpack(h, new_width, 'exactly')
235       setfield(box, 'height', hh)
236       setfield(box, 'depth', hd)
237       return box
238    end
239 end
240
241
242 ----------------------------------------------------------------
243 -- TeX interface
244 ----------------------------------------------------------------
245
246 -- rtlr: ルビ部分のボックスたち r1, r2, ...
247 -- rtlp: 親文字 のボックスたち p1, p2, ...
248 local function texiface_low(rst, rtlr, rtlp)
249    local w = node_new(id_whatsit, sid_user)
250    setfield(w, 'type', 110); setfield(w, 'user_id', RUBY_PRE)
251    local wv = node_new(id_whatsit, sid_user)
252    setfield(w, 'value', to_node(wv))
253    setfield(wv, 'type', 100)
254    setfield(wv, 'value', floor(#rtlr))
255    setfield(wv, 'user_id', RUBY_PRE) -- dummy
256    set_attr(wv, attr_ruby, rst.rubyzw)
257    set_attr(wv, attr_ruby_maxmargin, rst.maxmargin)
258    set_attr(wv, attr_ruby_maxprep, rst.pre)
259    set_attr(wv, attr_ruby_maxpostp, rst.post)
260    set_attr(wv, attr_ruby_intergap, rst.intergap)
261    set_attr(wv, attr_ruby_stretch, rst.stretch)
262    set_attr(wv, attr_ruby_mode, rst.mode)
263    local n = wv
264    for i = 1, #rtlr do
265       _, n = insert_after(wv, n, rtlr[i])
266       _, n = insert_after(wv, n, rtlp[i])
267    end
268    -- w.value: (whatsit) .. r1 .. p1 .. r2 .. p2
269    node.direct.write(w); return w,wv
270 end
271
272 -- rst: table
273 function luatexja.ruby.texiface(rst, rtlr, rtlp)
274    if #rtlr ~= #rtlp then
275       for i=1, #rtlr do node_free(rtlr[i]) end
276       for i=1, #rtlp do node_free(rtlp[i]) end
277       ltjb.package_error('luatexja-ruby',
278                                   'Group count mismatch between the ruby and\n' ..
279                                      'the body (' .. #rtlr .. ' != ' .. #rtlp .. ').',
280                                   '')
281    else
282       local f = true
283       for i = 1,#rtlr do
284          if getfield(rtlr[i], 'width') > getfield(rtlp[i], 'width') then
285             f = false; break
286          end
287       end
288       if f then -- モノルビ * n
289          local r,p = {true}, {true}
290          for i = 1,#rtlr do
291             r[1] = rtlr[i]; p[1] = rtlp[i]; texiface_low(rst, r, p)
292          end
293       else
294          local w, wv = texiface_low(rst, rtlr, rtlp)
295          local id = make_uniq_id(w)
296          set_attr(wv, attr_ruby_id, id)
297       end
298    end
299 end
300
301 ----------------------------------------------------------------
302 -- pre_line_break
303 ----------------------------------------------------------------
304
305 -- r, p の中身のノードは再利用される
306 local function enlarge_parent(r, p, ppre, pmid, ppost, mapre, mapost, intmode)
307    -- r: ルビ部分の格納された box,p: 同,親文字
308    local rwidth = getfield(r, 'width')
309    local sumprot = rwidth - getfield(p, 'width') -- >0
310    local pre_intrusion, post_intrusion
311    if intmode == 0 then --  とりあえず組んでから決める
312       p = enlarge(p, rwidth, ppre, pmid, ppost, 0, 0)
313       pre_intrusion  = min(mapre, round(ppre*getfield(p, 'glue_set')*65536))
314       post_intrusion = min(mapost, round(ppost*getfield(p, 'glue_set')*65536))
315    elseif intmode == 1 then
316       pre_intrusion = min(mapre, sumprot);
317       post_intrusion = min(mapost, max(sumprot-pre_intrusion, 0))
318       p = enlarge(p, rwidth, ppre, pmid, ppost, pre_intrusion, post_intrusion)
319    elseif intmode == 2 then
320       post_intrusion = min(mapost, sumprot);
321       pre_intrusion = min(mapre, max(sumprot-post_intrusion, 0))
322       p = enlarge(p, rwidth, ppre, pmid, ppost, pre_intrusion, post_intrusion)
323    else --  intmode == 3
324       local n = min(mapre, mapost)*2
325       if n < sumprot then
326          pre_intrusion = n/2; post_intrusion = n/2
327       else
328          pre_intrusion = floor(sumprot/2); post_intrusion = sumprot - pre_intrusion
329       end
330       p = enlarge(p, rwidth, ppre, pmid, ppost, pre_intrusion, post_intrusion)
331       pre_intrusion = min(mapre, pre_intrusion + round(ppre*getfield(p, 'glue_set')*65536))
332       post_intrusion = min(mapost, post_intrusion + round(ppost*getfield(p, 'glue_set')*65536))
333    end
334    setfield(r, 'shift', -pre_intrusion)
335    local rwidth = rwidth - pre_intrusion - post_intrusion
336    setfield(r, 'width', rwidth)
337    setfield(p, 'width', rwidth)
338    local ps = getlist(p)
339    setfield(ps, 'width', getfield(ps, 'width') - pre_intrusion)
340    return r, p, post_intrusion
341 end
342
343 -- ルビボックスの生成(単一グループ)
344 -- returned value: <new box>, <ruby width>, <post_intrusion>
345 local max_margin
346 local function new_ruby_box(r, p, ppre, pmid, ppost,
347                             mapre, mapost, imode, rgap)
348    local post_intrusion = 0
349    local intmode = imode%4
350    local rpre, rmid, rpost, rsmash
351    imode = floor(imode/262144); rsmash = (imode%2 ==1)
352    imode = floor(imode/2); rpost = imode%8;
353    imode = (imode-rpost)/8;  rmid  = imode%8;
354    imode = (imode-rmid)/8;   rpre  = imode%8
355    if getfield(r, 'width') > getfield(p, 'width') then  -- change the width of p
356       r, p, post_intrusion  = enlarge_parent(r, p, ppre, pmid, ppost, mapre, mapost, intmode)
357    elseif getfield(r, 'width') < getfield(p, 'width') then -- change the width of r
358       r = enlarge(r, getfield(p, 'width'), rpre, rmid, rpost, 0, 0)
359       post_intrusion = 0
360       local need_repack = false
361       -- margin が大きくなりすぎた時の処理
362       if round(rpre*getfield(r, 'glue_set')*65536) > max_margin then
363          local ps = getlist(r); need_repack = true
364          setfield(ps, 'width', max_margin)
365          setfield(ps, 'stretch', 1) -- 全く伸縮しないのも困る
366       end
367       if round(rpost*getfield(r, 'glue_set')*65536) > max_margin then
368          local ps = node_tail(getlist(r)); need_repack = true
369          setfield(ps, 'width', max_margin)
370          setfield(ps, 'stretch', 1) -- 全く伸縮しないのも困る
371       end
372       if need_repack then
373          local rt = r
374          r = node.direct.hpack(getlist(r), getfield(r, 'width'), 'exactly')
375          setfield(rt, 'head', nil); node_free(rt);
376       end
377    end
378    local a, k = node_new(id_rule), node_new(id_kern, 1)
379    setfield(a, 'width', 0); setfield(a, 'height', 0)
380    setfield(a, 'depth', 0); setfield(k, 'kern', rgap)
381    insert_after(r, r, a); insert_after(r, a, k);
382    insert_after(r, k, p); setfield(p, 'next', nil)
383    a = node.direct.vpack(r); setfield(a, 'shift', 0)
384    set_attr(a, attr_ruby, post_intrusion)
385    if rsmash or getfield(a, 'height')<getfield(p, 'height') then
386       local k = node_new(id_kern, 1)
387       setfield(k, 'kern', -getfield(a, 'height')+getfield(p, 'height'))
388       setfield(a, 'head', k); insert_before(r, r, k)
389       setfield(a, 'height', getfield(p, 'height'))
390    end
391
392    return a, getfield(r, 'width'), post_intrusion
393 end
394
395
396 -- High-level routine in pre_linebreak_filter
397 local post_intrusion_backup
398 local max_allow_pre, max_allow_post
399
400
401 -- 中付き熟語ルビ,cmp containers
402 -- 「文字の構成を考えた」やつはどうしよう
403 local function pre_low_cal_box(w, cmp)
404    local rb = {}
405    local pb = {}
406    local kf = {}
407    -- kf[i] : container 1--i からなる行末形
408    -- kf[cmp+i] : container i--cmp からなる行頭形
409    -- kf[2cmp+1] : 行中形
410    local wv = getfield(w, 'value')
411    local mdt -- nt*: node temp
412    local coef = {} -- 連立一次方程式の拡大係数行列
413    local rtb = expand_3bits(has_attr(wv, attr_ruby_stretch))
414    local rgap = has_attr(wv, attr_ruby_intergap)
415    local intmode = floor(has_attr(wv, attr_ruby_mode)/4)
416
417    -- node list 展開・行末形の計算
418    local nt, nta, ntb = wv, nil, nil -- nt*: node temp
419    for i = 1, cmp do
420       nt = node_next(nt); rb[i] = nt; nta = concat(nta, node_copy(nt))
421       nt = node_next(nt); pb[i] = nt; ntb = concat(ntb, node_copy(nt))
422       coef[i] = {}
423       for j = 1, 2*i do coef[i][j] = 1 end
424       for j = 2*i+1, 2*cmp+1 do coef[i][j] = 0 end
425       kf[i], coef[i][2*cmp+2]
426          = new_ruby_box(node_copy(nta), node_copy(ntb),
427                         rtb[6], rtb[5], rtb[4], max_allow_pre, 0, intmode, rgap)
428    end
429    node_free(nta); node_free(ntb)
430
431    -- 行頭形の計算
432    local nta, ntb = nil, nil
433    for i = cmp,1,-1 do
434       coef[cmp+i] = {}
435       for j = 1, 2*i-1 do coef[cmp+i][j] = 0 end
436       for j = 2*i, 2*cmp+1 do coef[cmp+i][j] = 1 end
437       nta = concat(node_copy(rb[i]), nta); ntb = concat(node_copy(pb[i]), ntb)
438       kf[cmp+i], coef[cmp+i][2*cmp+2]
439          = new_ruby_box(node_copy(nta), node_copy(ntb),
440                         rtb[9], rtb[8], rtb[7], 0, max_allow_post, intmode, rgap)
441    end
442
443    -- ここで,nta, ntb には全 container を連結した box が入っているので
444    -- それを使って行中形を計算する.
445    coef[2*cmp+1] = {}
446    for j = 1, 2*cmp+1 do coef[2*cmp+1][j] = 1 end
447    kf[2*cmp+1], coef[2*cmp+1][2*cmp+2], post_intrusion_backup
448       = new_ruby_box(nta, ntb, rtb[3], rtb[2], rtb[1],
449                      max_allow_pre, max_allow_post, intmode, rgap)
450
451    -- w.value の node list 更新.
452    local nt = wv
453    node.direct.flush_list(node_next(wv))
454    for i = 1, 2*cmp+1 do setfield(nt, 'next', kf[i]); nt = kf[i]  end
455
456    if cmp==1 then     solve_1(coef)
457    elseif cmp==2 then solve_2(coef)
458    else
459       gauss(coef) -- 掃きだし法で連立方程式形 coef を解く
460    end
461    return coef
462 end
463
464 local first_whatsit
465 do
466    local traverse_id = node.direct.traverse_id
467    function first_whatsit(n) -- n 以後で最初の whatsit
468       for h in traverse_id(id_whatsit, n) do
469          return h
470       end
471       return nil
472    end
473 end
474
475 local next_cluster_array = {}
476 -- ノード追加
477 local function pre_low_app_node(head, w, cmp, coef, ht, dp)
478    -- メインの node list 更新
479    local nt = node_new(id_glue)
480    setglue(nt, coef[1][2*cmp+2], 0, 0, 0, 0)
481    set_attr(nt, attr_ruby, 1); set_attr(w, attr_ruby, 2)
482    head = insert_before(head, w, nt)
483    nt = w
484    for i = 1, cmp do
485       -- rule
486       local nta = node_new(id_rule);
487       setfield(nta, 'width', coef[i*2][2*cmp+2])
488       setfield(nta, 'height', ht); setfield(nta, 'depth', dp)
489       setfield(nta, 'subtype', 0)
490       insert_after(head, nt, nta)
491       set_attr(nta, attr_ruby, 2*i+1)
492       -- glue
493       if i~=cmp or not next_cluster_array[w] then
494          nt = node_new(id_glue); insert_after(head, nta, nt)
495       else
496          nt = next_cluster_array[w]
497       end
498       setglue(nt, coef[i*2+1][2*cmp+2], 0, 0, 0, 0)
499       set_attr(nt, attr_ruby, 2*i+2)
500    end
501    tex.setattribute('global', attr_ruby, -0x7FFFFFFF)
502    setfield(w, 'user_id', RUBY_POST)
503    next_cluster_array[w]=nil
504    return head, first_whatsit(node_next(nt))
505 end
506
507 local function pre_high(ahead)
508    if not ahead then return ahead end
509    local head = to_direct(ahead)
510    post_intrusion_backup = 0
511    local n = first_whatsit(head)
512    while n do
513       if getsubtype(n) == sid_user and getfield(n, 'user_id') == RUBY_PRE then
514         local nv = getfield(n, 'value')
515          max_allow_pre = has_attr(nv, attr_ruby_maxprep) or 0
516          local atr = has_attr(n, attr_ruby) or 0
517          if max_allow_pre < 0 then
518             if atr>0 then
519                -- 直前のルビで intrusion がおこる可能性あり.
520                -- 前 run のデータが残っていればそれを使用,
521                -- そうでなければ行中形のデータを利用する
522                local op = old_break_info[atr] or post_intrusion_backup
523                max_allow_pre = max(0, -max_allow_pre - op)
524             else
525                max_allow_pre = -max_allow_pre
526             end
527          end
528          post_intrusion_backup = 0
529          max_allow_post = has_attr(nv, attr_ruby_maxpostp) or 0
530          max_margin = has_attr(nv, attr_ruby_maxmargin) or 0
531          local coef = pre_low_cal_box(n, getfield(nv, 'value'))
532          local s = node_tail(nv) --ルビ文字
533          head, n = pre_low_app_node(
534             head, n, getfield(nv, 'value'), coef,
535             getfield(s, 'height'), getfield(s, 'depth')
536          )
537       else
538          n = first_whatsit(node_next(n))
539       end
540    end
541    return to_node(head)
542 end
543 luatexbase.add_to_callback('pre_linebreak_filter', pre_high, 'ltj.ruby.pre', 100)
544 luatexbase.add_to_callback('hpack_filter', pre_high, 'ltj.ruby.pre', 100)
545
546 ----------------------------------------------------------------
547 -- post_line_break
548 ----------------------------------------------------------------
549 local post_lown
550 do
551    local function write_aux(wv, num)
552       local id = has_attr(wv, attr_ruby_id)
553       if id>0 and cache_handle then
554          cache_handle:write(
555                     'luatexja.ruby.old_break_info['
556                        .. tostring(id) .. ']=' .. num
557                        .. '\n')
558       end
559    end
560
561    post_lown = function (rs, rw, cmp, ch)
562       -- ch: the head of `current' hlist
563       if #rs ==0 or not rw then return ch end
564       local hn = has_attr(rs[1], attr_ruby)
565       local fn = has_attr(rs[#rs], attr_ruby)
566       local wv = getfield(rw, 'value')
567       if hn==1 then
568          if fn==2*cmp+2 then
569             local hn = node_tail(wv)
570             node_remove(wv, hn)
571             insert_after(ch, rs[1], hn)
572             set_attr(hn, attr_icflag,  PROCESSED)
573             write_aux(wv, has_attr(hn, attr_ruby))-- 行中形
574          else
575             local deg, hn = (fn-1)/2, wv
576             for i = 1, deg do hn = node_next(hn) end;
577             node_remove(wv, hn)
578             setfield(hn, 'next', nil)
579             insert_after(ch, rs[1], hn)
580             set_attr(hn, attr_icflag,  PROCESSED)
581             write_aux(wv, has_attr(hn, attr_ruby))
582          end
583       else
584          local deg, hn = max((hn-1)/2,2), wv
585          for i = 1, cmp+deg-1 do hn = node_next(hn) end
586          -- -1 is needed except the case hn = 3,
587          --   because a ending-line form is removed already from the list
588          node_remove(wv, hn); setfield(hn, 'next', nil)
589          insert_after(ch, rs[1], hn)
590          set_attr(hn, attr_icflag,  PROCESSED)
591          if fn == 2*cmp-1 then
592             write_aux(wv, has_attr(hn, attr_ruby))
593          end
594       end
595       for i = 1,#rs do
596          local ri = rs[i]
597          ch = node_remove(ch, ri); node_free(ri);
598       end
599       -- cleanup
600       if fn >= 2*cmp+1 then node_free(rw) end
601       return ch;
602    end
603 end
604
605 local function post_high_break(head)
606    local rs = {}   -- rs: sequence of ruby_nodes,
607    local rw = nil  -- rw: main whatsit
608    local cmp = -2  -- dummy
609    for h in node.direct.traverse_id(id_hlist, to_direct(head)) do
610       for i = 1, #rs do rs[i] = nil end
611       local ha = getlist(h)
612       while ha do
613          local hai = getid(ha)
614          local i = ((hai == id_glue and getsubtype(ha)==0)
615                        or (hai == id_rule and getsubtype(ha)==0)
616                        or (hai == id_whatsit and getsubtype(ha)==sid_user
617                               and getfield(ha, 'user_id', RUBY_POST)))
618             and has_attr(ha, attr_ruby) or 0
619          if i==0 then
620             ha = node_next(ha)
621          elseif i==1 then
622             setfield(h, 'head', post_lown(rs, rw, cmp, getlist(h)))
623             for i = 2, #rs do rs[i] = nil end -- rs[1] is set by the next statement
624             rs[1], rw = ha, nil; ha = node_next(ha)
625          elseif i==2 then
626             rw = ha
627             cmp = getfield(getfield(rw, 'value'), 'value')
628             local hb, hc =  node_remove(getlist(h), rw)
629             setfield(h, 'head', hb); ha = hc
630          else -- i>=3
631             rs[#rs+1] = ha; ha = node_next(ha)
632          end
633       end
634       setfield(h, 'head', post_lown(rs, rw, cmp, getlist(h)))
635    end
636    return head
637 end
638
639 local function post_high_hbox(ahead)
640    local ha = to_direct(ahead); local head = ha
641    local rs = {};  -- rs: sequence of ruby_nodes,
642    local rw = nil; -- rw: main whatsit
643    local cmp
644    while ha do
645       local hai = getid(ha)
646       local i = ((hai == id_glue and getsubtype(ha)==0)
647                     or (hai == id_rule and getsubtype(ha)==0)
648                     or (hai == id_whatsit and getsubtype(ha)==sid_user
649                            and getfield(ha, 'user_id', RUBY_POST)))
650          and has_attr(ha, attr_ruby) or 0
651       if i==0 then
652          ha = node_next(ha)
653       elseif i==1 then
654          head = post_lown(rs, rw, cmp, head)
655          for i = 2, #rs do rs[i] = nil end -- rs[1] is set by the next statement
656          rs[1], rw = ha, nil; ha = node_next(ha)
657       elseif i==2 then
658          rw = ha
659          cmp = getfield(getfield(rw, 'value'), 'value')
660          head, ha = node_remove(head, rw)
661       else -- i >= 3
662          rs[#rs+1] = ha; ha = node_next(ha)
663       end
664    end
665    return to_node(post_lown(rs, rw, cmp, head))
666 end
667
668 luatexbase.add_to_callback('post_linebreak_filter', post_high_break, 'ltj.ruby.post_break', 100)
669 luatexbase.add_to_callback('hpack_filter', post_high_hbox, 'ltj.ruby.post_hbox', 101)
670
671
672 ----------------------------------------------------------------
673 -- for jfmglue callbacks
674 ----------------------------------------------------------------
675 do
676    local RIPRE  = luatexja.stack_table_index.RIPRE
677    local function whatsit_callback(Np, lp, Nq)
678       if Np.nuc then return Np
679       elseif  getfield(lp, 'user_id') == RUBY_PRE then
680          Np.first, Np.nuc, Np.last = lp, lp, lp
681          local lpv = getfield(lp, 'value')
682          local x = node_next(node_next(lpv))
683          Np.last_char = luatexja.jfmglue.check_box_high(Np, getlist(x), nil)
684          if Nq.id ~=id_pbox_w then
685             if type(Nq.char)=='number' then
686                -- Nq is a JAchar
687                if has_attr(lpv, attr_ruby_maxprep) < 0 then -- auto
688                   local p = round((ltjs.table_current_stack[RIPRE + Nq.char] or 0)
689                                      *has_attr(lpv, attr_ruby))
690                   if has_attr(lpv, attr_ruby_mode)%2 == 0 then -- intrusion 無効
691                      p = 0
692                   end
693                   set_attr(lpv, attr_ruby_maxprep, -p)
694                end
695                if Nq.prev_ruby then
696                   set_attr(lp, attr_ruby, Nq.prev_ruby)
697                end
698             elseif has_attr(lpv, attr_ruby_maxprep) < 0 then -- auto
699                if Nq.char == 'parbdd' then
700                   local p = round((ltjs.table_current_stack[RIPRE-1] or 0)
701                                      *has_attr(lpv, attr_ruby))
702                   p = min(p, Nq.width)
703                  if has_attr(lpv, attr_ruby_mode)%2 == 0 then -- intrusion 無効
704                      p = 0
705                   end
706                   set_attr(lpv, attr_ruby_maxprep, p)
707                else
708                   set_attr(lpv, attr_ruby_maxprep, 0)
709                end
710             end
711          elseif has_attr(lpv, attr_ruby_maxprep) < 0 then -- auto
712             set_attr(lpv, attr_ruby_maxprep, 0)
713          end
714          return Np
715       else
716         return Np
717       end
718    end
719    luatexbase.add_to_callback("luatexja.jfmglue.whatsit_getinfo", whatsit_callback,
720                               "luatexja.ruby.np_info", 1)
721 end
722
723 do
724    local RIPOST = luatexja.stack_table_index.RIPOST
725    local function whatsit_after_callback(s, Nq, Np)
726       if not s and  getfield(Nq.nuc, 'user_id') == RUBY_PRE then
727          if Np then
728             local last_glue = node_new(id_glue)
729             set_attr(last_glue, attr_icflag, 0)
730             insert_before(Nq.nuc, Np.first, last_glue)
731             Np.first = last_glue
732             next_cluster_array[Nq.nuc] = last_glue -- ルビ処理用のグルー
733          end
734          local nqnv = getfield(Nq.nuc, 'value')
735          local x =  node_next(node_next(nqnv))
736          for i = 2, getfield(nqnv, 'value') do x = node_next(node_next(x)) end
737          Nq.last_char = luatexja.jfmglue.check_box_high(Nq, getlist(x), nil)
738          luatexja.jfmglue.after_hlist(Nq)
739          if Np and Np.id ~=id_pbox_w and type(Np.char)=='number' then
740             -- Np is a JAchar
741             local rm = has_attr(nqnv, attr_ruby_mode)
742             if has_attr(nqnv, attr_ruby_maxpostp) < 0 then -- auto
743                local p = round((ltjs.table_current_stack[RIPOST + Np.char] or 0)
744                                   *has_attr(nqnv, attr_ruby))
745                if rm%2 == 0 then -- intrusion 無効
746                   p = 0
747                end
748                if rm%4 >= 2 then
749                   local q = has_attr(nqnv, attr_ruby_maxprep)
750                   if q < p then p = q
751                   elseif q > p then
752                      set_attr(nqnv, attr_ruby_maxprep, p)
753                   end
754                end
755                set_attr(nqnv, attr_ruby_maxpostp, p)
756             end
757             Np.prev_ruby = has_attr(getfield(Nq.nuc, 'value'), attr_ruby_id)
758             -- 前のクラスタがルビであったことのフラグ
759          else -- 直前が文字以外
760             local nqnv = getfield(Nq.nuc, 'value')
761             if has_attr(nqnv, attr_ruby_maxpostp) < 0 then -- auto
762                set_attr(nqnv, attr_ruby_maxpostp, 0)
763                if has_attr(nqnv, attr_ruby_mode)%4 >= 2 then
764                   set_attr(nqnv, attr_ruby_maxprep, 0)
765                end
766             end
767          end
768          return true
769       else
770          return s
771       end
772    end
773    luatexbase.add_to_callback("luatexja.jfmglue.whatsit_after", whatsit_after_callback,
774                               "luatexja.ruby.np_info_after", 1)
775 end
776