1 local node_type = node.type
2 local node_new = node.new
3 local node_prev = node.prev
4 local node_next = node.next
5 local has_attr = node.has_attribute
6 local node_insert_before = node.insert_before
7 local node_insert_after = node.insert_after
8 local node_hpack = node.hpack
9 local round = tex.round
11 local id_penalty = node.id('penalty')
12 local id_glyph = node.id('glyph')
13 local id_glue_spec = node.id('glue_spec')
14 local id_glue = node.id('glue')
15 local id_kern = node.id('kern')
16 local id_hlist = node.id('hlist')
17 local id_vlist = node.id('vlist')
18 local id_rule = node.id('rule')
19 local id_math = node.id('math')
20 local id_whatsit = node.id('whatsit')
22 local attr_jchar_class = luatexbase.attributes['ltj@charclass']
23 local attr_curjfnt = luatexbase.attributes['ltj@curjfnt']
24 local attr_yablshift = luatexbase.attributes['ltj@yablshift']
25 local attr_ykblshift = luatexbase.attributes['ltj@ykblshift']
26 local attr_icflag = luatexbase.attributes['ltj@icflag']
27 -- attr_icflag: 1: kern from \/, 2: 'lineend' kern from JFM
29 local lang_ja_token = token.create('ltj@japanese')
30 local lang_ja = lang_ja_token[2]
33 local rgjc_get_range_setting = ltj.int_get_range_setting
34 local rgjc_char_to_range = ltj.int_char_to_range
35 local rgjc_is_ucs_in_japanese_char = ltj.int_is_ucs_in_japanese_char
36 local ljfm_find_char_class = ltj.int_find_char_class
38 ------------------------------------------------------------------------
40 -- ltj.ext_... : called from \directlua{}
41 -- ltj.int_... : called from other Lua codes, but not from \directlua{}
42 -- (other) : only called from this file
45 function ltj.error(s,t)
46 tex.error('LuaTeX-ja error: ' .. s ,t)
49 -- Three aux. functions, bollowed from tex.web
51 local function print_scaled(s)
57 out=out..tostring(math.floor(s/unity)) .. '.'
60 if delta>unity then s=s+32768-50000 end
61 out=out .. tostring(math.floor(s/unity))
68 local function print_glue(d,order)
69 local out=print_scaled(d)
73 out=out..'l'; order=order-1
81 local function print_spec(p)
82 local out=print_scaled(p.width)..'pt'
84 out=out..' plus '..print_glue(p.stretch,p.stretch_order)
87 out=out..' minus '..print_glue(p.shrink,p.shrink_order)
92 -- return true if and only if p is a Japanese character node
93 local function is_japanese_glyph_node(p)
94 return p and (p.id==id_glyph)
95 and (p.font==has_attr(p,attr_curjfnt))
99 ------------------------------------------------------------------------
100 -- CODE FOR STACK TABLE FOR CHARACTER PROPERTIES (prefix: cstb)
101 ------------------------------------------------------------------------
103 ---- table: charprop_stack_table [stack_level][chr_code].{pre|post|xsp}
104 local charprop_stack_table={}; charprop_stack_table[0]={}
106 local function cstb_get_stack_level()
107 local i = tex.getcount('ltj@@stack')
108 if tex.currentgrouplevel > tex.getcount('ltj@@group@level') then
109 i = i+1 -- new stack level
110 tex.setcount('ltj@@group@level', tex.currentgrouplevel)
111 for j,v in pairs(charprop_stack_table) do -- clear the stack above i
112 if j>=i then charprop_stack_table[j]=nil end
114 charprop_stack_table[i] = table.fastcopy(charprop_stack_table[i-1])
115 tex.setcount('ltj@@stack', i)
121 function ltj.ext_set_stack_table(g,m,c,p,lb,ub)
122 local i = cstb_get_stack_level()
124 ltj.error('Invalid code (' .. p .. '), should in the range '
125 .. tostring(lb) .. '..' .. tostring(ub) .. '.',
126 {"I'm going to use 0 instead of that illegal code value."})
128 elseif c<-1 or c>0x10FFFF then
129 ltj.error('Invalid character code (' .. p
130 .. '), should in the range -1.."10FFFF.',{})
132 elseif not charprop_stack_table[i][c] then
133 charprop_stack_table[i][c] = {}
135 charprop_stack_table[i][c][m] = p
137 for j,v in pairs(charprop_stack_table) do
138 if not charprop_stack_table[j][c] then charprop_stack_table[j][c] = {} end
139 charprop_stack_table[j][c][m] = p
144 local function cstb_get_penalty_table(m,c)
145 local i = charprop_stack_table[tex.getcount('ltj@@stack')][c]
150 local function cstb_get_inhibit_xsp_table(c)
151 local i = charprop_stack_table[tex.getcount('ltj@@stack')][c]
152 if i then i=i.xsp end
155 ltj.int_get_inhibit_xsp_table = cstb_get_inhibit_xsp_table
157 ------------------------------------------------------------------------
158 -- CODE FOR GETTING/SETTING PARAMETERS
159 ------------------------------------------------------------------------
161 -- EXT: print parameters that don't need arguments
162 function ltj.ext_get_parameter_unary(k)
163 if k == 'yalbaselineshift' then
164 tex.write(print_scaled(tex.getattribute('ltj@yablshift'))..'pt')
165 elseif k == 'yjabaselineshift' then
166 tex.write(print_scaled(tex.getattribute('ltj@ykblshift'))..'pt')
167 elseif k == 'kanjiskip' then
168 tex.write(print_spec(tex.getskip('kanjiskip')))
169 elseif k == 'xkanjiskip' then
170 tex.write(print_spec(tex.getskip('xkanjiskip')))
171 elseif k == 'jcharwidowpenalty' then
172 tex.write(tex.getcount('jcharwidowpenalty'))
173 elseif k == 'autospacing' then
174 tex.write(tostring(ltj.auto_spacing))
175 elseif k == 'autoxspacing' then
176 tex.write(tostring(ltj.auto_xspacing))
177 elseif k == 'differentjfm' then
178 if ltj.calc_between_two_jchar_aux==ltj.calc_between_two_jchar_aux_large then
180 elseif ltj.calc_between_two_jchar_aux==ltj.calc_between_two_jchar_aux_small then
182 elseif ltj.calc_between_two_jchar_aux==ltj.calc_between_two_jchar_aux_average then
184 elseif ltj.calc_between_two_jchar_aux==ltj.calc_between_two_jchar_aux_both then
186 else -- This can't happen.
192 -- EXT: print parameters that need arguments
193 function ltj.ext_get_parameter_binary(k,c)
194 if k == 'jacharrange' then
195 if c<0 or c>216 then c=0 end
196 tex.write(rgjc_get_range_setting(c))
198 if c<0 or c>0x10FFFF then
199 ltj.error('Invalid character code (' .. c
200 .. '), should in the range 0.."10FFFF.',
201 {"I'm going to use 0 instead of that illegal character code."})
204 if k == 'prebreakpenalty' then
205 tex.write(cstb_get_penalty_table('pre',c))
206 elseif k == 'postbreakpenalty' then
207 tex.write(cstb_get_penalty_table('post',c))
208 elseif k == 'kcatcode' then
209 tex.write(cstb_get_penalty_table('kcat',c))
210 elseif k == 'chartorange' then
211 tex.write(rgjc_char_to_range(c))
212 elseif k == 'jaxspmode' or k == 'alxspmode' then
213 tex.write(cstb_get_inhibit_xsp_table(c))
218 -- EXT: print \global if necessary
219 function ltj.ext_print_global()
220 if ltj.isglobal=='global' then tex.sprint('\\global') end
224 ------------------------------------------------------------------------
225 -- MAIN PROCESS STEP 1: replace fonts (prefix: main1)
226 ------------------------------------------------------------------------
228 --- the following function is modified from jafontspec.lua (by K. Maeda).
229 --- Instead of "%", we use U+FFFFF for suppressing spaces.
230 local function main1_process_input_buffer(buffer)
231 local c = utf.byte(buffer, utf.len(buffer))
232 local p = node_new(id_glyph)
234 if utf.len(buffer) > 0
235 and rgjc_is_ucs_in_japanese_char(p) then
236 buffer = buffer .. string.char(0xF3,0xBF,0xBF,0xBF) -- U+FFFFF
241 local function main1_suppress_hyphenate_ja(head)
243 for p in node.traverse(head) do
244 if p.id == id_glyph then
245 if rgjc_is_ucs_in_japanese_char(p) then
246 local v = has_attr(p, attr_curjfnt)
249 node.set_attribute(p,attr_jchar_class,
250 ljfm_find_char_class(p.char, ltj.font_metric_table[v].jfm))
252 v = has_attr(p, attr_ykblshift)
254 node.set_attribute(p, attr_yablshift, v)
256 node.unset_attribute(p, attr_yablshift)
263 return head -- 共通化のため値を返す
267 luatexbase.add_to_callback('process_input_buffer',
269 return main1_process_input_buffer(buffer)
270 end,'ltj.process_input_buffer')
271 luatexbase.add_to_callback('hpack_filter',
272 function (head,groupcode,size,packtype)
273 return main1_suppress_hyphenate_ja(head)
274 end,'ltj.hpack_filter_pre',0)
275 luatexbase.add_to_callback('hyphenate',
277 return main1_suppress_hyphenate_ja(head)
281 ------------------------------------------------------------------------
282 -- MAIN PROCESS STEP 2: insert glue/kerns from JFM (prefix: main2)
283 ------------------------------------------------------------------------
285 -- EXT: for \inhibitglue
286 function ltj.ext_create_inhibitglue_node()
287 local g=node_new(id_whatsit, node.subtype('user_defined'))
288 g.user_id=30111; g.type=number; g.value=1; node.write(g)
292 local function main2_find_size_metric(px)
293 if is_japanese_glyph_node(px) then
294 return ltj.font_metric_table[px.font].size,
295 ltj.font_metric_table[px.font].jfm, ltj.font_metric_table[px.font].var
301 local function main2_new_jfm_glue(size,mt,bc,ac)
302 -- mt: metric key, bc, ac: char classes
304 local z = ltj.metrics[mt].char_type[bc]
305 if z.glue and z.glue[ac] then
306 local h = node_new(id_glue_spec)
307 h.width = round(size*z.glue[ac][1])
308 h.stretch = round(size*z.glue[ac][2])
309 h.shrink = round(size*z.glue[ac][3])
310 h.stretch_order=0; h.shrink_order=0
311 g = node_new(id_glue)
312 g.subtype = 0; g.spec = h
313 elseif z.kern and z.kern[ac] then
314 g = node_new(id_kern)
315 g.subtype = 1; g.kern = round(size*z.kern[ac])
320 -- return value: g (glue/kern from JFM), w (width of 'lineend' kern)
321 local function main2_calc(qs,qm,qv,q,p,last,ihb_flag)
322 -- q, p: node (possibly null)
323 local ps, pm, pv, g, h
325 if (not p) or p==last then
326 -- q is the last node
329 elseif not ihb_flag then
330 g=main2_new_jfm_glue(qs,qm,
331 has_attr(q,attr_jchar_class),
332 ljfm_find_char_class('boxbdd',qm))
335 -- p is the first node etc.
336 ps, pm, pv = main2_find_size_metric(p)
339 elseif not ihb_flag then
340 g=main2_new_jfm_glue(ps,pm,
341 ljfm_find_char_class('boxbdd',pm),
342 has_attr(p,attr_jchar_class))
344 else -- p and q are not nil
345 ps, pm, pv = main2_find_size_metric(p)
346 if ihb_flag or ((not pm) and (not qm)) then
348 elseif (qs==ps) and (qm==pm) and (qv==pv) then
349 -- Both p and q are Japanese glyph nodes, and same metric and size
350 g = main2_new_jfm_glue(ps,pm,
351 has_attr(q,attr_jchar_class),
352 has_attr(p,attr_jchar_class))
354 -- q is not a Japanese glyph node
355 g = main2_new_jfm_glue(ps,pm,
356 ljfm_find_char_class('jcharbdd',pm),
357 has_attr(p,attr_jchar_class))
359 -- p is not a Japanese glyph node
360 g = main2_new_jfm_glue(qs,qm,
361 has_attr(q,attr_jchar_class),
362 ljfm_find_char_class('jcharbdd',qm))
364 g = main2_new_jfm_glue(qs,qm,
365 has_attr(q,attr_jchar_class),
366 ljfm_find_char_class('diffmet',qm))
367 h = main2_new_jfm_glue(ps,pm,
368 ljfm_find_char_class('diffmet',pm),
369 has_attr(p,attr_jchar_class))
370 g = ltj.calc_between_two_jchar_aux(g,h)
373 if g then node.set_attribute(g, attr_icflag, 3) end
375 local x = ljfm_find_char_class('lineend', qm)
377 qv = ltj.metrics[qm].char_type[has_attr(q,attr_jchar_class)]
378 if qv.kern and qv.kern[x] then
379 w = round(qs*qv.kern[x])
387 local function main2_between_two_char(head,q,p,p_bp,ihb_flag,last)
388 local qs = 0; local g, w
391 qs, qm, qv = main2_find_size_metric(q)
393 g, w = main2_calc(qs, qm, qv, q, p, last, ihb_flag)
394 if w~=0 and (not p_bp) then
395 p_bp = node_new(id_penalty); p_bp.penalty = 0
396 head = node_insert_before(head, p, p_bp)
399 if g.id==id_kern then
400 g.kern = round(g.kern - w)
402 g.spec.width = round(g.spec.width - w)
404 head = node_insert_before(head, p, g)
406 g = node_new(id_kern); g.kern = -w; g.subtype = 1
407 node.set_attribute(g,attr_icflag,2)
408 head = node_insert_before(head, p, g)
409 -- this g might be replaced by \[x]kanjiskip in step 3.
412 g = node_new(id_kern); g.kern = w; g.subtype = 0
413 head = node_insert_before(head, p_bp, g)
418 -- In the beginning of a hlist created by line breaking, there are the followings:
419 -- - a hbox by \parindent
420 -- - a whatsit node which contains local paragraph materials.
421 -- When we insert jfm glues, we ignore these nodes.
422 local function main2_is_parindent_box(p)
423 if p.id==id_hlist then
424 return (p.subtype==3)
425 -- hlist (subtype=3) is a box by \parindent
426 elseif p.id==id_whatsit then
427 return (p.subtype==node.subtype('local_par'))
431 -- next three functions deal with inserting penalty by kinsoku.
432 local function main2_add_penalty_before(head,p,p_bp,pen)
434 p_bp.penalty = p_bp.penalty + pen
435 else -- we must create a new penalty node
436 local g = node_new(id_penalty); g.penalty = pen
437 local q = node_prev(p)
439 if has_attr(q, attr_icflag) ~= 3 then
442 return node_insert_before(head, q, g)
448 local function main2_add_kinsoku_penalty(head,p,p_bp)
450 local e = cstb_get_penalty_table('pre',c)
452 head = main2_add_penalty_before(head, p, p_bp, e)
454 e = cstb_get_penalty_table('post',c)
456 local q = node_next(p)
457 if q and q.id==id_penalty then
458 q.penalty = q.penalty + e
461 q = node_new(id_penalty); q.penalty = e
462 node_insert_after(head,p,q)
468 local function main2_add_widow_penalty(head,widow_node,widow_bp)
469 if not widow_node then
472 return main2_add_penalty_before(head, widow_node,
473 widow_bp, tex.getcount('jcharwidowpenalty'))
479 -- Insert jfm glue: main routine
480 -- mode = true iff insert_jfm_glue is called from pre_linebreak_filter
481 local function main2_insert_jfm_glue(head, mode)
483 local p_bp = nil -- p と直前の文字の間の penalty node
484 local q = nil -- the previous node of p
485 local widow_node = nil -- 最後の「句読点扱いでない」和文文字
486 local widow_bp = nil -- \jcharwidowpenalty 挿入位置
487 local last -- the sentinel
488 local ihb_flag = false -- is \inhibitglue specified?
491 if not p then return head
493 while p and main2_is_parindent_box(p) do p=node_next(p) end
495 if last and last.id==id_glue and last.subtype==15 then
497 while (last and last.id==id_penalty) do last=node.prev(last) end
499 if last then last=node_next(last) end
501 last=node.tail(head); g = node_new('kern')
502 node_insert_after(head,last,g); last = g
506 if p.id==id_whatsit and p.subtype==node.subtype('user_defined')
507 and p.user_id==30111 then
508 g = p; p = node_next(p)
509 ihb_flag = true; head, p = node.remove(head, g)
511 head, p_bp = main2_between_two_char(head, q, p, p_bp, ihb_flag, last)
513 if is_japanese_glyph_node(p) then
514 if cstb_get_penalty_table('kcat',p.char)%2~=1 then
515 widow_node = p; widow_bp = p_bp
517 if main2_add_kinsoku_penalty(head, p, p_bp) then
518 p_bp = node_next(p); p = p_bp
527 -- Insert \jcharwidowpenalty
528 head = main2_add_widow_penalty(head, widow_node, widow_bp)
531 if p and p.id==id_kern and has_attr(p,attr_icflag)==2 then
532 head = node.remove(head, p)
535 head = node.remove(head, last)
541 ------------------------------------------------------------------------
542 -- MAIN PROCESS STEP 4: width of japanese chars (prefix: main4)
543 ------------------------------------------------------------------------
546 local function main4_get_hss()
547 local hss = node_new(id_glue)
548 local fil_spec = node_new(id_glue_spec)
550 fil_spec.stretch = 65536
551 fil_spec.stretch_order = 2
552 fil_spec.shrink = 65536
553 fil_spec.shrink_order = 2
558 local function main4_set_ja_width(head)
560 local met_tb, t, s, g, q, a, h
561 local m = false -- is in math mode?
563 local v=has_attr(p,attr_yablshift) or 0
564 if p.id==id_glyph then
565 p.yoffset = p.yoffset-v
566 if is_japanese_glyph_node(p) then
567 met_tb = ltj.font_metric_table[p.font]
568 t = ltj.metrics[met_tb.jfm]
569 s = t.char_type[has_attr(p,attr_jchar_class)]
570 if s.width ~= 'prop' and
571 not(s.left==0.0 and s.down==0.0 and s.align=='left'
572 and round(s.width*met_tb.size)==p.width) then
573 -- must be encapsuled by a \hbox
574 head, q = node.remove(head,p)
576 p.yoffset=round(p.yoffset-met_tb.size*s.down)
577 p.xoffset=round(p.xoffset-met_tb.size*s.left)
578 if s.align=='middle' or s.align=='right' then
579 h = node_insert_before(p, p, main4_get_hss())
581 if s.align=='middle' or s.align=='left' then
582 node_insert_after(h, p, main4_get_hss())
584 g = node_hpack(h, round(met_tb.size*s.width), 'exactly')
585 g.height = round(met_tb.size*s.height)
586 g.depth = round(met_tb.size*s.depth)
587 head, p = node_insert_before(head, q, g)
593 elseif p.id==id_math then
594 m=(p.subtype==0); p=node_next(p)
597 if p.id==id_hlist or p.id==id_vlist then
599 elseif p.id==id_rule then
600 p.height=p.height-v; p.depth=p.depth+v
610 -- mode = true iff main_process is called from pre_linebreak_filter
611 local function main_process(head, mode)
613 p = main2_insert_jfm_glue(p,mode)
614 p = ltj.int_insert_kanji_skip(p)
615 p = main4_set_ja_width(p)
622 function ltj.ext_show_node_list(head,depth,print_fn)
625 debug_show_node_list_X(head, print_fn)
627 print_fn(debug_depth .. ' (null list)')
630 function debug_show_node_list_X(p,print_fn)
631 debug_depth=debug_depth.. '.'
632 local k = debug_depth
634 local pt=node_type(p.id)
635 if pt == 'glyph' then
636 print_fn(debug_depth.. ' glyph ', p.subtype, utf.char(p.char), p.font)
637 elseif pt=='hlist' then
638 print_fn(debug_depth.. ' hlist ', p.subtype, '(' .. print_scaled(p.height)
639 .. '+' .. print_scaled(p.depth)
640 .. ')x' .. print_scaled(p.width) )
641 debug_show_node_list_X(p.head,print_fn)
643 elseif pt == 'whatsit' then
644 print_fn(debug_depth.. ' whatsit', p.subtype)
645 elseif pt == 'glue' then
646 print_fn(debug_depth.. ' glue ', p.subtype, print_spec(p.spec))
647 elseif pt == 'kern' then
648 print_fn(debug_depth.. ' kern ', p.subtype, print_scaled(p.kern) .. 'pt')
649 elseif pt == 'penalty' then
650 print_fn(debug_depth.. ' penalty', p.penalty)
652 print_fn(debug_depth.. ' ' .. node.type(p.id), p.subtype)
661 luatexbase.add_to_callback('pre_linebreak_filter',
662 function (head,groupcode)
663 return main_process(head, true)
664 end,'ltj.pre_linebreak_filter',2)
665 luatexbase.add_to_callback('hpack_filter',
666 function (head,groupcode,size,packtype)
667 return main_process(head, false)
668 end,'ltj.hpack_filter',2)