summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/tex/luatex/luatexja/ltj-adjust.lua
blob: 71584d8b5cf459a2f485585ad221d334282f7f3a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
--
-- ltj-adjust.lua
--
luatexja.load_module 'base';      local ltjb = luatexja.base
luatexja.load_module 'jfont';     local ltjf = luatexja.jfont
luatexja.load_module 'jfmglue';   local ltjj = luatexja.jfmglue
luatexja.load_module 'stack';     local ltjs = luatexja.stack
luatexja.load_module 'direction'; local ltjd = luatexja.direction
luatexja.load_module 'lineskip';  local ltjl = luatexja.lineskip
luatexja.adjust = luatexja.adjust or {}

local to_node = node.direct.tonode
local to_direct = node.direct.todirect

local setfield = node.direct.setfield
local setglue = luatexja.setglue
local getfield = node.direct.getfield
local getlist = node.direct.getlist
local getid = node.direct.getid
local getfont = node.direct.getfont
local getsubtype = node.direct.getsubtype

local node_traverse_id = node.direct.traverse_id
local node_new = node.direct.new
local node_next = node.direct.getnext
local node_free = node.direct.free
local node_prev = node.direct.getprev
local node_tail = node.direct.tail
local has_attr = node.direct.has_attribute
local set_attr = node.direct.set_attribute
local insert_after = node.direct.insert_after

local id_glyph   = node.id 'glyph'
local id_kern    = node.id 'kern'
local id_hlist   = node.id 'hlist'
local id_glue    = node.id 'glue'
local id_whatsit = node.id 'whatsit'
local id_penalty = node.id 'penalty'
local attr_icflag = luatexbase.attributes['ltj@icflag']
local attr_jchar_class = luatexbase.attributes['ltj@charclass']
local lang_ja = luatexja.lang_ja

local ltjf_font_metric_table = ltjf.font_metric_table
local ipairs, pairs = ipairs, pairs

local PACKED       = luatexja.icflag_table.PACKED
local LINEEND      = luatexja.icflag_table.LINEEND
local FROM_JFM     = luatexja.icflag_table.FROM_JFM
local KANJI_SKIP   = luatexja.icflag_table.KANJI_SKIP
local KANJI_SKIP_JFM = luatexja.icflag_table.KANJI_SKIP_JFM
local XKANJI_SKIP  = luatexja.icflag_table.XKANJI_SKIP
local XKANJI_SKIP_JFM  = luatexja.icflag_table.XKANJI_SKIP_JFM

local get_attr_icflag
do
   local PROCESSED_BEGIN_FLAG = luatexja.icflag_table.PROCESSED_BEGIN_FLAG
   get_attr_icflag = function(p)
      return (has_attr(p, attr_icflag) or 0) % PROCESSED_BEGIN_FLAG
   end
end

