OSDN Git Service

1cb9db7b8f9c5668dd183962ef38aabfeb7b8fc6
[luatex-ja/luatexja.git] / src / luatexja / jfmglue.lua
1 --
2 -- luatexja/jfmglue.lua
3 --
4 luatexbase.provides_module({
5   name = 'luatexja.jfmglue',
6   date = '2011/06/27',
7   version = '0.2',
8   description = 'Insertion process of JFM glues and kanjiskip',
9 })
10 module('luatexja.jfmglue', package.seeall)
11 local err, warn, info, log = luatexbase.errwarinf(_NAME)
12
13 require('luatexja.base');      local ltjb = luatexja.base
14 require('luatexja.stack');     local ltjs = luatexja.stack
15 require('luatexja.jfont');     local ltjf = luatexja.jfont
16 require('luatexja.pretreat');  local ltjp = luatexja.pretreat
17
18 local node_type = node.type
19 local node_new = node.new
20 local node_remove = node.remove
21 local node_prev = node.prev
22 local node_next = node.next
23 local node_copy = node.copy
24 local node_tail = node.tail
25 local node_free = node.free
26 local has_attr = node.has_attribute
27 local set_attr = node.set_attribute
28 local node_insert_before = node.insert_before
29 local node_insert_after = node.insert_after
30 local round = tex.round
31
32 local id_glyph = node.id('glyph')
33 local id_hlist = node.id('hlist')
34 local id_vlist = node.id('vlist')
35 local id_rule = node.id('rule')
36 local id_ins = node.id('ins')
37 local id_mark = node.id('mark')
38 local id_adjust = node.id('adjust')
39 local id_disc = node.id('disc')
40 local id_whatsit = node.id('whatsit')
41 local id_math = node.id('math')
42 local id_glue = node.id('glue')
43 local id_kern = node.id('kern')
44 local id_penalty = node.id('penalty')
45
46 local id_glue_spec = node.id('glue_spec')
47 local id_jglyph = node.id('glyph') + 256
48 local id_box_like = node.id('hlist') + 256
49 local id_pbox = node.id('hlist') + 512
50 local sid_user = node.subtype('user_defined')
51
52 local ITALIC = 1
53 local PACKED = 2
54 local KINSOKU = 3
55 local FROM_JFM = 4
56 local LINE_END = 5
57 local KANJI_SKIP = 6
58 local XKANJI_SKIP = 7
59 local PROCESSED = 8
60 local IC_PROCESSED = 9
61 local BOXBDD = 15
62
63 local kanji_skip
64 local xkanji_skip
65
66 local attr_jchar_class = luatexbase.attributes['ltj@charclass']
67 local attr_curjfnt = luatexbase.attributes['ltj@curjfnt']
68 local attr_icflag = luatexbase.attributes['ltj@icflag']
69 local attr_autospc = luatexbase.attributes['ltj@autospc']
70 local attr_autoxspc = luatexbase.attributes['ltj@autoxspc']
71 local max_dimen = 1073741823
72
73
74
75 -------------------- Helper functions
76
77 local function find_char_class(c,m)
78    return m.chars[c] or 0
79 end
80
81 local function get_zero_glue()
82    local g = node_new(id_glue_spec)
83    g.width = 0; g.stretch_order = 0; g.stretch = 0
84    g.shrink_order = 0; g.shrink = 0
85    return g
86 end
87
88 local function skip_table_to_spec(n)
89    local g = node_new(id_glue_spec)
90    local st = ltjs.get_skip_table(n, ltjp.box_stack_level)
91    g.width = st.width; g.stretch = st.stretch; g.shrink = st.shrink
92    g.stretch_order = st.stretch_order; g.shrink_order = st.shrink_order
93    return g
94 end
95
96 -- penalty 値の計算
97 local function add_penalty(p,e)
98    if p.penalty>=10000 then
99       if e<=-10000 then p.penalty = 0 end
100    elseif p.penalty<=-10000 then
101       if e>=10000 then p.penalty = 0 end
102    else
103       p.penalty = p.penalty + e
104       if p.penalty>=10000 then p.penalty = 10000
105       elseif p.penalty<=-10000 then p.penalty = -10000 end
106    end
107    return
108 end
109
110 -- 「異なる JFM」の間の調整方法
111 diffmet_rule = math.two_average
112 function math.two_add(a,b) return a+b end
113 function math.two_average(a,b) return (a+b)/2 end
114
115 -------------------- idea
116 -- 2 node の間に glue/kern/penalty を挿入する.
117 -- 基本方針: char node q と char node p の間
118
119 --  Np: 「p を核とする塊」
120 --   first: 最初の node,nuc: p,last: 最後の node
121 --   id: 核 node の種類
122 --  Nq: 「q を核とする塊」
123 --   実際の glue は Np.last, Nq.first の間に挿入される
124 --  Bp: Np.last, Nq.first の間の penalty node 達の配列
125
126 -- 核の定義:
127 --  node x が non-char node のときは,x のみ
128 --  x が char_node のときは,
129 --  - x が \accent の第二引数だったとき
130 --    [kern2 kern y kern2] x の 3 node が核に加わる
131 --  - x の直後に \/ 由来 kern があったとき
132 --    その \/ 由来の kern が核に加わる
133 -- p, q の走査で無視するもの:
134 --  ins, mark, adjust, whatsit, penalty
135 --
136 -- Nq.last .. + .. Bp.first .... Bp[last] .... * .. Np.first
137 -- +: kern from LINEEND はここに入る
138 -- *: jfm glue はここに入る
139
140 local head -- the head of current list
141 local last -- the last node of current list
142 local lp   -- 外側での list 走査時のカーソル
143
144 local Np, Nq, Bp
145 local widow_Bp, widow_Np -- \jcharwidowpenalty 挿入位置管理用
146
147 local ihb_flag -- JFM グルー挿入抑止用 flag
148                -- on: \inhibitglue 指定時,hlist の周囲
149
150 -------------------- hlist 内の文字の検索
151
152 local first_char, last_char, find_first_char
153
154 local function check_box(box_ptr, box_end)
155    local p = box_ptr; local found_visible_node = false
156    if not p then 
157       find_first_char = false; first_char = nil; last_char = nil
158       return true
159    end
160    while p and p~=box_end do
161       local pid = p.id
162       if pid==id_kern then
163          if p.subtype==2 then
164             p = node_next(node_next(node_next(p))); pid = p.id
165          elseif has_attr(p, attr_icflag)==IC_PROCESSED then
166             p = node_next(p); pid = p.id
167          end
168       end
169       if pid==id_glyph then
170          repeat 
171             if find_first_char then 
172                first_char = p; find_first_char = false
173             end
174             last_char = p; found_visible_node = true; p=node_next(p)
175             if (not p) or p==box_end then return found_visible_node end
176          until p.id~=id_glyph
177       end
178       if pid==id_hlist then
179          if has_attr(p, attr_icflag)==PACKED then
180             for q in node.traverse_id(id_glyph, p.head) do
181                if find_first_char then
182                   first_char = q; find_first_char = false
183                end
184                last_char = q; found_visible_node = true; break
185             end
186          else
187             if p.shift==0 then
188                if check_box(p.head, nil) then found_visible_node = true end
189             else if find_first_char then 
190                   find_first_char = false
191                else 
192                   last_char = nil
193                end
194             end
195          end
196       elseif not (pid==id_ins   or pid==id_mark
197                   or pid==id_adjust or pid==id_whatsit
198                   or pid==id_penalty) then
199          found_visible_node = true
200          if find_first_char then 
201             find_first_char = false
202          else 
203             last_char = nil
204          end
205       end
206       p = node_next(p)
207    end
208    return found_visible_node
209 end 
210
211 -------------------- Np の計算と情報取得
212 -- calc next Np
213 local function set_attr_icflag_processed(p)
214    local a = has_attr(p, attr_icflag) or 0
215    if a<=1 then set_attr(p, attr_icflag, PROCESSED) end
216 end
217
218 local function check_next_ickern()
219    if lp.id == id_kern and has_attr(lp, attr_icflag)==ITALIC then
220       set_attr(lp, attr_icflag, IC_PROCESSED); Np.last = lp; lp = node_next(lp)
221    else Np.last = Np.nuc end
222 end
223
224 local function calc_np_pbox()
225    Np.first = lp; Np.id = id_pbox
226    lpa = KINSOKU -- dummy
227    while lp~=last and lpa>=PACKED and lpa~=BOXBDD do
228       Np.nuc = lp; lp = node_next(lp); lpa = has_attr(lp, attr_icflag) or 0
229    end
230    check_next_ickern()
231 end
232
233 local function calc_np()
234    -- We assume lp = node_next(Np.last)
235    local pBp = Bp; local lpi, lpa
236    Nq = Np; Bp = {}; Bp[0] = 0; Np = {}; ihb_flag = false 
237    while true do
238       lpi = lp.id; lpa = has_attr(lp, attr_icflag) or 0
239       if lp==last then Np = nil; return
240       elseif lpa>=PACKED then
241          if lpa == BOXBDD then
242             local lq = node_next(lp)
243             head = node_remove(head, lp); lp = lq
244          else calc_np_pbox(); return end -- id_pbox
245       elseif lpi == id_ins or lpi == id_mark or lpi == id_adjust then
246          set_attr_icflag_processed(lp); lp = node_next(lp)
247       elseif lpi == id_penalty then
248          table.insert(Bp, lp); Bp[0] = Bp[0] + 1
249          set_attr_icflag_processed(lp); lp = node_next(lp)
250       elseif lpi == id_whatsit then
251          if lp.subtype==sid_user and lp.user_id==30111 then
252             local lq = node_next(lp)
253             head = node_remove(head, lp); lp = lq; ihb_flag = true
254          else
255             set_attr_icflag_processed(lp); lp = node_next(lp)
256          end
257       else -- a `cluster' is found
258          Np.first = lp
259          if lpi == id_glyph then -- id_[j]glyph
260             if lp.font == has_attr(lp, attr_curjfnt) then Np.id = id_jglyph 
261             else Np.id = id_glyph end
262             Np.nuc = lp; set_attr_icflag_processed(lp)
263             lp = node_next(lp); check_next_ickern(); return
264          elseif lpi == id_hlist then -- hlist
265             Np.last = lp; Np.nuc = lp; set_attr_icflag_processed(lp)
266             if lp.shift~=0 then Np.id = id_box_like
267             else Np.id = lpi end
268             lp = node_next(lp); return
269          elseif lpi == id_vlist or lpi == id_rule then -- id_box_like
270             Np.nuc = lp; Np.last = lp; Np.id = id_box_like; break
271          elseif lpi == id_math then -- id_math
272             Np.nuc = lp
273             while lp.id~=id_math do 
274                set_attr_icflag_processed(lp); lp  = node_next(lp) 
275             end; break
276          elseif lpi == id_kern and lp.subtype==2 then -- id_kern
277             set_attr_icflag_processed(lp); lp = node_next(lp)
278             set_attr_icflag_processed(lp); lp = node_next(lp)
279             set_attr_icflag_processed(lp); lp = node_next(lp)
280             set_attr_icflag_processed(lp); Np.nuc = lp
281             if lp.font == has_attr(lp, attr_curjfnt) then Np.id = id_jglyph 
282             else Np.id = id_glyph end
283             lp = node_next(lp); check_next_ickern(); return
284          else -- id_disc, id_glue, id_kern
285             Np.nuc = lp; break
286          end
287       end
288    end
289    set_attr_icflag_processed(lp); Np.last = lp; Np.id = lpi; lp = node_next(lp)
290 end
291
292 -- extract informations from Np
293 -- We think that "Np is a Japanese character" if Np.met~=nil,
294 --            "Np is an alphabetic character" if Np.pre~=nil,
295 --            "Np is not a character" otherwise.
296
297 -- 和文文字のデータを取得
298 local function set_np_xspc_jachar(c,x)
299    Np.class = has_attr(x, attr_jchar_class)
300    Np.char = c
301    local z = ltjf.font_metric_table[x.font]
302    Np.size= z.size
303    Np.met = ltjf.metrics[z.jfm]
304    Np.var = z.var
305    Np.pre = ltjs.get_penalty_table('pre', c, 0, ltjp.box_stack_level)
306    Np.post = ltjs.get_penalty_table('post', c, 0, ltjp.box_stack_level)
307    z = find_char_class('lineend', Np.met)
308    local y = Np.met.char_type[Np.class]
309    if y.kern and y.kern[z] then 
310       Np.lend = round(Np.size*y.kern[z]) 
311    else 
312       Np.lend = 0 
313    end
314    y = ltjs.get_penalty_table('xsp', c, 3, ltjp.box_stack_level)
315    Np.xspc_before = (y>=2)
316    Np.xspc_after  = (y%2==1)
317    Np.auto_kspc = (has_attr(x, attr_autospc)==1)
318    Np.auto_xspc = (has_attr(x, attr_autoxspc)==1)
319 end
320
321 -- 欧文文字のデータを取得
322 local ligature_head = 1
323 local ligature_tail = 2
324 local function set_np_xspc_alchar(c,x, lig)
325    if c~=-1 then
326       if lig == ligature_head then
327          while x.components and x.subtype and math.floor(x.subtype/2)%2==1 do
328             x = x.components; c = x.char
329          end
330       else
331          while x.components and x.subtype and math.floor(x.subtype/2)%2==1 do
332             x = node_tail(x.components); c = x.char
333          end
334       end
335       Np.pre = ltjs.get_penalty_table('pre', c, 0, ltjp.box_stack_level)
336       Np.post = ltjs.get_penalty_table('post', c, 0, ltjp.box_stack_level)
337    else
338       Np.pre = 0; Np.post = 0
339    end
340    Np.met = nil
341    local y = ltjs.get_penalty_table('xsp', c, 3, ltjp.box_stack_level)
342    Np.xspc_before = (y%2==1)
343    Np.xspc_after  = (y>=2)
344    Np.auto_xspc = (has_attr(x, attr_autoxspc)==1)
345 end
346
347 -- Np の情報取得メインルーチン
348 local function extract_np()
349    local x = Np.nuc
350    if Np.id ==  id_jglyph then
351       set_np_xspc_jachar(x.char, x)
352    elseif Np.id == id_glyph then
353       set_np_xspc_alchar(x.char, x, ligature_head)
354    elseif Np.id == id_hlist then
355       find_first_char = true; first_char = nil; last_char = nil
356       if check_box(x.head, nil) then
357          if first_char then
358             if first_char.font == has_attr(first_char, attr_curjfnt) then 
359                set_np_xspc_jachar(first_char.char,first_char)
360             else
361                set_np_xspc_alchar(first_char.char,first_char, ligature_head)
362             end
363          end
364       end
365    elseif Np.id == id_pbox then --  mikann 
366       find_first_char = true; first_char = nil; last_char = nil
367       if check_box(Np.first, node_next(Np.last)) then
368          if first_char then
369             if first_char.font == has_attr(first_char, attr_curjfnt) then 
370                set_np_xspc_jachar(first_char.char,first_char)
371             else
372                set_np_xspc_alchar(first_char.char,first_char, ligature_head)
373             end
374          end
375       end
376    elseif Np.id == id_disc then 
377       find_first_char = true; first_char = nil; last_char = nil
378       if check_box(x.replace, nil) then
379          if first_char then
380             if first_char.font == has_attr(first_char, attr_curjfnt) then 
381                set_np_xspc_jachar(first_char.char,first_char)
382             else
383                set_np_xspc_alchar(first_char.char,first_char, ligature_head)
384             end
385          end
386       end
387    elseif Np.id == id_math then
388       set_np_xspc_alchar(-1, x)
389    end
390 end
391
392 -- change the information for the next loop
393 -- (will be done if Np is an alphabetic character or a hlist)
394 local function after_hlist()
395    if last_char then
396       if last_char.font == has_attr(last_char, attr_curjfnt) then 
397          set_np_xspc_jachar(last_char.char,last_char, ligature_after)
398       else
399          set_np_xspc_alchar(last_char.char,last_char, ligature_after)
400       end
401    else
402       Np.pre = nil; Np.met = nil
403    end
404 end
405 local function after_alchar()
406    local x = Np.nuc
407    set_np_xspc_alchar(x.char,x, ligature_after)
408 end
409
410
411 -------------------- 最下層の処理
412
413 local function lineend_fix(g)
414    if g and g.id==id_kern then 
415       Nq.lend = 0
416    elseif Nq.lend and Nq.lend~=0 then
417       if not g then
418          g = node_new(id_kern); g.subtype = 1
419          g.kern = -Nq.lend; set_attr(g, attr_icflag, LINEEND)
420       elseif g.id==id_kern then
421          g.kern = g.kern - Nq.lend
422       else
423          g.spec.width = g.spec.width - Nq.lend
424       end
425    end
426    return g
427 end
428
429 -- change penalties (or create a new penalty, if needed)
430 local function handle_penalty_normal(post, pre, g)
431    local a = (pre or 0) + (post or 0)
432    if Bp[0] == 0 then
433       if (a~=0 and not(g and g.id==id_kern)) or (Nq.lend and Nq.lend~=0) then
434          local p = node_new(id_penalty)
435          if a<-10000 then a = -10000 elseif a>10000 then a = 10000 end
436          p.penalty = a
437          head = node_insert_before(head, Np.first, p)
438          Bp[1] = p; Bp[0] = 1; set_attr(p, attr_icflag, KINSOKU)
439       end
440    else for i, v in ipairs(Bp) do add_penalty(v,a) end
441    end
442 end
443
444 local function handle_penalty_always(post, pre, g)
445    local a = (pre or 0) + (post or 0)
446    if Bp[0] == 0 then
447       if not (g and g.id==id_glue) or (Nq.lend and Nq.lend~=0) then
448          local p = node_new(id_penalty)
449          if a<-10000 then a = -10000 elseif a>10000 then a = 10000 end
450          p.penalty = a
451          head = node_insert_before(head, Np.first, p)
452          Bp[1] = p; Bp[0] = 1; set_attr(p, attr_icflag, KINSOKU)
453       end
454    else for i, v in ipairs(Bp) do add_penalty(v,a) end
455    end
456 end
457
458 local function handle_penalty_suppress(post, pre, g)
459    local a = (pre or 0) + (post or 0)
460    if Bp[0] == 0 then
461       if g and g.id==id_glue then
462          local p = node_new(id_penalty)
463          p.penalty = 10000; head = node_insert_before(head, Np.first, p)
464          Bp[1] = p; Bp[0] = 1; set_attr(p, attr_icflag, KINSOKU)
465       end
466    else for i, v in ipairs(Bp) do add_penalty(v,a) end
467    end
468 end
469
470 -- 和文文字間の JFM glue を node 化
471 local function new_jfm_glue(Nn, bc, ac)
472 -- bc, ac: char classes
473    local g = nil
474    local z = Nn.met.char_type[bc]
475    if z.glue and z.glue[ac] then
476       local h = node_new(id_glue_spec)
477       h.width   = round(Nn.size*z.glue[ac][1])
478       h.stretch = round(Nn.size*z.glue[ac][2])
479       h.shrink  = round(Nn.size*z.glue[ac][3])
480       h.stretch_order=0; h.shrink_order=0
481       g = node_new(id_glue)
482       g.subtype = 0; g.spec = h
483    elseif z.kern and z.kern[ac] then
484       g = node_new(id_kern)
485       g.subtype = 1; g.kern = round(Nn.size*z.kern[ac])
486    end
487    if g then set_attr(g, attr_icflag, FROM_JFM) end
488    return g
489 end
490
491 -- Nq.last (kern w) .... (glue/kern g) Np.first
492 local function real_insert(w, g)
493    if w~=0 then
494       local h = node_new(id_kern)
495       set_attr(h, attr_icflag, LINE_END)
496       h.kern = Nq.lend; h.subtype = 1
497       head = node_insert_after(head, Nq.last, h)
498    end
499    if g then
500       head = node_insert_before(head, Np.first, g)
501       Np.first = g
502    end
503 end
504
505 -------------------- 和文文字間空白量の決定
506
507 -- get kanjiskip
508 local function get_kanji_skip_from_jfm(Nn)
509    local i = Nn.met.kanjiskip
510    if i then
511       return { round(i[1]*Nn.size), round(i[2]*Nn.size), round(i[3]*Nn.size) }
512    else return nil
513    end
514 end
515 local function get_kanjiskip()
516    local g = node_new(id_glue)
517    if Np.auto_kspc or Nq.auto_kspc then
518       if kanji_skip.width == max_dimen then
519          local gx = node_new(id_glue_spec);
520          gx.stretch_order = 0; gx.shrink_order = 0
521          local bk = get_kanji_skip_from_jfm(Nq)
522          local ak
523          if (Np.met==Nq.met) and (Nq.size==Np.size) and (Nq.var==Np.var) then
524             ak = nil
525          else
526             ak = get_kanji_skip_from_jfm(Np)
527          end
528          if bk then
529             if ak then
530                gx.width = round(diffmet_rule(bk[1], ak[1]))
531                gx.stretch = round(diffmet_rule(bk[2], ak[2]))
532                gx.shrink = -round(diffmet_rule(-bk[3], -ak[3]))
533             else
534                gx.width = bk[1]; gx.stretch = bk[2]; gx.shrink = bk[3]
535             end
536          elseif ak then
537             gx.width = ak[1]; gx.stretch = ak[2]; gx.shrink = ak[3]
538          else gx = get_zero_glue() -- fallback
539          end
540          g.spec = gx
541       else g.spec=node_copy(kanji_skip) end
542    else
543       local gx = get_zero_glue()
544       g.spec = gx
545    end
546    set_attr(g, attr_icflag, KANJI_SKIP)
547    return g
548 end
549
550 local function calc_ja_ja_aux(gb,ga)
551    if not gb then 
552       return ga
553    else
554       if not ga then return gb end
555       local k = node.type(gb.id) .. node.type(ga.id)
556       if k == 'glueglue' then 
557          -- 両方とも glue.
558          gb.spec.width   = round(diffmet_rule(gb.spec.width, ga.spec.width))
559          gb.spec.stretch = round(diffmet_rule(gb.spec.stretch,ga.spec.shrink))
560          gb.spec.shrink  = -round(diffmet_rule(-gb.spec.shrink, -ga.spec.shrink))
561          node_free(ga)
562          return gb
563       elseif k == 'kernkern' then
564          -- 両方とも kern.
565          gb.kern = round(diffmet_rule(gb.kern, ga.kern))
566          node_free(ga)
567          return gb
568       elseif k == 'kernglue' then 
569          -- gb: kern, ga: glue
570          ga.spec.width   = round(diffmet_rule(gb.kern,ga.spec.width))
571          ga.spec.stretch = round(diffmet_rule(ga.spec.stretch, 0))
572          ga.spec.shrink  = -round(diffmet_rule(-ga.spec.shrink, 0))
573          node_free(gb)
574          return ga
575       else
576          -- gb: glue, ga: kern
577          gb.spec.width   = round(diffmet_rule(ga.kern, gb.spec.width))
578          gb.spec.stretch = round(diffmet_rule(gb.spec.stretch, 0))
579          gb.spec.shrink  = -round(diffmet_rule(-gb.spec.shrink, 0))
580          node_free(ga)
581          return gb
582       end
583    end
584 end
585
586 local function calc_ja_ja_glue()
587    if  ihb_flag then return nil
588    elseif (Nq.size==Np.size) and (Nq.met==Np.met) and (Nq.var==Np.var) then
589       return new_jfm_glue(Nq, Nq.class, Np.class)
590    else
591       local g = new_jfm_glue(Nq, Nq.class,
592                              find_char_class('diffmet',Nq.met))
593       local h = new_jfm_glue(Np, find_char_class('diffmet',Np.met),
594                              Np.class)
595       return calc_ja_ja_aux(g,h)
596    end
597 end
598
599 -------------------- 和欧文間空白量の決定
600
601 -- get xkanjiskip
602 local function get_xkanji_skip_from_jfm(Nn)
603    local i = Nn.met.xkanjiskip
604    if i then
605       return { round(i[1]*Nn.size), round(i[2]*Nn.size), round(i[3]*Nn.size) }
606    else return nil
607    end
608 end
609 local function get_xkanjiskip(Nn)
610    local g = node_new(id_glue)
611    if Nq.xspc_after and Np.xspc_before and (Nq.auto_xspc or Np.auto_xspc) then
612       if xkanji_skip.width == max_dimen then
613          local gx = node_new(id_glue_spec);
614          gx.stretch_order = 0; gx.shrink_order = 0
615          local bk = get_xkanji_skip_from_jfm(Nn)
616          if bk then
617             gx.width = bk[1]; gx.stretch = bk[2]; gx.shrink = bk[3]
618          else gx = get_zero_glue() -- fallback
619          end
620          g.spec = gx
621       else g.spec=node_copy(xkanji_skip) end
622    else
623       local gx = get_zero_glue()
624       g.spec = gx
625    end
626    set_attr(g, attr_icflag, XKANJI_SKIP)
627    return g
628 end
629
630
631 -------------------- 隣接した「塊」間の処理
632
633 local function get_OA_skip()
634    if not ihb_flag then
635       return new_jfm_glue(Np, find_char_class('jcharbdd',Np.met), Np.class)
636    else return nil
637    end
638 end
639 local function get_OB_skip()
640    if not ihb_flag then
641       return new_jfm_glue(Nq, Nq.class, find_char_class('jcharbdd',Nq.met))
642    else return nil
643    end
644 end
645
646 -- (anything) .. jachar
647 local function handle_np_jachar()
648    local g = nil
649    if Nq.id==id_jglyph or (Nq.id==id_pbox and Nq.met) then 
650       g = calc_ja_ja_glue() or get_kanjiskip() -- M->K
651       g = lineend_fix(g)
652       handle_penalty_normal(Nq.post, Np.pre, g); real_insert(Nq.lend, g)
653    elseif Nq.met then  -- Nq.id==id_hlist
654       g = get_OA_skip() or get_kanjiskip() -- O_A->K
655       handle_penalty_normal(0, Np.pre, g); real_insert(0, g)
656    elseif Nq.pre then 
657       g = get_OA_skip() or get_xkanjiskip(Np) -- O_A->X
658       handle_penalty_normal(Nq.post, Np.pre, g); real_insert(0, g)
659    else
660       g = get_OA_skip() -- O_A
661       if Nq.id==id_glue then handle_penalty_normal(0, Np.pre, g)
662       elseif Nq.id==id_kern then handle_penalty_suppress(0, Np.pre, g)
663       else handle_penalty_always(0, Np.pre, g)
664       end
665       real_insert(0, g)
666    end
667    -- \jcharwidowpenalty 挿入予定箇所更新
668    if ltjs.get_penalty_table('kcat', Np.char, 0, ltjp.box_stack_level)%2~=1 then
669       widow_Np = Np; widow_Bp = Bp
670    end
671 end
672
673 -- jachar .. (anything)
674 local function handle_nq_jachar()
675    local g = nil
676    if Np.pre then 
677       g = get_OB_skip() or get_xkanjiskip(Nq) -- O_B->X
678       g = lineend_fix(g)
679       handle_penalty_normal(Nq.post, Np.pre, g); real_insert(Nq.lend, g)
680    else
681       g = get_OB_skip(); g = lineend_fix(g) -- O_B
682       if Np.id==id_glue then handle_penalty_normal(Nq.post, 0, g)
683       elseif Np.id==id_kern then handle_penalty_suppress(Nq.post, 0, g)
684       else handle_penalty_always(Nq.post, 0, g)
685       end
686       real_insert(Nq.lend, g)
687    end
688 end
689
690 -- (anything) .. (和文文字で終わる hlist)
691 local function handle_np_ja_hlist()
692    local g = nil
693    if Nq.id==id_jglyph or (Nq.id==id_pbox and Nq.met) then 
694       g = get_OB_skip() or get_kanjiskip() -- O_B->K
695       g = lineend_fix(g)
696       handle_penalty_normal(Nq.post, 0, g); real_insert(Nq.lend, g)
697    elseif Nq.met then  -- Nq.id==id_hlist
698       g = get_kanjiskip() -- K
699       handle_penalty_suppress(0, 0, g); real_insert(0, g)
700    elseif Nq.pre then 
701       g = get_xkanjiskip(Np) -- X
702       handle_penalty_suppress(0, 0, g); real_insert(0, g)
703    end
704 end
705
706 -- (和文文字で終わる hlist) .. (anything)
707 local function handle_nq_ja_hlist()
708    local g = nil
709    if Np.pre then 
710       g = get_xkanjiskip(Nq) -- X
711       handle_penalty_suppress(0, 0, g); real_insert(0, g)
712    end
713 end
714
715 -------------------- 開始・終了時の処理
716
717 -- リスト末尾の処理
718 local function handle_list_tail()
719    Np = Nq
720    if mode then
721       -- the current list is to be line-breaked:
722       if Np.id == id_jglyph or (Np.id==id_pbox and Np.met) then 
723          if Np.lend~=0 then
724             g = node_new(id_kern); g.subtype = 0; g.kern = Np.lend
725             set_attr(g, attr_icflag, BOXBDD)
726             node_insert_after(head, Np.last, g)
727          end
728       end
729       -- Insert \jcharwidowpenalty
730       Bp = widow_Bp; Np = widow_Np
731       if Np then
732          handle_penalty_normal(0,
733                                ltjs.get_penalty_table('jwp', 0, 0, ltjp.box_stack_level))
734       end
735    else
736       -- the current list is the contents of a hbox
737       if Np.id == id_jglyph or (Np.id==id_pbox and Np.met) then 
738          local g = new_jfm_glue(Np, Np.class, find_char_class('boxbdd',Np.met))
739          if g then
740             set_attr(g, attr_icflag, BOXBDD)
741             head = node_insert_after(head, Np.last, g)
742          end
743       end
744       head = node_remove(head, last) -- remove the sentinel
745    end
746 end
747
748 -- リスト先頭の処理
749 local function handle_list_head()
750    if Np.id ==  id_jglyph or (Np.id==id_pbox and Np.met) then 
751       local g = new_jfm_glue(Np, find_char_class('boxbdd',Np.met), Np.class)
752       if g then
753          set_attr(g, attr_icflag, BOXBDD)
754          if g.id==id_glue and Bp[0]==0 then
755             local h = node_new(id_penalty)
756             h.penalty = 10000; set_attr(h, attr_icflag, BOXBDD)
757          end
758          head = node_insert_before(head, Np.first, g)
759       end
760    end
761 end
762
763 -- initialize
764 local function init_var()
765    lp = head; widow_Bp = nil; widow_Np = nil
766    kanji_skip=skip_table_to_spec('kanjiskip')
767    xkanji_skip=skip_table_to_spec('xkanjiskip')
768    if mode then 
769       -- the current list is to be line-breaked:
770       -- hbox from \parindent is skipped.
771       while lp and (lp.id==id_whatsit or ((lp.id==id_hlist) and (lp.subtype==3))) do
772          lp=node_next(lp) end
773       last=node.tail(head)
774    else 
775       -- the current list is the contents of a hbox:
776       -- insert a sentinel
777       last=node.tail(head); local g = node_new(id_kern)
778       node_insert_after(head, last, g); last = g
779    end
780 end
781
782 -------------------- 外部から呼ばれる関数
783
784 -- main interface
785 function main(ahead, amode)
786    if not ahead then return ahead end
787    head = ahead; mode = amode; init_var(); calc_np()
788    if Np then 
789       extract_np(); handle_list_head()
790       if Np.id==id_glyph then after_alchar()
791       elseif Np.id==id_hlist or Np.id==id_pbox or Np.id==id_disc then after_hlist()
792       end
793    else
794       if not mode then head = node_remove(head, last) end
795       return head
796    end
797    calc_np()
798    while Np do
799       extract_np()
800       -- 挿入部
801       if Np.id == id_jglyph then 
802          handle_np_jachar()
803       elseif Np.met then 
804          if Np.id==id_hlist then handle_np_ja_hlist()
805          else handle_np_jachar() end
806       elseif Nq.met then 
807          if Nq.id==id_hlist then handle_nq_ja_hlist()
808          else handle_nq_jachar() end
809       end
810       -- Np の後処理
811       if Np.id==id_glyph then after_alchar()
812       elseif Np.id==id_hlist or Np.id==id_pbox or Np.id==id_disc then after_hlist()
813       end
814       calc_np()
815    end
816    handle_list_tail()
817    -- adjust attr_icflag
818    tex.attribute[attr_icflag] = -(0x7FFFFFFF)
819    return head
820 end
821
822 -- \inhibitglue
823 function create_inhibitglue_node()
824    local g=node_new(id_whatsit, sid_user)
825    g.user_id=30111; g.type=100; g.value=1; node.write(g)
826 end
827
828 -- TODO: 二重挿入の回避