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,x,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;
17 jfm.char_type[t].left=l; jfm.char_type[t].down=x
19 function jfm.define_glue(b,a,w,st,sh)
21 if not jfm.glue[j] then jfm.glue[j]={} end
22 jfm.glue[j].width=w; jfm.glue[j].stretch=st;
25 function jfm.define_kern(b,a,w)
27 if not jfm.kern[j] then jfm.kern[j]=w end
30 -- procedures for \loadjfontmetric
31 ltj.metrics={} -- this table stores all metric informations
32 ltj.font_metric_table={}
34 function ltj.search_metric(key)
35 for i,v in ipairs(ltj.metrics) do
36 if v.name==key then return i end
41 function ltj.loadjfontmetric()
42 if string.len(jfm.name)==0 then
43 ltj.error("the key of font metric is null"); return nil
44 elseif ltj.search_metric(jfm.name) then
45 ltj.error("the metric '" .. jfm.name .. "' is already loaded"); return nil
47 if jfm.dir~='yoko' then
48 ltj.error("jfm.dir must be 'yoko'"); return nil
55 t.char_type=jfm.char_type
58 table.insert(ltj.metrics,t)
61 function ltj.find_char_type(c,m)
62 -- c: character code, m
63 if not ltj.metrics[m] then return 0 end
64 for i, v in pairs(ltj.metrics[m].char_type) do
66 for j,w in pairs(v.chars) do
67 if w==c then return i end
74 -- procedures for \jfont command.
75 function ltj.jfontdefA(b)
76 ltj.fntbki=font.current()
77 ltj.cstemp=token.csname_name(token.get_next())
78 tex.sprint('\\csname ' .. ltj.cstemp .. '\\endcsname\\csname @jfont\\endcsname')
79 -- A trick to get font id associated of the argument of \jfont.
80 -- font.id() does not seem to work in my environment...
82 function ltj.jfontdefB(s) -- for horizontal font
83 local j=ltj.search_metric(s)
85 ltj.error("metric named '" .. s .. "' didn't loaded")
88 local fn=font.current()
89 local f = font.fonts[fn]
90 ltj.font_metric_table[fn]={}
91 ltj.font_metric_table[fn].jfm=j; ltj.font_metric_table[fn].size=f.size
92 tex.sprint('\\expandafter\\def\\csname ' .. ltj.cstemp .. '\\endcsname'
93 .. '{\\csname luatexja@curjfnt\\endcsname=' .. fn
94 .. ' \\zw=' .. tex.round(f.size*ltj.metrics[j].zw) .. 'sp'
95 .. '\\zh=' .. tex.round(f.size*ltj.metrics[j].zh) .. 'sp\\relax}')
96 font.current(ltj.fntbki); ltj.fntbk = {}; ltj.cstemp = {}
99 -- return true if and only if p is a Japanese character node
100 function ltj.is_japanese_glyph_node(p)
101 return p and (node.type(p.id)=='glyph')
102 and (p.font==node.has_attribute(p,luatexbase.attributes['luatexja@curjfnt']))
107 ltj.penalty_table = {}
108 function ltj.set_penalty_table(m,c,p)
109 if not ltj.penalty_table[c] then ltj.penalty_table[c]={} end
111 ltj.penalty_table[c].pre=p
112 elseif m=='post' then
113 ltj.penalty_table[c].post=p
116 function ltj.get_penalty_table(m,c)
117 local i=ltj.penalty_table[c]
119 i=(ltj.penalty_table[c])[m]
121 if not i then i=0 end
125 ltj.inhibit_xsp_table = {}
126 function ltj.set_inhibit_xsp_table(c,p)
127 ltj.inhibit_xsp_table[c]=p
129 function ltj.get_inhibit_xsp_table(c,p)
130 local i=ltj.inhibit_xsp_table[c]
131 if not i then i=3 end
137 function ltj.create_ihb_node()
138 local g=node.new(node.id('whatsit'), node.subtype('user_defined'))
139 g.user_id=30111; g.type=number; g.value=1
143 -- The fullname field of virtual font expresses its metric
144 function ltj.find_size_metric(px)
145 if ltj.is_japanese_glyph_node(px) then
146 return ltj.font_metric_table[px.font].size, ltj.font_metric_table[px.font].jfm
152 function ltj.new_jfm_glue(size,mt,bc,ac)
153 -- mt: metric key, bc, ac: char classes
157 if ltj.metrics[mt].glue[w] then
158 h=node.new(node.id('glue_spec'))
159 h.width =tex.round(size*ltj.metrics[mt].glue[w].width)
160 h.stretch=tex.round(size*ltj.metrics[mt].glue[w].stretch)
161 h.shrink =tex.round(size*ltj.metrics[mt].glue[w].shrink)
162 h.stretch_order=0; h.shrink_order=0
163 g=node.new(node.id('glue'))
164 g.subtype=0; g.spec=h; return g
165 elseif ltj.metrics[mt].kern[w] then
166 g=node.new(node.id('kern'))
167 g.subtype=0; g.kern=tex.round(size*ltj.metrics[mt].kern[w]); return g
173 -- The fullname field of virtual font expresses its metric
174 function ltj.calc_between_two_jchar(q,p)
175 -- q, p: node (possibly null)
176 local ps,pm,qs,qm,g,h
177 if not p then -- q is the last node
178 qs, qm = ltj.find_size_metric(q)
182 g=ltj.new_jfm_glue(qs,qm,
183 node.has_attribute(q,luatexbase.attributes['luatexja@charclass']),
184 ltj.find_char_type('boxbdd',qm))
187 -- p is the first node etc.
188 ps, pm = ltj.find_size_metric(p)
192 g=ltj.new_jfm_glue(ps,pm,
193 ltj.find_char_type('boxbdd',pm),
194 node.has_attribute(p,luatexbase.attributes['luatexja@charclass']))
196 else -- p and q are not nil
197 qs, qm = ltj.find_size_metric(q)
198 ps, pm = ltj.find_size_metric(p)
199 if (not pm) and (not qm) then
200 -- Both p and q are NOT Japanese glyph node
202 elseif (qs==ps) and (qm==pm) then
203 -- Both p and q are Japanese glyph node, and same metric and size
204 g=ltj.new_jfm_glue(ps,pm,
205 node.has_attribute(q,luatexbase.attributes['luatexja@charclass']),
206 node.has_attribute(p,luatexbase.attributes['luatexja@charclass']))
208 -- q is not Japanese glyph node
209 g=ltj.new_jfm_glue(ps,pm,
210 ltj.find_char_type('jcharbdd',pm),
211 node.has_attribute(p,luatexbase.attributes['luatexja@charclass']))
213 -- p is not Japanese glyph node
214 g=ltj.new_jfm_glue(qs,qm,
215 node.has_attribute(q,luatexbase.attributes['luatexja@charclass']),
216 ltj.find_char_type('jcharbdd',qm))
218 g=ltj.new_jfm_glue(qs,qm,
219 node.has_attribute(q,luatexbase.attributes['luatexja@charclass']),
220 ltj.find_char_type('diffmet',qm))
221 h=ltj.new_jfm_glue(ps,pm,
222 ltj.find_char_type('diffmet',pm),
223 node.has_attribute(p,luatexbase.attributes['luatexja@charclass']))
224 g=ltj.calc_between_two_jchar_aux(g,h)
231 -- In the beginning of a hbox created by line breaking, there are the followings:
232 -- o a hbox by \parindent
233 -- o a whatsit node which contains local paragraph materials.
234 -- When we insert jfm glues, we ignore these nodes.
235 function ltj.is_parindent_box(p)
236 if node.type(p.id)=='hlist' then
237 return (p.subtype==3)
238 -- hlist (subtype=3) is a box by \parindent
239 elseif node.type(p.id)=='whatsit' then
240 return (p.subtype==node.subtype('local_par'))
244 function ltj.add_kinsoku_penalty(head,p)
246 if not ltj.penalty_table[c] then return false; end
247 if ltj.penalty_table[c].pre then
248 local q = node.prev(p)
249 if q and node.type(q.id)=='penalty' then
250 q.penalty=q.penalty+ltj.penalty_table[c].pre
252 q=node.new(node.id('penalty'))
253 q.penalty=ltj.penalty_table[c].pre
254 node.insert_before(head,p,q)
257 if ltj.penalty_table[c].post then
258 local q = node.next(p)
259 if q and node.type(q.id)=='penalty' then
260 q.penalty=q.penalty+ltj.penalty_table[c].post
263 q=node.new(node.id('penalty'))
264 q.penalty=ltj.penalty_table[c].post
265 node.insert_after(head,p,q)
271 -- Insert jfm glue: main routine
273 function ltj.insert_jfm_glue(head)
275 local q = nil -- the previous node of p
277 local ihb_flag = false
281 while p and ltj.is_parindent_box(p) do p=node.next(p) end
283 if node.type(p.id)=='whatsit' and p.subtype==44
284 and p.user_id==30111 then
286 ihb_flag=true; head,p=node.remove(head, g)
288 g=ltj.calc_between_two_jchar(q,p)
289 if g and (not ihb_flag) then
290 h = node.insert_before(head,p,g)
291 if not q then head=h end
292 -- If p is the first node (=head), the skip is inserted
293 -- before head. So we must change head.
296 if ltj.is_japanese_glyph_node(p)
297 and ltj.add_kinsoku_penalty(head,p) then
303 -- Insert skip after the last node
304 g=ltj.calc_between_two_jchar(q,nil)
306 h = node.insert_after(head,q,g)
313 -- Insert \xkanjiskip at the boundaries between Japanese characters
314 -- and non-Japanese characters.
315 -- We also insert \kanjiskip between Kanji in this function.
320 -- 0: ``no_skip'', 1: ``after_schar'', 2: ``after_wchar''
321 -- These variables are ``global'', because we want to avoid to write one large function.
322 function ltj.insert_kanji_skip(head)
323 if tex.count['luatexja@autospc']==0 then
324 ltj.kanji_skip=tex.skip['kanjiskip']
326 ltj.kanji_skip=node.new(node.id('glue_spec'))
327 ltj.kanji_skip.width=0; ltj.kanji_skip.stretch=0; ltj.kanji_skip.shrink=0
329 if tex.count['luatexja@autoxspc']==0 then
330 ltj.xkanji_skip=tex.skip['xkanjiskip']
332 ltj.xkanji_skip=node.new(node.id('glue_spec'))
333 ltj.xkanji_skip.width=0; ltj.xkanji_skip.stretch=0; ltj.xkanji_skip.shrink=0
335 local p=head -- 「現在のnode」
339 if node.type(p.id)=='glyph' then
341 ltj.insks_around_char(head,q,p)
343 until (not p) or node.type(p.id)~='glyph'
345 if node.type(p.id) == 'hlist' then
346 ltj.insks_around_hbox(head,q,p)
347 elseif node.type(p.id) == 'penalty' then
348 ltj.insks_around_penalty(head,q,p)
349 elseif node.type(p.id) == 'kern' then
350 ltj.insks_around_kern(head,q,p)
351 elseif node.type(p.id) == 'math' then
352 ltj.insks_around_math(head,q,p)
353 elseif node.type(p.id) == 'ins' or node.type(p.id) == 'mark'
354 or node.type(p.id) == 'adjust'
355 or node.type(p.id) == 'whatsit' then
359 -- rule, disc, glue, margin_kern
368 -- Insert \xkanjiskip before p, a glyph node
370 function ltj.insks_around_char(head,q,p)
371 local a=ltj.inhibit_xsp_table[p.char]
372 if ltj.is_japanese_glyph_node(p) then
374 if ltj.is_japanese_glyph_node(q) then
375 local g = node.new(node.id('glue'))
376 g.subtype=0; g.spec=node.copy(ltj.kanji_skip)
377 node.insert_before(head,p,g)
378 elseif ltj.insert_skip==1 then
379 ltj.insert_akxsp(head,q)
383 if not a then a=3 end
384 if ltj.insert_skip==2 then
385 ltj.insert_kaxsp(head,q,a)
395 function ltj.insert_akxsp(head,q)
396 local f = ltj.inhibit_xsp_table[ltj.cx]
399 if f<=1 then return end
401 g = node.new(node.id('glue'))
402 g.subtype=0; g.spec=node.copy(ltj.xkanji_skip)
403 node.insert_after(head,q,g)
406 function ltj.insert_kaxsp(head,q,a)
408 local f=ltj.inhibit_xsp_table[ltj.cx]
411 if f%2==0 then g=false end
417 g = node.new(node.id('glue'))
418 g.subtype=0; g.spec=node.copy(ltj.xkanji_skip)
419 node.insert_after(head,q,g)
423 -- Return first and last glyph nodes in a hbox
426 ltj.find_first_char = nil
427 function ltj.check_box(bp)
431 if node.type(p.id)=='glyph' then
433 if ltj.find_first_char then
434 ltj.first_char=p; ltj.find_first_char=false
436 ltj.last_char=p; flag=true; p=node.next(p)
437 if not p then return flag end
438 until node.type(p.id)~='glyph'
440 if node.type(p.id)=='hlist' then
443 if ltj.check_box(p.head) then flag=true end
444 else if ltj.find_first_char then
445 ltj.find_first_char=false
450 elseif node.type(p.id) == 'ins' or node.type(p.id) == 'mark'
451 or node.type(p.id) == 'adjust'
452 or node.type(p.id) == 'whatsit' or node.type(p.id) == 'penalty' then
456 if ltj.find_first_char then
457 ltj.find_first_char=false
467 -- Insert \xkanjiskip around p, an hbox
468 function ltj.insks_around_hbox(head,q,p)
470 ltj.find_first_char=true
471 if ltj.check_box(p.head) then
473 if ltj.is_japanese_glyph_node(ltj.first_char) then
474 ltj.cx=ltj.first_char.char
475 if ltj.insert_skip==1 then
476 ltj.insert_akxsp(head,q)
477 elseif ltj.insert_skip==2 then
478 local g = node.new(node.id('glue'))
479 g.subtype=0; g.spec=node.copy(ltj.kanji_skip)
480 node.insert_before(head,p,g)
483 elseif ltj.first_char then
484 local a=ltj.inhibit_xsp_table[ltj.first_char.char]
485 if not a then a=3 end
486 if ltj.insert_skip==2 then
487 local g = node.new(node.id('glue'))
488 g.subtype=0; g.spec=node.copy(ltj.kanji_skip)
489 node.insert_after(head,q,g)
498 if ltj.is_japanese_glyph_node(ltj.last_char) then
499 if ltj.is_japanese_glyph_node(node.next(p)) then
500 local g = node.new(node.id('glue'))
501 g.subtype=0; g.spec=node.copy(ltj.kanji_skip)
502 node.insert_after(head,p,g)
505 elseif ltj.last_char then
506 local a=ltj.inhibit_xsp_table[ltj.last_char.char]
507 if not a then a=3 end
513 else ltj.insert_skip=0
515 else ltj.insert_skip=0
517 else ltj.insert_skip=0
521 -- Insert \xkanjiskip around p, a penalty
522 function ltj.insks_around_penalty(head,q,p)
524 if r and node.type(r.id)=='glyph' then
525 local a=ltj.inhibit_xsp_table[r.char]
526 if ltj.is_japanese_glyph_node(r) then
528 if ltj.is_japanese_glyph_node(p) then
529 local g = node.new(node.id('glue'))
530 g.subtype=0; g.spec=node.copy(ltj.kanji_skip)
531 node.insert_before(head,r,g)
532 elseif ltj.insert_skip==1 then
533 ltj.insert_akxsp(head,p)
538 if not a then a=3 end
539 if ltj.insert_skip==2 then
540 ltj.insert_kaxsp(head,p,a)
551 -- Insert \xkanjiskip around p, a kern
552 function ltj.insks_around_kern(head,q,p)
553 if p.subtype==1 then -- \kern or \/
554 if node.has_attribute(p,luatexbase.attributes['luatexja@icflag']) then
555 p=p -- p is a kern from \/: do nothing
559 elseif p.subtype==2 then -- \accent: We ignore the accent character.
560 local v = node.next(node.next(node.next(p)))
561 if v and node.type(v.id)=='glyph' then
562 ltj.insks_around_char(head,q,v)
567 -- Insert \xkanjiskip around p, a math_node
568 function ltj.insks_around_math(head,q,p)
569 local a=ltj.inhibit_xsp_table['math']
570 if not a then a=3 end
571 if (p.subtype==0) and (ltj.insert_skip==2) then
572 ltj.insert_kaxsp(head,q,a)
580 function ltj.baselineshift(head)
582 local m=false -- is in math mode?
584 local v=node.has_attribute(p,luatexbase.attributes['luatexja@yablshift'])
586 if node.type(p.id)=='glyph' then
587 p.yoffset=p.yoffset-v
588 elseif node.type(p.id)=='math' then
591 if m then -- boxes and rules are shifted only in math mode
592 if node.type(p.id)=='hlist' or node.type(p.id)=='vlist' then
594 elseif node.type(p.id)=='rule' then
595 p.height=p.height-v; p.depth=p.depth+v
606 function ltj.main_process(head)
608 p = ltj.insert_jfm_glue(p)
609 p = ltj.insert_kanji_skip(p)
610 p = ltj.baselineshift(p)
611 p = ltj.set_ja_width(p)
616 function ltj.get_hss()
617 local hss = node.new(node.id("glue"))
618 local hss_spec = node.new(node.id("glue_spec"))
620 hss_spec.stretch = 65536
621 hss_spec.stretch_order = 2
622 hss_spec.shrink = 65536
623 hss_spec.shrink_order = 2
628 function ltj.set_ja_width(head)
632 if ltj.is_japanese_glyph_node(p) then
633 t=ltj.metrics[ltj.font_metric_table[p.font].jfm]
634 s=t.char_type[node.has_attribute(p,luatexbase.attributes['luatexja@charclass'])]
635 if not(s.left==0.0 and s.down==0.0
636 and tex.round(s.width*ltj.font_metric_table[p.font].size)==p.width) then
637 -- must be encapsuled by a \hbox
638 head, q = node.remove(head,p)
640 p.yoffset=tex.round(p.yoffset-ltj.font_metric_table[p.font].size*s.down)
641 p.xoffset=tex.round(p.xoffset-ltj.font_metric_table[p.font].size*s.left)
642 node.insert_after(p,p,ltj.get_hss())
643 g=node.hpack(p, tex.round(ltj.font_metric_table[p.font].size*s.width)
645 g.height=tex.round(ltj.font_metric_table[p.font].size*s.height)
646 g.depth=tex.round(ltj.font_metric_table[p.font].size*s.depth)
647 head,p = node.insert_before(head,q,g)
659 function ltj.to_pt(a)
660 return math.floor(a/65536*100000)/100000
662 function ltj.show_node_list(head)
665 ltj.depth=ltj.depth .. '.'
667 local s=node.type(p.id)
669 print(ltj.depth .. ' glyph', p.subtype, unicode.utf8.char(p.char), p.font)
670 elseif s=='hlist' then
671 print(ltj.depth .. ' hlist', p.subtype, '(' .. ltj.to_pt(p.height)
672 .. '+' .. ltj.to_pt(p.depth)
673 .. ')x' .. ltj.to_pt(p.width) )
674 ltj.show_node_list(p.head)
676 elseif s=='whatsit' then
677 print(ltj.depth .. ' whatsit', p.subtype)
678 elseif s=='glue' then
679 print(ltj.depth .. ' glue', p.subtype, ltj.to_pt(p.spec.width))
681 print(ltj.depth .. ' ' .. s, s.subtype)
689 --- the following function is modified from jafontspec.lua (by K. Maeda).
690 --- Instead of "%", we use U+FFFFF for suppressing spaces.
692 function ltj.process_input_buffer(buffer)
693 if utf.len(buffer) > 0
694 and ltj.is_ucs_in_japanese_char(utf.byte(buffer, utf.len(buffer))) then
695 buffer = buffer .. string.char(0xF3,0xBF,0xBF,0xBF) -- U+FFFFF
704 function ltj.suppress_hyphenate_ja(head)
707 if node.type(p.id)=='glyph' and ltj.is_ucs_in_japanese_char(p.char) then
708 local v = node.has_attribute(p,luatexbase.attributes['luatexja@curjfnt'])
711 local l=ltj.find_char_type(p.char,ltj.font_metric_table[v].jfm)
712 if not l then l=0 end
713 node.set_attribute(p,luatexbase.attributes['luatexja@charclass'],l)
715 v=node.has_attribute(p,luatexbase.attributes['luatexja@ykblshift'])
717 node.set_attribute(p,luatexbase.attributes['luatexja@yablshift'],v)
719 node.unset_attribute(p,luatexbase.attributes['luatexja@yablshift'])
721 p.lang=ltj.ja_lang_number
726 return head -- 共通化のため値を返す
730 luatexbase.add_to_callback('process_input_buffer',
732 return ltj.process_input_buffer(buffer)
733 end,'ltj.process_input_buffer')
735 luatexbase.add_to_callback('pre_linebreak_filter',
736 function (head,groupcode)
737 return ltj.main_process(head)
738 end,'ltj.pre_linebreak_filter',2)
739 luatexbase.add_to_callback('hpack_filter',
740 function (head,groupcode,size,packtype)
741 return ltj.main_process(head)
742 end,'ltj.hpack_filter',2)
744 --insert before callbacks from luaotfload
745 luatexbase.add_to_callback('hpack_filter',
746 function (head,groupcode,size,packtype)
747 return ltj.suppress_hyphenate_ja(head)
748 end,'ltj.hpack_filter_pre',0)
749 luatexbase.add_to_callback('hyphenate',
751 return ltj.suppress_hyphenate_ja(head)