local priority_num = { 0, 0 }
local at2pr = { {}, {} }
local at2pr_st, at2pr_sh = at2pr[1], at2pr[2]
do
   local priority_table = {{},{}}
   luatexja.adjust.priority_table = priority_table
   local tmp = {}
   local function cmp(a,b) return a[1]>b[1] end -- 大きいほうが先!
   local function make_priority_table(glue_sign)
      for i,_ in pairs(tmp) do tmp[i]=nil end
      if glue_sign==2 then -- shrink
         for i=0,63 do tmp[#tmp+1] = { (i%8)-4, FROM_JFM+i } end
      else -- stretch
         for i=0,63 do tmp[#tmp+1] = { math.floor(i/8)-4, FROM_JFM+i } end
      end    
      local pt = priority_table[glue_sign]
      tmp[#tmp+1] = { pt[2]/10, XKANJI_SKIP }
      tmp[#tmp+1] = { pt[2]/10, XKANJI_SKIP_JFM }
      tmp[#tmp+1] = { pt[1]/10, KANJI_SKIP }
      tmp[#tmp+1] = { pt[1]/10, KANJI_SKIP_JFM }
      tmp[#tmp+1] = { pt[3]/10, -1 }
      table.sort(tmp, cmp)
      local a, m, n = at2pr[glue_sign], 10000000, 0
      for i=1,#tmp do
         if tmp[i][1]<m then n,m = n+1,tmp[i][1] end
         a[tmp[i][2]] = n
      end
      local o = a[-1]
      priority_num[glue_sign] = n
      setmetatable(a, {__index = function () return o end })
   end
   luatexja.adjust.make_priority_table = make_priority_table
end

-- box 内で伸縮された glue の合計値を計算

local total_stsh = {{},{}}
local total_st, total_sh = total_stsh[1], total_stsh[2]
local get_total_stretched
do
local dimensions = node.direct.dimensions
function get_total_stretched(p)
-- return value: <補正値(sp)>
   local ph = getlist(p)
   if not ph then return 0 end
   for i,_ in pairs(total_st) do total_st[i]=nil; total_sh[i]=nil end
   for i=1,priority_num[1] do total_st[i]=0 end
   for i=1,priority_num[2] do total_sh[i]=0 end
   for i=0,4 do total_st[i*65536]=0; total_sh[i*65536]=0 end
   for q in node_traverse_id(id_glue, ph) do
      local a = getfield(q, 'stretch_order')
      if a==0 then
         local b = at2pr_st[get_attr_icflag(q)]; 
         total_st[b] = total_st[b]+getfield(q, 'stretch')
      end
      total_st[a*65536] = total_st[a]+getfield(q, 'stretch')
      local a = getfield(q, 'shrink_order')
      if a==0 then
         local b = at2pr_sh[get_attr_icflag(q)]; 
         total_sh[b] = total_sh[b]+getfield(q, 'shrink')
      end
      total_sh[a*65536] = total_sh[a]+getfield(q, 'shrink')
   end
   for i=4,1,-1 do if total_st[i*65536]~=0 then total_st.order=i; break end; end
   if not total_st.order then
       total_st.order, total_st[-65536] = -1,0.1 -- dummy
   end
   for i=4,1,-1 do if total_sh[i*65536]~=0 then total_sh.order=i; break end; end
   if not total_sh.order then
       total_sh.order, total_sh[-65536] = -1,0.1 -- dummy
   end
   return getfield(p,'width') - dimensions(ph)
end
end

-- step 1: 行末に kern を挿入(句読点,中点用)
local abs = math.abs
local ltjd_glyph_from_packed = ltjd.glyph_from_packed
local function aw_step1(p, total)
   local head = getlist(p)
   local x = node_tail(head); if not x then return total, false end
   -- x: \rightskip
   x = node_prev(x); if not x then return total, false end
   local xi, xc = getid(x)
   -- x may be penalty
   while xi==id_penalty do
      x = node_prev(x); if not x then return total, false end
      xi = getid(x)
   end
   if (total>0 and total_st.order>0) or (total<0 and total_sh.order>0) then
       -- 無限大のグルーで処理が行われているときは処理中止.
       return total, false
   end
   if xi == id_glyph and getfield(x, 'lang')==lang_ja then
      -- 和文文字
      xc = x
   elseif xi == id_hlist and get_attr_icflag(x) == PACKED then
      -- packed JAchar
      xc = ltjd_glyph_from_packed(x)
      while getid(xc) == id_whatsit do xc = node_next(xc) end -- これはなんのために?
   else
      return total, false-- それ以外は対象外.
   end
   local eadt = ltjf_font_metric_table[getfont(xc)]
      .char_type[has_attr(xc, attr_jchar_class) or 0].end_adjust
   if not eadt then 
      return total, false
   end
   local eadt_ratio = {}
   for i, v in ipairs(eadt) do
      local t = total - v
      if t>0 then
         eadt_ratio[i] = {i, t/total_st[65536*total_st.order], t, v}
      else
         eadt_ratio[i] = {i, t/total_sh[65536*total_sh.order], t, v}
      end
   end
   table.sort(eadt_ratio, 
   function (a,b) 
       for i=2,4 do
           local at, bt = abs(a[i]), abs(b[i])
           if at~=bt then return at<bt end
       end
       return a[4]<b[4]
   end)
   if eadt[eadt_ratio[1][1]]~=0 then
      local kn = node_new(id_kern, 1)
      setfield(kn, 'kern', eadt[eadt_ratio[1][1]]); set_attr(kn, attr_icflag, LINEEND)
      insert_after(head, x, kn)
      return eadt_ratio[1][3], true
   else
      return total, false
   end
end

-- step 1 最終行用
local min, max = math.min, math.max
local function aw_step1_last(p, total)
   local head = getlist(p)
   local x = node_tail(head); if not x then return total, false end
   -- x: \rightskip
   local pf = node_prev(x); if not x then return total, false end
   if getid(pf) ~= id_glue or getsubtype(pf) ~= 15 then return total, false end
   x = node_prev(node_prev(pf))
   local xi, xc = getid(x)
   if xi == id_glyph and getfield(x, 'lang')==lang_ja then
      -- 和文文字
      xc = x
   elseif xi == id_hlist and get_attr_icflag(x) == PACKED then
      -- packed JAchar
      xc = ltjd_glyph_from_packed(x)
      while getid(xc) == id_whatsit do xc = node_next(xc) end -- これはなんのために?
   else
      return total, false-- それ以外は対象外.
   end
   -- 続行条件1:無限の伸縮度を持つグルーは \parfillskipのみ
   if total>0 and total_st.order>0 then
      if total_st.order ~= getfield(pf, 'stretch_order') then return total, false end
      if total_st[total_st.order*65536] ~= getfield(pf, 'stretch') then return total, false end
      for i=total_st.order-1, 1, -1 do
         if total_st[i*65536] ~= 0 then return total, false end
      end
   end
   if total<0 and total_sh.order>0 then
      if total_sh.order ~= getfield(pf, 'shrink_order') then return total, false end
      if total_sh[total_sh.order*65536] ~= getfield(pf, 'shrink') then return total, false end
      for i=total_sh.order-1, 1, -1 do
         if total_sh[i*65536] ~= 0 then return total, false end
      end
   end
   local eadt = ltjf_font_metric_table[getfont(xc)]
      .char_type[has_attr(xc, attr_jchar_class) or 0].end_adjust
   if not eadt then 
      return total, false
   end
   -- 続行条件2: min(eadt[1], 0)<= \parfillskip <= max(eadt[#eadt], 0)
   local pfw = getfield(pf, 'width') 
     + (total>0 and getfield(pf, 'stretch') or -getfield(pf, 'shrink')) *getfield(p, 'glue_set') 
   if pfw<min(0,eadt[1]) or max(0,eadt[#eadt])<pfw then return total, false end
   -- \parfillskip を 0 にする
   total = total + getfield(pf, 'width') 
   total_st.order, total_sh.order = 0, 0
   if getfield(pf, 'stretch_order')==0 then 
      local i = at2pr_st[-1] 
      total_st[0] = total_st[0] - getfield(pf, 'stretch') 
      total_st[i] = total_st[i] - getfield(pf, 'stretch') 
      total_st.order = (total_st[0]==0) and -1 or 0
   end
   if getfield(pf, 'shrink_order')==0 then 
      local i = at2pr_sh[-1] 
      total_sh[0] = total_sh[0] - getfield(pf, 'shrink') 
      total_sh[i] = total_sh[i] - getfield(pf, 'shrink') 
      total_sh.order = (total_sh[0]==0) and -1 or 0
   end
   setfield(pf, 'subtype', 1); setglue(pf)
   local eadt_ratio = {}
   for i, v in ipairs(eadt) do
      local t = total - v
      if t>0 then
         eadt_ratio[i] = {i, t/total_st[65536*total_st.order], t, v}
      else
         eadt_ratio[i] = {i, t/total_sh[65536*total_sh.order], t, v}
      end
   end
   table.sort(eadt_ratio, 
   function (a,b) 
       for i=2,4 do
           local at, bt = abs(a[i]), abs(b[i])
           if at~=bt then return at<bt end
       end
       return a[4]<b[4]
   end)
   if eadt[eadt_ratio[1][1]]~=0 then
      local kn = node_new(id_kern, 1)
      setfield(kn, 'kern', eadt[eadt_ratio[1][1]]); set_attr(kn, attr_icflag, LINEEND)
      insert_after(head, x, kn)
      return eadt_ratio[1][3], true
   else
      return total, false
   end
end


-- step 2: 行中の glue を変える
local aw_step2, aw_step2_dummy
do
local node_hpack = node.direct.hpack
local function repack(p)
   local orig_of, orig_hfuzz, orig_hbad = tex.overfullrule, tex.hfuzz, tex.hbadness
   tex.overfullrule=0; tex.hfuzz=1073741823; tex.hbadness=10000
   local f = node_hpack(getlist(p), getfield(p, 'width'), 'exactly')
   tex.overfullrule=orig_of; tex.hfuzz=orig_hfuzz; tex.hbadness=orig_hbad
   setfield(f, 'head', nil)
   setfield(p, 'glue_set', getfield(f, 'glue_set'))
   setfield(p, 'glue_order', getfield(f, 'glue_order'))
   setfield(p, 'glue_sign', getfield(f, 'glue_sign'))
   node_free(f)
   return
end
function aw_step2_dummy(p, _, added_flag)
   if added_flag then return repack(p) end
end

local function clear_stretch(p, ind, ap, name)
   for q in node_traverse_id(id_glue, getlist(p)) do
      local f = ap[get_attr_icflag(q)]
      if f == ind then
         setfield(q, name..'_order', 0); setfield(q, name, 0)
      end
   end
end

local function set_stretch(p, after, before, ind, ap, name)
   if before > 0 then
      local ratio = after/before
      for q in node_traverse_id(id_glue, getlist(p)) do
         local f = ap[get_attr_icflag(q)]
         if (f==ind) and getfield(q, name..'_order')==0 then
            setfield(q, name, getfield(q, name)*ratio)
         end
      end
   end
end

function aw_step2(p, total, added_flag)
   local name = (total>0) and 'stretch' or 'shrink'
   local id =  (total>0) and 1 or 2
   local res = total_stsh[id]
   local pnum = priority_num[id]
   if total==0 or res.order > 0 then 
      -- もともと伸縮の必要なしか,残りの伸縮量は無限大
      if added_flag then return repack(p) end
   end
   total = abs(total)
   for i = 1, pnum do
      if total <= res[i] then
         local a = at2pr[id]  
         for j = i+1,pnum do
            clear_stretch(p, j, a, name)
         end
         set_stretch(p, total, res[i], i, a, name); break
      end
      total = total - res[i]
   end
   return repack(p)
end
end

-- step 1': lineend=extended の場合(行分割時に考慮))
local insert_lineend_kern
do
   local insert_before = node.direct.insert_before
   local KINSOKU      = luatexja.icflag_table.KINSOKU
   insert_lineend_kern = function (head, nq, np, Bp)
      if nq.met then 
         local eadt = nq.met.char_type[nq.class].end_adjust
         if not eadt then return end
         if eadt[1]~=0 then
            local x = node_new(id_kern, 1)
            setfield(x, 'kern', eadt[1]); set_attr(x, attr_icflag, LINEEND)
            insert_before(head, np.first, x)
         end
         local eadt_num = #eadt
         for i=2,eadt_num do
            local x = node_new(id_penalty)
            setfield(x, 'penalty', 0); set_attr(x, attr_icflag, KINSOKU)
            insert_before(head, np.first, x); Bp[#Bp+1] = x
            local x = node_new(id_kern, 1)
            setfield(x, 'kern', eadt[i]-eadt[i-1]); set_attr(x, attr_icflag, LINEEND)
            insert_before(head, np.first, x)
         end
         if eadt_num>1 or eadt[1]~=0 then
            local x = node_new(id_penalty)
            setfield(x, 'penalty', 0); set_attr(x, attr_icflag, KINSOKU)
            insert_before(head, np.first, x); Bp[#Bp+1] = x
            local x = node_new(id_kern, 1)
            setfield(x, 'kern', -eadt[eadt_num]); set_attr(x, attr_icflag, LINEEND)
            insert_before(head, np.first, x)
            local x = node_new(id_penalty)
            setfield(x, 'penalty', 10000); set_attr(x, attr_icflag, KINSOKU)
            insert_before(head, np.first, x); Bp[#Bp+1] = x
         end
      end
   end
end

local adjust_width
do
   local myaw_step1, myaw_step2, myaw_step1_last
   local dummy =  function(p,t,n) return t, false end
   local ltjs_fast_get_stack_skip = ltjs.fast_get_stack_skip
   function adjust_width(head)
      if not head then return head end
      local last_p
      for p in node_traverse_id(id_hlist, to_direct(head)) do
         if last_p then
            myaw_step2(last_p, myaw_step1(last_p, get_total_stretched(last_p)))
         end
         last_p = p
      end
      if last_p then
         myaw_step2(last_p, myaw_step1_last(last_p, get_total_stretched(last_p)))
      end
      return to_node(head)
   end
   local is_reg = false
   local function enable_cb(status_le, status_pr, status_lp, status_ls)
      if (status_le>0 or status_pr>0) and (not is_reg) then
         ltjb.add_to_callback('post_linebreak_filter',
            adjust_width, 'Adjust width', 
            luatexbase.priority_in_callback('post_linebreak_filter', 'ltj.lineskip')-1)
         is_reg = true
      elseif is_reg and (status_le==0 and status_pr==0) then
         luatexbase.remove_from_callback('post_linebreak_filter', 'Adjust width')
         is_reg = false
      end
      if status_le==2 then
         if not luatexbase.in_callback('luatexja.adjust_jfmglue', 'luatexja.adjust') then
            ltjb.add_to_callback('luatexja.adjust_jfmglue', insert_lineend_kern, 'luatexja.adjust')
         end
         myaw_step1, myaw_step1_last = dummy, aw_step1_last
      else
         if status_le==0 then
            myaw_step1, myaw_step1_last = dummy, dummy
         else
            myaw_step1, myaw_step1_last = aw_step1, aw_step1_last
         end
         if luatexbase.in_callback('luatexja.adjust_jfmglue', 'luatexja.adjust') then
               luatexbase.remove_from_callback('luatexja.adjust_jfmglue', 'luatexja.adjust')
         end
      end
      myaw_step2 = (status_pr>0) and aw_step2 or aw_step2_dummy
      luatexja.lineskip.setting(
         status_lp>0 and 'profile' or 'dummy',
         status_ls>0 and 'step' or 'dummy'
      )
   end
   local function disable_cb() -- only for compatibility
       enable_cs(0,0,0,0)
   end
   luatexja.adjust.enable_cb=enable_cb
   luatexja.adjust.disable_cb=disable_cb
end

luatexja.unary_pars.adjust = function(t)
   return is_reg and 1 or 0
end

-- ----------------------------------
local init_range
do
  local max, ins, sort = math.max, table.insert, table.sort
  local function insert(package, ind, d, b, e)
    local bound = package[2]
    bound[b], bound[e]=true, true
    ins(package[1], {b,e,[ind]=d})
  end
  local function flatten(package)
    local bd = {} for i,_ in pairs(package[2]) do ins(bd,{i}) end
    sort(bd, function (a,b) return a[1]<b[1] end)
    local bdc=#bd; local t = package[1]
    sort(t, function (a,b) return a[1]<b[1] end)
    local bdi =1
    for i=1,#t do
      while bd[bdi][1]<t[i][1] do bdi=bdi+1 end
      local j = bdi
      while j<bdc and bd[j+1][1]<=t[i][2] do
        for k,w in pairs(t[i]) do
          if k>=3 then
            bd[j][k]=bd[j][k] and max(bd[j][k],w) or w
          end
        end
        j = j + 1
      end
    end
    package[2]=nil; package[1]=nil; package.flatten, package.insert=nil, nil
    bd[#bd]=nil
    return bd
  end
  init_range = function ()
    return {{},{}, insert=insert, flatten=flatten}
  end
end

-- -----------------------------------
luatexja.adjust.step_factor = 0.5
luatexja.unary_pars.linestep_factor = function(t)
   return luatexja.adjust.step_factor
end
luatexja.adjust.profile_hgap_factor = 1
luatexja.unary_pars.profile_hgap_factor = function(t)
   return luatexja.adjust.profile_hgap_factor
end
do
  local insert = table.insert
  local rangedimensions, max = node.direct.rangedimensions, math.max
  local function profile_inner(box, range, ind, vmirrored, adj)
    local w_acc, d_before = getfield(box,'shift'), 0
    local x = getlist(box); local xn = node_next(x)
    while x do
      local w, h, d
      if xn then w, h, d= rangedimensions(box,x,xn)
      else w, h, d= rangedimensions(box,x) end
      if vmirrored then h=d end
      local w_new = w_acc + w
      if w>=0 then
        range:insert(ind, h, w_acc-adj, w_new)
      else
        range:insert(ind, h, w_new-adj, w_acc)
      end
      w_acc = w_new; x = xn; if x then xn = node_next(x) end
    end
  end  
  function ltjl.p_profile(before, after, mirrored, bw)
    local range, tls 
      = init_range(), luatexja.adjust.profile_hgap_factor*tex.lineskip.width
    profile_inner(before, range, 3, true,     tls)
    profile_inner(after,  range, 4, mirrored, tls)
    range = range:flatten()
    do
      local dmax, d, hmax, h, lmin = 0, 0, 0, 0, 1/0
      for i,v in ipairs(range) do
        d, h = (v[3] or 0), (v[4] or 0)
        if d>dmax then dmax=d end
        if h>hmax then hmax=h end
        if bw-h-d<lmin then lmin=bw-h-d end
      end
      if lmin==1/0 then lmin = bw end
      return lmin, 
         bw - lmin - getfield(before, 'depth')
            - getfield(after, mirrored and 'depth' or 'height')
    end
  end
end

do
  local ltja = luatexja.adjust
  local copy_glue = ltjl.copy_glue
  local floor, max = math.floor, math.max
  function ltjl.l_step(dist, g, adj, normal, bw, loc)
    if loc=='alignment' then
      return ltjl.l_dummy(dist, g, adj, normal, bw, loc)
    end
    if dist < tex.lineskiplimit then
    local f = max(1, bw*ltja.step_factor)
       copy_glue(g, tex.baselineskip, 1, normal - f * floor((dist-tex.lineskip.width)/f))
    else
       copy_glue(g, tex.baselineskip, 2, normal)
    end
  end
end

do
  local ltja = luatexja.adjust
  local sid_user = node.subtype 'user_defined'
  local node_remove = node.direct.remove
  local node_write = node.direct.write
  local GHOST_JACHAR = luatexbase.newuserwhatsitid('ghost of a jachar',  'luatexja')
  luatexja.userid_table.GHOST_JACHAR = GHOST_JACHAR
  function ltja.create_ghost_jachar_node(cl)
    local tn = node_new(id_whatsit, sid_user)
    setfield(tn, 'user_id', GHOST_JACHAR)
    setfield(tn, 'type', 100)
    setfield(tn, 'value', cl)
    node_write(tn)
  end
  local attr_curjfnt = luatexbase.attributes['ltj@curjfnt']
  local attr_curtfnt = luatexbase.attributes['ltj@curtfnt']
  local dir_tate = luatexja.dir_table.dir_tate
  local get_dir_count = ltjd.get_dir_count
  local ltjf_font_metric_table = ltjf.font_metric_table
  local function get_current_metric(n)
     local fn = has_attr(n, (get_dir_count()==dir_tate) and attr_curtfnt or attr_curjfnt)
     return fn and ltjf_font_metric_table[fn]
  end
  local function whatsit_callback(Np, lp, Nq)
    if Np and Np.nuc then return Np
    elseif Np and getfield(lp, 'user_id') == GHOST_JACHAR then
      Np.first = lp; Np.nuc = lp; Np.last = lp; Np.class = 0
      if getfield(lp,'value')<2 then
        if Nq and Nq.met then Np.met = Nq.met; else Np.met = get_current_metric(lp) end
        Np.pre = 0; Np.post = 0; Np.xspc = 3
      else Np.met, Np.pre = nil, nil; end
      Np.auto_kspc, Np.auto_xspc = (has_attr(lp, attr_autospc)==1), (has_attr(lp, attr_autoxspc)==1)
      return Np
    else return Np end
  end
  local function whatsit_after_callback(s, Nq, Np, head)
    if not s and getfield(Nq.nuc, 'user_id') == GHOST_JACHAR then
      local x, y = node_prev(Nq.nuc), Nq.nuc
      Nq.first, Nq.nuc, Nq.last = x, x, x
      if getfield(y,'value')%2==0 then
        if Np and Nq.met then Nq.met = Np.met; else Nq.met = get_current_metric(y) end
        Nq.pre = 0; Nq.post = 0; Nq.xspc = 3
      else Nq.met, Nq.pre = nil, nil; end
      s = node_remove(head, y); node_free(y)
    end
    return s
  end
  luatexbase.add_to_callback("luatexja.jfmglue.whatsit_getinfo", whatsit_callback,
                             "ghost of a JACHAR", 1)
  luatexbase.add_to_callback("luatexja.jfmglue.whatsit_after", whatsit_after_callback,
                             "ghost of a JACHAR", 1)
end