2 -- luatexja/ltj-jfmglue.lua
4 luatexbase.provides_module({
5 name = 'luatexja.jfmglue',
7 description = 'Insertion process of JFM glues and kanjiskip',
9 module('luatexja.jfmglue', package.seeall)
10 local err, warn, info, log = luatexbase .errwarinf(_NAME)
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
19 local Dnode = node.direct or node
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
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
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
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')
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 local lang_ja = luatexja.lang_ja
71 local sid_start_link = node.subtype('pdf_start_link')
72 local sid_start_thread = node.subtype('pdf_start_thread')
73 local sid_end_link = node.subtype('pdf_end_link')
74 local sid_end_thread = node.subtype('pdf_end_thread')
76 local ITALIC = luatexja.icflag_table.ITALIC
77 local PACKED = luatexja.icflag_table.PACKED
78 local KINSOKU = luatexja.icflag_table.KINSOKU
79 local FROM_JFM = luatexja.icflag_table.FROM_JFM
80 local PROCESSED = luatexja.icflag_table.PROCESSED
81 local IC_PROCESSED = luatexja.icflag_table.IC_PROCESSED
82 local BOXBDD = luatexja.icflag_table.BOXBDD
83 local PROCESSED_BEGIN_FLAG = luatexja.icflag_table.PROCESSED_BEGIN_FLAG
85 local attr_icflag = luatexbase.attributes['ltj@icflag']
88 local table_current_stack
93 local set_np_xspc_jachar
94 local set_np_xspc_jachar_hbox
96 local ltjs_orig_char_table = ltjs.orig_char_table
98 local function get_attr_icflag(p)
99 return (has_attr(p, attr_icflag) or 0)%PROCESSED_BEGIN_FLAG
102 -------------------- Helper functions
104 -- This function is called only for acquiring `special' characters.
105 local function fast_find_char_class(c,m)
106 return m.chars[c] or 0
110 local slow_find_char_class
112 local start_time_measure = ltjb.start_time_measure
113 local stop_time_measure = ltjb.stop_time_measure
114 slow_find_char_class = function (c, m, oc)
115 local cls = ltjf_find_char_class(oc, m)
116 if oc~=c and c and cls==0 then
117 return ltjf_find_char_class(c, m)
124 local zero_glue = node_new(id_glue)
125 spec_zero_glue = to_node(node_new(id_glue_spec))
126 -- must be public, since mentioned from other sources
127 local spec_zero_glue = to_direct(spec_zero_glue)
128 setfield(spec_zero_glue, 'width', 0)
129 setfield(spec_zero_glue, 'stretch', 0)
130 setfield(spec_zero_glue, 'shrink', 0)
131 setfield(spec_zero_glue, 'stretch_order', 0)
132 setfield(spec_zero_glue, 'shrink_order', 0)
133 setfield(zero_glue, 'spec', spec_zero_glue)
135 local function skip_table_to_spec(n)
136 local g, st = node_new(id_glue_spec), ltjs.fast_get_stack_skip(n)
137 setfield(g, 'width', st.width)
138 setfield(g, 'stretch', st.stretch)
139 setfield(g, 'shrink', st.shrink)
140 setfield(g, 'stretch_order', st.stretch_order)
141 setfield(g, 'shrink_order', st.shrink_order)
147 local function add_penalty(p,e)
148 local pp = getfield(p, 'penalty')
150 if e<=-10000 then setfield(p, 'penalty', 0) end
151 elseif pp<=-10000 then
152 if e>=10000 then setfield(p, 'penalty', 0) end
155 if pp>=10000 then setfield(p, 'penalty', 10000)
156 elseif pp<=-10000 then setfield(p, 'penalty', -10000)
157 else setfield(p, 'penalty', pp) end
162 diffmet_rule = math.two_paverage
163 function math.two_add(a,b) return a+b end
164 function math.two_average(a,b) return (a+b)*0.5 end
165 function math.two_paverage(a,b) return (a+b)/2 end
166 function math.two_pleft(a,b) return a end
167 function math.two_pright(a,b) return b end
169 local head -- the head of current list
172 local widow_Bp, widow_Np -- \jcharwidowpenalty 挿入位置管理用
174 local non_ihb_flag -- JFM グルー挿入抑止用 flag
175 -- false: \inhibitglue 指定時 true: それ以外
177 -------------------- hlist 内の文字の検索
179 local first_char, last_char, find_first_char
181 local ltjd_glyph_from_packed = ltjd.glyph_from_packed
182 local function check_box(box_ptr, box_end)
183 local p = box_ptr; local found_visible_node = false
185 find_first_char = false; last_char = nil
188 while p and p~=box_end do
190 if pid==id_kern and getsubtype(p)==2 then
191 p = node_next(node_next(node_next(p))); pid = getid(p) -- p must be glyph_node
193 if pid==id_glyph then
195 if find_first_char then
196 first_char = p; find_first_char = false
198 last_char = p; found_visible_node = true; p=node_next(p)
199 if (not p) or p==box_end then
200 return found_visible_node
202 until getid(p)~=id_glyph
203 pid = getid(p) -- p must be non-nil
206 local pa = get_attr_icflag(p)
207 if pa==IC_PROCESSED then
209 elseif getsubtype(p)==2 then
210 p = node_next(node_next(p));
211 -- Note that another node_next will be executed outside this if-statement.
213 found_visible_node = true
214 find_first_char = false; last_char = nil
216 elseif pid==id_hlist then
217 if PACKED == get_attr_icflag(p) then
218 local s = ltjd_glyph_from_packed(p)
219 if find_first_char then
220 first_char = s; find_first_char = false
222 last_char = s; found_visible_node = true
224 if getfield(p, 'shift')==0 then
226 if check_box(getlist(p), nil) then found_visible_node = true end
228 find_first_char = false; last_char = nil
231 elseif pid==id_math then
232 if find_first_char then
233 first_char = p; find_first_char = false
235 last_char = p; found_visible_node = true
236 elseif pid==id_rule and get_attr_icflag(p)==PACKED then
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
246 return found_visible_node
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
254 if getid(first_char)==id_glyph then
255 if getfield(first_char, 'lang') == lang_ja then
256 set_np_xspc_jachar_hbox(Nx, first_char)
258 set_np_xspc_alchar(Nx, getchar(first_char),first_char, 1)
261 set_np_xspc_alchar(Nx, -1,first_char)
268 -------------------- Np の計算と情報取得
270 luatexbase.create_callback("luatexja.jfmglue.whatsit_getinfo", "data",
271 function (Np, lp, Nq)
272 if Np.nuc then return Np
274 return Np -- your code
277 luatexbase.create_callback("luatexja.jfmglue.whatsit_after", "data",
278 function (stat, Nq, Np) return false end)
284 local traverse = Dnode.traverse
285 local function check_next_ickern(lp)
286 if lp and getid(lp) == id_kern and ( getsubtype(lp)==3 or ITALIC == get_attr_icflag(lp)) then
287 set_attr(lp, attr_icflag, IC_PROCESSED);
288 Np.last = lp; return node_next(lp)
290 Np.last = Np.nuc; return lp
294 local function calc_np_pbox(lp, last)
295 local first, lpa, nc = (not Np.first), KINSOKU, nil
296 Np.first = Np.first or lp; Np.id = id_pbox
297 set_attr(lp, attr_icflag, get_attr_icflag(lp));
298 while lp ~=last and (lpa>=PACKED) and (lpa<BOXBDD) do
299 local lpi = getid(lp)
300 if lpi==id_hlist or lpi==id_vlist then
301 head, lp, nc = ltjd_make_dir_whatsit(head, lp, list_dir, 'jfm pbox')
302 Np.first = first and nc or Np.first
303 elseif (lpi==id_rule) and (lpa==PACKED) then
305 nc, lp = lp, node_next(lp)
307 nc, lp = lp, node_next(lp)
309 first, lpa = false, (lp and has_attr(lp, attr_icflag) or 0)
310 -- get_attr_icflag() ではいけない!
313 lp = check_next_ickern(lp)
314 Np.last_char = check_box_high(Np, Np.first, lp)
318 local ltjw_apply_ashift_math = ltjw.apply_ashift_math
319 local ltjw_apply_ashift_disc = ltjw.apply_ashift_disc
320 local min, max = math.min, math.max
321 local rule_subtype = (status.luatex_version>=85) and 3 or 0
322 local function calc_np_aux_glyph_common(lp)
324 Np.first= (Np.first or lp)
325 if getfield(lp, 'lang') == lang_ja then
327 local m, mc, cls = set_np_xspc_jachar(Np, lp)
329 lp, head, npi, npf = capsule_glyph(lp, m, mc[cls], head, tex_dir)
330 Np.first = (Np.first~=Np.nuc) and Np.first or npf or npi
332 return true, check_next_ickern(lp);
335 set_np_xspc_alchar(Np, getchar(lp), lp, 1)
337 local first_glyph, last_glyph = lp
338 set_attr(lp, attr_icflag, PROCESSED); Np.last = lp
339 local y_adjust = has_attr(lp,attr_ablshift) or 0
340 local node_depth = getfield(lp, 'depth') + min(y_adjust, 0)
341 local adj_depth = (y_adjust>0) and (getfield(lp, 'depth') + y_adjust) or 0
342 setfield(lp, 'yoffset', getfield(lp, 'yoffset') - y_adjust)
344 for lx in traverse(lp) do
345 local lai = get_attr_icflag(lx)
346 if lx==last or lai>=PACKED then
349 local lid = getid(lx)
350 if lid==id_glyph and getfield(lx, 'lang') ~= lang_ja then
352 last_glyph = lx; set_attr(lx, attr_icflag, PROCESSED); Np.last = lx
353 y_adjust = has_attr(lx,attr_ablshift) or 0
354 node_depth = max(getfield(lx, 'depth') + min(y_adjust, 0), node_depth)
355 adj_depth = (y_adjust>0) and max(getfield(lx, 'depth') + y_adjust, adj_depth) or adj_depth
356 setfield(lx, 'yoffset', getfield(lx, 'yoffset') - y_adjust)
357 elseif lid==id_kern then
358 local ls = getsubtype(lx)
359 if ls==2 then -- アクセント用の kern
360 set_attr(lx, attr_icflag, PROCESSED)
361 lx = node_next(lx) -- lx: アクセント本体
362 if getid(lx)==id_glyph then
363 setfield(lx, 'yoffset', getfield(lx, 'yoffset') - (has_attr(lx,attr_ablshift) or 0))
364 else -- アクセントは上下にシフトされている
365 setfield(lx, 'shift', getfield(lx, 'shift') + (has_attr(lx,attr_ablshift) or 0))
367 lx = node_next(node_next(lx))
370 elseif (ls==3) or (lai==ITALIC) then
371 Np.last = lx; set_attr(lx, attr_icflag, IC_PROCESSED)
381 if adj_depth>node_depth then
382 r = node_new(id_rule,rule_subtype)
383 setfield(r, 'width', 0); setfield(r, 'height', 0)
384 setfield(r, 'depth',adj_depth); setfield(r, 'dir', tex_dir)
385 set_attr(r, attr_icflag, PROCESSED)
388 Np.last_char = last_glyph
389 if r then insert_after(head, first_glyph, r) end
394 local nf, nc = getfont(npn), getchar(npn)
395 local ct = (font.getfont(nf) or font.fonts[nf] ).characters[nc]
396 if not ct then -- variation selector
398 elseif (ct.left_protruding or 0) == 0 then
399 head = insert_before(head, npn, r)
400 Np.first = (Np.first==npn) and r or npn
401 elseif (ct.right_protruding or 0) == 0 then
402 insert_after(head, npn, r); Np.last, lp = r, r
404 ltjb.package_warning_no_line(
406 'Check depth of glyph node ' .. tostring(npn) .. '(font=' .. nf
407 .. ', char=' .. nc .. '), because its \\lpcode is ' .. tostring(ct.left_protruding)
408 .. ' and its \\rpcode is ' .. tostring(ct.right_protruding)
416 local calc_np_auxtable = {
417 [id_glyph] = calc_np_aux_glyph_common,
418 [id_hlist] = function(lp)
420 head, lp, op, flag = ltjd_make_dir_whatsit(head, lp, list_dir, 'jfm hlist')
421 set_attr(op, attr_icflag, PROCESSED)
422 Np.first = Np.first or op; Np.last = op; Np.nuc = op;
423 if (flag or getfield(op, 'shift')~=0) then
427 Np.last_char = check_box_high(Np, getlist(op), nil)
431 [id_vlist] = function(lp)
433 head, lp, op = ltjd_make_dir_whatsit(head, lp, list_dir, 'jfm:' .. getid(lp))
434 Np.first = Np.first or op; Np.last = op; Np.nuc = op;
438 box_like = function(lp)
439 Np.first = Np.first or lp; Np.last = lp; Np.nuc = lp;
441 return true, node_next(lp)
444 set_attr(lp, attr_icflag, PROCESSED)
445 return false, node_next(lp)
447 [id_whatsit] = function(lp)
448 local lps = getsubtype(lp)
449 if lps==sid_user then
450 if getfield(lp, 'user_id')==luatexja.userid_table.IHB then
451 local lq = node_next(lp);
452 head = node_remove(head, lp); node_free(lp); non_ihb_flag = false
455 set_attr(lp, attr_icflag, PROCESSED)
456 luatexbase.call_callback("luatexja.jfmglue.whatsit_getinfo",
459 Np.id = id_pbox_w; Np.first = Np.nuc; Np.last = Np.nuc;
460 return true, node_next(lp)
462 return false, node_next(lp)
466 -- we do special treatment for these whatsit nodes.
467 if lps == sid_start_link or lps == sid_start_thread then
469 elseif lps == sid_end_link or lps == sid_end_thread then
470 Np.first, Nq.last = nil, lp;
472 set_attr(lp, attr_icflag, PROCESSED)
473 return false, node_next(lp)
476 [id_math] = function(lp)
477 Np.first, Np.nuc = (Np.first or lp), lp;
478 set_attr(lp, attr_icflag, PROCESSED)
479 set_np_xspc_alchar(Np, -1, lp)
480 local end_math = node_end_of_math(lp)
481 ltjw_apply_ashift_math(lp, end_math, attr_ablshift)
482 set_attr(end_math, attr_icflag, PROCESSED)
483 Np.last, Np.id = end_math, id_math;
484 return true, node_next(end_math);
486 [id_glue] = function(lp)
487 Np.first, Np.nuc, Np.last = (Np.first or lp), lp, lp;
488 Np.id = getid(lp); set_attr(lp, attr_icflag, PROCESSED)
489 return true, node_next(lp)
491 [id_disc] = function(lp)
492 Np.first, Np.nuc, Np.last = (Np.first or lp), lp, lp;
493 Np.id = getid(lp); set_attr(lp, attr_icflag, PROCESSED)
494 ltjw_apply_ashift_disc(lp, (list_dir==dir_tate), tex_dir)
495 Np.last_char = check_box_high(Np, getfield(lp, 'replace'), nil)
496 return true, node_next(lp)
498 [id_kern] = function(lp)
499 if getsubtype(lp)==2 then
500 Np.first = Np.first or lp
501 set_attr(lp, attr_icflag, PROCESSED); lp = node_next(lp)
502 if getid(lp)==id_glyph then -- アクセント本体
503 setfield(lp, 'yoffset', getfield(lp, 'yoffset') - (has_attr(lp,attr_ablshift) or 0))
504 else -- アクセントは上下にシフトされている
505 setfield(lp, 'shift', getfield(lp, 'shift') + (has_attr(lp,attr_ablshift) or 0))
507 set_attr(lp, attr_icflag, PROCESSED); lp = node_next(lp)
508 set_attr(lp, attr_icflag, PROCESSED); lp = node_next(lp)
509 set_attr(lp, attr_icflag, PROCESSED);
510 return calc_np_aux_glyph_common(lp)
512 Np.first = Np.first or lp
513 Np.id = id_kern; set_attr(lp, attr_icflag, PROCESSED)
514 Np.last = lp; return true, node_next(lp)
517 [id_penalty] = function(lp)
518 Bp[#Bp+1] = lp; set_attr(lp, attr_icflag, PROCESSED)
519 return false, node_next(lp)
522 calc_np_auxtable[id_rule] = calc_np_auxtable.box_like
523 if status.luatex_version>=85 then
524 calc_np_auxtable[15] = calc_np_auxtable.box_like
526 calc_np_auxtable[13] = calc_np_auxtable.box_like
528 calc_np_auxtable[id_ins] = calc_np_auxtable.skip
529 calc_np_auxtable[id_mark] = calc_np_auxtable.skip
530 calc_np_auxtable[id_adjust] = calc_np_auxtable.skip
531 if node.id('local_par') then
532 calc_np_auxtable[node.id('local_par')] = calc_np_auxtable.skip
535 function calc_np(last, lp)
537 -- We assume lp = node_next(Np.last)
538 Np, Nq, non_ihb_flag = Nq, Np, true
539 -- We clear `predefined' entries of Np before pairs() loop,
540 -- because using only pairs() loop is slower.
541 Np.post, Np.pre, Np.xspc = nil, nil, nil
542 Np.first, Np.id, Np.last, Np.met, Np.class= nil, nil, nil, nil
543 Np.auto_kspc, Np.auto_xspc, Np.char, Np.nuc = nil, nil, nil, nil
544 for k in pairs(Np) do Np[k] = nil end
546 for k = 1,#Bp do Bp[k] = nil end
548 local lpa = has_attr(lp, attr_icflag) or 0
551 if lpa%PROCESSED_BEGIN_FLAG == BOXBDD then
552 local lq = node_next(lp)
553 head = node_remove(head, lp); node_free(lp); lp = lq
555 return calc_np_pbox(lp, last)
558 k, lp = calc_np_auxtable[getid(lp)](lp)
559 if k then return lp end
566 -- extract informations from Np
567 -- We think that "Np is a Japanese character" if Np.met~=nil,
568 -- "Np is an alphabetic character" if Np.pre~=nil,
569 -- "Np is not a character" otherwise.
570 after_hlist = nil -- global
571 local after_alchar, extract_np
573 local PRE = luatexja.stack_table_index.PRE
574 local POST = luatexja.stack_table_index.POST
575 local KCAT = luatexja.stack_table_index.KCAT
576 local XSP = luatexja.stack_table_index.XSP
577 local dir_tate = luatexja.dir_table.dir_tate
580 local attr_jchar_class = luatexbase.attributes['ltj@charclass']
581 local attr_jchar_code = luatexbase.attributes['ltj@charcode']
582 local attr_autospc = luatexbase.attributes['ltj@autospc']
583 local attr_autoxspc = luatexbase.attributes['ltj@autoxspc']
584 --local ltjf_get_vert_glyph = ltjf.get_vert_glyph
585 function set_np_xspc_jachar(Nx, x)
586 local m = ltjf_font_metric_table[getfont(x)]
587 local c, c_glyph = ltjs_orig_char_table[x], getchar(x)
589 local cls = slow_find_char_class(c, m, c_glyph)
590 Nx.met, Nx.class, Nx.char = m, cls, c;
591 local mc = m.char_type; Nx.char_type = mc
592 if cls~=0 then set_attr(x, attr_jchar_class, cls) end
593 if c~=c_glyph then set_attr(x, attr_jchar_code, c) end
594 Nx.pre = table_current_stack[PRE + c] or 0
595 Nx.post = table_current_stack[POST + c] or 0
596 Nx.xspc = table_current_stack[XSP + c] or 3
597 Nx.kcat = table_current_stack[KCAT + c] or 0
598 Nx.auto_kspc, Nx.auto_xspc = (has_attr(x, attr_autospc)==1), (has_attr(x, attr_autoxspc)==1)
601 function set_np_xspc_jachar_hbox(Nx, x)
602 local m = ltjf_font_metric_table[getfont(x)]
603 local c = has_attr(x, attr_jchar_code) or getchar(x)
604 Nx.met, Nx.char = m, c; Nx.class = has_attr(x, attr_jchar_class) or 0;
605 local mc = m.char_type; Nx.char_type = mc
606 Nx.pre = table_current_stack[PRE + c] or 0
607 Nx.post = table_current_stack[POST + c] or 0
608 Nx.xspc = table_current_stack[XSP + c] or 3
609 Nx.kcat = table_current_stack[KCAT + c] or 0
610 Nx.auto_kspc, Nx.auto_xspc = (has_attr(x, attr_autospc)==1), (has_attr(x, attr_autoxspc)==1)
614 local floor = math.floor
615 function set_np_xspc_alchar(Nx, c,x, lig)
617 local f = (lig ==1) and nullfunc or node_tail
618 local xc, xs = getfield(x, 'components'), getsubtype(x)
619 while xc and xs and xs%4>=2 do
620 x = f(xc); xc, xs = getfield(x, 'components'), getsubtype(x)
623 Nx.pre = table_current_stack[PRE + c] or 0
624 Nx.post = table_current_stack[POST + c] or 0
625 Nx.xspc = table_current_stack[XSP + c] or 3
627 Nx.pre, Nx.post = 0, 0
628 Nx.xspc = table_current_stack[XSP - 1] or 3
631 Nx.auto_xspc = (has_attr(x, attr_autoxspc)==1)
633 local set_np_xspc_alchar = set_np_xspc_alchar
635 -- change the information for the next loop
636 -- (will be done if Nx is an alphabetic character or a hlist)
637 after_hlist = function (Nx)
638 local s = Nx.last_char
640 if getid(s)==id_glyph then
641 if getfield(s, 'lang') == lang_ja then
642 set_np_xspc_jachar_hbox(Nx, s)
644 set_np_xspc_alchar(Nx, getchar(s), s, 2)
647 set_np_xspc_alchar(Nx, -1, s)
650 Nx.pre, Nx.met = nil, nil
654 after_alchar = function (Nx)
655 local x = Nx.last_char
656 return set_np_xspc_alchar(Nx, getchar(x), x, 2)
661 -------------------- 最下層の処理
663 -- change penalties (or create a new penalty, if needed)
664 local function handle_penalty_normal(post, pre, g)
665 local a = (pre or 0) + (post or 0)
667 if (a~=0 and not(g and getid(g)==id_kern)) then
668 local p = node_new(id_penalty)
669 if a<-10000 then a = -10000 elseif a>10000 then a = 10000 end
670 setfield(p, 'penalty', a)
671 head = insert_before(head, Np.first, p)
673 set_attr(p, attr_icflag, KINSOKU)
675 else for _, v in pairs(Bp) do add_penalty(v,a) end
679 local function handle_penalty_always(post, pre, g)
680 local a = (pre or 0) + (post or 0)
682 if not (g and getid(g)==id_glue) or a~=0 then
683 local p = node_new(id_penalty)
684 if a<-10000 then a = -10000 elseif a>10000 then a = 10000 end
685 setfield(p, 'penalty', a)
686 head = insert_before(head, Np.first, p)
688 set_attr(p, attr_icflag, KINSOKU)
690 else for _, v in pairs(Bp) do add_penalty(v,a) end
694 local function handle_penalty_suppress(post, pre, g)
695 local a = (pre or 0) + (post or 0)
697 if g and getid(g)==id_glue then
698 local p = node_new(id_penalty)
699 setfield(p, 'penalty', 10000); head = insert_before(head, Np.first, p)
701 set_attr(p, attr_icflag, KINSOKU)
703 else for _, v in pairs(Bp) do add_penalty(v,a) end
707 -- 和文文字間の JFM glue を node 化
708 local function new_jfm_glue(mc, bc, ac)
709 -- bc, ac: char classes
713 local f = node_new(id_glue)
714 set_attr(f, attr_icflag, g.priority)
715 setfield(f, 'spec', node_copy(g[2]))
716 return f, g.ratio, g.kanjiskip_natural, g.kanjiskip_stretch, g.kanjiskip_shrink
718 return node_copy(g[2]), g.ratio, false, false, false
724 -- Nq.last (kern w) .... (glue/kern g) Np.first
725 local function real_insert(g)
727 head = insert_before(head, Np.first, g)
733 -------------------- 和文文字間空白量の決定
736 local bg_ag = 2*id_glue - id_glue
737 local bg_ak = 2*id_glue - id_kern
738 local bk_ag = 2*id_kern - id_glue
739 local bk_ak = 2*id_kern - id_kern
741 local function blend_diffmet(b, a, rb, ra)
742 return round(diffmet_rule((1-rb)*b+rb*a, (1-ra)*b+ra*a))
744 calc_ja_ja_aux = function (gb, ga, db, da)
745 if diffmet_rule ~= math.two_pleft and diffmet_rule ~= math.two_pright
746 and diffmet_rule ~= math.two_paverage then
751 gb = node_new(id_kern); setfield(gb, 'kern', 0)
754 ga = node_new(id_kern); setfield(ga, 'kern', 0)
757 local k = 2*getid(gb) - getid(ga)
759 local bs, as = getfield(gb, 'spec'), getfield(ga, 'spec')
761 setfield(bs, 'width', blend_diffmet(
762 getfield(bs, 'width'), getfield(as, 'width'), db, da))
763 setfield(bs, 'stretch', blend_diffmet(
764 getfield(bs, 'stretch'), getfield(as, 'stretch'), db, da))
765 setfield(bs, 'shrink', -blend_diffmet(
766 -getfield(bs, 'shrink'), -getfield(as, 'shrink'), db, da))
769 elseif k == bk_ak then
771 setfield(gb, 'kern', blend_diffmet(
772 getfield(gb, 'kern'), getfield(ga, 'kern'), db, da))
775 elseif k == bk_ag then
776 local as = getfield(ga, 'spec')
777 -- gb: kern, ga: glue
778 setfield(as, 'width', blend_diffmet(
779 getfield(gb, 'kern'), getfield(as, 'width'), db, da))
780 setfield(as, 'stretch', blend_diffmet(
781 0, getfield(as, 'stretch'), db, da))
782 setfield(as, 'shrink', -blend_diffmet(
783 0, -getfield(as, 'shrink'), db, da))
787 local bs = getfield(gb, 'spec')
788 -- gb: glue, ga: kern
789 setfield(bs, 'width', blend_diffmet(
790 getfield(bs, 'width'), getfield(ga, 'kern'), db, da))
791 setfield(bs, 'stretch', blend_diffmet(
792 getfield(bs, 'stretch'), 0, db, da))
793 setfield(bs, 'shrink', -blend_diffmet(
794 -getfield(bs, 'shrink'), 0, db, da))
801 local null_skip_table = {0, 0, 0}
803 local get_kanjiskip, kanjiskip_jfm_flag
804 local calc_ja_ja_glue
806 local KANJI_SKIP = luatexja.icflag_table.KANJI_SKIP
807 local KANJI_SKIP_JFM = luatexja.icflag_table.KANJI_SKIP_JFM
809 get_kanjiskip_low = function(flag, qm, bn, bp, bh)
810 if flag or (qm.with_kanjiskip and (bn or bp or bh)) then
811 if kanjiskip_jfm_flag then
812 local g = node_new(id_glue);
813 local gx = node_new(id_glue_spec);
814 setfield(gx, 'stretch_order', 0); setfield(gx, 'shrink_order', 0)
815 local bk = qm.kanjiskip or null_skip_table
816 setfield(gx, 'width', bn and (bn*bk[1]) or 0)
817 setfield(gx, 'stretch', bp and (bp*bk[2]) or 0)
818 setfield(gx, 'shrink', bh and (bh*bk[3]) or 0)
819 setfield(g, 'spec', gx)
820 set_attr(g, attr_icflag, KANJI_SKIP_JFM)
823 return node_copy(kanji_skip)
825 local g = node_new(id_glue);
826 local gx = node_new(id_glue_spec);
827 setfield(gx, 'stretch_order', 0); setfield(gx, 'shrink_order', 0)
828 local ks = getfield(kanji_skip, 'spec')
829 setfield(gx, 'width', bn and (bn*getfield(ks, 'width')) or 0)
830 setfield(gx, 'stretch', bp and (bp*getfield(ks, 'stretch')) or 0)
831 setfield(gx, 'shrink', bh and (bh*getfield(ks, 'shrink')) or 0)
832 setfield(g, 'spec', gx)
833 set_attr(g, attr_icflag, KANJI_SKIP_JFM)
839 get_kanjiskip = function()
840 if Np.auto_kspc or Nq.auto_kspc then
841 local pm, qm = Np.met, Nq.met
842 if (pm.char_type==qm.char_type) and (qm.var==pm.var) then
843 return get_kanjiskip_low(true, qm, 1, 1, 1)
845 local gb = get_kanjiskip_low(true, qm, 1, 1, 1)
846 local ga = get_kanjiskip_low(true, pm, 1, 1, 1)
847 return calc_ja_ja_aux(gb, ga, 0, 1)
850 local g = node_copy(zero_glue)
851 set_attr(g, attr_icflag, kanjiskip_jfm_flag and KANJI_SKIP_JFM or KANJI_SKIP)
856 calc_ja_ja_glue = function ()
857 local qm, pm = Nq.met, Np.met
858 local qmc, pmc = qm.char_type, pm.char_type
859 if (qmc==pmc) and (qm.var==pm.var) then
860 local g, _, kn, kp, kh = new_jfm_glue(qmc, Nq.class, Np.class)
861 return g, (Np.auto_kspc or Nq.auto_kspc) and get_kanjiskip_low(false, qm, kn, kp, kh)
863 local npn, nqn = Np.nuc, Nq.nuc
864 local gb, db, bn, bp, bh
865 = new_jfm_glue(qmc, Nq.class,
866 slow_find_char_class(Np.char,
868 local ga, da, an, ap, ah
870 slow_find_char_class(Nq.char,
873 local g = calc_ja_ja_aux(gb, ga, db, da)
875 if (pmc==qmc) and (qm.var==pm.var) then
876 gb = get_kanjiskip_low(false, qm, bn, bp, bh)
877 ga = get_kanjiskip_low(false, pm, an, ap, ah)
878 k = calc_ja_ja_aux(gb, ga, db, da)
885 -------------------- 和欧文間空白量の決定
888 local get_xkanjiskip, xkanjiskip_jfm_flag
889 local get_xkanjiskip_normal, get_xkanjiskip_jfm
891 local XKANJI_SKIP = luatexja.icflag_table.XKANJI_SKIP
892 local XKANJI_SKIP_JFM = luatexja.icflag_table.XKANJI_SKIP_JFM
894 get_xkanjiskip_low = function(flag, qm, bn, bp, bh)
895 if flag or (qm.with_kanjiskip and (bn or bp or bh)) then
896 if xkanjiskip_jfm_flag then
897 local g = node_new(id_glue);
898 local gx = node_new(id_glue_spec);
899 setfield(gx, 'stretch_order', 0); setfield(gx, 'shrink_order', 0)
900 local bk = qm.xkanjiskip or null_skip_table
901 setfield(gx, 'width', bn and bk[1] or 0)
902 setfield(gx, 'stretch', bp and bk[2] or 0)
903 setfield(gx, 'shrink', bh and bk[3] or 0)
904 setfield(g, 'spec', gx)
905 set_attr(g, attr_icflag, XKANJI_SKIP_JFM)
908 return node_copy(xkanji_skip)
910 local g = node_new(id_glue);
911 local gx = node_new(id_glue_spec);
912 setfield(gx, 'stretch_order', 0); setfield(gx, 'shrink_order', 0)
913 local ks = getfield(xkanji_skip, 'spec')
914 setfield(gx, 'width', bn and getfield(ks, 'width') or 0)
915 setfield(gx, 'stretch', bp and getfield(ks, 'stretch') or 0)
916 setfield(gx, 'shrink', bh and getfield(ks, 'shrink') or 0)
917 setfield(g, 'spec', gx)
918 set_attr(g, attr_icflag, XKANJI_SKIP_JFM)
924 get_xkanjiskip = function(Nn)
925 if (Nq.xspc>=2) and (Np.xspc%2==1) and (Nq.auto_xspc or Np.auto_xspc) then
926 return get_xkanjiskip_low(true, Nn.met, 1, 1, 1)
928 local g = node_copy(zero_glue)
929 set_attr(g, attr_icflag, xkanjiskip_jfm_flag and XKANJI_SKIP_JFM or XKANJI_SKIP)
935 -------------------- 隣接した「塊」間の処理
937 local function get_OA_skip(is_kanji)
939 local g, _, kn, kp, kh = new_jfm_glue(
941 fast_find_char_class((Nq.id == id_math and -1 or 'jcharbdd'), pm),
945 k = (Np.auto_kspc or Nq.auto_kspc) and get_kanjiskip_low(false, pm, kn, kp, kh)
946 elseif is_kanji==1 then
947 k = ((Nq.xspc>=2) and (Np.xspc%2==1) and (Nq.auto_xspc or Np.auto_xspc))
948 and get_xkanjiskip_low(false, pm, kn, kp, kh)
952 local function get_OB_skip(is_kanji)
954 local g, _, kn, kp, kh = new_jfm_glue(
955 qm.char_type, Nq.class,
956 fast_find_char_class((Np.id == id_math and -1 or'jcharbdd'), qm))
959 k = (Np.auto_kspc or Nq.auto_kspc) and get_kanjiskip_low(false, qm, kn, kp, kh)
960 elseif is_kanji==1 then
961 k = ((Nq.xspc>=2) and (Np.xspc%2==1) and (Nq.auto_xspc or Np.auto_xspc))
962 and get_xkanjiskip_low(false, qm, kn, kp, kh)
967 -- (anything) .. jachar
968 local function handle_np_jachar(mode)
970 if qid==id_jglyph or ((qid==id_pbox or qid==id_pbox_w) and Nq.met) then
972 if non_ihb_flag then g, k = calc_ja_ja_glue() end -- M->K
973 if not g then g = get_kanjiskip() end
974 handle_penalty_normal(Nq.post, Np.pre, g);
975 real_insert(g); real_insert(k)
976 elseif Nq.met then -- qid==id_hlist
978 if non_ihb_flag then g, k = get_OA_skip(0) end -- O_A->K
979 if not g then g = get_kanjiskip() end
980 handle_penalty_normal(0, Np.pre, g); real_insert(g); real_insert(k)
983 if non_ihb_flag then g, k = get_OA_skip(1) end -- O_A->X
984 if not g then g = get_xkanjiskip(Np) end
985 handle_penalty_normal((qid==id_hlist and 0 or Nq.post), Np.pre, g);
986 real_insert(g); real_insert(k)
988 local g = non_ihb_flag and (get_OA_skip()) -- O_A
989 if qid==id_glue then handle_penalty_normal(0, Np.pre, g)
990 elseif qid==id_kern then handle_penalty_suppress(0, Np.pre, g)
991 else handle_penalty_always(0, Np.pre, g)
995 if mode and Np.kcat%2~=1 then
996 widow_Np.first, widow_Bp, Bp = Np.first, Bp, widow_Bp
1001 -- jachar .. (anything)
1002 local function handle_nq_jachar()
1004 local g = non_ihb_flag and get_OB_skip(1) or get_xkanjiskip(Nq) -- O_B->X
1005 handle_penalty_normal(Nq.post, (Np.id==id_hlist and 0 or Np.pre), g); real_insert(g)
1007 local g =non_ihb_flag and (get_OB_skip()) -- O_B
1008 if Np.id==id_glue then handle_penalty_normal(Nq.post, 0, g)
1009 elseif Np.id==id_kern then handle_penalty_suppress(Nq.post, 0, g)
1010 else handle_penalty_always(Nq.post, 0, g)
1016 -- (anything) .. (和文文字で始まる hlist)
1017 local function handle_np_ja_hlist()
1019 if qid==id_jglyph or ((qid==id_pbox or Nq.id == id_pbox_w) and Nq.met) then
1020 local g = non_ihb_flag and get_OB_skip(0) or get_kanjiskip() -- O_B->K
1021 handle_penalty_normal(Nq.post, 0, g); real_insert(g)
1022 elseif Nq.met then -- Nq.id==id_hlist
1023 local g = get_kanjiskip() -- K
1024 handle_penalty_suppress(0, 0, g); real_insert(g)
1026 local g = get_xkanjiskip(Np) -- X
1027 handle_penalty_suppress(0, 0, g); real_insert(g)
1031 -- (和文文字で終わる hlist) .. (anything)
1032 local function handle_nq_ja_hlist()
1034 local g = get_xkanjiskip(Nq) -- X
1035 handle_penalty_suppress(0, 0, g); real_insert(g)
1040 -- Nq が前側のクラスタとなることによる修正
1042 local adjust_nq_aux = {
1043 [id_glyph] = function() after_alchar(Nq) end, -- after_alchar(Nq)
1044 [id_hlist] = function() after_hlist(Nq) end,
1045 [id_pbox] = function() after_hlist(Nq) end,
1046 [id_disc] = function() after_hlist(Nq) end,
1047 [id_pbox_w] = function()
1048 luatexbase.call_callback("luatexja.jfmglue.whatsit_after",
1053 function adjust_nq()
1054 local x = adjust_nq_aux[Nq.id]
1060 -------------------- 開始・終了時の処理
1064 local JWP = luatexja.stack_table_index.JWP
1065 local function handle_list_tail(mode)
1066 adjust_nq(); Np = Nq
1068 -- the current list is to be line-breaked.
1069 -- Insert \jcharwidowpenalty
1070 Bp = widow_Bp; Np = widow_Np
1072 handle_penalty_normal(0, table_current_stack[JWP] or 0)
1075 -- the current list is the contents of a hbox
1076 local npi, pm = Np.id, Np.met
1077 if npi == id_jglyph or (npi==id_pbox and pm) then
1078 local g = new_jfm_glue(pm.char_type, Np.class, fast_find_char_class('boxbdd', pm))
1080 set_attr(g, attr_icflag, BOXBDD)
1081 head = insert_after(head, Np.last, g)
1088 local function handle_list_head(par_indented)
1089 local npi, pm = Np.id, Np.met
1090 if npi == id_jglyph or (npi==id_pbox and pm) then
1091 if non_ihb_flag then
1092 local g = new_jfm_glue(pm.char_type, fast_find_char_class(par_indented, pm), Np.class)
1094 set_attr(g, attr_icflag, BOXBDD)
1095 if getid(g)==id_glue and #Bp==0 then
1096 local h = node_new(id_penalty)
1097 setfield(h, 'penalty', 10000); set_attr(h, attr_icflag, BOXBDD)
1099 head = insert_before(head, Np.first, g)
1106 -- return value: (the initial cursor lp), (last node)
1109 local id_local = node.id('local_par')
1110 local KANJI_SKIP = luatexja.icflag_table.KANJI_SKIP
1111 local XKANJI_SKIP = luatexja.icflag_table.XKANJI_SKIP
1112 local KSK = luatexja.stack_table_index.KSK
1113 local XSK = luatexja.stack_table_index.XSK
1114 local dir_yoko = luatexja.dir_table.dir_yoko
1115 local dir_tate = luatexja.dir_table.dir_tate
1116 local attr_yablshift = luatexbase.attributes['ltj@yablshift']
1117 local attr_tablshift = luatexbase.attributes['ltj@tablshift']
1118 local table_pool = {
1119 {}, {}, {first=nil},
1120 { auto_kspc=nil, auto_xspc=nil, char=nil, class=nil,
1121 first=nil, id=nil, last=nil, met=nil, nuc=nil,
1122 post=nil, pre=nil, xspc=nil, },
1123 { auto_kspc=nil, auto_xspc=nil, char=nil, class=nil,
1124 first=nil, id=nil, last=nil, met=nil, nuc=nil,
1125 post=nil, pre=nil, xspc=nil, },
1127 init_var = function (mode,dir)
1128 -- 1073741823: max_dimen
1129 Bp, widow_Bp, widow_Np, Np, Nq
1130 = table_pool[1], table_pool[2], table_pool[3], table_pool[4], table_pool[5]
1131 for i=1,5 do for j,_ in pairs(table_pool[i]) do table_pool[i][j]=nil end end
1132 table_current_stack = ltjs.table_current_stack
1134 list_dir, tex_dir = (ltjs.list_dir or dir_yoko), (dir or 'TLT')
1135 local is_dir_tate = list_dir==dir_tate
1136 capsule_glyph = is_dir_tate and ltjw.capsule_glyph_tate or ltjw.capsule_glyph_yoko
1137 attr_ablshift = is_dir_tate and attr_tablshift or attr_yablshift
1138 local TEMP = node_new(id_glue)
1139 -- TEMP is a dummy node, which will be freed at the end of the callback.
1140 -- ithout this node, set_attr(kanji_skip, ...) somehow creates an "orphaned" attribute list.
1143 kanji_skip = node_new(id_glue); set_attr(kanji_skip, attr_icflag, KANJI_SKIP)
1144 local s = skip_table_to_spec(KSK)
1145 setfield(kanji_skip, 'spec', s)
1146 kanjiskip_jfm_flag = (getfield(s, 'width') == 1073741823)
1150 xkanji_skip = node_new(id_glue); set_attr(xkanji_skip, attr_icflag, XKANJI_SKIP)
1151 local s = skip_table_to_spec(XSK)
1152 setfield(xkanji_skip, 'spec', s)
1153 xkanjiskip_jfm_flag = (getfield(s, 'width') == 1073741823)
1157 -- the current list is to be line-breaked:
1158 -- hbox from \parindent is skipped.
1159 local lp, par_indented, lpi, lps = head, 'boxbdd', getid(head), getsubtype(head)
1161 ((lpi==id_whatsit and lps~=sid_user)
1162 or ((lpi==id_hlist) and (lps==3))
1163 or (lpi==id_local)) do
1164 if (lpi==id_hlist) and (lps==3) then
1165 Np.char, par_indented = 'parbdd', 'parbdd'
1166 Np.width = getfield(lp, 'width')
1168 lp=node_next(lp); lpi, lps = getid(lp), getsubtype(lp) end
1169 return lp, node_tail(head), par_indented, TEMP
1171 return head, nil, 'boxbdd', TEMP
1176 local ensure_tex_attr = ltjb.ensure_tex_attr
1177 local function cleanup(mode, TEMP)
1178 -- adjust attr_icflag for avoiding error
1179 if tex.getattribute(attr_icflag)~=0 then ensure_tex_attr(attr_icflag, 0) end
1180 node_free(kanji_skip);
1181 node_free(xkanji_skip); node_free(TEMP)
1184 local h = node_next(head)
1185 if getid(h) == id_penalty and getfield(h, 'penalty') == 10000 then
1187 if getid(h) == id_glue and getsubtype(h) == 15 and not node_next(h) then
1194 -------------------- 外部から呼ばれる関数
1197 function main(ahead, mode, dir)
1198 if not ahead then return ahead end
1200 local lp, last, par_indented, TEMP = init_var(mode,dir)
1201 lp = calc_np(last, lp)
1203 handle_list_head(par_indented)
1204 lp = calc_np(last,lp);
1207 local pid, pm = Np.id, Np.met
1209 if pid == id_jglyph then
1210 handle_np_jachar(mode)
1212 if pid==id_hlist then handle_np_ja_hlist()
1213 else handle_np_jachar() end
1215 if Nq.id==id_hlist then handle_nq_ja_hlist()
1216 else handle_nq_jachar() end
1218 lp = calc_np(last,lp)
1220 handle_list_tail(mode)
1222 return cleanup(mode, TEMP)
1227 local IHB = luatexja.userid_table.IHB
1228 local BPAR = luatexja.userid_table.BPAR
1229 local node_prev = (Dnode ~= node) and Dnode.getprev or node.prev
1230 local node_write = Dnode.write
1233 function create_inhibitglue_node()
1234 local tn = node_new(id_whatsit, sid_user)
1235 setfield(tn, 'user_id', IHB)
1236 setfield(tn, 'type', 100)
1237 setfield(tn, 'value', 1)
1241 -- Node for indicating beginning of a paragraph
1242 -- (for ltjsclasses)
1243 function create_beginpar_node()
1244 local tn = node_new(id_whatsit, sid_user)
1245 setfield(tn, 'user_id', BPAR)
1246 setfield(tn, 'type', 100)
1247 setfield(tn, 'value', 1)
1251 local function whatsit_callback(Np, lp, Nq)
1252 if Np and Np.nuc then return Np
1253 elseif Np and getfield(lp, 'user_id') == BPAR then
1254 Np.first = lp; Np.nuc = lp; Np.last = lp
1259 local function whatsit_after_callback(s, Nq, Np)
1260 if not s and getfield(Nq.nuc, 'user_id') == BPAR then
1261 local x, y = node_prev(Nq.nuc), Nq.nuc
1262 Nq.first, Nq.nuc, Nq.last = x, x, x
1265 Nq.class = fast_find_char_class('parbdd', Np.met)
1267 Nq.met = Np.met; Nq.pre = 0; Nq.post = 0; Nq.xspc = 0
1268 Nq.auto_xspc = false
1270 head = node_remove(head, y)
1276 luatexbase.add_to_callback("luatexja.jfmglue.whatsit_getinfo", whatsit_callback,
1277 "luatexja.beginpar.np_info", 1)
1278 luatexbase.add_to_callback("luatexja.jfmglue.whatsit_after", whatsit_after_callback,
1279 "luatexja.beginpar.np_info_after", 1)