OSDN Git Service

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