OSDN Git Service

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