OSDN Git Service

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