OSDN Git Service

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