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 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 if getid(lp)==id_hlist or getid(lp)==id_vlist then
300 head, lp, nc = ltjd_make_dir_whatsit(head, lp, list_dir, 'jfm pbox')
301 Np.first = first and nc or Np.first
303 nc, lp = lp, node_next(lp)
305 first, lpa = false, (lp and has_attr(lp, attr_icflag) or 0)
306 -- get_attr_icflag() ではいけない!
309 lp = check_next_ickern(lp)
310 Np.last_char = check_box_high(Np, Np.first, lp)
314 local ltjw_apply_ashift_math = ltjw.apply_ashift_math
315 local ltjw_apply_ashift_disc = ltjw.apply_ashift_disc
316 local min, max = math.min, math.max
317 local function calc_np_aux_glyph_common(lp)
319 Np.first= (Np.first or lp)
320 if getfield(lp, 'lang') == lang_ja then
322 local m, mc, cls = set_np_xspc_jachar(Np, lp)
324 lp, head, npi, npf = capsule_glyph(lp, m, mc[cls], head, tex_dir)
325 Np.first = (Np.first~=Np.nuc) and Np.first or npf or npi
327 return true, check_next_ickern(lp);
330 set_np_xspc_alchar(Np, getchar(lp), lp, 1)
332 local first_glyph, last_glyph = lp
333 set_attr(lp, attr_icflag, PROCESSED); Np.last = lp
334 local y_adjust = has_attr(lp,attr_ablshift) or 0
335 local node_depth = getfield(lp, 'depth') + min(y_adjust, 0)
336 local adj_depth = (y_adjust>0) and (getfield(lp, 'depth') + y_adjust) or 0
337 setfield(lp, 'yoffset', getfield(lp, 'yoffset') - y_adjust)
339 for lx in traverse(lp) do
340 local lai = get_attr_icflag(lx)
341 if lx==last or lai>=PACKED then
344 local lid = getid(lx)
345 if lid==id_glyph and getfield(lx, 'lang') ~= lang_ja then
347 last_glyph = lx; set_attr(lx, attr_icflag, PROCESSED); Np.last = lx
348 y_adjust = has_attr(lx,attr_ablshift) or 0
349 node_depth = max(getfield(lx, 'depth') + min(y_adjust, 0), node_depth)
350 adj_depth = (y_adjust>0) and max(getfield(lx, 'depth') + y_adjust, adj_depth) or adj_depth
351 setfield(lx, 'yoffset', getfield(lx, 'yoffset') - y_adjust)
352 elseif lid==id_kern then
353 local ls = getsubtype(lx)
354 if ls==2 then -- アクセント用の kern
355 set_attr(lx, attr_icflag, PROCESSED)
356 lx = node_next(lx) -- lx: アクセント本体
357 setfield(lx, 'yoffset', getfield(lx, 'yoffset') - (has_attr(lx,attr_ablshift) or 0))
358 lx = node_next(node_next(lx))
361 elseif (ls==1 and lai==ITALIC) then
362 Np.last = lx; set_attr(lx, attr_icflag, IC_PROCESSED)
372 if adj_depth>node_depth then
373 r = node_new(id_rule)
374 setfield(r, 'width', 0); setfield(r, 'height', 0)
375 setfield(r, 'depth',adj_depth); setfield(r, 'dir', tex_dir)
376 set_attr(r, attr_icflag, PROCESSED)
379 Np.last_char = last_glyph
380 if r then insert_after(head, first_glyph, r) end
385 local nf, nc = getfont(npn), getchar(npn)
386 local ct = (font.getfont(nf) or font.fonts[nf] ).characters[nc]
387 if not ct then -- variation selector
389 elseif (ct.left_protruding or 0) == 0 then
390 head = insert_before(head, npn, r)
391 Np.first = (Np.first==npn) and r or npn
392 elseif (ct.right_protruding or 0) == 0 then
393 insert_after(head, npn, r); Np.last, lp = r, r
395 ltjb.package_warning_no_line(
397 'Check depth of glyph node ' .. tostring(npn) .. '(font=' .. nf
398 .. ', char=' .. nc .. '), because its \\lpcode is ' .. tostring(ct.left_protruding)
399 .. ' and its \\rpcode is ' .. tostring(ct.right_protruding)
407 local calc_np_auxtable = {
408 [id_glyph] = calc_np_aux_glyph_common,
409 [id_hlist] = function(lp)
411 head, lp, op, flag = ltjd_make_dir_whatsit(head, lp, list_dir, 'jfm hlist')
412 set_attr(op, attr_icflag, PROCESSED)
413 Np.first = Np.first or op; Np.last = op; Np.nuc = op;
414 if (flag or getfield(op, 'shift')~=0) then
418 Np.last_char = check_box_high(Np, getlist(op), nil)
422 [id_vlist] = function(lp)
424 head, lp, op = ltjd_make_dir_whatsit(head, lp, list_dir, 'jfm:' .. getid(lp))
425 Np.first = Np.first or op; Np.last = op; Np.nuc = op;
429 box_like = function(lp)
430 Np.first = Np.first or lp; Np.last = lp; Np.nuc = lp;
432 return true, node_next(lp)
435 set_attr(lp, attr_icflag, PROCESSED)
436 return false, node_next(lp)
438 [id_whatsit] = function(lp)
439 local lps = getsubtype(lp)
440 if lps==sid_user then
441 if getfield(lp, 'user_id')==luatexja.userid_table.IHB then
442 local lq = node_next(lp);
443 head = node_remove(head, lp); node_free(lp); non_ihb_flag = false
446 set_attr(lp, attr_icflag, PROCESSED)
447 luatexbase.call_callback("luatexja.jfmglue.whatsit_getinfo",
450 Np.id = id_pbox_w; Np.first = Np.nuc; Np.last = Np.nuc;
451 return true, node_next(lp)
453 return false, node_next(lp)
457 -- we do special treatment for these whatsit nodes.
458 if lps == sid_start_link or lps == sid_start_thread then
460 elseif lps == sid_end_link or lps == sid_end_thread then
461 Np.first, Nq.last = nil, lp;
463 set_attr(lp, attr_icflag, PROCESSED)
464 return false, node_next(lp)
467 [id_math] = function(lp)
468 Np.first, Np.nuc = (Np.first or lp), lp;
469 set_attr(lp, attr_icflag, PROCESSED)
470 set_np_xspc_alchar(Np, -1, lp)
471 local end_math = node_end_of_math(lp)
472 ltjw_apply_ashift_math(lp, end_math, attr_ablshift)
473 set_attr(end_math, attr_icflag, PROCESSED)
474 Np.last, Np.id = end_math, id_math;
475 return true, node_next(end_math);
477 [id_glue] = function(lp)
478 Np.first, Np.nuc, Np.last = (Np.first or lp), lp, lp;
479 Np.id = getid(lp); set_attr(lp, attr_icflag, PROCESSED)
480 return true, node_next(lp)
482 [id_disc] = function(lp)
483 Np.first, Np.nuc, Np.last = (Np.first or lp), lp, lp;
484 Np.id = getid(lp); set_attr(lp, attr_icflag, PROCESSED)
485 ltjw_apply_ashift_disc(lp, (list_dir==dir_tate), tex_dir)
486 Np.last_char = check_box_high(Np, getfield(lp, 'replace'), nil)
487 return true, node_next(lp)
489 [id_kern] = function(lp)
490 if getsubtype(lp)==2 then
491 Np.first = Np.first or lp
492 set_attr(lp, attr_icflag, PROCESSED); lp = node_next(lp)
493 set_attr(lp, attr_icflag, PROCESSED); lp = node_next(lp)
494 set_attr(lp, attr_icflag, PROCESSED); lp = node_next(lp)
495 set_attr(lp, attr_icflag, PROCESSED);
496 return calc_np_aux_glyph_common(lp)
498 Np.first = Np.first or lp
499 Np.id = id_kern; set_attr(lp, attr_icflag, PROCESSED)
500 Np.last = lp; return true, node_next(lp)
503 [id_penalty] = function(lp)
504 Bp[#Bp+1] = lp; set_attr(lp, attr_icflag, PROCESSED)
505 return false, node_next(lp)
508 calc_np_auxtable[id_rule] = calc_np_auxtable.box_like
509 calc_np_auxtable[13] = calc_np_auxtable.box_like
510 calc_np_auxtable[id_ins] = calc_np_auxtable.skip
511 calc_np_auxtable[id_mark] = calc_np_auxtable.skip
512 calc_np_auxtable[id_adjust] = calc_np_auxtable.skip
514 function calc_np(last, lp)
516 -- We assume lp = node_next(Np.last)
517 Np, Nq, non_ihb_flag = Nq, Np, true
518 -- We clear `predefined' entries of Np before pairs() loop,
519 -- because using only pairs() loop is slower.
520 Np.post, Np.pre, Np.xspc = nil, nil, nil
521 Np.first, Np.id, Np.last, Np.met, Np.class= nil, nil, nil, nil
522 Np.auto_kspc, Np.auto_xspc, Np.char, Np.nuc = nil, nil, nil, nil
523 for k in pairs(Np) do Np[k] = nil end
525 for k = 1,#Bp do Bp[k] = nil end
527 local lpa = has_attr(lp, attr_icflag) or 0
530 if lpa%PROCESSED_BEGIN_FLAG == BOXBDD then
531 local lq = node_next(lp)
532 head = node_remove(head, lp); node_free(lp); lp = lq
534 return calc_np_pbox(lp, last)
537 k, lp = calc_np_auxtable[getid(lp)](lp)
538 if k then return lp end
545 -- extract informations from Np
546 -- We think that "Np is a Japanese character" if Np.met~=nil,
547 -- "Np is an alphabetic character" if Np.pre~=nil,
548 -- "Np is not a character" otherwise.
549 after_hlist = nil -- global
550 local after_alchar, extract_np
552 local PRE = luatexja.stack_table_index.PRE
553 local POST = luatexja.stack_table_index.POST
554 local KCAT = luatexja.stack_table_index.KCAT
555 local XSP = luatexja.stack_table_index.XSP
556 local dir_tate = luatexja.dir_table.dir_tate
559 local attr_jchar_class = luatexbase.attributes['ltj@charclass']
560 local attr_jchar_code = luatexbase.attributes['ltj@charcode']
561 local attr_autospc = luatexbase.attributes['ltj@autospc']
562 local attr_autoxspc = luatexbase.attributes['ltj@autoxspc']
563 --local ltjf_get_vert_glyph = ltjf.get_vert_glyph
564 function set_np_xspc_jachar(Nx, x)
565 local m = ltjf_font_metric_table[getfont(x)]
566 local c, c_glyph = ltjs_orig_char_table[x], getchar(x)
568 local cls = slow_find_char_class(c, m, c_glyph)
569 Nx.met, Nx.class, Nx.char = m, cls, c;
570 local mc = m.char_type; Nx.char_type = mc
571 if cls~=0 then set_attr(x, attr_jchar_class, cls) end
572 if c~=c_glyph then set_attr(x, attr_jchar_code, c) end
573 Nx.pre = table_current_stack[PRE + c] or 0
574 Nx.post = table_current_stack[POST + c] or 0
575 Nx.xspc = table_current_stack[XSP + c] or 3
576 Nx.kcat = table_current_stack[KCAT + c] or 0
577 Nx.auto_kspc, Nx.auto_xspc = (has_attr(x, attr_autospc)==1), (has_attr(x, attr_autoxspc)==1)
580 function set_np_xspc_jachar_hbox(Nx, x)
581 local m = ltjf_font_metric_table[getfont(x)]
582 local c = has_attr(x, attr_jchar_code) or getchar(x)
583 Nx.met, Nx.char = m, c; Nx.class = has_attr(x, attr_jchar_class) or 0;
584 local mc = m.char_type; Nx.char_type = mc
585 Nx.pre = table_current_stack[PRE + c] or 0
586 Nx.post = table_current_stack[POST + c] or 0
587 Nx.xspc = table_current_stack[XSP + c] or 3
588 Nx.kcat = table_current_stack[KCAT + c] or 0
589 Nx.auto_kspc, Nx.auto_xspc = (has_attr(x, attr_autospc)==1), (has_attr(x, attr_autoxspc)==1)
593 local floor = math.floor
594 function set_np_xspc_alchar(Nx, c,x, lig)
596 local f = (lig ==1) and nullfunc or node_tail
597 local xc, xs = getfield(x, 'components'), getsubtype(x)
598 while xc and xs and xs%4>=2 do
599 x = f(xc); xc, xs = getfield(x, 'components'), getsubtype(x)
602 Nx.pre = table_current_stack[PRE + c] or 0
603 Nx.post = table_current_stack[POST + c] or 0
604 Nx.xspc = table_current_stack[XSP + c] or 3
606 Nx.pre, Nx.post = 0, 0
607 Nx.xspc = table_current_stack[XSP - 1] or 3
610 Nx.auto_xspc = (has_attr(x, attr_autoxspc)==1)
612 local set_np_xspc_alchar = set_np_xspc_alchar
614 -- change the information for the next loop
615 -- (will be done if Nx is an alphabetic character or a hlist)
616 after_hlist = function (Nx)
617 local s = Nx.last_char
619 if getid(s)==id_glyph then
620 if getfield(s, 'lang') == lang_ja then
621 set_np_xspc_jachar_hbox(Nx, s)
623 set_np_xspc_alchar(Nx, getchar(s), s, 2)
626 set_np_xspc_alchar(Nx, -1, s)
629 Nx.pre, Nx.met = nil, nil
633 after_alchar = function (Nx)
634 local x = Nx.last_char
635 return set_np_xspc_alchar(Nx, getchar(x), x, 2)
640 -------------------- 最下層の処理
642 -- change penalties (or create a new penalty, if needed)
643 local function handle_penalty_normal(post, pre, g)
644 local a = (pre or 0) + (post or 0)
646 if (a~=0 and not(g and getid(g)==id_kern)) then
647 local p = node_new(id_penalty)
648 if a<-10000 then a = -10000 elseif a>10000 then a = 10000 end
649 setfield(p, 'penalty', a)
650 head = insert_before(head, Np.first, p)
652 set_attr(p, attr_icflag, KINSOKU)
654 else for _, v in pairs(Bp) do add_penalty(v,a) end
658 local function handle_penalty_always(post, pre, g)
659 local a = (pre or 0) + (post or 0)
661 if not (g and getid(g)==id_glue) or a~=0 then
662 local p = node_new(id_penalty)
663 if a<-10000 then a = -10000 elseif a>10000 then a = 10000 end
664 setfield(p, 'penalty', a)
665 head = insert_before(head, Np.first, p)
667 set_attr(p, attr_icflag, KINSOKU)
669 else for _, v in pairs(Bp) do add_penalty(v,a) end
673 local function handle_penalty_suppress(post, pre, g)
674 local a = (pre or 0) + (post or 0)
676 if g and getid(g)==id_glue then
677 local p = node_new(id_penalty)
678 setfield(p, 'penalty', 10000); head = insert_before(head, Np.first, p)
680 set_attr(p, attr_icflag, KINSOKU)
682 else for _, v in pairs(Bp) do add_penalty(v,a) end
686 -- 和文文字間の JFM glue を node 化
687 local function new_jfm_glue(mc, bc, ac)
688 -- bc, ac: char classes
692 local f = node_new(id_glue)
693 set_attr(f, attr_icflag, g.priority)
694 setfield(f, 'spec', node_copy(g[2]))
695 return f, g.ratio, g.kanjiskip_natural, g.kanjiskip_stretch, g.kanjiskip_shrink
697 return node_copy(g[2]), g.ratio, false, false, false
703 -- Nq.last (kern w) .... (glue/kern g) Np.first
704 local function real_insert(g)
706 head = insert_before(head, Np.first, g)
712 -------------------- 和文文字間空白量の決定
715 local bg_ag = 2*id_glue - id_glue
716 local bg_ak = 2*id_glue - id_kern
717 local bk_ag = 2*id_kern - id_glue
718 local bk_ak = 2*id_kern - id_kern
720 local function blend_diffmet(b, a, rb, ra)
721 return round(diffmet_rule((1-rb)*b+rb*a, (1-ra)*b+ra*a))
723 calc_ja_ja_aux = function (gb, ga, db, da)
724 if diffmet_rule ~= math.two_pleft and diffmet_rule ~= math.two_pright
725 and diffmet_rule ~= math.two_paverage then
730 gb = node_new(id_kern); setfield(gb, 'kern', 0)
733 ga = node_new(id_kern); setfield(ga, 'kern', 0)
736 local k = 2*getid(gb) - getid(ga)
738 local bs, as = getfield(gb, 'spec'), getfield(ga, 'spec')
740 setfield(bs, 'width', blend_diffmet(
741 getfield(bs, 'width'), getfield(as, 'width'), db, da))
742 setfield(bs, 'stretch', blend_diffmet(
743 getfield(bs, 'stretch'), getfield(as, 'stretch'), db, da))
744 setfield(bs, 'shrink', -blend_diffmet(
745 -getfield(bs, 'shrink'), -getfield(as, 'shrink'), db, da))
748 elseif k == bk_ak then
750 setfield(gb, 'kern', blend_diffmet(
751 getfield(gb, 'kern'), getfield(ga, 'kern'), db, da))
754 elseif k == bk_ag then
755 local as = getfield(ga, 'spec')
756 -- gb: kern, ga: glue
757 setfield(as, 'width', blend_diffmet(
758 getfield(gb, 'kern'), getfield(as, 'width'), db, da))
759 setfield(as, 'stretch', blend_diffmet(
760 0, getfield(as, 'stretch'), db, da))
761 setfield(as, 'shrink', -blend_diffmet(
762 0, -getfield(as, 'shrink'), db, da))
766 local bs = getfield(gb, 'spec')
767 -- gb: glue, ga: kern
768 setfield(bs, 'width', blend_diffmet(
769 getfield(bs, 'width'), getfield(ga, 'kern'), db, da))
770 setfield(bs, 'stretch', blend_diffmet(
771 getfield(bs, 'stretch'), 0, db, da))
772 setfield(bs, 'shrink', -blend_diffmet(
773 -getfield(bs, 'shrink'), 0, db, da))
780 local null_skip_table = {0, 0, 0}
782 local get_kanjiskip, kanjiskip_jfm_flag
783 local calc_ja_ja_glue
785 local KANJI_SKIP = luatexja.icflag_table.KANJI_SKIP
786 local KANJI_SKIP_JFM = luatexja.icflag_table.KANJI_SKIP_JFM
788 get_kanjiskip_low = function(flag, qm, bn, bp, bh)
789 if flag or (qm.with_kanjiskip and (bn or bp or bh)) then
790 if kanjiskip_jfm_flag then
791 local g = node_new(id_glue);
792 local gx = node_new(id_glue_spec);
793 setfield(gx, 'stretch_order', 0); setfield(gx, 'shrink_order', 0)
794 local bk = qm.kanjiskip or null_skip_table
795 setfield(gx, 'width', bn and (bn*bk[1]) or 0)
796 setfield(gx, 'stretch', bp and (bp*bk[2]) or 0)
797 setfield(gx, 'shrink', bh and (bh*bk[3]) or 0)
798 setfield(g, 'spec', gx)
799 set_attr(g, attr_icflag, KANJI_SKIP_JFM)
802 return node_copy(kanji_skip)
804 local g = node_new(id_glue);
805 local gx = node_new(id_glue_spec);
806 setfield(gx, 'stretch_order', 0); setfield(gx, 'shrink_order', 0)
807 local ks = getfield(kanji_skip, 'spec')
808 setfield(gx, 'width', bn and (bn*getfield(ks, 'width')) or 0)
809 setfield(gx, 'stretch', bp and (bp*getfield(ks, 'stretch')) or 0)
810 setfield(gx, 'shrink', bh and (bh*getfield(ks, 'shrink')) or 0)
811 setfield(g, 'spec', gx)
812 set_attr(g, attr_icflag, KANJI_SKIP_JFM)
818 get_kanjiskip = function()
819 if Np.auto_kspc or Nq.auto_kspc then
820 local pm, qm = Np.met, Nq.met
821 if (pm.char_type==qm.char_type) and (qm.var==pm.var) then
822 return get_kanjiskip_low(true, qm, 1, 1, 1)
824 local gb = get_kanjiskip_low(true, qm, 1, 1, 1)
825 local ga = get_kanjiskip_low(true, pm, 1, 1, 1)
826 return calc_ja_ja_aux(gb, ga, 0, 1)
829 local g = node_copy(zero_glue)
830 set_attr(g, attr_icflag, kanjiskip_jfm_flag and KANJI_SKIP_JFM or KANJI_SKIP)
835 calc_ja_ja_glue = function ()
836 local qm, pm = Nq.met, Np.met
837 local qmc, pmc = qm.char_type, pm.char_type
838 if (qmc==pmc) and (qm.var==pm.var) then
839 local g, _, kn, kp, kh = new_jfm_glue(qmc, Nq.class, Np.class)
840 return g, (Np.auto_kspc or Nq.auto_kspc) and get_kanjiskip_low(false, qm, kn, kp, kh)
842 local npn, nqn = Np.nuc, Nq.nuc
843 local gb, db, bn, bp, bh
844 = new_jfm_glue(qmc, Nq.class,
845 slow_find_char_class(Np.char,
847 local ga, da, an, ap, ah
849 slow_find_char_class(Nq.char,
852 local g = calc_ja_ja_aux(gb, ga, db, da)
854 if (pmc==qmc) and (qm.var==pm.var) then
855 gb = get_kanjiskip_low(false, qm, bn, bp, bh)
856 ga = get_kanjiskip_low(false, pm, an, ap, ah)
857 k = calc_ja_ja_aux(gb, ga, db, da)
864 -------------------- 和欧文間空白量の決定
867 local get_xkanjiskip, xkanjiskip_jfm_flag
868 local get_xkanjiskip_normal, get_xkanjiskip_jfm
870 local XKANJI_SKIP = luatexja.icflag_table.XKANJI_SKIP
871 local XKANJI_SKIP_JFM = luatexja.icflag_table.XKANJI_SKIP_JFM
873 get_xkanjiskip_low = function(flag, qm, bn, bp, bh)
874 if flag or (qm.with_kanjiskip and (bn or bp or bh)) then
875 if xkanjiskip_jfm_flag then
876 local g = node_new(id_glue);
877 local gx = node_new(id_glue_spec);
878 setfield(gx, 'stretch_order', 0); setfield(gx, 'shrink_order', 0)
879 local bk = qm.xkanjiskip or null_skip_table
880 setfield(gx, 'width', bn and bk[1] or 0)
881 setfield(gx, 'stretch', bp and bk[2] or 0)
882 setfield(gx, 'shrink', bh and bk[3] or 0)
883 setfield(g, 'spec', gx)
884 set_attr(g, attr_icflag, XKANJI_SKIP_JFM)
887 return node_copy(xkanji_skip)
889 local g = node_new(id_glue);
890 local gx = node_new(id_glue_spec);
891 setfield(gx, 'stretch_order', 0); setfield(gx, 'shrink_order', 0)
892 local ks = getfield(xkanji_skip, 'spec')
893 setfield(gx, 'width', bn and getfield(ks, 'width') or 0)
894 setfield(gx, 'stretch', bp and getfield(ks, 'stretch') or 0)
895 setfield(gx, 'shrink', bh and getfield(ks, 'shrink') or 0)
896 setfield(g, 'spec', gx)
897 set_attr(g, attr_icflag, XKANJI_SKIP_JFM)
903 get_xkanjiskip = function(Nn)
904 if (Nq.xspc>=2) and (Np.xspc%2==1) and (Nq.auto_xspc or Np.auto_xspc) then
905 return get_xkanjiskip_low(true, Nn.met, 1, 1, 1)
907 local g = node_copy(zero_glue)
908 set_attr(g, attr_icflag, xkanjiskip_jfm_flag and XKANJI_SKIP_JFM or XKANJI_SKIP)
914 -------------------- 隣接した「塊」間の処理
916 local function get_OA_skip(is_kanji)
918 local g, _, kn, kp, kh = new_jfm_glue(
920 fast_find_char_class((Nq.id == id_math and -1 or 'jcharbdd'), pm),
924 k = (Np.auto_kspc or Nq.auto_kspc) and get_kanjiskip_low(false, pm, kn, kp, kh)
925 elseif is_kanji==1 then
926 k = ((Nq.xspc>=2) and (Np.xspc%2==1) and (Nq.auto_xspc or Np.auto_xspc))
927 and get_xkanjiskip_low(false, pm, kn, kp, kh)
931 local function get_OB_skip(is_kanji)
933 local g, _, kn, kp, kh = new_jfm_glue(
934 qm.char_type, Nq.class,
935 fast_find_char_class((Np.id == id_math and -1 or'jcharbdd'), qm))
938 k = (Np.auto_kspc or Nq.auto_kspc) and get_kanjiskip_low(false, qm, kn, kp, kh)
939 elseif is_kanji==1 then
940 k = ((Nq.xspc>=2) and (Np.xspc%2==1) and (Nq.auto_xspc or Np.auto_xspc))
941 and get_xkanjiskip_low(false, qm, kn, kp, kh)
946 -- (anything) .. jachar
947 local function handle_np_jachar(mode)
949 if qid==id_jglyph or ((qid==id_pbox or qid==id_pbox_w) and Nq.met) then
951 if non_ihb_flag then g, k = calc_ja_ja_glue() end -- M->K
952 if not g then g = get_kanjiskip() end
953 handle_penalty_normal(Nq.post, Np.pre, g);
954 real_insert(g); real_insert(k)
955 elseif Nq.met then -- qid==id_hlist
957 if non_ihb_flag then g, k = get_OA_skip(0) end -- O_A->K
958 if not g then g = get_kanjiskip() end
959 handle_penalty_normal(0, Np.pre, g); real_insert(g); real_insert(k)
962 if non_ihb_flag then g, k = get_OA_skip(1) end -- O_A->X
963 if not g then g = get_xkanjiskip(Np) end
964 handle_penalty_normal((qid==id_hlist and 0 or Nq.post), Np.pre, g);
965 real_insert(g); real_insert(k)
967 local g = non_ihb_flag and (get_OA_skip()) -- O_A
968 if qid==id_glue then handle_penalty_normal(0, Np.pre, g)
969 elseif qid==id_kern then handle_penalty_suppress(0, Np.pre, g)
970 else handle_penalty_always(0, Np.pre, g)
974 if mode and Np.kcat%2~=1 then
975 widow_Np.first, widow_Bp, Bp = Np.first, Bp, widow_Bp
980 -- jachar .. (anything)
981 local function handle_nq_jachar()
983 local g = non_ihb_flag and get_OB_skip(1) or get_xkanjiskip(Nq) -- O_B->X
984 handle_penalty_normal(Nq.post, (Np.id==id_hlist and 0 or Np.pre), g); real_insert(g)
986 local g =non_ihb_flag and (get_OB_skip()) -- O_B
987 if Np.id==id_glue then handle_penalty_normal(Nq.post, 0, g)
988 elseif Np.id==id_kern then handle_penalty_suppress(Nq.post, 0, g)
989 else handle_penalty_always(Nq.post, 0, g)
995 -- (anything) .. (和文文字で始まる hlist)
996 local function handle_np_ja_hlist()
998 if qid==id_jglyph or ((qid==id_pbox or Nq.id == id_pbox_w) and Nq.met) then
999 local g = non_ihb_flag and get_OB_skip(0) or get_kanjiskip() -- O_B->K
1000 handle_penalty_normal(Nq.post, 0, g); real_insert(g)
1001 elseif Nq.met then -- Nq.id==id_hlist
1002 local g = get_kanjiskip() -- K
1003 handle_penalty_suppress(0, 0, g); real_insert(g)
1005 local g = get_xkanjiskip(Np) -- X
1006 handle_penalty_suppress(0, 0, g); real_insert(g)
1010 -- (和文文字で終わる hlist) .. (anything)
1011 local function handle_nq_ja_hlist()
1013 local g = get_xkanjiskip(Nq) -- X
1014 handle_penalty_suppress(0, 0, g); real_insert(g)
1019 -- Nq が前側のクラスタとなることによる修正
1021 local adjust_nq_aux = {
1022 [id_glyph] = function() after_alchar(Nq) end, -- after_alchar(Nq)
1023 [id_hlist] = function() after_hlist(Nq) end,
1024 [id_pbox] = function() after_hlist(Nq) end,
1025 [id_disc] = function() after_hlist(Nq) end,
1026 [id_pbox_w] = function()
1027 luatexbase.call_callback("luatexja.jfmglue.whatsit_after",
1032 function adjust_nq()
1033 local x = adjust_nq_aux[Nq.id]
1039 -------------------- 開始・終了時の処理
1043 local JWP = luatexja.stack_table_index.JWP
1044 local function handle_list_tail(mode)
1045 adjust_nq(); Np = Nq
1047 -- the current list is to be line-breaked.
1048 -- Insert \jcharwidowpenalty
1049 Bp = widow_Bp; Np = widow_Np
1051 handle_penalty_normal(0, table_current_stack[JWP] or 0)
1054 -- the current list is the contents of a hbox
1055 local npi, pm = Np.id, Np.met
1056 if npi == id_jglyph or (npi==id_pbox and pm) then
1057 local g = new_jfm_glue(pm.char_type, Np.class, fast_find_char_class('boxbdd', pm))
1059 set_attr(g, attr_icflag, BOXBDD)
1060 head = insert_after(head, Np.last, g)
1067 local function handle_list_head(par_indented)
1068 local npi, pm = Np.id, Np.met
1069 if npi == id_jglyph or (npi==id_pbox and pm) then
1070 if non_ihb_flag then
1071 local g = new_jfm_glue(pm.char_type, fast_find_char_class(par_indented, pm), Np.class)
1073 set_attr(g, attr_icflag, BOXBDD)
1074 if getid(g)==id_glue and #Bp==0 then
1075 local h = node_new(id_penalty)
1076 setfield(h, 'penalty', 10000); set_attr(h, attr_icflag, BOXBDD)
1078 head = insert_before(head, Np.first, g)
1085 -- return value: (the initial cursor lp), (last node)
1088 local KANJI_SKIP = luatexja.icflag_table.KANJI_SKIP
1089 local XKANJI_SKIP = luatexja.icflag_table.XKANJI_SKIP
1090 local KSK = luatexja.stack_table_index.KSK
1091 local XSK = luatexja.stack_table_index.XSK
1092 local dir_yoko = luatexja.dir_table.dir_yoko
1093 local dir_tate = luatexja.dir_table.dir_tate
1094 local attr_yablshift = luatexbase.attributes['ltj@yablshift']
1095 local attr_tablshift = luatexbase.attributes['ltj@tablshift']
1096 local table_pool = {
1097 {}, {}, {first=nil},
1098 { auto_kspc=nil, auto_xspc=nil, char=nil, class=nil,
1099 first=nil, id=nil, last=nil, met=nil, nuc=nil,
1100 post=nil, pre=nil, xspc=nil, },
1101 { auto_kspc=nil, auto_xspc=nil, char=nil, class=nil,
1102 first=nil, id=nil, last=nil, met=nil, nuc=nil,
1103 post=nil, pre=nil, xspc=nil, },
1105 init_var = function (mode,dir)
1106 -- 1073741823: max_dimen
1107 Bp, widow_Bp, widow_Np, Np, Nq
1108 = table_pool[1], table_pool[2], table_pool[3], table_pool[4], table_pool[5]
1109 for i=1,5 do for j,_ in pairs(table_pool[i]) do table_pool[i][j]=nil end end
1110 table_current_stack = ltjs.table_current_stack
1112 list_dir, tex_dir = (ltjs.list_dir or dir_yoko), (dir or 'TLT')
1113 local is_dir_tate = list_dir==dir_tate
1114 capsule_glyph = is_dir_tate and ltjw.capsule_glyph_tate or ltjw.capsule_glyph_yoko
1115 attr_ablshift = is_dir_tate and attr_tablshift or attr_yablshift
1116 local TEMP = node_new(id_glue)
1117 -- TEMP is a dummy node, which will be freed at the end of the callback.
1118 -- ithout this node, set_attr(kanji_skip, ...) somehow creates an "orphaned" attribute list.
1121 kanji_skip = node_new(id_glue); set_attr(kanji_skip, attr_icflag, KANJI_SKIP)
1122 local s = skip_table_to_spec(KSK)
1123 setfield(kanji_skip, 'spec', s)
1124 kanjiskip_jfm_flag = (getfield(s, 'width') == 1073741823)
1128 xkanji_skip = node_new(id_glue); set_attr(xkanji_skip, attr_icflag, XKANJI_SKIP)
1129 local s = skip_table_to_spec(XSK)
1130 setfield(xkanji_skip, 'spec', s)
1131 xkanjiskip_jfm_flag = (getfield(s, 'width') == 1073741823)
1135 -- the current list is to be line-breaked:
1136 -- hbox from \parindent is skipped.
1137 local lp, par_indented, lpi, lps = head, 'boxbdd', getid(head), getsubtype(head)
1138 while lp and ((lpi==id_whatsit and lps~=sid_user)
1139 or ((lpi==id_hlist) and (lps==3))) do
1140 if (lpi==id_hlist) and (lps==3) then
1141 Np.char, par_indented = 'parbdd', 'parbdd'
1142 Np.width = getfield(lp, 'width')
1144 lp=node_next(lp); lpi, lps = getid(lp), getsubtype(lp) end
1145 return lp, node_tail(head), par_indented, TEMP
1147 return head, nil, 'boxbdd', TEMP
1152 local ensure_tex_attr = ltjb.ensure_tex_attr
1153 local function cleanup(mode, TEMP)
1154 -- adjust attr_icflag for avoiding error
1155 if tex.getattribute(attr_icflag)~=0 then ensure_tex_attr(attr_icflag, 0) end
1156 node_free(kanji_skip);
1157 node_free(xkanji_skip); node_free(TEMP)
1160 local h = node_next(head)
1161 if getid(h) == id_penalty and getfield(h, 'penalty') == 10000 then
1163 if getid(h) == id_glue and getsubtype(h) == 15 and not node_next(h) then
1170 -------------------- 外部から呼ばれる関数
1173 function main(ahead, mode, dir)
1174 if not ahead then return ahead end
1176 local lp, last, par_indented, TEMP = init_var(mode,dir)
1177 lp = calc_np(last, lp)
1179 handle_list_head(par_indented)
1180 lp = calc_np(last,lp); while Np do
1182 local pid, pm = Np.id, Np.met
1184 if pid == id_jglyph then
1185 handle_np_jachar(mode)
1187 if pid==id_hlist then handle_np_ja_hlist()
1188 else handle_np_jachar() end
1190 if Nq.id==id_hlist then handle_nq_ja_hlist()
1191 else handle_nq_jachar() end
1193 lp = calc_np(last,lp)
1195 handle_list_tail(mode)
1197 return cleanup(mode, TEMP)
1202 local IHB = luatexja.userid_table.IHB
1203 local BPAR = luatexja.userid_table.BPAR
1204 local node_prev = (Dnode ~= node) and Dnode.getprev or node.prev
1205 local node_write = Dnode.write
1208 function create_inhibitglue_node()
1209 local tn = node_new(id_whatsit, sid_user)
1210 setfield(tn, 'user_id', IHB)
1211 setfield(tn, 'type', 100)
1212 setfield(tn, 'value', 1)
1216 -- Node for indicating beginning of a paragraph
1217 -- (for ltjsclasses)
1218 function create_beginpar_node()
1219 local tn = node_new(id_whatsit, sid_user)
1220 setfield(tn, 'user_id', BPAR)
1221 setfield(tn, 'type', 100)
1222 setfield(tn, 'value', 1)
1226 local function whatsit_callback(Np, lp, Nq)
1227 if Np and Np.nuc then return Np
1228 elseif Np and getfield(lp, 'user_id') == BPAR then
1229 Np.first = lp; Np.nuc = lp; Np.last = lp
1234 local function whatsit_after_callback(s, Nq, Np)
1235 if not s and getfield(Nq.nuc, 'user_id') == BPAR then
1236 local x, y = node_prev(Nq.nuc), Nq.nuc
1237 Nq.first, Nq.nuc, Nq.last = x, x, x
1240 Nq.class = fast_find_char_class('parbdd', Np.met)
1242 Nq.met = Np.met; Nq.pre = 0; Nq.post = 0; Nq.xspc = 0
1243 Nq.auto_xspc = false
1245 head = node_remove(head, y)
1251 luatexbase.add_to_callback("luatexja.jfmglue.whatsit_getinfo", whatsit_callback,
1252 "luatexja.beginpar.np_info", 1)
1253 luatexbase.add_to_callback("luatexja.jfmglue.whatsit_after", whatsit_after_callback,
1254 "luatexja.beginpar.np_info_after", 1)