OSDN Git Service

ce766785d8e1d7b66121f17fd075068bdd41feed
[luatex-ja/luatexja.git] / src / luatexja-core.lua
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
10
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')
21 local sid_user = node.subtype('user_defined')
22
23 local attr_jchar_class = luatexbase.attributes['ltj@charclass']
24 local attr_curjfnt = luatexbase.attributes['ltj@curjfnt']
25 local attr_yablshift = luatexbase.attributes['ltj@yablshift']
26 local attr_ykblshift = luatexbase.attributes['ltj@ykblshift']
27 local attr_icflag = luatexbase.attributes['ltj@icflag']
28
29 local lang_ja_token = token.create('ltj@japanese')
30 local lang_ja = lang_ja_token[2]
31
32 -- 
33 local ljfm_find_char_class = ltj.int_find_char_class
34
35
36 local ITALIC = 1
37 local TEMPORARY = 2
38 local FROM_JFM = 3
39 local KINSOKU = 4
40 local LINE_END = 5
41 local KANJI_SKIP = 6
42 local XKANJI_SKIP = 7
43 local PACKED = 8
44
45 ------------------------------------------------------------------------
46 -- naming:
47 --    ltj.ext_... : called from \directlua{}
48 --    ltj.int_... : called from other Lua codes, but not from \directlua{}
49 --    (other)     : only called from this file
50
51 -- error messages
52 function ltj.error(s,t)
53   tex.error('LuaTeX-ja error: ' .. s ,t) 
54 end
55
56 -- Three aux. functions, bollowed from tex.web
57 local unity=65536
58 local function print_scaled(s)
59    local out=''
60    local delta=10
61    if s<0 then 
62       out=out..'-'; s=-s
63    end
64    out=out..tostring(math.floor(s/unity)) .. '.'
65    s=10*(s%unity)+5
66    repeat
67       if delta>unity then s=s+32768-50000 end
68       out=out .. tostring(math.floor(s/unity)) 
69       s=10*(s%unity)
70       delta=delta*10
71    until s<=delta
72    return out
73 end
74
75 local function print_glue(d,order)
76    local out=print_scaled(d)
77    if order>0 then
78       out=out..'fi'
79       while order>1 do
80          out=out..'l'; order=order-1
81       end
82    else 
83       out=out..'pt'
84    end
85    return out
86 end
87
88 local function print_spec(p)
89    local out=print_scaled(p.width)..'pt'
90    if p.stretch~=0 then
91       out=out..' plus '..print_glue(p.stretch,p.stretch_order)
92    end
93    if p.shrink~=0 then
94       out=out..' minus '..print_glue(p.shrink,p.shrink_order)
95    end
96 return out
97 end
98
99 -- return true if and only if p is a Japanese character node
100 local function is_japanese_glyph_node(p)
101    return p and (p.id==id_glyph) 
102    and (p.font==has_attr(p,attr_curjfnt))
103 end
104
105 function math.two_add(a,b) return a+b end
106 function math.two_average(a,b) return (a+b)/2 end
107
108 ---- table: charprop_stack_table [stack_level][chr_code].{pre|post|xsp}
109
110 ------------------------------------------------------------------------
111 -- CODE FOR GETTING/SETTING PARAMETERS 
112 ------------------------------------------------------------------------
113
114 -- EXT: print parameters that don't need arguments
115 function ltj.ext_get_parameter_unary(k)
116    if k == 'yalbaselineshift' then
117       tex.write(print_scaled(tex.getattribute('ltj@yablshift'))..'pt')
118    elseif k == 'yjabaselineshift' then
119       tex.write(print_scaled(tex.getattribute('ltj@ykblshift'))..'pt')
120    elseif k == 'kanjiskip' then
121       tex.write(print_spec(luatexja.stack.get_skip_table('kanjiskip', tex.getcount('ltj@@stack'))))
122    elseif k == 'xkanjiskip' then
123       tex.write(print_spec(luatexja.stack.get_skip_table('xkanjiskip', tex.getcount('ltj@@stack'))))
124    elseif k == 'jcharwidowpenalty' then
125       tex.write(luatexja.stack.get_penalty_table('jwp', 0, 0, tex.getcount('ltj@@stack')))
126    elseif k == 'autospacing' then
127       tex.write(tex.getattribute('ltj@autospc'))
128    elseif k == 'autoxspacing' then
129       tex.write(tex.getattribute('ltj@autoxspc'))
130    elseif k == 'differentjfm' then
131       if ltj.ja_diffmet_rule == math.max then
132          tex.write('large')
133       elseif ltj.ja_diffmet_rule == math.min then
134          tex.write('small')
135       elseif ltj.ja_diffmet_rule == math.two_average then
136          tex.write('average')
137       elseif ltj.ja_diffmet_rule == math.two_add then
138          tex.write('both')
139       else -- This can't happen.
140          tex.write('???')
141       end
142    end
143 end
144
145 -- EXT: print parameters that need arguments
146 function ltj.ext_get_parameter_binary(k,c)
147    if k == 'jacharrange' then
148       if c<0 or c>216 then c=0 end
149       tex.write(luatexja.charrange.get_range_setting(c))
150    else
151       if c<0 or c>0x10FFFF then
152          ltj.error('Invalid character code (' .. c 
153                    .. '), should in the range 0.."10FFFF.',
154                 {"I'm going to use 0 instead of that illegal character code."})
155          c=0
156       end
157       if k == 'prebreakpenalty' then
158          tex.write(luatexja.stack.get_penalty_table('pre', c, 0, tex.getcount('ltj@@stack')))
159       elseif k == 'postbreakpenalty' then
160          tex.write(luatexja.stack.get_penalty_table('post', c, 0, tex.getcount('ltj@@stack')))
161       elseif k == 'kcatcode' then
162          tex.write(luatexja.stack.get_penalty_table('kcat', c, 0, tex.getcount('ltj@@stack')))
163       elseif k == 'chartorange' then 
164          tex.write(luatexja.charrange.char_to_range(c))
165       elseif k == 'jaxspmode' or k == 'alxspmode' then
166          tex.write(luatexja.stack.get_penalty_table('xsp', c, 3, tex.getcount('ltj@@stack')))
167       end
168    end
169 end
170
171 -- EXT: print \global if necessary
172 function ltj.ext_print_global()
173   if ltj.isglobal=='global' then tex.sprint('\\global') end
174 end
175
176
177 ------------------------------------------------------------------------
178 -- MAIN PROCESS STEP 1: replace fonts (prefix: main1)
179 ------------------------------------------------------------------------
180 ltj.box_stack_level = 0
181 -- This is used in Step 2 (JFM glue/kern) and Step 3 (\[x]kanjiskip).
182
183 local function main1_suppress_hyphenate_ja(head)
184    for p in node.traverse_id(id_glyph, head) do
185       if luatexja.charrange.is_ucs_in_japanese_char(p) then
186          local v = has_attr(p, attr_curjfnt)
187          if v then 
188             p.font = v 
189             node.set_attribute(p, attr_jchar_class,
190                                ljfm_find_char_class(p.char, ltj.font_metric_table[v].jfm))
191          end
192          v = has_attr(p, attr_ykblshift)
193          if v then 
194             node.set_attribute(p, attr_yablshift, v)
195          else
196             node.unset_attribute(p, attr_yablshift)
197          end
198          p.lang=lang_ja
199       end
200    end
201    lang.hyphenate(head)
202    return head
203 end
204
205 -- mode: true iff this function is called from hpack_filter
206 local function main1_set_box_stack_level(head, mode)
207    local box_set = false
208    local p = head
209    local cl = tex.currentgrouplevel + 1
210    while p do
211       if p.id==id_whatsit and p.subtype==sid_user and p.user_id==30112 then
212          local g = p
213          if mode and g.value==cl then box_set = true end
214          head, p = node.remove(head, g)
215       else p = node_next(p)
216       end
217    end
218    if box_set then 
219       ltj.box_stack_level = tex.getcount('ltj@@stack') + 1 
220    else 
221       ltj.box_stack_level = tex.getcount('ltj@@stack') 
222    end
223    if not head then -- prevent that the list is null
224       head = node_new(id_kern); head.kern = 0; head.subtype = 1
225    end
226    return head
227 end
228
229 -- CALLBACKS
230 luatexbase.add_to_callback('hpack_filter', 
231    function (head)
232      return main1_set_box_stack_level(head, true)
233    end,'ltj.hpack_filter_pre',1)
234 luatexbase.add_to_callback('pre_linebreak_filter', 
235   function (head)
236      return main1_set_box_stack_level(head, false)
237   end,'ltj.pre_linebreak_filter_pre',1)
238 luatexbase.add_to_callback('hyphenate', 
239  function (head,tail)
240     return main1_suppress_hyphenate_ja(head)
241  end,'ltj.hyphenate')
242
243
244 ------------------------------------------------------------------------
245 -- MAIN PROCESS STEP 4: width of japanese chars (prefix: main4)
246 ------------------------------------------------------------------------
247
248 -- TeX's \hss
249 local function main4_get_hss()
250    local hss = node_new(id_glue)
251    local fil_spec = node_new(id_glue_spec)
252    fil_spec.width = 0
253    fil_spec.stretch = 65536
254    fil_spec.stretch_order = 2
255    fil_spec.shrink = 65536
256    fil_spec.shrink_order = 2
257    hss.spec = fil_spec
258    return hss
259 end
260
261 local function main4_set_ja_width(head)
262    local p = head
263    local met_tb, t, s, g, q, a, h
264    local m = false -- is in math mode?
265    while p do
266       local v=has_attr(p,attr_yablshift) or 0
267       if p.id==id_glyph then
268          p.yoffset = p.yoffset-v
269          if is_japanese_glyph_node(p) then
270             met_tb = ltj.font_metric_table[p.font]
271             t = ltj.metrics[met_tb.jfm]
272             s = t.char_type[has_attr(p,attr_jchar_class)]
273             if s.width ~= 'prop' and
274                not(s.left==0.0 and s.down==0.0 and s.align=='left' 
275                    and round(s.width*met_tb.size)==p.width) then
276                -- must be encapsuled by a \hbox
277                head, q = node.remove(head,p)
278                p.next = nil
279                p.yoffset=round(p.yoffset-met_tb.size*s.down)
280                p.xoffset=round(p.xoffset-met_tb.size*s.left)
281                if s.align=='middle' or s.align=='right' then
282                   h = node_insert_before(p, p, main4_get_hss())
283                else h=p end
284                if s.align=='middle' or s.align=='left' then
285                   node_insert_after(h, p, main4_get_hss())
286                end
287                g = node_hpack(h, round(met_tb.size*s.width), 'exactly')
288                g.height = round(met_tb.size*s.height)
289                g.depth = round(met_tb.size*s.depth)
290                node.set_attribute(g, attr_icflag, PACKED)
291                if q then
292                   head = node_insert_before(head, q, g)
293                else
294                   head = node_insert_after(head, node.tail(head), g)
295                end
296                p = q
297             else p=node_next(p)
298             end
299          else p=node_next(p)
300          end
301       elseif p.id==id_math then
302          m=(p.subtype==0); p=node_next(p)
303       else
304          if m then
305             if p.id==id_hlist or p.id==id_vlist then
306                p.shift=p.shift+v
307             elseif p.id==id_rule then
308                p.height=p.height-v; p.depth=p.depth+v 
309             end
310          end
311          p=node_next(p)
312       end
313    end
314 return head
315 end
316
317 -- main process
318 -- mode = true iff main_process is called from pre_linebreak_filter
319 local function main_process(head, mode)
320    local p = head
321    p = ltj.int_insert_jfm_glue(p,mode)
322    p = ltj.int_insert_kanji_skip(p)
323    p = main4_set_ja_width(p)
324    return p
325 end
326
327
328 -- debug
329 local debug_depth
330 function ltj.ext_show_node_list(head,depth,print_fn)
331    debug_depth = depth
332    if head then
333       while head do
334          debug_show_node_X(head, print_fn); head = node_next(head)
335       end
336    else
337       print_fn(debug_depth .. ' (null list)')
338    end
339 end
340 function ltj.ext_show_node(head,depth,print_fn)
341    debug_depth = depth
342    if head then
343       debug_show_node_X(head, print_fn)
344    else
345       print_fn(debug_depth .. ' (null list)')
346    end
347 end
348 function debug_show_node_X(p,print_fn)
349    local k = debug_depth
350    local s
351    local pt=node_type(p.id)
352    if pt == 'glyph' then
353       print_fn(debug_depth.. ' GLYPH  ', p.subtype, utf.char(p.char), p.font)
354    elseif pt=='hlist' then
355       s = debug_depth .. ' hlist  ' ..  p.subtype
356          .. '(' .. print_scaled(p.height) .. '+' .. print_scaled(p.depth) .. ')x'
357          .. print_scaled(p.width)
358       if p.glue_sign >= 1 then 
359          s = s .. ' glue set '
360          if p.glue_sign == 2 then s = s .. '-' end
361          s = s .. tostring(math.floor(p.glue_set*10000)/10000)
362          if p.glue_order == 0 then 
363             s = s .. 'pt' 
364          else 
365             s = s .. 'fi'
366             for i = 2,  p.glue_order do s = s .. 'l' end
367          end
368       end
369       print_fn(s)
370       local q = p.head
371       debug_depth=debug_depth.. '.'
372       while q do 
373          debug_show_node_X(q, print_fn); q = node_next(q)
374       end
375       debug_depth=k
376    elseif pt == 'glue' then
377       s = debug_depth.. ' glue   ' ..  p.subtype 
378          .. ' ' ..  print_spec(p.spec)
379       if has_attr(p, attr_icflag)==TEMPORARY then
380          s = s .. ' (might be replaced)'
381       elseif has_attr(p, attr_icflag)==FROM_JFM then
382             s = s .. ' (from JFM)'
383       elseif has_attr(p, attr_icflag)==KANJI_SKIP then
384          s = s .. ' (kanjiskip)'
385       elseif has_attr(p, attr_icflag)==XKANJI_SKIP then
386          s = s .. ' (xkanjiskip)'
387       end
388       print_fn(s)
389    elseif pt == 'kern' then
390       s = debug_depth.. ' kern   ' ..  p.subtype
391          .. ' ' .. print_scaled(p.kern) .. 'pt'
392       if has_attr(p, attr_icflag)==ITALIC then
393          s = s .. ' (italic correction)'
394       elseif has_attr(p, attr_icflag)==TEMPORARY then
395          s = s .. ' (might be replaced)'
396       elseif has_attr(p, attr_icflag)==FROM_JFM then
397          s = s .. ' (from JFM)'
398       elseif has_attr(p, attr_icflag)==LINE_END then
399          s = s .. " (from 'lineend' in JFM)"
400       end
401       print_fn(s)
402    elseif pt == 'penalty' then
403       s = debug_depth.. ' penalty ' ..  tostring(p.penalty)
404       if has_attr(p, attr_icflag)==KINSOKU then
405          s = s .. ' (for kinsoku)'
406       end
407       print_fn(s)
408    elseif pt == 'whatsit' then
409       s = debug_depth.. ' whatsit ' ..  tostring(p.subtype)
410       if p.subtype==sid_user then
411          s = s .. ' user_id: ' .. p.user_id .. ' ' .. p.value
412       else
413          s = s .. node.subtype(p.subtype)
414       end
415       print_fn(s)
416    else
417       print_fn(debug_depth.. ' ' .. node.type(p.id), p.subtype)
418    end
419    p=node_next(p)
420 end
421
422
423 -- callbacks
424 luatexbase.add_to_callback('pre_linebreak_filter', 
425    function (head,groupcode)
426      return main_process(head, true)
427    end,'ltj.pre_linebreak_filter',2)
428 luatexbase.add_to_callback('hpack_filter', 
429   function (head,groupcode,size,packtype)
430      return main_process(head, false)
431   end,'ltj.hpack_filter',2)