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 node_insert_before = node.insert_before
12 local node_insert_after = node.insert_after
13 local round = tex.round
15 local id_penalty = node.id('penalty')
16 local id_glyph = node.id('glyph')
17 local id_glue_spec = node.id('glue_spec')
18 local id_glue = node.id('glue')
19 local id_kern = node.id('kern')
20 local id_hlist = node.id('hlist')
21 local id_whatsit = node.id('whatsit')
22 local sid_user = node.subtype('user_defined')
30 local attr_jchar_class = luatexbase.attributes['ltj@charclass']
31 local attr_curjfnt = luatexbase.attributes['ltj@curjfnt']
32 local attr_icflag = luatexbase.attributes['ltj@icflag']
33 -- attr_icflag: 1: kern from \/, 2: 'lineend' kern from JFM
36 local cstb_get_penalty_table = ltj.int_get_penalty_table
37 local ljfm_find_char_class = ltj.int_find_char_class
39 -- arithmetic with penalty.
41 local function add_penalty(p,e)
44 if e<=-10000 then i = 0 end
46 if e>=10000 then i = 0 end
49 if i>=10000 then i = 10000
50 elseif i<=-10000 then i = -10000 end
55 -- return true if and only if p is a Japanese character node
56 local function is_japanese_glyph_node(p)
57 return (p.id==id_glyph) and (p.font==has_attr(p,attr_curjfnt))
60 -- EXT: for \inhibitglue
61 function ltj.ext_create_inhibitglue_node()
62 local g=node_new(id_whatsit, sid_user)
63 g.user_id=30111; g.type=number; g.value=1; node.write(g)
66 -- In the beginning of a hlist created by line breaking, there are the followings:
67 -- - a hbox by \parindent
68 -- - a whatsit node which contains local paragraph materials.
69 -- When we insert jfm glues, we ignore these nodes.
70 local function is_parindent_box(p)
71 if p.id==id_hlist then
73 -- hlist (subtype=3) is a box by \parindent
74 elseif p.id==id_whatsit then
75 return (p.subtype==node.subtype('local_par'))
79 local function find_size_metric(px)
80 if is_japanese_glyph_node(px) then
81 return { ltj.font_metric_table[px.font].size,
82 ltj.font_metric_table[px.font].jfm, ltj.font_metric_table[px.font].var }
88 local function new_jfm_glue(size,mt,bc,ac)
89 -- mt: metric key, bc, ac: char classes
91 local z = ltj.metrics[mt].char_type[bc]
92 if z.glue and z.glue[ac] then
93 local h = node_new(id_glue_spec)
94 h.width = round(size*z.glue[ac][1])
95 h.stretch = round(size*z.glue[ac][2])
96 h.shrink = round(size*z.glue[ac][3])
97 h.stretch_order=0; h.shrink_order=0
99 g.subtype = 0; g.spec = h
100 elseif z.kern and z.kern[ac] then
101 g = node_new(id_kern)
102 g.subtype = 1; g.kern = round(size*z.kern[ac])
107 -- Insert jfm glue: main routine
110 local q = nil -- the previous node of p
111 local q_post -- the postbreakpenalty of q
113 local widow_node -- 最後の「句読点扱いでない」和文文字
114 local widow_bp -- 挿入位置 (a penalty)
115 local last -- the sentinel
116 local chain = false -- is q a Japanese character?
117 local ihb_flag = false -- is \inhibitglue specified?
118 local head -- the head of current list
119 local mode -- true iff insert_jfm_glue is called from pre_linebreak_filter
121 -- initialize (insert the sentinel, etc.)
122 local function init_var()
123 p = head; q = nil; widow_node = nil; widow_bp = nil
124 chain = false; ihb_flag = false;
126 while p and is_parindent_box(p) do p=node_next(p) end
128 if last and last.id==id_glue and last.subtype==15 then
130 while (last and last.id==id_penalty) do last=node.prev(last) end
132 if last then last=node_next(last) end
134 last=node.tail(head); local g = node_new('kern')
135 node_insert_after(head, last, g); last = g
139 -- Insert JFM glue before the first node (which is a Japanese glyph node)
140 local function ins_gk_head()
141 if is_japanese_glyph_node(p) then
142 ps = find_size_metric(p)
143 local g = new_jfm_glue(ps[1], ps[2],
144 ljfm_find_char_class('boxbdd',ps[2]),
145 has_attr(p,attr_jchar_class))
147 node.set_attribute(g, attr_icflag, FROM_JFM)
148 head = node_insert_before(head, p, g)
150 q_post = cstb_get_penalty_table('post',p.char); chain = true
151 elseif p.id==id_glyph then
152 q_post = cstb_get_penalty_table('post',p.char)
154 qs = ps; q = p; p = node_next(p)
157 -- The real insertion process is handled in this procedure.
158 local function real_insert(g, w, pen, always_penalty_ins)
159 -- g: glur/kern from JFM
160 -- w: the width of kern that will be inserted between q and the end of a line
162 -- always_penalty_ins: true iff we insert a penalty,
163 -- for the linebreak between q and p.
166 g = node_new(id_kern); g.kern = -w; g.subtype = 1
167 node.set_attribute(g, attr_icflag, TEMPORARY)
168 -- this g might be replaced by \[x]kanjiskip in step 3.
170 node.set_attribute(g, attr_icflag, FROM_JFM)
171 if g.id==id_kern then w=0
172 else g.spec.width = round(g.spec.width - w)
177 local h = node_new(id_kern)
178 node.set_attribute(h, attr_icflag, LINE_END)
179 h.kern = w; h.subtype = 0; node_insert_before(head, p, h)
181 node.set_attribute(g, attr_icflag, FROM_JFM)
182 if g.id==id_kern then
183 pen=0; always_penalty_ins = false
186 if w~=0 or pen~=0 or ((not g) and always_penalty_ins) then
187 local h = node_new(id_penalty)
188 h.penalty = pen; node.set_attribute(h, attr_icflag, KINSOKU)
189 node_insert_before(head, p, h)
192 node_insert_before(head, p, g);
196 -- This is a variant of real_insert (the case which a kern is related)
197 local function real_insert_kern(g)
199 if g.id==id_glue then
200 local h = node_new(id_penalty)
201 h.penalty = 10000; node.set_attribute(h, attr_icflag, KINSOKU)
202 node_insert_before(head, p, h)
204 node.set_attribute(g, attr_icflag, FROM_JFM)
205 node_insert_before(head, p, g)
209 ltj.ja_diffmet_rule = math.two_average
211 local function calc_ja_ja_aux(gb,ga)
215 if not ga then return gb end
216 local k = node.type(gb.id) .. node.type(ga.id)
217 if k == 'glueglue' then
219 gb.spec.width = round(ltj.ja_diffmet_rule(gb.spec.width, ga.spec.width))
220 gb.spec.stretch = round(ltj.ja_diffmet_rule(gb.spec.stretch,ga.spec.shrink))
221 gb.spec.shrink = -round(ltj.ja_diffmet_rule(-gb.spec.shrink, -ga.spec.shrink))
223 elseif k == 'kernkern' then
225 gb.kern = round(ltj.ja_diffmet_rule(gb.kern, ga.kern))
227 elseif k == 'kernglue' then
228 -- gb: kern, ga: glue
229 ga.spec.width = round(ltj.ja_diffmet_rule(gb.kern,ga.spec.width))
230 ga.spec.stretch = round(ltj.ja_diffmet_rule(ga.spec.stretch, 0))
231 ga.spec.shrink = -round(ltj.ja_diffmet_rule(-ga.spec.shrink, 0))
234 -- gb: glue, ga: kern
235 gb.spec.width = round(ltj.ja_diffmet_rule(ga.kern, gb.spec.width))
236 gb.spec.stretch = round(ltj.ja_diffmet_rule(gb.spec.stretch, 0))
237 gb.spec.shrink = -round(ltj.ja_diffmet_rule(-gb.spec.shrink, 0))
243 -- Calc the glue between two Japanese characters
244 local function calc_ja_ja_glue()
245 if ihb_flag then return nil
246 elseif table.are_equal(qs,ps) then
247 return new_jfm_glue(ps[1],ps[2],
248 has_attr(q,attr_jchar_class),
249 has_attr(p,attr_jchar_class))
251 local g = new_jfm_glue(qs[1],qs[2],
252 has_attr(q,attr_jchar_class),
253 ljfm_find_char_class('diffmet',qs[2]))
254 local h = new_jfm_glue(ps[1],ps[2],
255 ljfm_find_char_class('diffmet',ps[2]),
256 has_attr(p,attr_jchar_class))
257 return calc_ja_ja_aux(g,h)
261 local function ins_gk_any_JA()
262 ps = find_size_metric(p)
263 if chain then -- (q,p): JA-JA
264 local g = calc_ja_ja_glue()
265 local w = 0; local x = ljfm_find_char_class('lineend', qs[2])
266 if (not ihb_flag) and x~=0 then
267 local h = ltj.metrics[qs[2]].char_type[has_attr(q, attr_jchar_class)]
268 if h.kern and h.kern[x] then w = round(qs[1]*h.kern[x]) end
270 q_post = add_penalty(q_post, cstb_get_penalty_table('pre', p.char))
271 real_insert(g, w, q_post, false)
272 elseif q.id==id_glyph then -- (q,p): AL-JA
275 g = new_jfm_glue(ps[1], ps[2],
276 ljfm_find_char_class('jcharbdd',ps[2]),
277 has_attr(p,attr_jchar_class))
279 q_post = add_penalty(q_post, cstb_get_penalty_table('pre', p.char))
280 real_insert(g, 0, q_post, true)
281 elseif q.id==id_kern then -- (q,p): kern-JA
284 g = new_jfm_glue(ps[1], ps[2],
285 ljfm_find_char_class('jcharbdd',ps[2]),
286 has_attr(p,attr_jchar_class))
292 g = new_jfm_glue(ps[1], ps[2],
293 ljfm_find_char_class('jcharbdd',ps[2]),
294 has_attr(p,attr_jchar_class))
296 real_insert(g, 0, q_post, true)
298 q, qs, q_post = p, ps, cstb_get_penalty_table('post',p.char)
299 if cstb_get_penalty_table('kcat',p.char)%2~=1 then
302 p = node_next(p); chain = true
305 local function ins_gk_JA_any()
306 -- the case (q,p): JA-JA is treated in ins_gk_any_JA()
309 g = new_jfm_glue(qs[1], qs[2],
310 has_attr(q,attr_jchar_class),
311 ljfm_find_char_class('jcharbdd',qs[2]))
313 if p.id==id_glyph then -- (q,p): JA-AL
314 local w = 0; local x = ljfm_find_char_class('lineend', qs[2])
315 if (not ihb_flag) and x~=0 then
316 local h = ltj.metrics[qs[2]].char_type[has_attr(q,attr_jchar_class)]
317 if h.kern and h.kern[x] then w = round(qs[1]*h.kern[x]) end
319 q_post = add_penalty(q_post, cstb_get_penalty_table('pre',p.char))
320 real_insert(g, w, q_post, true)
321 elseif p.id==id_kern then -- (q,p): JA-kern
324 local w = 0; local x = ljfm_find_char_class('lineend', qs[2])
325 if (not ihb_flag) and x~=0 then
326 local h = ltj.metrics[qs[2]].char_type[has_attr(q,attr_jchar_class)]
327 if h.kern and h.kern[x] then w = round(qs[1]*h.kern[x]) end
329 if p.id~=penalty then
330 real_insert(g, w, q_post, true)
332 real_insert(g, w, q_post, false)
335 chain = false; q, qs, q_post = p, nil, 0; p = node_next(p)
338 -- Insert JFM glue after thr last node
339 local function ins_gk_tail()
341 if is_japanese_glyph_node(p) then
343 local w = 0; local x = ljfm_find_char_class('lineend', qs[2])
344 if (not ihb_flag) and x~=0 then
345 local h = ltj.metrics[qs[2]].char_type[has_attr(q,attr_jchar_class)]
346 if h.kern and h.kern[x] then w = round(qs[1]*h.kern[x]) end
349 g = node_new(id_kern); g.subtype = 0; g.kern = w
350 node.set_attribute(g, attr_icflag, LINE_END)
351 node_insert_before(head, last, g)
357 local function add_widow_penalty()
358 -- widoe_node: must be
359 if not widow_node then return end
360 local a = node_prev(widow_node)
361 local i = has_attr(a, attr_icflag) or 0
362 local wp = tex.getcount('jcharwidowpenalty')
364 a.penalty=add_penalty(a.penalty, wp)
366 local b = node_prev(a)
368 b.penalty=add_penalty(b.penalty, wp)
370 local g = node_new(id_penalty)
371 g.penalty = wp; head = node_insert_before(head,a,g)
374 local g = node_new(id_penalty)
375 g.penalty = wp; head = node_insert_before(head,widow_node,g)
379 -- Finishing: add \jcharwidowpenalty or remove the sentinel
380 local function finishing()
382 -- Insert \jcharwidowpenalty
385 head = node_remove(head, last)
390 function ltj.int_insert_jfm_glue(ahead, amode)
391 if not ahead then return ahead end
392 head = ahead; mode = amode; init_var();
393 while p~=last and p.id==id_whatsit and p.subtype==sid_user and p.user_id==30111 do
394 local g = p; p = node_next(p); ihb_flag = true; head, p = node.remove(head, g)
396 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)
414 ins_gk_tail(); finishing(); return head