OSDN Git Service

use node.direct.{g/s}etdata
[luatex-ja/luatexja.git] / src / ltj-adjust.lua
1 --
2 -- ltj-adjust.lua
3 --
4 luatexja.load_module 'base';      local ltjb = luatexja.base
5 luatexja.load_module 'jfont';     local ltjf = luatexja.jfont
6 luatexja.load_module 'jfmglue';   local ltjj = luatexja.jfmglue
7 luatexja.load_module 'stack';     local ltjs = luatexja.stack
8 luatexja.load_module 'direction'; local ltjd = luatexja.direction
9 luatexja.load_module 'lineskip';  local ltjl = luatexja.lineskip
10 luatexja.adjust = luatexja.adjust or {}
11
12 local to_node = node.direct.tonode
13 local to_direct = node.direct.todirect
14
15 local getfield = node.direct.getfield
16 local getlist = node.direct.getlist
17 local getid = node.direct.getid
18 local getfont = node.direct.getfont
19 local getsubtype = node.direct.getsubtype
20 local getlang = node.direct.getlang
21 local getkern = node.direct.getkern
22 local getshift = node.direct.getshift
23 local getwidth = node.direct.getwidth
24 local getdepth = node.direct.getdepth
25 local setfield = node.direct.setfield
26 local setpenalty = node.direct.setpenalty
27 local setglue = luatexja.setglue
28 local setkern = node.direct.setkern
29 local setlist = node.direct.setlist
30
31 local node_traverse_id = node.direct.traverse_id
32 local node_new = node.direct.new
33 local node_next = node.direct.getnext
34 local node_free = node.direct.flush_node or node.direct.free
35 local node_prev = node.direct.getprev
36 local node_tail = node.direct.tail
37 local get_attr = node.direct.get_attribute
38 local set_attr = node.direct.set_attribute
39 local insert_after = node.direct.insert_after
40
41 local id_glyph   = node.id 'glyph'
42 local id_kern    = node.id 'kern'
43 local id_hlist   = node.id 'hlist'
44 local id_glue    = node.id 'glue'
45 local id_whatsit = node.id 'whatsit'
46 local id_penalty = node.id 'penalty'
47 local attr_icflag = luatexbase.attributes['ltj@icflag']
48 local attr_jchar_class = luatexbase.attributes['ltj@charclass']
49 local lang_ja = luatexja.lang_ja
50
51 local ltjf_font_metric_table = ltjf.font_metric_table
52 local ipairs, pairs = ipairs, pairs
53
54 local PACKED       = luatexja.icflag_table.PACKED
55 local LINEEND      = luatexja.icflag_table.LINEEND
56 local FROM_JFM     = luatexja.icflag_table.FROM_JFM
57 local KANJI_SKIP   = luatexja.icflag_table.KANJI_SKIP
58 local KANJI_SKIP_JFM = luatexja.icflag_table.KANJI_SKIP_JFM
59 local XKANJI_SKIP  = luatexja.icflag_table.XKANJI_SKIP
60 local XKANJI_SKIP_JFM  = luatexja.icflag_table.XKANJI_SKIP_JFM
61
62 local get_attr_icflag
63 do
64    local PROCESSED_BEGIN_FLAG = luatexja.icflag_table.PROCESSED_BEGIN_FLAG
65    get_attr_icflag = function(p)
66       return (get_attr(p, attr_icflag) or 0) % PROCESSED_BEGIN_FLAG
67    end
68 end
69
70 local priority_num = { 0, 0 }
71 local at2pr = { {}, {} }
72 local at2pr_st, at2pr_sh = at2pr[1], at2pr[2]
73 do
74    local priority_table = {{},{}}
75    luatexja.adjust.priority_table = priority_table
76    local tmp = {}
77    local function cmp(a,b) return a[1]>b[1] end -- 大きいほうが先!
78    local function make_priority_table(glue_sign)
79       for i,_ in pairs(tmp) do tmp[i]=nil end
80       if glue_sign==2 then -- shrink
81          for i=0,63 do tmp[#tmp+1] = { (i%8)-4, FROM_JFM+i } end
82       else -- stretch
83          for i=0,63 do tmp[#tmp+1] = { math.floor(i/8)-4, FROM_JFM+i } end
84       end
85       local pt = priority_table[glue_sign]
86       tmp[#tmp+1] = { pt[2]/10, XKANJI_SKIP }
87       tmp[#tmp+1] = { pt[2]/10, XKANJI_SKIP_JFM }
88       tmp[#tmp+1] = { pt[1]/10, KANJI_SKIP }
89       tmp[#tmp+1] = { pt[1]/10, KANJI_SKIP_JFM }
90       tmp[#tmp+1] = { pt[3]/10, -1 }
91       table.sort(tmp, cmp)
92       local a, m, n = at2pr[glue_sign], 10000000, 0
93       for i=1,#tmp do
94          if tmp[i][1]<m then n,m = n+1,tmp[i][1] end
95          a[tmp[i][2]] = n
96       end
97       local o = a[-1]
98       priority_num[glue_sign] = n
99       setmetatable(a, {__index = function () return o end })
100    end
101    luatexja.adjust.make_priority_table = make_priority_table
102 end
103
104 -- box 内で伸縮された glue の合計値を計算
105
106 local total_stsh = {{},{}}
107 local total_st, total_sh = total_stsh[1], total_stsh[2]
108 local get_total_stretched
109 do
110 local dimensions = node.direct.dimensions
111 function get_total_stretched(p)
112 -- return value: <補正値(sp)>
113    local ph = getlist(p)
114    if not ph then return 0 end
115    for i,_ in pairs(total_st) do total_st[i]=nil; total_sh[i]=nil end
116    for i=1,priority_num[1] do total_st[i]=0 end
117    for i=1,priority_num[2] do total_sh[i]=0 end
118    for i=0,4 do total_st[i*65536]=0; total_sh[i*65536]=0 end
119    for q in node_traverse_id(id_glue, ph) do
120       local a = getfield(q, 'stretch_order')
121       if a==0 then
122          local b = at2pr_st[get_attr_icflag(q)];
123          total_st[b] = total_st[b]+getfield(q, 'stretch')
124       end
125       total_st[a*65536] = total_st[a]+getfield(q, 'stretch')
126       local a = getfield(q, 'shrink_order')
127       if a==0 then
128          local b = at2pr_sh[get_attr_icflag(q)];
129          total_sh[b] = total_sh[b]+getfield(q, 'shrink')
130       end
131       total_sh[a*65536] = total_sh[a]+getfield(q, 'shrink')
132    end
133    for i=4,1,-1 do if total_st[i*65536]~=0 then total_st.order=i; break end; end
134    if not total_st.order then
135        total_st.order, total_st[-65536] = -1,0.1 -- dummy
136    end
137    for i=4,1,-1 do if total_sh[i*65536]~=0 then total_sh.order=i; break end; end
138    if not total_sh.order then
139        total_sh.order, total_sh[-65536] = -1,0.1 -- dummy
140    end
141    return getwidth(p) - dimensions(ph)
142 end
143 end
144
145 -- step 1: 行末に kern を挿入(句読点,中点用)
146 local abs = math.abs
147 local ltjd_glyph_from_packed = ltjd.glyph_from_packed
148 local function aw_step1(p, total)
149    local head = getlist(p)
150    local x = node_tail(head); if not x then return total, false end
151    -- x: \rightskip
152    x = node_prev(x); if not x then return total, false end
153    local xi, xc = getid(x)
154    -- x may be penalty
155    while xi==id_penalty do
156       x = node_prev(x); if not x then return total, false end
157       xi = getid(x)
158    end
159    if (total>0 and total_st.order>0) or (total<0 and total_sh.order>0) then
160        -- 無限大のグルーで処理が行われているときは処理中止.
161        return total, false
162    end
163    if xi == id_glyph and getlang(x)==lang_ja then
164       -- 和文文字
165       xc = x
166    elseif xi == id_hlist and get_attr_icflag(x) == PACKED then
167       -- packed JAchar
168       xc = ltjd_glyph_from_packed(x)
169       while getid(xc) == id_whatsit do xc = node_next(xc) end -- これはなんのために?
170    else
171       return total, false-- それ以外は対象外.
172    end
173    local eadt = ltjf_font_metric_table[getfont(xc)]
174       .char_type[get_attr(xc, attr_jchar_class) or 0].end_adjust
175    if not eadt then
176       return total, false
177    end
178    local eadt_ratio = {}
179    for i, v in ipairs(eadt) do
180       local t = total - v
181       if t>0 then
182          eadt_ratio[i] = {i, t/total_st[65536*total_st.order], t, v}
183       else
184          eadt_ratio[i] = {i, t/total_sh[65536*total_sh.order], t, v}
185       end
186    end
187    table.sort(eadt_ratio,
188    function (a,b)
189        for i=2,4 do
190            local at, bt = abs(a[i]), abs(b[i])
191            if at~=bt then return at<bt end
192        end
193        return a[4]<b[4]
194    end)
195    if eadt[eadt_ratio[1][1]]~=0 then
196       local kn = node_new(id_kern, 1)
197       setkern(kn, eadt[eadt_ratio[1][1]]); set_attr(kn, attr_icflag, LINEEND)
198       insert_after(head, x, kn)
199       return eadt_ratio[1][3], true
200    else
201       return total, false
202    end
203 end
204
205 -- step 1 最終行用
206 local min, max = math.min, math.max
207 local setsubtype = node.direct.setsubtype
208 local function aw_step1_last(p, total)
209    local head = getlist(p)
210    local x = node_tail(head); if not x then return total, false end
211    -- x: \rightskip
212    local pf = node_prev(x); if not x then return total, false end
213    if getid(pf) ~= id_glue or getsubtype(pf) ~= 15 then return total, false end
214    x = node_prev(node_prev(pf))
215    local xi, xc = getid(x)
216    if xi == id_glyph and getlang(x)==lang_ja then
217       -- 和文文字
218       xc = x
219    elseif xi == id_hlist and get_attr_icflag(x) == PACKED then
220       -- packed JAchar
221       xc = ltjd_glyph_from_packed(x)
222       while getid(xc) == id_whatsit do xc = node_next(xc) end -- これはなんのために?
223    else
224       return total, false-- それ以外は対象外.
225    end
226    -- 続行条件1:無限の伸縮度を持つグルーは \parfillskipのみ
227    if total>0 and total_st.order>0 then
228       if total_st.order ~= getfield(pf, 'stretch_order') then return total, false end
229       if total_st[total_st.order*65536] ~= getfield(pf, 'stretch') then return total, false end
230       for i=total_st.order-1, 1, -1 do
231          if total_st[i*65536] ~= 0 then return total, false end
232       end
233    end
234    if total<0 and total_sh.order>0 then
235       if total_sh.order ~= getfield(pf, 'shrink_order') then return total, false end
236       if total_sh[total_sh.order*65536] ~= getfield(pf, 'shrink') then return total, false end
237       for i=total_sh.order-1, 1, -1 do
238          if total_sh[i*65536] ~= 0 then return total, false end
239       end
240    end
241    local eadt = ltjf_font_metric_table[getfont(xc)]
242       .char_type[get_attr(xc, attr_jchar_class) or 0].end_adjust
243    if not eadt then
244       return total, false
245    end
246    -- 続行条件2: min(eadt[1], 0)<= \parfillskip <= max(eadt[#eadt], 0)
247    local pfw = getwidth(pf)
248      + (total>0 and getfield(pf, 'stretch') or -getfield(pf, 'shrink')) *getfield(p, 'glue_set')
249    if pfw<min(0,eadt[1]) or max(0,eadt[#eadt])<pfw then return total, false end
250    -- \parfillskip を 0 にする
251    total = total + getwidth(pf)
252    total_st.order, total_sh.order = 0, 0
253    if getfield(pf, 'stretch_order')==0 then
254       local i = at2pr_st[-1]
255       total_st[0] = total_st[0] - getfield(pf, 'stretch')
256       total_st[i] = total_st[i] - getfield(pf, 'stretch')
257       total_st.order = (total_st[0]==0) and -1 or 0
258    end
259    if getfield(pf, 'shrink_order')==0 then
260       local i = at2pr_sh[-1]
261       total_sh[0] = total_sh[0] - getfield(pf, 'shrink')
262       total_sh[i] = total_sh[i] - getfield(pf, 'shrink')
263       total_sh.order = (total_sh[0]==0) and -1 or 0
264    end
265    setsubtype(pf, 1); setglue(pf)
266    local eadt_ratio = {}
267    for i, v in ipairs(eadt) do
268       local t = total - v
269       if t>0 then
270          eadt_ratio[i] = {i, t/total_st[65536*total_st.order], t, v}
271       else
272          eadt_ratio[i] = {i, t/total_sh[65536*total_sh.order], t, v}
273       end
274    end
275    table.sort(eadt_ratio,
276    function (a,b)
277        for i=2,4 do
278            local at, bt = abs(a[i]), abs(b[i])
279            if at~=bt then return at<bt end
280        end
281        return a[4]<b[4]
282    end)
283    if eadt[eadt_ratio[1][1]]~=0 then
284       local kn = node_new(id_kern, 1)
285       setkern(kn, eadt[eadt_ratio[1][1]]); set_attr(kn, attr_icflag, LINEEND)
286       insert_after(head, x, kn)
287       return eadt_ratio[1][3], true
288    else
289       return total, false
290    end
291 end
292
293
294 -- step 2: 行中の glue を変える
295 local aw_step2, aw_step2_dummy
296 do
297 local node_hpack = node.direct.hpack
298 local function repack(p)
299    local orig_of, orig_hfuzz, orig_hbad = tex.overfullrule, tex.hfuzz, tex.hbadness
300    tex.overfullrule=0; tex.hfuzz=1073741823; tex.hbadness=10000
301    local f = node_hpack(getlist(p), getwidth(p), 'exactly')
302    tex.overfullrule=orig_of; tex.hfuzz=orig_hfuzz; tex.hbadness=orig_hbad
303    setlist(f, nil)
304    setfield(p, 'glue_set', getfield(f, 'glue_set'))
305    setfield(p, 'glue_order', getfield(f, 'glue_order'))
306    setfield(p, 'glue_sign', getfield(f, 'glue_sign'))
307    node_free(f)
308    return
309 end
310 function aw_step2_dummy(p, _, added_flag)
311    if added_flag then return repack(p) end
312 end
313
314 local function clear_stretch(p, ind, ap, name)
315    for q in node_traverse_id(id_glue, getlist(p)) do
316       local f = ap[get_attr_icflag(q)]
317       if f == ind then
318          setfield(q, name..'_order', 0); setfield(q, name, 0)
319       end
320    end
321 end
322
323 local function set_stretch(p, after, before, ind, ap, name)
324    if before > 0 then
325       local ratio = after/before
326       for q in node_traverse_id(id_glue, getlist(p)) do
327          local f = ap[get_attr_icflag(q)]
328          if (f==ind) and getfield(q, name..'_order')==0 then
329             setfield(q, name, getfield(q, name)*ratio)
330          end
331       end
332    end
333 end
334
335 function aw_step2(p, total, added_flag)
336    local name = (total>0) and 'stretch' or 'shrink'
337    local id =  (total>0) and 1 or 2
338    local res = total_stsh[id]
339    local pnum = priority_num[id]
340    if total==0 or res.order > 0 then
341       -- もともと伸縮の必要なしか,残りの伸縮量は無限大
342       if added_flag then return repack(p) end
343    end
344    total = abs(total)
345    for i = 1, pnum do
346       if total <= res[i] then
347          local a = at2pr[id]
348          for j = i+1,pnum do
349             clear_stretch(p, j, a, name)
350          end
351          set_stretch(p, total, res[i], i, a, name); break
352       end
353       total = total - res[i]
354    end
355    return repack(p)
356 end
357 end
358
359 -- step 1': lineend=extended の場合(行分割時に考慮))
360 local insert_lineend_kern
361 do
362    local insert_before = node.direct.insert_before
363    local KINSOKU      = luatexja.icflag_table.KINSOKU
364    insert_lineend_kern = function (head, nq, np, Bp)
365       if nq.met then
366          local eadt = nq.met.char_type[nq.class].end_adjust
367          if not eadt then return end
368          if eadt[1]~=0 then
369             local x = node_new(id_kern, 1)
370             setkern(x, eadt[1]); set_attr(x, attr_icflag, LINEEND)
371             insert_before(head, np.first, x)
372          end
373          local eadt_num = #eadt
374          for i=2,eadt_num do
375             local x = node_new(id_penalty)
376             setpenalty(x, 0); set_attr(x, attr_icflag, KINSOKU)
377             insert_before(head, np.first, x); Bp[#Bp+1] = x
378             local x = node_new(id_kern, 1)
379             setkern(x, eadt[i]-eadt[i-1]); set_attr(x, attr_icflag, LINEEND)
380             insert_before(head, np.first, x)
381          end
382          if eadt_num>1 or eadt[1]~=0 then
383             local x = node_new(id_penalty)
384             setpenalty(x, 0); set_attr(x, attr_icflag, KINSOKU)
385             insert_before(head, np.first, x); Bp[#Bp+1] = x
386             local x = node_new(id_kern, 1)
387             setkern(x, -eadt[eadt_num]); set_attr(x, attr_icflag, LINEEND)
388             insert_before(head, np.first, x)
389             local x = node_new(id_penalty)
390             setpenalty(x, 10000); set_attr(x, attr_icflag, KINSOKU)
391             insert_before(head, np.first, x); Bp[#Bp+1] = x
392          end
393       end
394    end
395 end
396
397 local adjust_width
398 do
399    local myaw_step1, myaw_step2, myaw_step1_last
400    local dummy =  function(p,t,n) return t, false end
401    function adjust_width(head)
402       if not head then return head end
403       local last_p
404       for p in node_traverse_id(id_hlist, to_direct(head)) do
405          if last_p then
406             myaw_step2(last_p, myaw_step1(last_p, get_total_stretched(last_p)))
407          end
408          last_p = p
409       end
410       if last_p then
411          myaw_step2(last_p, myaw_step1_last(last_p, get_total_stretched(last_p)))
412       end
413       return to_node(head)
414    end
415    local is_reg = false
416    local function enable_cb(status_le, status_pr, status_lp, status_ls)
417       if (status_le>0 or status_pr>0) and (not is_reg) then
418          ltjb.add_to_callback('post_linebreak_filter',
419             adjust_width, 'Adjust width',
420             luatexbase.priority_in_callback('post_linebreak_filter', 'ltj.lineskip')-1)
421          is_reg = true
422       elseif is_reg and (status_le==0 and status_pr==0) then
423          luatexbase.remove_from_callback('post_linebreak_filter', 'Adjust width')
424          is_reg = false
425       end
426       if status_le==2 then
427          if not luatexbase.in_callback('luatexja.adjust_jfmglue', 'luatexja.adjust') then
428             ltjb.add_to_callback('luatexja.adjust_jfmglue', insert_lineend_kern, 'luatexja.adjust')
429          end
430          myaw_step1, myaw_step1_last = dummy, aw_step1_last
431       else
432          if status_le==0 then
433             myaw_step1, myaw_step1_last = dummy, dummy
434          else
435             myaw_step1, myaw_step1_last = aw_step1, aw_step1_last
436          end
437          if luatexbase.in_callback('luatexja.adjust_jfmglue', 'luatexja.adjust') then
438                luatexbase.remove_from_callback('luatexja.adjust_jfmglue', 'luatexja.adjust')
439          end
440       end
441       myaw_step2 = (status_pr>0) and aw_step2 or aw_step2_dummy
442       luatexja.lineskip.setting(
443          status_lp>0 and 'profile' or 'dummy',
444          status_ls>0 and 'step' or 'dummy'
445       )
446    end
447    local function disable_cb() -- only for compatibility
448        enable_cs(0,0,0,0)
449    end
450    luatexja.adjust.enable_cb=enable_cb
451    luatexja.adjust.disable_cb=disable_cb
452 end
453
454 luatexja.unary_pars.adjust = function(t)
455    return is_reg and 1 or 0
456 end
457
458 -- ----------------------------------
459 local init_range
460 do
461   local max, ins, sort = math.max, table.insert, table.sort
462   local function insert(package, ind, d, b, e)
463     local bound = package[2]
464     bound[b], bound[e]=true, true
465     ins(package[1], {b,e,[ind]=d})
466   end
467   local function flatten(package)
468     local bd = {} for i,_ in pairs(package[2]) do ins(bd,{i}) end
469     sort(bd, function (a,b) return a[1]<b[1] end)
470     local bdc=#bd; local t = package[1]
471     sort(t, function (a,b) return a[1]<b[1] end)
472     local bdi =1
473     for i=1,#t do
474       while bd[bdi][1]<t[i][1] do bdi=bdi+1 end
475       local j = bdi
476       while j<bdc and bd[j+1][1]<=t[i][2] do
477         for k,w in pairs(t[i]) do
478           if k>=3 then
479             bd[j][k]=bd[j][k] and max(bd[j][k],w) or w
480           end
481         end
482         j = j + 1
483       end
484     end
485     package[2]=nil; package[1]=nil; package.flatten, package.insert=nil, nil
486     bd[#bd]=nil
487     return bd
488   end
489   init_range = function ()
490     return {{},{}, insert=insert, flatten=flatten}
491   end
492 end
493
494 -- -----------------------------------
495 luatexja.adjust.step_factor = 0.5
496 luatexja.unary_pars.linestep_factor = function(t)
497    return luatexja.adjust.step_factor
498 end
499 luatexja.adjust.profile_hgap_factor = 1
500 luatexja.unary_pars.profile_hgap_factor = function(t)
501    return luatexja.adjust.profile_hgap_factor
502 end
503 do
504   local insert, texget = table.insert, tex.get
505   local rangedimensions, max = node.direct.rangedimensions, math.max
506   local function profile_inner(box, range, ind, vmirrored, adj)
507     local w_acc, d_before = getshift(box), 0
508     local x = getlist(box); local xn = node_next(x)
509     while x do
510       local w, h, d
511       if xn then w, h, d = rangedimensions(box,x,xn)
512       else w, h, d = rangedimensions(box,x) end
513       if vmirrored then h=d end
514       local w_new = w_acc + w
515       if w>=0 then range:insert(ind, h, w_acc-adj, w_new)
516       else range:insert(ind, h, w_new-adj, w_acc)
517       end
518       w_acc = w_new; x = xn; if x then xn = node_next(x) end
519     end
520   end
521   function ltjl.p_profile(before, after, mirrored, bw)
522     local range, tls
523       = init_range(), luatexja.adjust.profile_hgap_factor*texget('lineskip', false)
524     profile_inner(before, range, 3, true,     tls)
525     profile_inner(after,  range, 4, mirrored, tls)
526     range = range:flatten()
527     do
528       local dmax, d, hmax, h, lmin = 0, 0, 0, 0, 1/0
529       for i,v in ipairs(range) do
530         d, h = (v[3] or 0), (v[4] or 0)
531         if d>dmax then dmax=d end
532         if h>hmax then hmax=h end
533         if bw-h-d<lmin then lmin=bw-h-d end
534       end
535       if lmin==1/0 then lmin = bw end
536       return lmin,
537          bw - lmin - getdepth(before)
538             - getfield(after, mirrored and 'depth' or 'height')
539     end
540   end
541 end
542
543 do
544   local ltja = luatexja.adjust
545   local copy_glue, texget = ltjl.copy_glue, tex.get
546   local floor, max = math.floor, math.max
547   function ltjl.l_step(dist, g, adj, normal, bw, loc)
548     if loc=='alignment' then
549       return ltjl.l_dummy(dist, g, adj, normal, bw, loc)
550     end
551     if dist < tex.lineskiplimit then
552     local f = max(1, bw*ltja.step_factor)
553        copy_glue(g, 'baselineskip', 1, normal - f * floor((dist-texget('lineskip', false))/f))
554     else
555        copy_glue(g, 'baselineskip', 2, normal)
556     end
557   end
558 end
559
560 do
561   local ltja = luatexja.adjust
562   local sid_user = node.subtype 'user_defined'
563   local node_remove = node.direct.remove
564   local node_write = node.direct.write
565   local getvalue = node.direct.getdata
566   local setvalue = node.direct.setdata
567   local GHOST_JACHAR = luatexbase.newuserwhatsitid('ghost of a jachar',  'luatexja')
568   luatexja.userid_table.GHOST_JACHAR = GHOST_JACHAR
569   function ltja.create_ghost_jachar_node(cl)
570     local tn = node_new(id_whatsit, sid_user)
571     setfield(tn, 'user_id', GHOST_JACHAR)
572     setfield(tn, 'type', 100)
573     setvalue(tn, cl)
574     node_write(tn)
575   end
576   local attr_curjfnt = luatexbase.attributes['ltj@curjfnt']
577   local attr_curtfnt = luatexbase.attributes['ltj@curtfnt']
578   local dir_tate = luatexja.dir_table.dir_tate
579   local get_dir_count = ltjd.get_dir_count
580   local ltjf_font_metric_table = ltjf.font_metric_table
581   local has_attr = node.direct.has_attribute
582   local function get_current_metric(n)
583      local fn = get_attr(n, (get_dir_count()==dir_tate) and attr_curtfnt or attr_curjfnt)
584      return fn and ltjf_font_metric_table[fn]
585   end
586   local function whatsit_callback(Np, lp, Nq)
587     if Np and Np.nuc then return Np
588     elseif Np and getfield(lp, 'user_id') == GHOST_JACHAR then
589       Np.first = lp; Np.nuc = lp; Np.last = lp; Np.class = 0
590       if getvalue(lp)<2 then
591         if Nq and Nq.met then Np.met = Nq.met; else Np.met = get_current_metric(lp) end
592         Np.pre = 0; Np.post = 0; Np.xspc = 3
593       else Np.met, Np.pre = nil, nil; end
594       Np.auto_kspc, Np.auto_xspc
595         = not has_attr(lp, attr_autospc, 0), not has_attr(lp, attr_autoxspc, 0)
596       return Np
597     else return Np end
598   end
599   local function whatsit_after_callback(s, Nq, Np, head)
600     if not s and getfield(Nq.nuc, 'user_id') == GHOST_JACHAR then
601       local x, y = node_prev(Nq.nuc), Nq.nuc
602       Nq.first, Nq.nuc, Nq.last = x, x, x
603       if getvalue(y)%2==0 then
604         if Np and Nq.met then Nq.met = Np.met; else Nq.met = get_current_metric(y) end
605         Nq.pre = 0; Nq.post = 0; Nq.xspc = 3
606       else Nq.met, Nq.pre = nil, nil; end
607       s = node_remove(head, y); node_free(y)
608     end
609     return s
610   end
611   luatexbase.add_to_callback("luatexja.jfmglue.whatsit_getinfo", whatsit_callback,
612                              "ghost of a JACHAR", 1)
613   luatexbase.add_to_callback("luatexja.jfmglue.whatsit_after", whatsit_after_callback,
614                              "ghost of a JACHAR", 1)
615 end