3 tex.error("LuaTeX-ja error: " .. s );
6 -- procedures for loading Japanese font metric
7 jfm={}; jfm.char_type={}; jfm.glue={}; jfm.kern={}
9 function jfm.define_char_type(t,lt)
10 if not jfm.char_type[t] then jfm.char_type[t]={} end
11 jfm.char_type[t].chars=lt
13 function jfm.define_type_dim(t,l,w,h,d,i)
14 if not jfm.char_type[t] then jfm.char_type[t]={} end
15 jfm.char_type[t].width=w; jfm.char_type[t].height=h;
16 jfm.char_type[t].depth=d; jfm.char_type[t].italic=i; jfm.char_type[t].left=l
18 function jfm.define_glue(b,a,w,st,sh)
20 if not jfm.glue[j] then jfm.glue[j]={} end
21 jfm.glue[j].width=w; jfm.glue[j].stretch=st;
24 function jfm.define_kern(b,a,w)
26 if not jfm.kern[j] then jfm.kern[j]=w end
29 -- procedures for \loadjfontmetric
30 ltj.metrics={} -- this table stores all metric information
32 function ltj.loadjfontmetric()
33 if string.len(jfm.name)==0 then
34 ltj.error("the key of font metric is null"); return nil
35 elseif ltj.metrics[jfm.name] then
36 ltj.error("the metric '" .. jfm.name .. "' is already loaded"); return nil
38 ltj.metrics[jfm.name]={}
39 if jfm.dir~='yoko' then
40 ltj.error("jfm.dir must be 'yoko'"); return nil
42 ltj.metrics[jfm.name].dir=jfm.dir
43 ltj.metrics[jfm.name].zw=jfm.zw
44 ltj.metrics[jfm.name].zh=jfm.zh
45 ltj.metrics[jfm.name].char_type=jfm.char_type
46 ltj.metrics[jfm.name].glue=jfm.glue
47 ltj.metrics[jfm.name].kern=jfm.kern
50 function ltj.find_char_type(c,metrickey)
51 -- c: character code, metrickey: key
52 for i, v in pairs(ltj.metrics[metrickey].char_type) do
54 for j,w in pairs(v.chars) do
55 if w==c then return i; end
62 -- procedures for \jfont command.
63 function ltj.jfontdefA(b)
64 ltj.fntbki=font.current()
65 ltj.cstemp=token.csname_name(token.get_next())
66 tex.sprint('\\csname ' .. ltj.cstemp .. '\\endcsname\\csname @jfont\\endcsname')
67 -- A trick to get font id associated of the argument of \jfont.
68 -- font.id() does not seem to work in my environment...
70 function ltj.jfontdefB(s) -- for horizontal font
71 if not ltj.metrics[s] then
72 ltj.error("metric named '" .. s .. "' didn't loaded")
75 local i = ltj.transform_jfont(font.current(), s)
76 tex.sprint('\\expandafter\\def\\csname ' .. ltj.cstemp .. '\\endcsname'
77 .. '{\\csname luatexja@curjfnt\\endcsname=' .. i
78 .. '\\zw=' .. font.fonts[i].parameters.quad .. 'sp'
79 .. '\\zh=' .. font.fonts[i].parameters.x_height .. 'sp\\relax}')
80 font.current(ltj.fntbki); ltj.fntbk = {}; ltj.cstemp = {}
83 -- "transform" font according to a font metric
84 function ltj.transform_jfont(index,metrickey)
86 local of=font.fonts[index]
88 f.fullname = metrickey
91 f.parameters.quad=tex.round(f.size*ltj.metrics[metrickey].zw)
92 f.parameters.x_height=tex.round(f.size*ltj.metrics[metrickey].zh)
93 f.designsize = of.designsize
96 f.fonts = { { id = index } }
97 for i,v in pairs(of.characters) do
99 local ci = ltj.metrics[metrickey].char_type[ltj.find_char_type(i,metrickey)]
101 f.characters[i].commands = {
102 {'right', tex.round(-ci.left*f.size)},
106 f.characters[i].commands = { {'char',i} }
108 f.characters[i].italic = tex.round(ci.italic*f.size)
109 f.characters[i].width = tex.round(ci.width*f.size)
110 f.characters[i].height = tex.round(ci.height*f.size)
111 f.characters[i].depth = tex.round(ci.depth*f.size)
113 if not f.characters[0x3000] then
114 f.characters[0x3000]={}
115 local ci = ltj.metrics[metrickey].char_type[ltj.find_char_type(0x3000,metrickey)]
116 f.characters[0x3000].commands = {{'right', tex.round(ci.width*f.size)} }
117 f.characters[0x3000].italic = tex.round(ci.italic*f.size)
118 f.characters[0x3000].width = tex.round(ci.width*f.size)
119 f.characters[0x3000].height = tex.round(ci.height*f.size)
120 f.characters[0x3000].depth = tex.round(ci.depth*f.size)
122 return font.define(f)
126 -- Replace font in nodes which character code is in the range of
127 -- Japanese characters, using two attributes
128 function ltj.replace_ja_font(head)
131 if node.type(p.id)=='glyph' then
132 if ltj.is_ucs_in_japanese_char(p.char) then
133 local v = node.has_attribute(p,luatexbase.attributes['luatexja@curjfnt'])
134 if v then p.font=v end
135 v=node.has_attribute(p,luatexbase.attributes['luatexja@ykblshift'])
137 node.set_attribute(p,luatexbase.attributes['luatexja@yablshift'],v)
139 node.unset_attribute(p,luatexbase.attributes['luatexja@yablshift'])
149 -- return true if and only if p is a Japanese character node
150 function ltj.is_japanese_glyph_node(p)
151 if not p then return false
152 elseif node.type(p.id)~='glyph' then return false
153 elseif p.font==node.has_attribute(p,luatexbase.attributes['luatexja@curjfnt']) then
161 ltj.penalty_table = {}
162 function ltj.set_penalty_table(m,c,p)
163 if not ltj.penalty_table[c] then ltj.penalty_table[c]={} end
165 ltj.penalty_table[c].pre=p
166 elseif m=='post' then
167 ltj.penalty_table[c].post=p
171 ltj.inhibit_xsp_table = {}
172 function ltj.set_inhibit_xsp_table(c,p)
173 ltj.inhibit_xsp_table[c]=p
178 function ltj.create_ihb_node()
179 local g=node.new(node.id('whatsit'), node.subtype('user_defined'))
180 g.user_id=30111; g.type=number; g.value=1
184 -- The fullname field of virtual font expresses its metric
185 function ltj.find_size_metrickey(p)
186 if ltj.is_japanese_glyph_node(p) then
187 return font.fonts[p.font].size, font.fonts[p.font].fullname
193 function ltj.new_jfm_glue(size,mt,bc,ac)
194 -- mt: metric key, bc, ac: char classes
198 if ltj.metrics[mt].glue[w] then
199 h=node.new(node.id('glue_spec'))
200 h.width =tex.round(size*ltj.metrics[mt].glue[w].width)
201 h.stretch=tex.round(size*ltj.metrics[mt].glue[w].stretch)
202 h.shrink =tex.round(size*ltj.metrics[mt].glue[w].shrink)
203 h.stretch_order=0; h.shrink_order=0
204 g=node.new(node.id('glue'))
205 g.subtype=0; g.spec=h; return g
206 elseif ltj.metrics[mt].kern[w] then
207 g=node.new(node.id('kern'))
208 g.subtype=0; g.kern=tex.round(size*ltj.metrics[mt].kern[w]); return g
214 -- The fullname field of virtual font expresses its metric
215 function ltj.calc_between_two_jchar(q,p)
216 -- q, p: node (possibly null)
217 local ps,pm,qs,qm,g,h
218 if not p then -- q is the last node
219 qs, qm = ltj.find_size_metrickey(q)
223 g=ltj.new_jfm_glue(qs,qm,
224 ltj.find_char_type(q.char,qm),
225 ltj.find_char_type('boxbdd',qm))
228 -- p is the first node etc.
229 if q then print(node.type(q.id)) end
230 ps, pm = ltj.find_size_metrickey(p)
234 g=ltj.new_jfm_glue(ps,pm,
235 ltj.find_char_type('boxbdd',pm),
236 ltj.find_char_type(p.char,pm))
238 else -- p and q are not nil
239 qs, qm = ltj.find_size_metrickey(q)
240 ps, pm = ltj.find_size_metrickey(p)
241 if (not pm) and (not qm) then
242 -- Both p and q are NOT Japanese glyph node
244 elseif (qs==ps) and (qm==pm) then
245 -- Both p and q are Japanese glyph node, and same metric and size
246 g=ltj.new_jfm_glue(ps,pm,
247 ltj.find_char_type(q.char,qm),
248 ltj.find_char_type(p.char,pm))
250 -- q is not Japanese glyph node
251 g=ltj.new_jfm_glue(ps,pm,
252 ltj.find_char_type('jcharbdd',pm),
253 ltj.find_char_type(p.char,pm))
255 -- p is not Japanese glyph node
256 g=ltj.new_jfm_glue(qs,qm,
257 ltj.find_char_type(q.char,qm),
258 ltj.find_char_type('jcharbdd',qm))
260 g=ltj.new_jfm_glue(qs,qm,
261 ltj.find_char_type(q.char,qm),
262 ltj.find_char_type('diffmet',qm))
263 h=ltj.new_jfm_glue(ps,pm,
264 ltj.find_char_type('diffmet',pm),
265 ltj.find_char_type(p.char,pm))
266 g=ltj.calc_between_two_jchar_aux(g,h)
273 -- In the beginning of a hbox created by line breaking, there are the followings:
274 -- o a hbox by \parindent
275 -- o a whatsit node which contains local paragraph materials.
276 -- When we insert jfm glues, we ignore these nodes.
277 function ltj.is_parindent_box(p)
278 if node.type(p.id)=='hlist' then
279 return (p.subtype==3)
280 -- hlist (subtype=3) is a box by \parindent
281 elseif node.type(p.id)=='whatsit' then
282 return (p.subtype==node.subtype('local_par'))
286 function ltj.add_kinsoku_penalty(head,p)
288 if not ltj.penalty_table[c] then return false; end
289 if ltj.penalty_table[c].pre then
290 local q = node.prev(p)
291 if (not q) and node.type(q.id)=='penalty' then
292 q.penalty=q.penalty+ltj.penalty_table[c].pre
294 q=node.new(node.id('penalty'))
295 q.penalty=ltj.penalty_table[c].pre
296 node.insert_before(head,p,q)
299 if ltj.penalty_table[c].post then
300 local q = node.next(p)
301 if (not q) and node.type(q.id)=='penalty' then
302 q.penalty=q.penalty+ltj.penalty_table[c].post
305 q=node.new(node.id('penalty'))
306 q.penalty=ltj.penalty_table[c].post
307 node.insert_after(head,p,q)
313 -- Insert jfm glue: main routine
315 function ltj.insert_jfm_glue(head)
317 local q = nil -- the previous node of p
319 local ihb_flag = false
323 while p and ltj.is_parindent_box(p) do p=node.next(p) end
325 if node.type(p.id)=='whatsit' and p.subtype==44
326 and p.user_id==30111 then
328 ihb_flag=true; head,p=node.remove(head, g)
330 g=ltj.calc_between_two_jchar(q,p)
331 if g and (not ihb_flag) then
332 h = node.insert_before(head,p,g)
333 if not q then head=h end
334 -- If p is the first node (=head), the skip is inserted
335 -- before head. So we must change head.
338 if ltj.is_japanese_glyph_node(p)
339 and ltj.add_kinsoku_penalty(head,p) then
345 -- Insert skip after the last node
346 g=ltj.calc_between_two_jchar(q,nil)
348 h = node.insert_after(head,q,g)
355 -- Insert \xkanjiskip at the boundaries between Japanese characters
356 -- and non-Japanese characters.
357 -- We also insert \kanjiskip between Kanji in this function.
362 -- 0: ``no_skip'', 1: ``after_schar'', 2: ``after_wchar''
363 -- These variables are ``global'', because we want to avoid to write one large function.
364 function ltj.insert_kanji_skip(head)
365 if tex.count['luatexja@autospc']==0 then
366 ltj.kanji_skip=tex.skip['kanjiskip']
368 ltj.kanji_skip=node.new(node.id('glue_spec'))
369 ltj.kanji_skip.width=0; ltj.kanji_skip.stretch=0; ltj.kanji_skip.shrink=0
371 if tex.count['luatexja@autoxspc']==0 then
372 ltj.xkanji_skip=tex.skip['xkanjiskip']
374 ltj.xkanji_skip=node.new(node.id('glue_spec'))
375 ltj.xkanji_skip.width=0; ltj.xkanji_skip.stretch=0; ltj.xkanji_skip.shrink=0
377 local p=head -- 「現在のnode」
381 if node.type(p.id)=='glyph' then
383 ltj.insks_around_char(head,q,p)
385 until (not p) or node.type(p.id)~='glyph'
387 if node.type(p.id) == 'hlist' then
388 ltj.insks_around_hbox(head,q,p)
389 elseif node.type(p.id) == 'penalty' then
390 ltj.insks_around_penalty(head,q,p)
391 elseif node.type(p.id) == 'kern' then
392 ltj.insks_around_kern(head,q,p)
393 elseif node.type(p.id) == 'math' then
394 ltj.insks_around_math(head,q,p)
395 elseif node.type(p.id) == 'ins' or node.type(p.id) == 'mark'
396 or node.type(p.id) == 'adjust'
397 or node.type(p.id) == 'whatsit' then
401 -- rule, disc, glue, margin_kern
410 -- Insert \xkanjiskip before p, a glyph node
412 function ltj.insks_around_char(head,q,p)
413 local a=ltj.inhibit_xsp_table[p.char]
414 if ltj.is_japanese_glyph_node(p) then
416 if ltj.is_japanese_glyph_node(q) then
417 local g = node.new(node.id('glue'))
418 g.subtype=0; g.spec=node.copy(ltj.kanji_skip)
419 node.insert_before(head,p,g)
420 elseif ltj.insert_skip==1 then
421 ltj.insert_akxsp(head,q)
425 if not a then a=3 end
426 if ltj.insert_skip==2 then
427 ltj.insert_kaxsp(head,q,a)
437 function ltj.insert_akxsp(head,q)
438 local f = ltj.inhibit_xsp_table[ltj.cx]
441 if f<=1 then return end
443 g = node.new(node.id('glue'))
444 g.subtype=0; g.spec=node.copy(ltj.xkanji_skip)
445 node.insert_after(head,q,g)
448 function ltj.insert_kaxsp(head,q,a)
450 local f=ltj.inhibit_xsp_table[ltj.cx]
453 if f%2==0 then g=false end
459 g = node.new(node.id('glue'))
460 g.subtype=0; g.spec=node.copy(ltj.xkanji_skip)
461 node.insert_after(head,q,g)
465 -- Return first and last glyph nodes in a hbox
468 ltj.find_first_char = nil
469 function ltj.check_box(bp)
473 if node.type(p.id)=='glyph' then
475 if ltj.find_first_char then
476 ltj.first_char=p; ltj.find_first_char=false
478 ltj.last_char=p; flag=true; p=node.next(p)
479 if not p then return flag end
480 until node.type(p.id)~='glyph'
482 if node.type(p.id)=='hlist' then
485 if ltj.check_box(p.head) then flag=true end
486 else if ltj.find_first_char then
487 ltj.find_first_char=false
492 elseif node.type(p.id) == 'ins' or node.type(p.id) == 'mark'
493 or node.type(p.id) == 'adjust'
494 or node.type(p.id) == 'whatsit' or node.type(p.id) == 'penalty' then
498 if ltj.find_first_char then
499 ltj.find_first_char=false
509 -- Insert \xkanjiskip around p, an hbox
510 function ltj.insks_around_hbox(head,q,p)
512 ltj.find_first_char=true
513 if ltj.check_box(p.head) then
515 if ltj.is_japanese_glyph_node(ltj.first_char) then
516 ltj.cx=ltj.first_char.char
517 if ltj.insert_skip==1 then
518 ltj.insert_akxsp(head,q)
519 elseif ltj.insert_skip==2 then
520 local g = node.new(node.id('glue'))
521 g.subtype=0; g.spec=node.copy(ltj.kanji_skip)
522 node.insert_before(head,p,g)
525 elseif ltj.first_char then
526 local a=ltj.inhibit_xsp_table[ltj.first_char.char]
527 if not a then a=3 end
528 if ltj.insert_skip==2 then
529 local g = node.new(node.id('glue'))
530 g.subtype=0; g.spec=node.copy(ltj.kanji_skip)
531 node.insert_after(head,q,g)
540 if ltj.is_japanese_glyph_node(ltj.last_char) then
541 if ltj.is_japanese_glyph_node(node.next(p)) then
542 local g = node.new(node.id('glue'))
543 g.subtype=0; g.spec=node.copy(ltj.kanji_skip)
544 node.insert_after(head,p,g)
547 elseif ltj.last_char then
548 local a=ltj.inhibit_xsp_table[ltj.last_char.char]
549 if not a then a=3 end
555 else ltj.insert_skip=0
557 else ltj.insert_skip=0
559 else ltj.insert_skip=0
563 -- Insert \xkanjiskip around p, a penalty
564 function ltj.insks_around_penalty(head,q,p)
566 if (not r) and node.type(r.id)=='glyph' then
567 local a=ltj.inhibit_xsp_table[r.char]
568 if ltj.is_japanese_glyph_node(r) then
570 if ltj.is_japanese_glyph_node(p) then
571 local g = node.new(node.id('glue'))
572 g.subtype=0; g.spec=node.copy(ltj.kanji_skip)
573 node.insert_before(head,r,g)
574 elseif ltj.insert_skip==1 then
575 ltj.insert_akxsp(head,p)
580 if not a then a=3 end
581 if ltj.insert_skip==2 then
582 ltj.insert_kaxsp(head,p,a)
593 -- Insert \xkanjiskip around p, a kern
594 function ltj.insks_around_kern(head,q,p)
595 if p.subtype==1 then -- \kern or \/
596 if node.has_attribute(p,luatexbase.attributes['luatexja@icflag']) then
597 p=p -- p is a kern from \/: do nothing
601 elseif p.subtype==2 then -- \accent: We ignore the accent character.
602 local v = node.next(node.next(node.next(p)))
603 if v and node.type(v.id)=='glyph' then
604 ltj.insks_around_char(head,q,v)
609 -- Insert \xkanjiskip around p, a math_node
610 function ltj.insks_around_math(head,q,p)
611 local a=ltj.inhibit_xsp_table['math']
612 if not a then a=3 end
613 if (p.subtype==0) and (ltj.insert_skip==2) then
614 ltj.insert_kaxsp(head,q,a)
622 function ltj.baselineshift(head)
624 local m=false -- is in math mode?
626 local v=node.has_attribute(p,luatexbase.attributes['luatexja@yablshift'])
628 if node.type(p.id)=='glyph' then
629 p.yoffset=p.yoffset-v
630 elseif node.type(p.id)=='math' then
633 if m then -- boxes and rules are shifted only in math mode
634 if node.type(p.id)=='hlist' or node.type(p.id)=='vlist' then
636 elseif node.type(p.id)=='rule' then
637 p.height=p.height-v; p.depth=p.depth+v
648 function ltj.main_process(head)
650 p = ltj.insert_jfm_glue(p)
651 p = ltj.insert_kanji_skip(p)
652 p = ltj.baselineshift(p)
656 --- the following function is modified from jafontspec.lua (by K. Maeda).
657 --- Instead of "%", we use U+FFFFF for suppressing spaces.
659 function ltj.process_input_buffer(buffer)
660 -- print(utf.byte(buffer, utf.len(buffer)))
661 if utf.len(buffer) > 0
662 and ltj.is_ucs_in_japanese_char(utf.byte(buffer, utf.len(buffer))) then
663 buffer = buffer .. string.char(0xF3,0xBF,0xBF,0xBF) -- U+FFFFF
672 function ltj.suppress_hyphenate_ja(head)
675 if node.type(p.id)=='glyph' and ltj.is_ucs_in_japanese_char(p.char) then
676 local v = node.has_attribute(p,luatexbase.attributes['luatexja@curjfnt'])
677 if v then p.font=v end
678 v=node.has_attribute(p,luatexbase.attributes['luatexja@ykblshift'])
680 node.set_attribute(p,luatexbase.attributes['luatexja@yablshift'],v)
682 node.unset_attribute(p,luatexbase.attributes['luatexja@yablshift'])
684 p.lang=ltj.ja_lang_number
692 luatexbase.add_to_callback('process_input_buffer',
694 return ltj.process_input_buffer(buffer)
695 end,'ltj.process_input_buffer')
696 luatexbase.add_to_callback('pre_linebreak_filter',
697 function (head,groupcode)
698 return ltj.main_process(head)
699 end,'ltj.pre_linebreak_filter')
700 luatexbase.add_to_callback('hpack_filter',
701 function (head,groupcode,size,packtype)
702 return ltj.main_process(ltj.replace_ja_font(head))
703 end,'ltj.hpack_filter')
704 luatexbase.add_to_callback('hyphenate',
706 return ltj.suppress_hyphenate_ja(head)