OSDN Git Service

7c5dfbb1e5320cedfcb1a1ff5cb0a0e961f4a475
[luatex-ja/luatexja.git] / src / ltj-jfmglue.lua
1 --
2 -- luatexja/jfmglue.lua
3 --
4 luatexbase.provides_module({
5   name = 'luatexja.jfmglue',
6   date = '2012/07/19',
7   version = '0.5',
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 luatexja.load_module('stack');     local ltjs = luatexja.stack
14 luatexja.load_module('jfont');     local ltjf = luatexja.jfont
15 luatexja.load_module('pretreat');  local ltjp = luatexja.pretreat
16
17 local has_attr = node.has_attribute
18 local set_attr = node.set_attribute
19 local insert_before = node.insert_before
20 local node_next = node.next
21 local round = tex.round
22 local ltjs_fast_get_penalty_table  = ltjs.fast_get_penalty_table
23 local ltjf_font_metric_table = ltjf.font_metric_table
24 local ltjf_find_char_class = ltjf.find_char_class
25 local node_new = node.new
26 local node_copy = node.copy
27
28 local ligature_head = 1
29 local ligature_tail = 2
30
31 local id_glyph = node.id('glyph')
32 local id_hlist = node.id('hlist')
33 local id_vlist = node.id('vlist')
34 local id_rule = node.id('rule')
35 local id_ins = node.id('ins')
36 local id_mark = node.id('mark')
37 local id_adjust = node.id('adjust')
38 local id_disc = node.id('disc')
39 local id_whatsit = node.id('whatsit')
40 local id_math = node.id('math')
41 local id_glue = node.id('glue')
42 local id_kern = node.id('kern')
43 local id_penalty = node.id('penalty')
44
45 local id_glue_spec = node.id('glue_spec')
46 local id_jglyph = node.id('glyph') + 256      -- Japanese character
47 local id_box_like = node.id('hlist') + 256    -- vbox, shifted hbox
48 local id_pbox = node.id('hlist') + 512        -- already processed nodes (by \unhbox)
49 local id_pbox_w = node.id('hlist') + 513      -- cluster which consists of a whatsit
50 local sid_user = node.subtype('user_defined')
51
52 local sid_start_link = node.subtype('pdf_start_link')
53 local sid_start_thread = node.subtype('pdf_start_thread')
54 local sid_end_link = node.subtype('pdf_end_link')
55 local sid_end_thread = node.subtype('pdf_end_thread')
56
57 local ITALIC = 1
58 -- 実装予定: non-packed jchar 2
59 local PACKED = 2
60 local KINSOKU = 3
61 local FROM_JFM = 6
62 -- FROM_JFM: 4, 5, 6, 7, 8 →優先度高
63 -- 6 が標準
64 local KANJI_SKIP = 9
65 local XKANJI_SKIP = 10
66 local PROCESSED = 11
67 local IC_PROCESSED = 12
68 local BOXBDD = 15
69 local PROCESSED_BEGIN_FLAG = 32
70
71 local kanji_skip
72 local xkanji_skip
73
74 local attr_curjfnt = luatexbase.attributes['ltj@curjfnt']
75 local attr_icflag = luatexbase.attributes['ltj@icflag']
76 local max_dimen = 1073741823
77
78 local function get_attr_icflag(p)
79    return (has_attr(p, attr_icflag) or 0)%PROCESSED_BEGIN_FLAG
80 end
81
82 -------------------- Helper functions
83
84 local function copy_attr(new, old) 
85   -- 仕様が決まるまで off にしておく
86 end
87
88 -- This function is called only for acquiring `special' characters.
89 local function fast_find_char_class(c,m)
90    return m.size_cache.chars[c] or 0
91 end
92
93 -- 文字クラスの決定
94 local function slow_find_char_class(c, m, oc)
95    local xc = c or oc
96    local cls = ltjf_find_char_class(oc, m)
97    if xc ~= oc and  cls==0 then cls, xc = ltjf_find_char_class(-xc, m) end
98    return cls, xc
99 end
100
101 local zero_glue = node_new(id_glue)
102 spec_zero_glue = node_new(id_glue_spec) -- must be public, since mentioned from other sources
103 local spec_zero_glue = spec_zero_glue
104    spec_zero_glue.width = 0; spec_zero_glue.stretch_order = 0; spec_zero_glue.stretch = 0
105    spec_zero_glue.shrink_order = 0; spec_zero_glue.shrink = 0
106    zero_glue.spec = spec_zero_glue
107
108 local function skip_table_to_spec(n)
109    local g, st = node_new(id_glue_spec), ltjs.fast_get_skip_table(n)
110    g.width = st.width; g.stretch = st.stretch; g.shrink = st.shrink
111    g.stretch_order = st.stretch_order; g.shrink_order = st.shrink_order
112    return g
113 end
114
115
116 -- penalty 値の計算
117 local function add_penalty(p,e)
118    local pp = p.penalty
119    if pp>=10000 then
120       if e<=-10000 then pp = 0 end
121    elseif pp<=-10000 then
122       if e>=10000 then pp = 0 end
123    else
124       pp = pp + e
125       if pp>=10000 then      p.penalty = 10000
126       elseif pp<=-10000 then p.penalty = -10000 
127       else                   p.penalty = pp end
128    end
129    return
130 end
131
132 -- 「異なる JFM」の間の調整方法
133 diffmet_rule = math.two_paverage
134 function math.two_add(a,b) return a+b end
135 function math.two_average(a,b) return (a+b)*0.5 end
136 function math.two_paverage(a,b) return (a+b)*0.5 end
137 function math.two_pleft(a,b) return a end
138 function math.two_pright(a,b) return b end
139
140 local head -- the head of current list
141
142 local Np, Nq, Bp
143 local widow_Bp, widow_Np -- \jcharwidowpenalty 挿入位置管理用
144
145 local ihb_flag -- JFM グルー挿入抑止用 flag
146                -- on: \inhibitglue 指定時,hlist の周囲
147
148 -------------------- hlist 内の文字の検索
149
150 local first_char, last_char, find_first_char
151
152 local function check_box(box_ptr, box_end)
153    local p = box_ptr; local found_visible_node = false
154    if not p then 
155       find_first_char = false; first_char = nil; last_char = nil
156       return true
157    end
158    while p and p~=box_end do
159       local pid = p.id
160       if pid==id_kern and p.subtype==2 then
161          p = node_next(node_next(node_next(p))); pid = p.id -- p must be glyph_node
162        end
163       if pid==id_glyph then
164          repeat 
165             if find_first_char then 
166                first_char = p; find_first_char = false
167             end
168             last_char = p; found_visible_node = true; p=node_next(p)
169             if (not p) or p==box_end then return found_visible_node end
170          until p.id~=id_glyph
171          pid = p.id -- p must be non-nil
172       end
173       if pid==id_kern and get_attr_icflag(p)==IC_PROCESSED then
174          p = node_next(p); 
175       elseif pid==id_hlist then
176          if PACKED == get_attr_icflag(p) then
177             if find_first_char then
178                first_char = p.head; find_first_char = false
179             end
180             last_char = p.head; found_visible_node = true
181          else
182             if p.shift==0 then
183                if check_box(p.head, nil) then found_visible_node = true end
184             else if find_first_char then 
185                   find_first_char = false
186                else 
187                   last_char = nil
188                end
189             end
190          end
191       elseif not (pid==id_ins   or pid==id_mark
192                   or pid==id_adjust or pid==id_whatsit
193                   or pid==id_penalty) then
194          found_visible_node = true
195          if find_first_char then 
196             find_first_char = false
197          else 
198             last_char = nil
199          end
200       end
201       p = node_next(p)
202    end
203    return found_visible_node
204 end 
205
206 function check_box_high(Nx, box_ptr, box_end)
207    first_char = nil;  last_char = nil;  find_first_char = true
208    if check_box(box_ptr, box_end) then
209       if first_char then
210          if first_char.font == (has_attr(first_char, attr_curjfnt) or -1) then 
211             set_np_xspc_jachar(Nx, first_char)
212          else
213             set_np_xspc_alchar(Nx, first_char.char,first_char, ligature_head)
214          end
215       end
216    end
217    return last_char
218 end
219
220 -------------------- Np の計算と情報取得
221
222 luatexbase.create_callback("luatexja.jfmglue.whatsit_getinfo", "data", 
223                            function (Np, lp, Nq) 
224                               if Np.nuc then return Np 
225                               else 
226                                  return Np  -- your code
227                               end
228                            end)
229 luatexbase.create_callback("luatexja.jfmglue.whatsit_after", "data", 
230                            function (stat, Nq, Np) return false end)
231
232 -- calc next Np
233 do
234
235 local function set_attr_icflag_processed(p)
236    if get_attr_icflag(p)<= ITALIC then 
237       set_attr(p, attr_icflag, PROCESSED) 
238    end
239 end
240
241 local function check_next_ickern(lp)
242    if lp.id == id_kern and ITALIC == get_attr_icflag(lp) then
243       set_attr(lp, attr_icflag, IC_PROCESSED)
244       Np.last = lp; return node_next(lp)
245    else 
246       Np.last = Np.nuc; return lp
247    end
248 end
249
250 local function calc_np_pbox(lp, last)
251    Np.first = Np.first or lp; Np.id = id_pbox
252    local lpa = KINSOKU -- dummy=
253    set_attr(lp, attr_icflag, get_attr_icflag(lp));
254    while lp~=last and lpa>=PACKED and lpa<BOXBDD do
255       Np.nuc = lp;
256       lp = node_next(lp); lpa = has_attr(lp, attr_icflag) or 0
257       -- get_attr_icflag() ではいけない!
258    end
259    return check_next_ickern(lp)
260 end
261
262
263 local calc_np_auxtable = {
264    [id_glyph] = function (lp) 
265                    Np.first, Np.nuc = (Np.first or lp), lp;
266                    Np.id = (lp.font == (has_attr(lp, attr_curjfnt) or -1)) and id_jglyph or id_glyph
267                    --set_attr_icflag_processed(lp) treated in ltj-setwidth.lua
268                    return true, check_next_ickern(node_next(lp)); 
269                 end,
270    [id_hlist] = function(lp) 
271                    Np.first = Np.first or lp; Np.last = lp; Np.nuc = lp; 
272                    set_attr_icflag_processed(lp)
273                    Np.id = (lp.shift~=0) and id_box_like or id_hlist
274                    return true, node_next(lp)
275                 end,
276    box_like = function(lp)
277                  Np.first = Np.first or lp; Np.nuc = lp; Np.last = lp;
278                  Np.id = id_box_like; set_attr_icflag_processed(lp); 
279                  return true, node_next(lp);
280               end,
281    skip = function(lp) 
282              set_attr_icflag_processed(lp); return false, node_next(lp)
283           end,
284    [id_whatsit] = function(lp) 
285                   if lp.subtype==sid_user then
286                      if lp.user_id==30111 then
287                         local lq = node_next(lp); 
288                         head = node.remove(head, lp); node.free(lp); ihb_flag = true
289                         return false, lq;
290                      else
291                         set_attr_icflag_processed(lp)
292                         luatexbase.call_callback("luatexja.jfmglue.whatsit_getinfo",
293                                                  Np, lp, Nq)
294                         if Np.nuc then 
295                            Np.id = id_pbox_w; Np.first = Np.nuc; Np.last = Np.nuc; 
296                            return true, node_next(lp)
297                         else
298                            return false, node_next(lp)
299                         end
300                      end
301                   else
302                      -- we do special treatment for these whatsit nodes.
303                      if lp.subtype == sid_start_link or lp.subtype == sid_start_thread then
304                         Np.first = lp 
305                      elseif lp.subtype == sid_end_link or lp.subtype == sid_end_thread then
306                         Np.first, Nq.last = nil, lp;
307                      end
308                      set_attr_icflag_processed(lp); return false, node_next(lp)
309                   end
310                   end,
311    [id_math] = function(lp)
312                   Np.first, Np.nuc = (Np.first or lp), lp; 
313                   set_attr_icflag_processed(lp); lp  = node_next(lp) 
314                   while lp.id~=id_math do 
315                      set_attr_icflag_processed(lp); lp  = node_next(lp) 
316                   end
317                   set_attr_icflag_processed(lp); 
318                   Np.last, Np.id = lp, id_math;
319                   return true, node_next(lp); 
320                end,
321    discglue = function(lp)
322                  Np.first, Np.nuc, Np.last = (Np.first or lp), lp, lp; 
323                  Np.id = lp.id; set_attr_icflag_processed(lp); return true, node_next(lp)
324                end,
325    [id_kern] = function(lp) 
326                   Np.first = Np.first or lp
327                   if lp.subtype==2 then
328                      set_attr_icflag_processed(lp); lp = node_next(lp)
329                      set_attr_icflag_processed(lp); lp = node_next(lp)
330                      set_attr_icflag_processed(lp); lp = node_next(lp)
331                      set_attr_icflag_processed(lp); Np.nuc = lp
332                      Np.id = (lp.font == (has_attr(lp, attr_curjfnt) or -1)) and id_jglyph or id_glyph
333                      return true, check_next_ickern(node_next(lp)); 
334                   else
335                      Np.id = id_kern; set_attr_icflag_processed(lp);
336                      Np.last = lp; return true, node_next(lp)
337                   end
338                end,
339    [id_penalty] = function(lp)
340                      Bp[#Bp+1] = lp; set_attr_icflag_processed(lp); 
341                      return false, node_next(lp)
342                   end,
343 }
344 calc_np_auxtable[id_vlist]  = calc_np_auxtable.box_like
345 calc_np_auxtable[id_rule]   = calc_np_auxtable.box_like
346 calc_np_auxtable[13]        = calc_np_auxtable.box_like
347 calc_np_auxtable[id_ins]    = calc_np_auxtable.skip
348 calc_np_auxtable[id_mark]   = calc_np_auxtable.skip
349 calc_np_auxtable[id_adjust] = calc_np_auxtable.skip
350 calc_np_auxtable[id_disc]   = calc_np_auxtable.discglue
351 calc_np_auxtable[id_glue]   = calc_np_auxtable.discglue
352
353 local pairs = pairs
354 function calc_np(lp, last)
355    local k
356    -- We assume lp = node_next(Np.last)
357    Np, Nq, ihb_flag = Nq, Np, false
358    -- We clear `predefined' entries of Np before pairs() loop,
359    -- because using only pairs() loop is slower.
360    Np.post, Np.pre, Np.xspc = nil, nil, nil
361    Np.first, Np.id, Np.last, Np.met = nil, nil, nil
362    Np.auto_kspc, Np.auto_xspc, Np.char, Np.class, Np.nuc = nil, nil, nil, nil, nil
363    for k in pairs(Np) do Np[k] = nil end
364
365    for k = 1,#Bp do Bp[k] = nil end
366    while lp ~= last do
367       local lpa = has_attr(lp, attr_icflag) or 0
368       -- unbox 由来ノードの検出
369       if lpa>=PACKED then
370          if lpa == BOXBDD then
371             local lq = node_next(lp) 
372             head = node.remove(head, lp); node.free(lp); lp = lq
373          else return calc_np_pbox(lp, last)
374          end -- id_pbox
375       else
376          k, lp = calc_np_auxtable[lp.id](lp)
377          if k then return lp end
378       end
379    end
380    Np = nil; return lp
381 end
382
383 end
384 local calc_np = calc_np
385
386 -- extract informations from Np
387 -- We think that "Np is a Japanese character" if Np.met~=nil,
388 --            "Np is an alphabetic character" if Np.pre~=nil,
389 --            "Np is not a character" otherwise.
390 do
391
392 -- 和文文字のデータを取得
393    local attr_jchar_class = luatexbase.attributes['ltj@charclass']
394    local attr_orig_char = luatexbase.attributes['ltj@origchar']
395    local attr_autospc = luatexbase.attributes['ltj@autospc']
396    local attr_autoxspc = luatexbase.attributes['ltj@autoxspc']
397    function set_np_xspc_jachar(Nx, x)
398       local m = ltjf_font_metric_table[x.font]
399       local cls, c = slow_find_char_class(has_attr(x, attr_orig_char), m, x.char)
400       Nx.class = cls; set_attr(x, attr_jchar_class, cls)
401       Nx.met, Nx.char = m, c
402       local t = ltjs.fast_get_penalty_table_parent(c)
403       Nx.pre = t.pre or 0
404       Nx.post = t.post or 0
405       Nx.xspc = t.xsp or 3
406       Nx.kcat = t.kcat or 0
407       Nx.auto_kspc, Nx.auto_xspc = (has_attr(x, attr_autospc)==1), (has_attr(x, attr_autoxspc)==1)
408    end 
409    local set_np_xspc_jachar = set_np_xspc_jachar
410
411 -- 欧文文字のデータを取得
412    local floor = math.floor
413    function set_np_xspc_alchar(Nx, c,x, lig)
414       if c~=-1 then
415          local xc, xs = x.components, x.subtype
416          if lig == ligature_head then
417             while xc and xs and xs%4>=2 do
418                x = xc; xc, xs = x.components, x.subtype
419             end
420             c = x.char
421          else
422             while xc and xs and xs%4>=2 do
423                x = node.tail(xc); xc, xs = x.components, x.subtype
424             end
425             c = x.char
426          end
427          local t = ltjs.fast_get_penalty_table_parent(c) 
428          Nx.pre = t.pre or 0
429          Nx.post = t.post or 0
430          Nx.xspc = t.xsp or 3
431          Nx.char = 'jcharbdd'
432       else
433          Nx.pre, Nx.post, Nx.char = 0, 0, -1
434          Nx.xspc = ltjs_fast_get_penalty_table('xsp', -1) or 3
435       end
436       Nx.met = nil
437       Nx.auto_xspc = (has_attr(x, attr_autoxspc)==1)
438    end
439    local set_np_xspc_alchar = set_np_xspc_alchar
440
441 -- Np の情報取得メインルーチン
442    function extract_np()
443       local x, i = Np.nuc, Np.id;
444       if i ==  id_jglyph then return set_np_xspc_jachar(Np, x)
445       elseif i == id_glyph then return set_np_xspc_alchar(Np, x.char, x, ligature_head)
446       elseif i == id_hlist then Np.last_char = check_box_high(Np, x.head, nil)
447       elseif i == id_pbox then Np.last_char = check_box_high(Np, Np.first, node_next(Np.last))
448       elseif i == id_disc then Np.last_char = check_box_high(Np, x.replace, nil)
449       elseif i == id_math then return set_np_xspc_alchar(Np, -1, x)
450       end
451    end
452    
453    -- change the information for the next loop
454    -- (will be done if Nx is an alphabetic character or a hlist)
455    function after_hlist(Nx)
456       local s = Nx.last_char
457       if s then
458          if s.font == (has_attr(s, attr_curjfnt) or -1) then 
459             set_np_xspc_jachar(Nx, s)
460          else
461             set_np_xspc_alchar(Nx, s.char, s, ligature_tail)
462          end
463       else
464          Nx.pre, Nx.met = nil, nil
465       end
466    end
467    
468    function after_alchar(Nx)
469       local x = Nx.nuc
470       return set_np_xspc_alchar(Nx, x.char,x, ligature_tail)
471    end
472
473 end
474 local after_hlist, after_alchar, extract_np = after_hlist, after_alchar, extract_np
475
476 -------------------- 最下層の処理
477
478 -- change penalties (or create a new penalty, if needed)
479 local function handle_penalty_normal(post, pre, g)
480    local a = (pre or 0) + (post or 0)
481    if #Bp == 0 then
482       if (a~=0 and not(g and g.id==id_kern)) then
483          local p = node_new(id_penalty); --copy_attr(p, Nq.nuc)
484          if a<-10000 then a = -10000 elseif a>10000 then a = 10000 end
485          p.penalty = a
486          head = insert_before(head, Np.first, p)
487          Bp[1]=p; 
488          set_attr(p, attr_icflag, KINSOKU)
489       end
490    else for _, v in pairs(Bp) do add_penalty(v,a) end
491    end
492 end
493
494 local function handle_penalty_always(post, pre, g)
495    local a = (pre or 0) + (post or 0)
496    if #Bp == 0 then
497       if not (g and g.id==id_glue) then
498          local p = node_new(id_penalty); --copy_attr(p, Nq.nuc)
499          if a<-10000 then a = -10000 elseif a>10000 then a = 10000 end
500          p.penalty = a
501          head = insert_before(head, Np.first, p)
502          Bp[1]=p
503          set_attr(p, attr_icflag, KINSOKU)
504       end
505    else for _, v in pairs(Bp) do add_penalty(v,a) end
506    end
507 end
508
509 local function handle_penalty_suppress(post, pre, g)
510    local a = (pre or 0) + (post or 0)
511    if #Bp == 0 then
512       if g and g.id==id_glue then
513          local p = node_new(id_penalty); --copy_attr(p, Nq.nuc)
514          p.penalty = 10000; head = insert_before(head, Np.first, p)
515          Bp[1]=p
516          set_attr(p, attr_icflag, KINSOKU)
517       end
518    else for _, v in pairs(Bp) do add_penalty(v,a) end
519    end
520 end
521
522 -- 和文文字間の JFM glue を node 化
523 local function new_jfm_glue(m, bc, ac)
524 -- bc, ac: char classes
525    local z = m.size_cache.char_type[bc]
526    local g, d = z.glue[ac], 0 
527    if g then
528       g,d = node_copy(g[1]), g[2]; 
529       g.spec = node.copy(g.spec); -- node_copy は spec を copy しない(参照を増やすのみ)
530    else
531       local k = z.kern[ac]
532       if k then
533          g = node_new(id_kern); --copy_attr(g, Nn.nuc)
534          g.subtype = 1; g.kern, d = k[1], k[2]
535          set_attr(g, attr_icflag, FROM_JFM);
536       end
537    end
538    return g, d
539 end
540
541 -- Nq.last (kern w) .... (glue/kern g) Np.first
542 local function real_insert(w, g)
543    if g then
544       head  = insert_before(head, Np.first, g)
545       Np.first = g
546    end
547 end
548
549
550 -------------------- 和文文字間空白量の決定
551
552 -- get kanjiskip
553 local get_kanjiskip
554
555 local function get_kanjiskip_normal()
556    if Np.auto_kspc or Nq.auto_kspc then
557       return node_copy(kanji_skip)
558    else
559       local g = node_copy(zero_glue)
560       set_attr(g, attr_icflag, KANJI_SKIP)
561       return g
562    end
563 end
564 local function get_kanjiskip_jfm()
565    local g
566    if Np.auto_kspc or Nq.auto_kspc then
567       g = node_new(id_glue); --copy_attr(g, Nq.nuc)
568       local gx = node_new(id_glue_spec);
569       gx.stretch_order, gx.shrink_order = 0, 0
570       local pm, qm = Np.met, Nq.met
571       local bk = qm.size_cache.kanjiskip or {0, 0, 0}
572       if (pm.size_cache==qm.size_cache) and (qm.var==pm.var) then
573          gx.width = bk[1]; gx.stretch = bk[2]; gx.shrink = bk[3]
574       else
575          local ak = pm.size_cache.kanjiskip or {0, 0, 0}
576          gx.width = round(diffmet_rule(bk[1], ak[1]))
577          gx.stretch = round(diffmet_rule(bk[2], ak[2]))
578          gx.shrink = -round(diffmet_rule(-bk[3], -ak[3]))
579       end
580       g.spec = gx
581    else
582       g =  node_copy(zero_glue)
583    end
584    set_attr(g, attr_icflag, KANJI_SKIP)
585    return g
586 end
587
588 local function calc_ja_ja_aux(gb,ga, db, da)
589    local rbb, rab = (1-db)/2, (1-da)/2 -- 「前の文字」由来のグルーの割合
590    local rba, raa = (1+db)/2, (1+da)/2 -- 「前の文字」由来のグルーの割合
591    if diffmet_rule ~= math.two_pleft and diffmet_rule ~= math.two_pright 
592       and diffmet_rule ~= math.two_paverage then
593       rbb, rab, rba, raa = 1,0,0,1
594    end
595    if not gb then 
596       if ga then gb = node_new(id_kern); gb.kern = 0 else return nil end
597    elseif not ga then 
598       ga = node_new(id_kern); ga.kern = 0
599    end
600
601    local k = node.type(gb.id) .. node.type(ga.id)
602    if k == 'glueglue' then 
603       -- 両方とも glue.
604       gb.spec.width   = round(diffmet_rule(
605                                  rbb*gb.spec.width + rba*ga.spec.width,
606                                  rab*gb.spec.width + raa*ga.spec.width ))
607       gb.spec.stretch = round(diffmet_rule(
608                                  rbb*gb.spec.stretch + rba*ga.spec.stretch,
609                                  rab*gb.spec.stretch + raa*ga.spec.stretch ))
610       gb.spec.shrink  = -round(diffmet_rule(
611                                   -rbb*gb.spec.shrink - rba*ga.spec.shrink,
612                                   -rab*gb.spec.shrink - raa*ga.spec.shrink ))
613       node.free(ga)
614       return gb
615    elseif k == 'kernkern' then
616       -- 両方とも kern.
617       gb.kern   = round(diffmet_rule(
618                                  rbb*gb.kern + rba*ga.kern,
619                                  rab*gb.kern + raa*ga.kern ))
620       node.free(ga)
621       return gb
622    elseif k == 'kernglue' then 
623       -- gb: kern, ga: glue
624       ga.spec.width   = round(diffmet_rule(
625                                  rbb*gb.kern + rba*ga.spec.width,
626                                  rab*gb.kern + raa*ga.spec.width ))
627       ga.spec.stretch = round(diffmet_rule(
628                                  rba*ga.spec.stretch, raa*ga.spec.stretch ))
629       ga.spec.shrink  = -round(diffmet_rule(
630                                   -rba*ga.spec.shrink,-raa*ga.spec.shrink ))
631       node.free(gb)
632       return ga
633    else
634       -- gb: glue, ga: kern
635       gb.spec.width   = round(diffmet_rule(
636                                  rba*ga.kern + rbb*gb.spec.width,
637                                  raa*ga.kern + rab*gb.spec.width ))
638       gb.spec.stretch = round(diffmet_rule(
639                                  rbb*gb.spec.stretch, rab*gb.spec.stretch ))
640       gb.spec.shrink  = -round(diffmet_rule(
641                                   -rbb*gb.spec.shrink,-rab*gb.spec.shrink ))
642       node.free(ga)
643       return gb
644    end
645 end
646
647 local function calc_ja_ja_glue()
648    if  ihb_flag then return nil
649    else
650       local qm, pm = Nq.met, Np.met
651       if (qm.size_cache==pm.size_cache) and (qm.var==pm.var) then
652          return new_jfm_glue(qm, Nq.class, Np.class)
653       else
654          local npn, nqn = Np.nuc, Nq.nuc
655          local gb, db = new_jfm_glue(qm, Nq.class,
656                                slow_find_char_class(has_attr(npn, attr_orig_char), qm, npn.char))
657          local ga, da = new_jfm_glue(pm, 
658                                slow_find_char_class(has_attr(nqn, attr_orig_char), pm, nqn.char),
659                                Np.class)
660          return calc_ja_ja_aux(gb, ga, db, da); 
661       end
662    end
663 end
664
665 -------------------- 和欧文間空白量の決定
666
667 -- get xkanjiskip
668 local get_xkanjiskip
669 local function get_xkanjiskip_normal(Nn)
670    if (Nq.xspc>=2) and (Np.xspc%2==1) and (Nq.auto_xspc or Np.auto_xspc) then
671       return node_copy(xkanji_skip)
672    else
673       local g = node_copy(zero_glue)
674       set_attr(g, attr_icflag, XKANJI_SKIP)
675       return g
676    end
677 end
678 local function get_xkanjiskip_jfm(Nn)
679    local g
680    if (Nq.xspc>=2) and (Np.xspc%2==1) and (Nq.auto_xspc or Np.auto_xspc) then
681       g =  node_new(id_glue); --copy_attr(g, Nn.nuc)
682       local gx = node_new(id_glue_spec);
683       gx.stretch_order, gx.shrink_order = 0, 0
684       local bk = Nn.met.size_cache.xkanjiskip or {0, 0, 0}
685       gx.width = bk[1]; gx.stretch = bk[2]; gx.shrink = bk[3]
686       g.spec = gx
687    else
688       g = node_copy(zero_glue)
689    end
690    set_attr(g, attr_icflag, XKANJI_SKIP)
691    return g
692 end
693
694
695
696 -------------------- 隣接した「塊」間の処理
697
698 local function get_OA_skip()
699    if not ihb_flag then
700       local pm = Np.met
701       return new_jfm_glue(pm, 
702         fast_find_char_class(((Nq.id == id_math and -1) or 'jcharbdd'), pm), Np.class)
703    else return nil
704    end
705 end
706 local function get_OB_skip()
707    if not ihb_flag then
708       local qm = Nq.met
709       return new_jfm_glue(qm, Nq.class, 
710         fast_find_char_class(((Np.id == id_math and -1) or'jcharbdd'), qm))
711    else return nil
712    end
713 end
714
715 -- (anything) .. jachar
716 local function handle_np_jachar(mode)
717    local qid = Nq.id
718    if qid==id_jglyph or ((qid==id_pbox or qid==id_pbox_w) and Nq.met) then 
719       local g = calc_ja_ja_glue() or get_kanjiskip() -- M->K
720       handle_penalty_normal(Nq.post, Np.pre, g); real_insert(0, g)
721    elseif Nq.met then  -- qid==id_hlist
722       local g = get_OA_skip() or get_kanjiskip() -- O_A->K
723       handle_penalty_normal(0, Np.pre, g); real_insert(0, g)
724    elseif Nq.pre then 
725       local g = get_OA_skip() or get_xkanjiskip(Np) -- O_A->X
726       if qid==id_hlist then Nq.post = 0 end
727       handle_penalty_normal(Nq.post, Np.pre, g); real_insert(0, g)
728    else
729       local g = get_OA_skip() -- O_A
730       if qid==id_glue then handle_penalty_normal(0, Np.pre, g)
731       elseif qid==id_kern then handle_penalty_suppress(0, Np.pre, g)
732       else handle_penalty_always(0, Np.pre, g)
733       end
734       real_insert(0, g)
735    end
736    if mode and Np.kcat%2~=1 then
737       widow_Np.first, widow_Bp, Bp = Np.first, Bp, widow_Bp
738    end
739 end
740
741
742 -- jachar .. (anything)
743 local function handle_nq_jachar()
744     if Np.pre then 
745       if Np.id==id_hlist then Np.pre = 0 end
746       local g = get_OB_skip() or get_xkanjiskip(Nq) -- O_B->X
747       handle_penalty_normal(Nq.post, Np.pre, g); real_insert(0, g)
748    else
749       local g = get_OB_skip() -- O_B
750       if Np.id==id_glue then handle_penalty_normal(Nq.post, 0, g)
751       elseif Np.id==id_kern then handle_penalty_suppress(Nq.post, 0, g)
752       else handle_penalty_always(Nq.post, 0, g)
753       end
754       real_insert(0, g)
755    end
756 end
757
758 -- (anything) .. (和文文字で始まる hlist)
759 local function handle_np_ja_hlist()
760    local qid = Nq.id
761    if qid==id_jglyph or ((qid==id_pbox or Nq.id == id_pbox_w) and Nq.met) then 
762       local g = get_OB_skip() or get_kanjiskip() -- O_B->K
763       handle_penalty_normal(Nq.post, 0, g); real_insert(0, g)
764    elseif Nq.met then  -- Nq.id==id_hlist
765       local g = get_kanjiskip() -- K
766       handle_penalty_suppress(0, 0, g); real_insert(0, g)
767    elseif Nq.pre then 
768       local g = get_xkanjiskip(Np) -- X
769       handle_penalty_suppress(0, 0, g); real_insert(0, g)
770    end
771 end
772
773 -- (和文文字で終わる hlist) .. (anything)
774 local function handle_nq_ja_hlist()
775    if Np.pre then 
776       local g = get_xkanjiskip(Nq) -- X
777       handle_penalty_suppress(0, 0, g); real_insert(0, g)
778    end
779 end
780
781 -- Nq が前側のクラスタとなることによる修正
782 local function adjust_nq()
783    if Nq.id==id_glyph then after_alchar(Nq)
784    elseif Nq.id==id_hlist or Nq.id==id_pbox or Nq.id==id_disc then after_hlist(Nq)
785    elseif Nq.id == id_pbox_w then 
786       luatexbase.call_callback("luatexja.jfmglue.whatsit_after",
787                                false, Nq, Np)
788    end
789 end
790
791 -------------------- 開始・終了時の処理
792
793 -- リスト末尾の処理
794 local function handle_list_tail(mode)
795    adjust_nq(); Np = Nq
796    if mode then
797       -- the current list is to be line-breaked.
798       -- Insert \jcharwidowpenalty
799       Bp = widow_Bp; Np = widow_Np
800       if Np.first then
801          handle_penalty_normal(0,
802                                ltjs_fast_get_penalty_table('jwp', 0) or 0)
803       end
804    else
805       -- the current list is the contents of a hbox
806       local npi, pm = Np.id, Np.met
807       if npi == id_jglyph or (npi==id_pbox and pm) then 
808          local g = new_jfm_glue(pm, Np.class, fast_find_char_class('boxbdd', pm))
809          if g then
810             set_attr(g, attr_icflag, BOXBDD)
811             head = node.insert_after(head, Np.last, g)
812          end
813       end
814    end
815 end
816
817 -- リスト先頭の処理
818 local function handle_list_head(par_indented)
819    local npi, pm = Np.id, Np.met
820    if npi ==  id_jglyph or (npi==id_pbox and pm) then 
821       if not ihb_flag then
822          local g = new_jfm_glue(pm, fast_find_char_class(par_indented, pm), Np.class)
823          if g then
824             set_attr(g, attr_icflag, BOXBDD)
825             if g.id==id_glue and #Bp==0 then
826                local h = node_new(id_penalty); --copy_attr(h, Np.nuc)
827                h.penalty = 10000; set_attr(h, attr_icflag, BOXBDD)
828             end
829             head = insert_before(head, Np.first, g)
830          end
831       end
832    end
833 end
834
835 -- initialize
836 -- return value: (the initial cursor lp), (last node)
837 local function init_var(mode)
838    Bp, widow_Bp, widow_Np = {}, {}, {first = nil}
839    
840    kanji_skip = node_new(id_glue)
841    kanji_skip.spec = skip_table_to_spec('kanjiskip')
842    set_attr(kanji_skip, attr_icflag, KANJI_SKIP)
843    get_kanjiskip = (kanji_skip.spec.width == max_dimen)
844       and get_kanjiskip_jfm or get_kanjiskip_normal
845
846    xkanji_skip = node_new(id_glue)
847    xkanji_skip.spec = skip_table_to_spec('xkanjiskip')
848    set_attr(xkanji_skip, attr_icflag, XKANJI_SKIP)
849    get_xkanjiskip = (xkanji_skip.spec.width == max_dimen) 
850       and get_xkanjiskip_jfm or get_xkanjiskip_normal
851
852    Np = {
853       auto_kspc=nil, auto_xspc=nil, char=nil, class=nil, 
854       first=nil, id=nil, last=nil, met=nil, nuc=nil, 
855       post=nil, pre=nil, xspc=nil, 
856    }
857    Nq = {
858       auto_kspc=nil, auto_xspc=nil, char=nil, class=nil, 
859       first=nil, id=nil, last=nil, met=nil, nuc=nil, 
860       post=nil, pre=nil, xspc=nil, 
861    }
862    if mode then 
863       -- the current list is to be line-breaked:
864       -- hbox from \parindent is skipped.
865       local lp, par_indented, lpi, lps  = head, 'boxbdd', head.id, head.subtype
866       while lp and ((lpi==id_whatsit and lps~=sid_user) 
867                  or ((lpi==id_hlist) and (lps==3))) do
868          if (lpi==id_hlist) and (lps==3) then par_indented = 'parbdd' end
869          lp=node_next(lp); lpi, lps = lp.id, lp.subtype end
870      return lp, node.tail(head), par_indented
871    else 
872       -- the current list is the contents of a hbox:
873       -- insert a sentinel
874       local g = node_new(id_kern)
875       node.insert_after(head, node.tail(head), g); last = g
876       return head, g, 'boxbdd'
877    end
878 end
879
880 local function cleanup(mode, last)
881    -- adjust attr_icflag for avoiding error
882    tex.setattribute('global', attr_icflag, 0)
883    node.free(kanji_skip); node.free(xkanji_skip)
884    if mode then
885       local h = node_next(head)
886       if h.id == id_penalty and h.penalty == 10000 then
887          h = h.next
888          if h.id == id_glue and h.subtype == 15 and not h.next then
889             return false
890          end
891       end
892       return head
893    else
894       head = node.remove(head, last); node.free(last);-- remove the sentinel
895       set_attr(head, attr_icflag, 
896                get_attr_icflag(head) + PROCESSED_BEGIN_FLAG);
897       return head
898    end
899 end
900 -------------------- 外部から呼ばれる関数
901
902 -- main interface
903 function main(ahead, mode)
904    if not ahead then return ahead end
905    head = ahead;
906    local lp, last, par_indented = init_var(mode); 
907    lp = calc_np(lp, last)
908    if Np then 
909       extract_np(); handle_list_head(par_indented)
910    else
911       return cleanup(mode, last)
912    end
913    lp = calc_np(lp, last)
914    while Np do
915       extract_np(); adjust_nq()
916       local pid, pm = Np.id, Np.met
917       -- 挿入部
918       if pid == id_jglyph then 
919          handle_np_jachar(mode)
920       elseif pm then 
921          if pid==id_hlist then handle_np_ja_hlist()
922          else handle_np_jachar() end
923       elseif Nq.met then 
924          if Nq.id==id_hlist then handle_nq_ja_hlist()
925          else handle_nq_jachar() end
926       end
927       lp = calc_np(lp, last)
928    end
929    handle_list_tail(mode)
930    return cleanup(mode, last)
931 end
932
933 -- \inhibitglue
934
935 function create_inhibitglue_node()
936    local tn = node_new(id_whatsit, sid_user)
937    tn.user_id=30111; tn.type=100; tn.value=1
938    node.write(tn)
939 end
940
941 -- Node for indicating beginning of a paragraph
942 -- (for ltjsclasses)
943 function create_beginpar_node()
944    local tn = node_new(id_whatsit, sid_user)
945    tn.user_id=30114; tn.type=100; tn.value=1
946    node.write(tn)
947 end
948
949 do
950
951 local function whatsit_callback(Np, lp, Nq)
952    if Np and Np.nuc then return Np 
953    elseif Np and lp.user_id == 30114 then
954       Np.first = lp; Np.nuc = lp; Np.last = lp
955       Np.char = 'parbdd'
956       Np.met = nil
957       Np.pre = 0; Np.post = 0
958       Np.xspc = 0
959       Np.auto_xspc = false
960       return Np
961    end
962 end
963
964
965 local function whatsit_after_callback(s, Nq, Np)
966    if not s and Nq.nuc.user_id == 30114 then
967       local x, y = node.prev(Nq.nuc), Nq.nuc
968       Nq.first, Nq.nuc, Nq.last = x, x, x
969       head = node.remove(head, y)
970    end
971    return s
972 end
973
974 luatexbase.add_to_callback("luatexja.jfmglue.whatsit_getinfo", whatsit_callback,
975                            "luatexja.beginpar.np_info", 1)
976 luatexbase.add_to_callback("luatexja.jfmglue.whatsit_after", whatsit_after_callback,
977                            "luatexja.beginpar.np_info_after", 1)
978
979 end