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 function calc_np_aux_glyph_common(lp)
323 Np.first= (Np.first or lp)
324 if getfield(lp, 'lang') == lang_ja then
326 local m, mc, cls = set_np_xspc_jachar(Np, lp)
328 lp, head, npi, npf = capsule_glyph(lp, m, mc[cls], head, tex_dir)
329 Np.first = (Np.first~=Np.nuc) and Np.first or npf or npi
331 return true, check_next_ickern(lp);
334 set_np_xspc_alchar(Np, getchar(lp), lp, 1)
336 local first_glyph, last_glyph = lp
337 set_attr(lp, attr_icflag, PROCESSED); Np.last = lp
338 local y_adjust = has_attr(lp,attr_ablshift) or 0
339 local node_depth = getfield(lp, 'depth') + min(y_adjust, 0)
340 local adj_depth = (y_adjust>0) and (getfield(lp, 'depth') + y_adjust) or 0
341 setfield(lp, 'yoffset', getfield(lp, 'yoffset') - y_adjust)
343 for lx in traverse(lp) do
344 local lai = get_attr_icflag(lx)
345 if lx==last or lai>=PACKED then
348 local lid = getid(lx)
349 if lid==id_glyph and getfield(lx, 'lang') ~= lang_ja then
351 last_glyph = lx; set_attr(lx, attr_icflag, PROCESSED); Np.last = lx
352 y_adjust = has_attr(lx,attr_ablshift) or 0
353 node_depth = max(getfield(lx, 'depth') + min(y_adjust, 0), node_depth)
354 adj_depth = (y_adjust>0) and max(getfield(lx, 'depth') + y_adjust, adj_depth) or adj_depth
355 setfield(lx, 'yoffset', getfield(lx, 'yoffset') - y_adjust)
356 elseif lid==id_kern then
357 local ls = getsubtype(lx)
358 if ls==2 then -- アクセント用の kern
359 set_attr(lx, attr_icflag, PROCESSED)
360 lx = node_next(lx) -- lx: アクセント本体
361 if getid(lx)==id_glyph then
362 setfield(lx, 'yoffset', getfield(lx, 'yoffset') - (has_attr(lx,attr_ablshift) or 0))
363 else -- アクセントは上下にシフトされている
364 setfield(lx, 'shift', getfield(lx, 'shift') + (has_attr(lx,attr_ablshift) or 0))
366 lx = node_next(node_next(lx))
369 elseif (ls==3) or (lai==ITALIC) then
370 Np.last = lx; set_attr(lx, attr_icflag, IC_PROCESSED)
380 if adj_depth>node_depth then
381 r = node_new(id_rule)
382 setfield(r, 'width', 0); setfield(r, 'height', 0)
383 setfield(r, 'depth',adj_depth); setfield(r, 'dir', tex_dir)
384 set_attr(r, attr_icflag, PROCESSED)
387 Np.last_char = last_glyph
388 if r then insert_after(head, first_glyph, r) end
393 local nf, nc = getfont(npn), getchar(npn)
394 local ct = (font.getfont(nf) or font.fonts[nf] ).characters[nc]
395 if not ct then -- variation selector
397 elseif (ct.left_protruding or 0) == 0 then
398 head = insert_before(head, npn, r)
399 Np.first = (Np.first==npn) and r or npn
400 elseif (ct.right_protruding or 0) == 0 then
401 insert_after(head, npn, r); Np.last, lp = r, r
403 ltjb.package_warning_no_line(
405 'Check depth of glyph node ' .. tostring(npn) .. '(font=' .. nf
406 .. ', char=' .. nc .. '), because its \\lpcode is ' .. tostring(ct.left_protruding)
407 .. ' and its \\rpcode is ' .. tostring(ct.right_protruding)
415 local calc_np_auxtable = {
416 [id_glyph] = calc_np_aux_glyph_common,
417 [id_hlist] = function(lp)
419 head, lp, op, flag = ltjd_make_dir_whatsit(head, lp, list_dir, 'jfm hlist')
420 set_attr(op, attr_icflag, PROCESSED)
421 Np.first = Np.first or op; Np.last = op; Np.nuc = op;
422 if (flag or getfield(op, 'shift')~=0) then
426 Np.last_char = check_box_high(Np, getlist(op), nil)
430 [id_vlist] = function(lp)
432 head, lp, op = ltjd_make_dir_whatsit(head, lp, list_dir, 'jfm:' .. getid(lp))
433 Np.first = Np.first or op; Np.last = op; Np.nuc = op;
437 box_like = function(lp)
438 Np.first = Np.first or lp; Np.last = lp; Np.nuc = lp;
440 return true, node_next(lp)
443 set_attr(lp, attr_icflag, PROCESSED)
444 return false, node_next(lp)
446 [id_whatsit] = function(lp)
447 local lps = getsubtype(lp)
448 if lps==sid_user then
449 if getfield(lp, 'user_id')==luatexja.userid_table.IHB then
450 local lq = node_next(lp);
451 head = node_remove(head, lp); node_free(lp); non_ihb_flag = false
454 set_attr(lp, attr_icflag, PROCESSED)
455 luatexbase.call_callback("luatexja.jfmglue.whatsit_getinfo",
458 Np.id = id_pbox_w; Np.first = Np.nuc; Np.last = Np.nuc;
459 return true, node_next(lp)
461 return false, node_next(lp)
465 -- we do special treatment for these whatsit nodes.
466 if lps == sid_start_link or lps == sid_start_thread then
468 elseif lps == sid_end_link or lps == sid_end_thread then
469 Np.first, Nq.last = nil, lp;
471 set_attr(lp, attr_icflag, PROCESSED)
472 return false, node_next(lp)
475 [id_math] = function(lp)
476 Np.first, Np.nuc = (Np.first or lp), lp;
477 set_attr(lp, attr_icflag, PROCESSED)
478 set_np_xspc_alchar(Np, -1, lp)
479 local end_math = node_end_of_math(lp)
480 ltjw_apply_ashift_math(lp, end_math, attr_ablshift)
481 set_attr(end_math, attr_icflag, PROCESSED)
482 Np.last, Np.id = end_math, id_math;
483 return true, node_next(end_math);
485 [id_glue] = function(lp)
486 Np.first, Np.nuc, Np.last = (Np.first or lp), lp, lp;
487 Np.id = getid(lp); set_attr(lp, attr_icflag, PROCESSED)
488 return true, node_next(lp)
490 [id_disc] = function(lp)
491 Np.first, Np.nuc, Np.last = (Np.first or lp), lp, lp;
492 Np.id = getid(lp); set_attr(lp, attr_icflag, PROCESSED)
493 ltjw_apply_ashift_disc(lp, (list_dir==dir_tate), tex_dir)
494 Np.last_char = check_box_high(Np, getfield(lp, 'replace'), nil)
495 return true, node_next(lp)
497 [id_kern] = function(lp)
498 if getsubtype(lp)==2 then
499 Np.first = Np.first or lp
500 set_attr(lp, attr_icflag, PROCESSED); lp = node_next(lp)
501 if getid(lp)==id_glyph then -- アクセント本体
502 setfield(lp, 'yoffset', getfield(lp, 'yoffset') - (has_attr(lp,attr_ablshift) or 0))
503 else -- アクセントは上下にシフトされている
504 setfield(lp, 'shift', getfield(lp, 'shift') + (has_attr(lp,attr_ablshift) or 0))
506 set_attr(lp, attr_icflag, PROCESSED); lp = node_next(lp)
507 set_attr(lp, attr_icflag, PROCESSED); lp = node_next(lp)
508 set_attr(lp, attr_icflag, PROCESSED);
509 return calc_np_aux_glyph_common(lp)
511 Np.first = Np.first or lp
512 Np.id = id_kern; set_attr(lp, attr_icflag, PROCESSED)
513 Np.last = lp; return true, node_next(lp)
516 [id_penalty] = function(lp)
517 Bp[#Bp+1] = lp; set_attr(lp, attr_icflag, PROCESSED)
518 return false, node_next(lp)
521 calc_np_auxtable[id_rule] = calc_np_auxtable.box_like
522 if status.luatex_version>=85 then
523 calc_np_auxtable[15] = calc_np_auxtable.box_like
525 calc_np_auxtable[13] = calc_np_auxtable.box_like
527 calc_np_auxtable[id_ins] = calc_np_auxtable.skip
528 calc_np_auxtable[id_mark] = calc_np_auxtable.skip
529 calc_np_auxtable[id_adjust] = calc_np_auxtable.skip
530 if node.id('local_par') then
531 calc_np_auxtable[node.id('local_par')] = calc_np_auxtable.skip
534 function calc_np(last, lp)
536 -- We assume lp = node_next(Np.last)
537 Np, Nq, non_ihb_flag = Nq, Np, true
538 -- We clear `predefined' entries of Np before pairs() loop,
539 -- because using only pairs() loop is slower.
540 Np.post, Np.pre, Np.xspc = nil, nil, nil
541 Np.first, Np.id, Np.last, Np.met, Np.class= nil, nil, nil, nil
542 Np.auto_kspc, Np.auto_xspc, Np.char, Np.nuc = nil, nil, nil, nil
543 for k in pairs(Np) do Np[k] = nil end
545 for k = 1,#Bp do Bp[k] = nil end
547 local lpa = has_attr(lp, attr_icflag) or 0
550 if lpa%PROCESSED_BEGIN_FLAG == BOXBDD then
551 local lq = node_next(lp)
552 head = node_remove(head, lp); node_free(lp); lp = lq
554 return calc_np_pbox(lp, last)
557 k, lp = calc_np_auxtable[getid(lp)](lp)
558 if k then return lp end
565 -- extract informations from Np
566 -- We think that "Np is a Japanese character" if Np.met~=nil,
567 -- "Np is an alphabetic character" if Np.pre~=nil,
568 -- "Np is not a character" otherwise.
569 after_hlist = nil -- global
570 local after_alchar, extract_np
572 local PRE = luatexja.stack_table_index.PRE
573 local POST = luatexja.stack_table_index.POST
574 local KCAT = luatexja.stack_table_index.KCAT
575 local XSP = luatexja.stack_table_index.XSP
576 local dir_tate = luatexja.dir_table.dir_tate
579 local attr_jchar_class = luatexbase.attributes['ltj@charclass']
580 local attr_jchar_code = luatexbase.attributes['ltj@charcode']
581 local attr_autospc = luatexbase.attributes['ltj@autospc']
582 local attr_autoxspc = luatexbase.attributes['ltj@autoxspc']
583 --local ltjf_get_vert_glyph = ltjf.get_vert_glyph
584 function set_np_xspc_jachar(Nx, x)
585 local m = ltjf_font_metric_table[getfont(x)]
586 local c, c_glyph = ltjs_orig_char_table[x], getchar(x)
588 local cls = slow_find_char_class(c, m, c_glyph)
589 Nx.met, Nx.class, Nx.char = m, cls, c;
590 local mc = m.char_type; Nx.char_type = mc
591 if cls~=0 then set_attr(x, attr_jchar_class, cls) end
592 if c~=c_glyph then set_attr(x, attr_jchar_code, c) end
593 Nx.pre = table_current_stack[PRE + c] or 0
594 Nx.post = table_current_stack[POST + c] or 0
595 Nx.xspc = table_current_stack[XSP + c] or 3
596 Nx.kcat = table_current_stack[KCAT + c] or 0
597 Nx.auto_kspc, Nx.auto_xspc = (has_attr(x, attr_autospc)==1), (has_attr(x, attr_autoxspc)==1)
600 function set_np_xspc_jachar_hbox(Nx, x)
601 local m = ltjf_font_metric_table[getfont(x)]
602 local c = has_attr(x, attr_jchar_code) or getchar(x)
603 Nx.met, Nx.char = m, c; Nx.class = has_attr(x, attr_jchar_class) or 0;
604 local mc = m.char_type; Nx.char_type = mc
605 Nx.pre = table_current_stack[PRE + c] or 0
606 Nx.post = table_current_stack[POST + c] or 0
607 Nx.xspc = table_current_stack[XSP + c] or 3
608 Nx.kcat = table_current_stack[KCAT + c] or 0
609 Nx.auto_kspc, Nx.auto_xspc = (has_attr(x, attr_autospc)==1), (has_attr(x, attr_autoxspc)==1)
613 local floor = math.floor
614 function set_np_xspc_alchar(Nx, c,x, lig)
616 local f = (lig ==1) and nullfunc or node_tail
617 local xc, xs = getfield(x, 'components'), getsubtype(x)
618 while xc and xs and xs%4>=2 do
619 x = f(xc); xc, xs = getfield(x, 'components'), getsubtype(x)
622 Nx.pre = table_current_stack[PRE + c] or 0
623 Nx.post = table_current_stack[POST + c] or 0
624 Nx.xspc = table_current_stack[XSP + c] or 3
626 Nx.pre, Nx.post = 0, 0
627 Nx.xspc = table_current_stack[XSP - 1] or 3
630 Nx.auto_xspc = (has_attr(x, attr_autoxspc)==1)
632 local set_np_xspc_alchar = set_np_xspc_alchar
634 -- change the information for the next loop
635 -- (will be done if Nx is an alphabetic character or a hlist)
636 after_hlist = function (Nx)
637 local s = Nx.last_char
639 if getid(s)==id_glyph then
640 if getfield(s, 'lang') == lang_ja then
641 set_np_xspc_jachar_hbox(Nx, s)
643 set_np_xspc_alchar(Nx, getchar(s), s, 2)
646 set_np_xspc_alchar(Nx, -1, s)
649 Nx.pre, Nx.met = nil, nil
653 after_alchar = function (Nx)
654 local x = Nx.last_char
655 return set_np_xspc_alchar(Nx, getchar(x), x, 2)
660 -------------------- 最下層の処理
662 -- change penalties (or create a new penalty, if needed)
663 local function handle_penalty_normal(post, pre, g)
664 local a = (pre or 0) + (post or 0)
666 if (a~=0 and not(g and getid(g)==id_kern)) then
667 local p = node_new(id_penalty)
668 if a<-10000 then a = -10000 elseif a>10000 then a = 10000 end
669 setfield(p, 'penalty', a)
670 head = insert_before(head, Np.first, p)
672 set_attr(p, attr_icflag, KINSOKU)
674 else for _, v in pairs(Bp) do add_penalty(v,a) end
678 local function handle_penalty_always(post, pre, g)
679 local a = (pre or 0) + (post or 0)
681 if not (g and getid(g)==id_glue) or a~=0 then
682 local p = node_new(id_penalty)
683 if a<-10000 then a = -10000 elseif a>10000 then a = 10000 end
684 setfield(p, 'penalty', a)
685 head = insert_before(head, Np.first, p)
687 set_attr(p, attr_icflag, KINSOKU)
689 else for _, v in pairs(Bp) do add_penalty(v,a) end
693 local function handle_penalty_suppress(post, pre, g)
694 local a = (pre or 0) + (post or 0)
696 if g and getid(g)==id_glue then
697 local p = node_new(id_penalty)
698 setfield(p, 'penalty', 10000); head = insert_before(head, Np.first, p)
700 set_attr(p, attr_icflag, KINSOKU)
702 else for _, v in pairs(Bp) do add_penalty(v,a) end
706 -- 和文文字間の JFM glue を node 化
707 local function new_jfm_glue(mc, bc, ac)
708 -- bc, ac: char classes
712 local f = node_new(id_glue)
713 set_attr(f, attr_icflag, g.priority)
714 setfield(f, 'spec', node_copy(g[2]))
715 return f, g.ratio, g.kanjiskip_natural, g.kanjiskip_stretch, g.kanjiskip_shrink
717 return node_copy(g[2]), g.ratio, false, false, false
723 -- Nq.last (kern w) .... (glue/kern g) Np.first
724 local function real_insert(g)
726 head = insert_before(head, Np.first, g)
732 -------------------- 和文文字間空白量の決定
735 local bg_ag = 2*id_glue - id_glue
736 local bg_ak = 2*id_glue - id_kern
737 local bk_ag = 2*id_kern - id_glue
738 local bk_ak = 2*id_kern - id_kern
740 local function blend_diffmet(b, a, rb, ra)
741 return round(diffmet_rule((1-rb)*b+rb*a, (1-ra)*b+ra*a))
743 calc_ja_ja_aux = function (gb, ga, db, da)
744 if diffmet_rule ~= math.two_pleft and diffmet_rule ~= math.two_pright
745 and diffmet_rule ~= math.two_paverage then
750 gb = node_new(id_kern); setfield(gb, 'kern', 0)
753 ga = node_new(id_kern); setfield(ga, 'kern', 0)
756 local k = 2*getid(gb) - getid(ga)
758 local bs, as = getfield(gb, 'spec'), getfield(ga, 'spec')
760 setfield(bs, 'width', blend_diffmet(
761 getfield(bs, 'width'), getfield(as, 'width'), db, da))
762 setfield(bs, 'stretch', blend_diffmet(
763 getfield(bs, 'stretch'), getfield(as, 'stretch'), db, da))
764 setfield(bs, 'shrink', -blend_diffmet(
765 -getfield(bs, 'shrink'), -getfield(as, 'shrink'), db, da))
768 elseif k == bk_ak then
770 setfield(gb, 'kern', blend_diffmet(
771 getfield(gb, 'kern'), getfield(ga, 'kern'), db, da))
774 elseif k == bk_ag then
775 local as = getfield(ga, 'spec')
776 -- gb: kern, ga: glue
777 setfield(as, 'width', blend_diffmet(
778 getfield(gb, 'kern'), getfield(as, 'width'), db, da))
779 setfield(as, 'stretch', blend_diffmet(
780 0, getfield(as, 'stretch'), db, da))
781 setfield(as, 'shrink', -blend_diffmet(
782 0, -getfield(as, 'shrink'), db, da))
786 local bs = getfield(gb, 'spec')
787 -- gb: glue, ga: kern
788 setfield(bs, 'width', blend_diffmet(
789 getfield(bs, 'width'), getfield(ga, 'kern'), db, da))
790 setfield(bs, 'stretch', blend_diffmet(
791 getfield(bs, 'stretch'), 0, db, da))
792 setfield(bs, 'shrink', -blend_diffmet(
793 -getfield(bs, 'shrink'), 0, db, da))
800 local null_skip_table = {0, 0, 0}
802 local get_kanjiskip, kanjiskip_jfm_flag
803 local calc_ja_ja_glue
805 local KANJI_SKIP = luatexja.icflag_table.KANJI_SKIP
806 local KANJI_SKIP_JFM = luatexja.icflag_table.KANJI_SKIP_JFM
808 get_kanjiskip_low = function(flag, qm, bn, bp, bh)
809 if flag or (qm.with_kanjiskip and (bn or bp or bh)) then
810 if kanjiskip_jfm_flag then
811 local g = node_new(id_glue);
812 local gx = node_new(id_glue_spec);
813 setfield(gx, 'stretch_order', 0); setfield(gx, 'shrink_order', 0)
814 local bk = qm.kanjiskip or null_skip_table
815 setfield(gx, 'width', bn and (bn*bk[1]) or 0)
816 setfield(gx, 'stretch', bp and (bp*bk[2]) or 0)
817 setfield(gx, 'shrink', bh and (bh*bk[3]) or 0)
818 setfield(g, 'spec', gx)
819 set_attr(g, attr_icflag, KANJI_SKIP_JFM)
822 return node_copy(kanji_skip)
824 local g = node_new(id_glue);
825 local gx = node_new(id_glue_spec);
826 setfield(gx, 'stretch_order', 0); setfield(gx, 'shrink_order', 0)
827 local ks = getfield(kanji_skip, 'spec')
828 setfield(gx, 'width', bn and (bn*getfield(ks, 'width')) or 0)
829 setfield(gx, 'stretch', bp and (bp*getfield(ks, 'stretch')) or 0)
830 setfield(gx, 'shrink', bh and (bh*getfield(ks, 'shrink')) or 0)
831 setfield(g, 'spec', gx)
832 set_attr(g, attr_icflag, KANJI_SKIP_JFM)
838 get_kanjiskip = function()
839 if Np.auto_kspc or Nq.auto_kspc then
840 local pm, qm = Np.met, Nq.met
841 if (pm.char_type==qm.char_type) and (qm.var==pm.var) then
842 return get_kanjiskip_low(true, qm, 1, 1, 1)
844 local gb = get_kanjiskip_low(true, qm, 1, 1, 1)
845 local ga = get_kanjiskip_low(true, pm, 1, 1, 1)
846 return calc_ja_ja_aux(gb, ga, 0, 1)
849 local g = node_copy(zero_glue)
850 set_attr(g, attr_icflag, kanjiskip_jfm_flag and KANJI_SKIP_JFM or KANJI_SKIP)
855 calc_ja_ja_glue = function ()
856 local qm, pm = Nq.met, Np.met
857 local qmc, pmc = qm.char_type, pm.char_type
858 if (qmc==pmc) and (qm.var==pm.var) then
859 local g, _, kn, kp, kh = new_jfm_glue(qmc, Nq.class, Np.class)
860 return g, (Np.auto_kspc or Nq.auto_kspc) and get_kanjiskip_low(false, qm, kn, kp, kh)
862 local npn, nqn = Np.nuc, Nq.nuc
863 local gb, db, bn, bp, bh
864 = new_jfm_glue(qmc, Nq.class,
865 slow_find_char_class(Np.char,
867 local ga, da, an, ap, ah
869 slow_find_char_class(Nq.char,
872 local g = calc_ja_ja_aux(gb, ga, db, da)
874 if (pmc==qmc) and (qm.var==pm.var) then
875 gb = get_kanjiskip_low(false, qm, bn, bp, bh)
876 ga = get_kanjiskip_low(false, pm, an, ap, ah)
877 k = calc_ja_ja_aux(gb, ga, db, da)
884 -------------------- 和欧文間空白量の決定
887 local get_xkanjiskip, xkanjiskip_jfm_flag
888 local get_xkanjiskip_normal, get_xkanjiskip_jfm
890 local XKANJI_SKIP = luatexja.icflag_table.XKANJI_SKIP
891 local XKANJI_SKIP_JFM = luatexja.icflag_table.XKANJI_SKIP_JFM
893 get_xkanjiskip_low = function(flag, qm, bn, bp, bh)
894 if flag or (qm.with_kanjiskip and (bn or bp or bh)) then
895 if xkanjiskip_jfm_flag then
896 local g = node_new(id_glue);
897 local gx = node_new(id_glue_spec);
898 setfield(gx, 'stretch_order', 0); setfield(gx, 'shrink_order', 0)
899 local bk = qm.xkanjiskip or null_skip_table
900 setfield(gx, 'width', bn and bk[1] or 0)
901 setfield(gx, 'stretch', bp and bk[2] or 0)
902 setfield(gx, 'shrink', bh and bk[3] or 0)
903 setfield(g, 'spec', gx)
904 set_attr(g, attr_icflag, XKANJI_SKIP_JFM)
907 return node_copy(xkanji_skip)
909 local g = node_new(id_glue);
910 local gx = node_new(id_glue_spec);
911 setfield(gx, 'stretch_order', 0); setfield(gx, 'shrink_order', 0)
912 local ks = getfield(xkanji_skip, 'spec')
913 setfield(gx, 'width', bn and getfield(ks, 'width') or 0)
914 setfield(gx, 'stretch', bp and getfield(ks, 'stretch') or 0)
915 setfield(gx, 'shrink', bh and getfield(ks, 'shrink') or 0)
916 setfield(g, 'spec', gx)
917 set_attr(g, attr_icflag, XKANJI_SKIP_JFM)
923 get_xkanjiskip = function(Nn)
924 if (Nq.xspc>=2) and (Np.xspc%2==1) and (Nq.auto_xspc or Np.auto_xspc) then
925 return get_xkanjiskip_low(true, Nn.met, 1, 1, 1)
927 local g = node_copy(zero_glue)
928 set_attr(g, attr_icflag, xkanjiskip_jfm_flag and XKANJI_SKIP_JFM or XKANJI_SKIP)
934 -------------------- 隣接した「塊」間の処理
936 local function get_OA_skip(is_kanji)
938 local g, _, kn, kp, kh = new_jfm_glue(
940 fast_find_char_class((Nq.id == id_math and -1 or 'jcharbdd'), pm),
944 k = (Np.auto_kspc or Nq.auto_kspc) and get_kanjiskip_low(false, pm, kn, kp, kh)
945 elseif is_kanji==1 then
946 k = ((Nq.xspc>=2) and (Np.xspc%2==1) and (Nq.auto_xspc or Np.auto_xspc))
947 and get_xkanjiskip_low(false, pm, kn, kp, kh)
951 local function get_OB_skip(is_kanji)
953 local g, _, kn, kp, kh = new_jfm_glue(
954 qm.char_type, Nq.class,
955 fast_find_char_class((Np.id == id_math and -1 or'jcharbdd'), qm))
958 k = (Np.auto_kspc or Nq.auto_kspc) and get_kanjiskip_low(false, qm, kn, kp, kh)
959 elseif is_kanji==1 then
960 k = ((Nq.xspc>=2) and (Np.xspc%2==1) and (Nq.auto_xspc or Np.auto_xspc))
961 and get_xkanjiskip_low(false, qm, kn, kp, kh)
966 -- (anything) .. jachar
967 local function handle_np_jachar(mode)
969 if qid==id_jglyph or ((qid==id_pbox or qid==id_pbox_w) and Nq.met) then
971 if non_ihb_flag then g, k = calc_ja_ja_glue() end -- M->K
972 if not g then g = get_kanjiskip() end
973 handle_penalty_normal(Nq.post, Np.pre, g);
974 real_insert(g); real_insert(k)
975 elseif Nq.met then -- qid==id_hlist
977 if non_ihb_flag then g, k = get_OA_skip(0) end -- O_A->K
978 if not g then g = get_kanjiskip() end
979 handle_penalty_normal(0, Np.pre, g); real_insert(g); real_insert(k)
982 if non_ihb_flag then g, k = get_OA_skip(1) end -- O_A->X
983 if not g then g = get_xkanjiskip(Np) end
984 handle_penalty_normal((qid==id_hlist and 0 or Nq.post), Np.pre, g);
985 real_insert(g); real_insert(k)
987 local g = non_ihb_flag and (get_OA_skip()) -- O_A
988 if qid==id_glue then handle_penalty_normal(0, Np.pre, g)
989 elseif qid==id_kern then handle_penalty_suppress(0, Np.pre, g)
990 else handle_penalty_always(0, Np.pre, g)
994 if mode and Np.kcat%2~=1 then
995 widow_Np.first, widow_Bp, Bp = Np.first, Bp, widow_Bp
1000 -- jachar .. (anything)
1001 local function handle_nq_jachar()
1003 local g = non_ihb_flag and get_OB_skip(1) or get_xkanjiskip(Nq) -- O_B->X
1004 handle_penalty_normal(Nq.post, (Np.id==id_hlist and 0 or Np.pre), g); real_insert(g)
1006 local g =non_ihb_flag and (get_OB_skip()) -- O_B
1007 if Np.id==id_glue then handle_penalty_normal(Nq.post, 0, g)
1008 elseif Np.id==id_kern then handle_penalty_suppress(Nq.post, 0, g)
1009 else handle_penalty_always(Nq.post, 0, g)
1015 -- (anything) .. (和文文字で始まる hlist)
1016 local function handle_np_ja_hlist()
1018 if qid==id_jglyph or ((qid==id_pbox or Nq.id == id_pbox_w) and Nq.met) then
1019 local g = non_ihb_flag and get_OB_skip(0) or get_kanjiskip() -- O_B->K
1020 handle_penalty_normal(Nq.post, 0, g); real_insert(g)
1021 elseif Nq.met then -- Nq.id==id_hlist
1022 local g = get_kanjiskip() -- K
1023 handle_penalty_suppress(0, 0, g); real_insert(g)
1025 local g = get_xkanjiskip(Np) -- X
1026 handle_penalty_suppress(0, 0, g); real_insert(g)
1030 -- (和文文字で終わる hlist) .. (anything)
1031 local function handle_nq_ja_hlist()
1033 local g = get_xkanjiskip(Nq) -- X
1034 handle_penalty_suppress(0, 0, g); real_insert(g)
1039 -- Nq が前側のクラスタとなることによる修正
1041 local adjust_nq_aux = {
1042 [id_glyph] = function() after_alchar(Nq) end, -- after_alchar(Nq)
1043 [id_hlist] = function() after_hlist(Nq) end,
1044 [id_pbox] = function() after_hlist(Nq) end,
1045 [id_disc] = function() after_hlist(Nq) end,
1046 [id_pbox_w] = function()
1047 luatexbase.call_callback("luatexja.jfmglue.whatsit_after",
1052 function adjust_nq()
1053 local x = adjust_nq_aux[Nq.id]
1059 -------------------- 開始・終了時の処理
1063 local JWP = luatexja.stack_table_index.JWP
1064 local function handle_list_tail(mode)
1065 adjust_nq(); Np = Nq
1067 -- the current list is to be line-breaked.
1068 -- Insert \jcharwidowpenalty
1069 Bp = widow_Bp; Np = widow_Np
1071 handle_penalty_normal(0, table_current_stack[JWP] or 0)
1074 -- the current list is the contents of a hbox
1075 local npi, pm = Np.id, Np.met
1076 if npi == id_jglyph or (npi==id_pbox and pm) then
1077 local g = new_jfm_glue(pm.char_type, Np.class, fast_find_char_class('boxbdd', pm))
1079 set_attr(g, attr_icflag, BOXBDD)
1080 head = insert_after(head, Np.last, g)
1087 local function handle_list_head(par_indented)
1088 local npi, pm = Np.id, Np.met
1089 if npi == id_jglyph or (npi==id_pbox and pm) then
1090 if non_ihb_flag then
1091 local g = new_jfm_glue(pm.char_type, fast_find_char_class(par_indented, pm), Np.class)
1093 set_attr(g, attr_icflag, BOXBDD)
1094 if getid(g)==id_glue and #Bp==0 then
1095 local h = node_new(id_penalty)
1096 setfield(h, 'penalty', 10000); set_attr(h, attr_icflag, BOXBDD)
1098 head = insert_before(head, Np.first, g)
1105 -- return value: (the initial cursor lp), (last node)
1108 local id_local = node.id('local_par')
1109 local KANJI_SKIP = luatexja.icflag_table.KANJI_SKIP
1110 local XKANJI_SKIP = luatexja.icflag_table.XKANJI_SKIP
1111 local KSK = luatexja.stack_table_index.KSK
1112 local XSK = luatexja.stack_table_index.XSK
1113 local dir_yoko = luatexja.dir_table.dir_yoko
1114 local dir_tate = luatexja.dir_table.dir_tate
1115 local attr_yablshift = luatexbase.attributes['ltj@yablshift']
1116 local attr_tablshift = luatexbase.attributes['ltj@tablshift']
1117 local table_pool = {
1118 {}, {}, {first=nil},
1119 { auto_kspc=nil, auto_xspc=nil, char=nil, class=nil,
1120 first=nil, id=nil, last=nil, met=nil, nuc=nil,
1121 post=nil, pre=nil, xspc=nil, },
1122 { auto_kspc=nil, auto_xspc=nil, char=nil, class=nil,
1123 first=nil, id=nil, last=nil, met=nil, nuc=nil,
1124 post=nil, pre=nil, xspc=nil, },
1126 init_var = function (mode,dir)
1127 -- 1073741823: max_dimen
1128 Bp, widow_Bp, widow_Np, Np, Nq
1129 = table_pool[1], table_pool[2], table_pool[3], table_pool[4], table_pool[5]
1130 for i=1,5 do for j,_ in pairs(table_pool[i]) do table_pool[i][j]=nil end end
1131 table_current_stack = ltjs.table_current_stack
1133 list_dir, tex_dir = (ltjs.list_dir or dir_yoko), (dir or 'TLT')
1134 local is_dir_tate = list_dir==dir_tate
1135 capsule_glyph = is_dir_tate and ltjw.capsule_glyph_tate or ltjw.capsule_glyph_yoko
1136 attr_ablshift = is_dir_tate and attr_tablshift or attr_yablshift
1137 local TEMP = node_new(id_glue)
1138 -- TEMP is a dummy node, which will be freed at the end of the callback.
1139 -- ithout this node, set_attr(kanji_skip, ...) somehow creates an "orphaned" attribute list.
1142 kanji_skip = node_new(id_glue); set_attr(kanji_skip, attr_icflag, KANJI_SKIP)
1143 local s = skip_table_to_spec(KSK)
1144 setfield(kanji_skip, 'spec', s)
1145 kanjiskip_jfm_flag = (getfield(s, 'width') == 1073741823)
1149 xkanji_skip = node_new(id_glue); set_attr(xkanji_skip, attr_icflag, XKANJI_SKIP)
1150 local s = skip_table_to_spec(XSK)
1151 setfield(xkanji_skip, 'spec', s)
1152 xkanjiskip_jfm_flag = (getfield(s, 'width') == 1073741823)
1156 -- the current list is to be line-breaked:
1157 -- hbox from \parindent is skipped.
1158 local lp, par_indented, lpi, lps = head, 'boxbdd', getid(head), getsubtype(head)
1160 ((lpi==id_whatsit and lps~=sid_user)
1161 or ((lpi==id_hlist) and (lps==3))
1162 or (lpi==id_local)) do
1163 if (lpi==id_hlist) and (lps==3) then
1164 Np.char, par_indented = 'parbdd', 'parbdd'
1165 Np.width = getfield(lp, 'width')
1167 lp=node_next(lp); lpi, lps = getid(lp), getsubtype(lp) end
1168 return lp, node_tail(head), par_indented, TEMP
1170 return head, nil, 'boxbdd', TEMP
1175 local ensure_tex_attr = ltjb.ensure_tex_attr
1176 local function cleanup(mode, TEMP)
1177 -- adjust attr_icflag for avoiding error
1178 if tex.getattribute(attr_icflag)~=0 then ensure_tex_attr(attr_icflag, 0) end
1179 node_free(kanji_skip);
1180 node_free(xkanji_skip); node_free(TEMP)
1183 local h = node_next(head)
1184 if getid(h) == id_penalty and getfield(h, 'penalty') == 10000 then
1186 if getid(h) == id_glue and getsubtype(h) == 15 and not node_next(h) then
1193 -------------------- 外部から呼ばれる関数
1196 function main(ahead, mode, dir)
1197 if not ahead then return ahead end
1199 local lp, last, par_indented, TEMP = init_var(mode,dir)
1200 lp = calc_np(last, lp)
1202 handle_list_head(par_indented)
1203 lp = calc_np(last,lp);
1206 local pid, pm = Np.id, Np.met
1208 if pid == id_jglyph then
1209 handle_np_jachar(mode)
1211 if pid==id_hlist then handle_np_ja_hlist()
1212 else handle_np_jachar() end
1214 if Nq.id==id_hlist then handle_nq_ja_hlist()
1215 else handle_nq_jachar() end
1217 lp = calc_np(last,lp)
1219 handle_list_tail(mode)
1221 return cleanup(mode, TEMP)
1226 local IHB = luatexja.userid_table.IHB
1227 local BPAR = luatexja.userid_table.BPAR
1228 local node_prev = (Dnode ~= node) and Dnode.getprev or node.prev
1229 local node_write = Dnode.write
1232 function create_inhibitglue_node()
1233 local tn = node_new(id_whatsit, sid_user)
1234 setfield(tn, 'user_id', IHB)
1235 setfield(tn, 'type', 100)
1236 setfield(tn, 'value', 1)
1240 -- Node for indicating beginning of a paragraph
1241 -- (for ltjsclasses)
1242 function create_beginpar_node()
1243 local tn = node_new(id_whatsit, sid_user)
1244 setfield(tn, 'user_id', BPAR)
1245 setfield(tn, 'type', 100)
1246 setfield(tn, 'value', 1)
1250 local function whatsit_callback(Np, lp, Nq)
1251 if Np and Np.nuc then return Np
1252 elseif Np and getfield(lp, 'user_id') == BPAR then
1253 Np.first = lp; Np.nuc = lp; Np.last = lp
1258 local function whatsit_after_callback(s, Nq, Np)
1259 if not s and getfield(Nq.nuc, 'user_id') == BPAR then
1260 local x, y = node_prev(Nq.nuc), Nq.nuc
1261 Nq.first, Nq.nuc, Nq.last = x, x, x
1264 Nq.class = fast_find_char_class('parbdd', Np.met)
1266 Nq.met = Np.met; Nq.pre = 0; Nq.post = 0; Nq.xspc = 0
1267 Nq.auto_xspc = false
1269 head = node_remove(head, y)
1275 luatexbase.add_to_callback("luatexja.jfmglue.whatsit_getinfo", whatsit_callback,
1276 "luatexja.beginpar.np_info", 1)
1277 luatexbase.add_to_callback("luatexja.jfmglue.whatsit_after", whatsit_after_callback,
1278 "luatexja.beginpar.np_info_after", 1)