1 ------------------------------------------------------------------------
2 -- MAIN PROCESS STEP 2: insert glue/kerns from JFM (prefix: none)
3 ------------------------------------------------------------------------
5 local node_type = node.type
6 local node_new = node.new
7 local node_remove = node.remove
8 local node_prev = node.prev
9 local node_next = node.next
10 local has_attr = node.has_attribute
11 local set_attr = node.set_attribute
12 local node_insert_before = node.insert_before
13 local node_insert_after = node.insert_after
14 local round = tex.round
16 local id_penalty = node.id('penalty')
17 local id_glyph = node.id('glyph')
18 local id_glue_spec = node.id('glue_spec')
19 local id_glue = node.id('glue')
20 local id_kern = node.id('kern')
21 local id_hlist = node.id('hlist')
22 local id_whatsit = node.id('whatsit')
23 local sid_user = node.subtype('user_defined')
31 local attr_jchar_class = luatexbase.attributes['ltj@charclass']
32 local attr_curjfnt = luatexbase.attributes['ltj@curjfnt']
33 local attr_icflag = luatexbase.attributes['ltj@icflag']
34 -- attr_icflag: 1: kern from \/, 2: 'lineend' kern from JFM
37 local cstb_get_penalty_table = ltj.int_get_penalty_table
38 local ljfm_find_char_class = ltj.int_find_char_class
40 -- arithmetic with penalty.
42 local function add_penalty(p,e)
45 if e<=-10000 then i = 0 end
47 if e>=10000 then i = 0 end
50 if i>=10000 then i = 10000
51 elseif i<=-10000 then i = -10000 end
56 -- return true if and only if p is a Japanese character node
57 local function is_japanese_glyph_node(p)
58 return (p.id==id_glyph) and (p.font==has_attr(p,attr_curjfnt))
61 -- EXT: for \inhibitglue
62 function ltj.ext_create_inhibitglue_node()
63 local g=node_new(id_whatsit, sid_user)
64 g.user_id=30111; g.type=100; g.value=1; node.write(g)
67 -- In the beginning of a hlist created by line breaking, there are the followings:
68 -- - a hbox by \parindent
69 -- - a whatsit node which contains local paragraph materials.
70 -- When we insert jfm glues, we ignore these nodes.
71 local function is_parindent_box(p)
72 if p.id==id_hlist then
74 -- hlist (subtype=3) is a box by \parindent
75 elseif p.id==id_whatsit then
76 return (p.subtype==node.subtype('local_par'))
80 local function find_size_metric(px)
81 if is_japanese_glyph_node(px) then
82 return { ltj.font_metric_table[px.font].size,
83 ltj.font_metric_table[px.font].jfm, ltj.font_metric_table[px.font].var }
89 local function new_jfm_glue(size,mt,bc,ac)
90 -- mt: metric key, bc, ac: char classes
92 local z = ltj.metrics[mt].char_type[bc]
93 if z.glue and z.glue[ac] then
94 local h = node_new(id_glue_spec)
95 h.width = round(size*z.glue[ac][1])
96 h.stretch = round(size*z.glue[ac][2])
97 h.shrink = round(size*z.glue[ac][3])
98 h.stretch_order=0; h.shrink_order=0
100 g.subtype = 0; g.spec = h
101 elseif z.kern and z.kern[ac] then
102 g = node_new(id_kern)
103 g.subtype = 1; g.kern = round(size*z.kern[ac])
108 -- Insert jfm glue: main routine
111 local q = nil -- the previous node of p
112 local q_post -- the postbreakpenalty of q
114 local widow_node -- 最後の「句読点扱いでない」和文文字
115 local widow_bp -- 挿入位置 (a penalty)
116 local last -- the sentinel
117 local chain = false -- is q a Japanese character?
118 local ihb_flag = false -- is \inhibitglue specified?
119 local head -- the head of current list
120 local mode -- true iff insert_jfm_glue is called from pre_linebreak_filter
122 -- initialize (insert the sentinel, etc.)
123 local function init_var()
124 p = head; q = nil; widow_node = nil; widow_bp = nil
125 chain = false; ihb_flag = false;
127 while p and is_parindent_box(p) do p=node_next(p) end
129 if last and last.id==id_glue and last.subtype==15 then
131 while (last and last.id==id_penalty) do last=node.prev(last) end
133 if last then last=node_next(last) end
135 last=node.tail(head); local g = node_new('kern')
136 node_insert_after(head, last, g); last = g
140 -- Insert JFM glue before the first node (which is a Japanese glyph node)
141 local function ins_gk_head()
142 if is_japanese_glyph_node(p) then
143 ps = find_size_metric(p)
144 local g = new_jfm_glue(ps[1], ps[2],
145 ljfm_find_char_class('boxbdd',ps[2]),
146 has_attr(p,attr_jchar_class))
148 set_attr(g, attr_icflag, FROM_JFM)
149 head = node_insert_before(head, p, g)
151 q_post = cstb_get_penalty_table('post', p.char, 0, ltj.box_stack_level); chain = true
152 elseif p.id==id_glyph then
153 q_post = cstb_get_penalty_table('post', p.char, 0, ltj.box_stack_level)
155 qs = ps; q = p; p = node_next(p)
158 -- The real insertion process is handled in this procedure.
159 local function real_insert(g, w, pen, always_penalty_ins)
160 -- g: glur/kern from JFM
161 -- w: the width of kern that will be inserted between q and the end of a line
163 -- always_penalty_ins: true iff we insert a penalty,
164 -- for the linebreak between q and p.
167 g = node_new(id_kern); g.kern = -w; g.subtype = 1
168 set_attr(g, attr_icflag, TEMPORARY)
169 -- this g might be replaced by \[x]kanjiskip in step 3.
171 set_attr(g, attr_icflag, FROM_JFM)
172 if g.id==id_kern then w=0
173 else g.spec.width = round(g.spec.width - w)
178 local h = node_new(id_kern)
179 set_attr(h, attr_icflag, LINE_END)
180 h.kern = w; h.subtype = 0; node_insert_before(head, p, h)
182 set_attr(g, attr_icflag, FROM_JFM)
183 if g.id==id_kern then
184 pen=0; always_penalty_ins = false
187 if w~=0 or pen~=0 or ((not g) and always_penalty_ins) then
188 local h = node_new(id_penalty)
189 h.penalty = pen; set_attr(h, attr_icflag, KINSOKU)
190 node_insert_before(head, p, h)
193 node_insert_before(head, p, g);
197 -- This is a variant of real_insert (the case which a kern is related)
198 local function real_insert_kern(g)
200 if g.id==id_glue then
201 local h = node_new(id_penalty)
202 h.penalty = 10000; set_attr(h, attr_icflag, KINSOKU)
203 node_insert_before(head, p, h)
205 set_attr(g, attr_icflag, FROM_JFM)
206 node_insert_before(head, p, g)
210 ltj.ja_diffmet_rule = math.two_average
212 local function calc_ja_ja_aux(gb,ga)
216 if not ga then return gb end
217 local k = node.type(gb.id) .. node.type(ga.id)
218 if k == 'glueglue' then
220 gb.spec.width = round(ltj.ja_diffmet_rule(gb.spec.width, ga.spec.width))
221 gb.spec.stretch = round(ltj.ja_diffmet_rule(gb.spec.stretch,ga.spec.shrink))
222 gb.spec.shrink = -round(ltj.ja_diffmet_rule(-gb.spec.shrink, -ga.spec.shrink))
224 elseif k == 'kernkern' then
226 gb.kern = round(ltj.ja_diffmet_rule(gb.kern, ga.kern))
228 elseif k == 'kernglue' then
229 -- gb: kern, ga: glue
230 ga.spec.width = round(ltj.ja_diffmet_rule(gb.kern,ga.spec.width))
231 ga.spec.stretch = round(ltj.ja_diffmet_rule(ga.spec.stretch, 0))
232 ga.spec.shrink = -round(ltj.ja_diffmet_rule(-ga.spec.shrink, 0))
235 -- gb: glue, ga: kern
236 gb.spec.width = round(ltj.ja_diffmet_rule(ga.kern, gb.spec.width))
237 gb.spec.stretch = round(ltj.ja_diffmet_rule(gb.spec.stretch, 0))
238 gb.spec.shrink = -round(ltj.ja_diffmet_rule(-gb.spec.shrink, 0))
244 -- Calc the glue between two Japanese characters
245 local function calc_ja_ja_glue()
246 if ihb_flag then return nil
247 elseif table.are_equal(qs,ps) then
248 return new_jfm_glue(ps[1],ps[2],
249 has_attr(q,attr_jchar_class),
250 has_attr(p,attr_jchar_class))
252 local g = new_jfm_glue(qs[1],qs[2],
253 has_attr(q,attr_jchar_class),
254 ljfm_find_char_class('diffmet',qs[2]))
255 local h = new_jfm_glue(ps[1],ps[2],
256 ljfm_find_char_class('diffmet',ps[2]),
257 has_attr(p,attr_jchar_class))
258 return calc_ja_ja_aux(g,h)
262 local function ins_gk_any_JA()
263 ps = find_size_metric(p)
264 if chain then -- (q,p): JA-JA
265 local g = calc_ja_ja_glue()
266 local w = 0; local x = ljfm_find_char_class('lineend', qs[2])
267 if (not ihb_flag) and x~=0 then
268 local h = ltj.metrics[qs[2]].char_type[has_attr(q, attr_jchar_class)]
269 if h.kern and h.kern[x] then w = round(qs[1]*h.kern[x]) end
271 q_post = add_penalty(q_post, cstb_get_penalty_table('pre', p.char, 0, ltj.box_stack_level))
272 real_insert(g, w, q_post, false)
273 elseif q.id==id_glyph then -- (q,p): AL-JA
276 g = new_jfm_glue(ps[1], ps[2],
277 ljfm_find_char_class('jcharbdd',ps[2]),
278 has_attr(p,attr_jchar_class))
280 q_post = add_penalty(q_post, cstb_get_penalty_table('pre', p.char, 0, ltj.box_stack_level))
281 real_insert(g, 0, q_post, true)
282 elseif q.id==id_kern then -- (q,p): kern-JA
285 g = new_jfm_glue(ps[1], ps[2],
286 ljfm_find_char_class('jcharbdd',ps[2]),
287 has_attr(p,attr_jchar_class))
293 g = new_jfm_glue(ps[1], ps[2],
294 ljfm_find_char_class('jcharbdd',ps[2]),
295 has_attr(p,attr_jchar_class))
297 real_insert(g, 0, q_post, true)
299 q, qs, q_post = p, ps, cstb_get_penalty_table('post', p.char, 0, ltj.box_stack_level)
300 if cstb_get_penalty_table('kcat', p.char, 0, ltj.box_stack_level)%2~=1 then
303 p = node_next(p); chain = true
306 local function ins_gk_JA_any()
307 -- the case (q,p): JA-JA is treated in ins_gk_any_JA()
310 g = new_jfm_glue(qs[1], qs[2],
311 has_attr(q,attr_jchar_class),
312 ljfm_find_char_class('jcharbdd',qs[2]))
314 if p.id==id_glyph then -- (q,p): JA-AL
315 local w = 0; local x = ljfm_find_char_class('lineend', qs[2])
316 if (not ihb_flag) and x~=0 then
317 local h = ltj.metrics[qs[2]].char_type[has_attr(q,attr_jchar_class)]
318 if h.kern and h.kern[x] then w = round(qs[1]*h.kern[x]) end
320 q_post = add_penalty(q_post, cstb_get_penalty_table('pre', p.char, 0, ltj.box_stack_level))
321 real_insert(g, w, q_post, true)
322 elseif p.id==id_kern then -- (q,p): JA-kern
325 local w = 0; local x = ljfm_find_char_class('lineend', qs[2])
326 if (not ihb_flag) and x~=0 then
327 local h = ltj.metrics[qs[2]].char_type[has_attr(q,attr_jchar_class)]
328 if h.kern and h.kern[x] then w = round(qs[1]*h.kern[x]) end
330 if p.id~=penalty then
331 real_insert(g, w, q_post, true)
333 real_insert(g, w, q_post, false)
336 chain = false; q, qs, q_post = p, nil, 0; p = node_next(p)
339 -- Insert JFM glue after thr last node
340 local function ins_gk_tail()
342 if is_japanese_glyph_node(p) then
344 local w = 0; local x = ljfm_find_char_class('lineend', qs[2])
345 if (not ihb_flag) and x~=0 then
346 local h = ltj.metrics[qs[2]].char_type[has_attr(q,attr_jchar_class)]
347 if h.kern and h.kern[x] then w = round(qs[1]*h.kern[x]) end
350 g = node_new(id_kern); g.subtype = 0; g.kern = w
351 set_attr(g, attr_icflag, LINE_END)
352 node_insert_before(head, last, g)
358 local function add_widow_penalty()
359 -- widoe_node: must be
360 if not widow_node then return end
361 local a = node_prev(widow_node)
362 local i = has_attr(a, attr_icflag) or 0
363 local wp = cstb_get_penalty_table('jwp', 0, 0, ltj.box_stack_level)
365 a.penalty=add_penalty(a.penalty, wp)
367 local b = node_prev(a)
369 b.penalty=add_penalty(b.penalty, wp)
371 local g = node_new(id_penalty)
372 g.penalty = wp; head = node_insert_before(head,a,g)
375 local g = node_new(id_penalty)
376 g.penalty = wp; head = node_insert_before(head,widow_node,g)
380 -- Finishing: add \jcharwidowpenalty or remove the sentinel
381 local function finishing()
383 -- Insert \jcharwidowpenalty
386 head = node_remove(head, last)
391 function ltj.int_insert_jfm_glue(ahead, amode)
392 if not ahead then return ahead end
393 head = ahead; mode = amode; init_var();
394 while p~=last and p.id==id_whatsit and p.subtype==sid_user and p.user_id==30111 do
395 local g = p; p = node_next(p); ihb_flag = true; head, p = node.remove(head, g)
397 if p~=last then ins_gk_head() else finishing() return head end
399 if p.id==id_whatsit and p.subtype==sid_user and p.user_id==30111 then
400 local g = p; p = node_next(p)
401 ihb_flag = true; head, p = node.remove(head, g)
403 if is_japanese_glyph_node(p) then -- p: JAchar
405 elseif chain then -- q: JAchar
408 q, qs, q_post = p, nil, 0; p = node_next(p)
413 ins_gk_tail(); finishing(); return head