OSDN Git Service

ca6391c788b203c370b978e6898b16445ab0decf
[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
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
28
29 local lang_ja_token = token.create('ltj@japanese')
30 local lang_ja = lang_ja_token[2]
31
32 -- 
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
37
38
39 local ITALIC = 1
40 local TEMPORARY = 2
41 local FROM_JFM = 3
42 local KINSOKU = 4
43 local LINE_END = 5
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 ------------------------------------------------------------------------
109 -- CODE FOR STACK TABLE FOR CHARACTER PROPERTIES (prefix: cstb)
110 ------------------------------------------------------------------------
111
112 ---- table: charprop_stack_table [stack_level][chr_code].{pre|post|xsp}
113 local charprop_stack_table={}; charprop_stack_table[0]={}
114
115 local function cstb_get_stack_level()
116   local i = tex.getcount('ltj@@stack')
117   if tex.currentgrouplevel > tex.getcount('ltj@@group@level') then
118     i = i+1 -- new stack level
119     tex.setcount('ltj@@group@level', tex.currentgrouplevel)
120     for j,v in pairs(charprop_stack_table) do -- clear the stack above i
121       if j>=i then charprop_stack_table[j]=nil end
122     end
123     charprop_stack_table[i] = table.fastcopy(charprop_stack_table[i-1])
124     tex.setcount('ltj@@stack', i)
125   end
126   return i
127 end
128
129 -- EXT
130 function ltj.ext_set_stack_table(g,m,c,p,lb,ub)
131   local i = cstb_get_stack_level()
132   if p<lb or p>ub then 
133      ltj.error('Invalid code (' .. p .. '), should in the range '
134                .. tostring(lb) .. '..' .. tostring(ub) .. '.',
135             {"I'm going to use 0 instead of that illegal code value."})
136      p=0
137   elseif c<-1 or c>0x10FFFF then
138      ltj.error('Invalid character code (' .. c
139                .. '), should in the range -1.."10FFFF.',{})
140      return 
141   elseif not charprop_stack_table[i][c] then 
142      charprop_stack_table[i][c] = {} 
143   end
144   charprop_stack_table[i][c][m] = p
145   if g=='global' then
146     for j,v in pairs(charprop_stack_table) do 
147       if not charprop_stack_table[j][c] then charprop_stack_table[j][c] = {} end
148       charprop_stack_table[j][c][m] = p
149     end
150   end
151 end
152
153 local function cstb_get_penalty_table(m,c)
154   local i = charprop_stack_table[tex.getcount('ltj@@stack')][c]
155   if i then i=i[m] end
156   return i or 0
157 end
158 ltj.int_get_penalty_table = cstb_get_penalty_table
159
160 local function cstb_get_inhibit_xsp_table(c)
161   local i = charprop_stack_table[tex.getcount('ltj@@stack')][c]
162   if i then i=i.xsp end
163   return i or 3
164 end
165 ltj.int_get_inhibit_xsp_table = cstb_get_inhibit_xsp_table
166
167 ------------------------------------------------------------------------
168 -- CODE FOR GETTING/SETTING PARAMETERS 
169 ------------------------------------------------------------------------
170
171 -- EXT: print parameters that don't need arguments
172 function ltj.ext_get_parameter_unary(k)
173    if k == 'yalbaselineshift' then
174       tex.write(print_scaled(tex.getattribute('ltj@yablshift'))..'pt')
175    elseif k == 'yjabaselineshift' then
176       tex.write(print_scaled(tex.getattribute('ltj@ykblshift'))..'pt')
177    elseif k == 'kanjiskip' then
178       tex.write(print_spec(tex.getskip('kanjiskip')))
179    elseif k == 'xkanjiskip' then
180       tex.write(print_spec(tex.getskip('xkanjiskip')))
181    elseif k == 'jcharwidowpenalty' then
182       tex.write(tex.getcount('jcharwidowpenalty'))
183    elseif k == 'autospacing' then
184       tex.write(tex.getattribute('ltj@autospc'))
185    elseif k == 'autoxspacing' then
186       tex.write(tex.getattribute('ltj@autoxspc'))
187    elseif k == 'differentjfm' then
188       if ltj.ja_diffmet_rule == math.max then
189          tex.write('large')
190       elseif ltj.ja_diffmet_rule == math.min then
191          tex.write('small')
192       elseif ltj.ja_diffmet_rule == math.two_average then
193          tex.write('average')
194       elseif ltj.ja_diffmet_rule == math.two_add then
195          tex.write('both')
196       else -- This can't happen.
197          tex.write('???')
198       end
199    end
200 end
201
202 -- EXT: print parameters that need arguments
203 function ltj.ext_get_parameter_binary(k,c)
204    if k == 'jacharrange' then
205       if c<0 or c>216 then c=0 end
206       tex.write(rgjc_get_range_setting(c))
207    else
208       if c<0 or c>0x10FFFF then
209          ltj.error('Invalid character code (' .. c 
210                    .. '), should in the range 0.."10FFFF.',
211                 {"I'm going to use 0 instead of that illegal character code."})
212          c=0
213       end
214       if k == 'prebreakpenalty' then
215          tex.write(cstb_get_penalty_table('pre',c))
216       elseif k == 'postbreakpenalty' then
217          tex.write(cstb_get_penalty_table('post',c))
218       elseif k == 'kcatcode' then
219          tex.write(cstb_get_penalty_table('kcat',c))
220       elseif k == 'chartorange' then 
221          tex.write(rgjc_char_to_range(c))
222       elseif k == 'jaxspmode' or k == 'alxspmode' then
223          tex.write(cstb_get_inhibit_xsp_table(c))
224       end
225    end
226 end
227
228 -- EXT: print \global if necessary
229 function ltj.ext_print_global()
230   if ltj.isglobal=='global' then tex.sprint('\\global') end
231 end
232
233
234 ------------------------------------------------------------------------
235 -- MAIN PROCESS STEP 1: replace fonts (prefix: main1)
236 ------------------------------------------------------------------------
237
238 --- the following function is modified from jafontspec.lua (by K. Maeda).
239 --- Instead of "%", we use U+FFFFF for suppressing spaces.
240 local function main1_process_input_buffer(buffer)
241    local c = utf.byte(buffer, utf.len(buffer))
242    local p = node_new(id_glyph)
243    p.char = c
244    if utf.len(buffer) > 0 
245    and rgjc_is_ucs_in_japanese_char(p) then
246         buffer = buffer .. string.char(0xF3,0xBF,0xBF,0xBF) -- U+FFFFF
247    end
248    return buffer
249 end
250
251 local function main1_suppress_hyphenate_ja(head)
252    local p
253    for p in node.traverse(head) do
254       if p.id == id_glyph then
255          if rgjc_is_ucs_in_japanese_char(p) then
256             local v = has_attr(p, attr_curjfnt)
257             if v then 
258                p.font = v 
259                node.set_attribute(p,attr_jchar_class,
260                  ljfm_find_char_class(p.char, ltj.font_metric_table[v].jfm))
261             end
262             v = has_attr(p, attr_ykblshift)
263             if v then 
264                node.set_attribute(p, attr_yablshift, v)
265             else
266                node.unset_attribute(p, attr_yablshift)
267             end
268             p.lang=lang_ja
269          end
270       end
271    end
272    lang.hyphenate(head)
273    return head -- 互換性のために値を返す
274 end
275
276 -- CALLBACKS
277 luatexbase.add_to_callback('process_input_buffer', 
278    function (buffer)
279      return main1_process_input_buffer(buffer)
280    end,'ltj.process_input_buffer')
281 luatexbase.add_to_callback('hpack_filter', 
282   function (head,groupcode,size,packtype)
283      return main1_suppress_hyphenate_ja(head)
284   end,'ltj.hpack_filter_pre',0)
285 luatexbase.add_to_callback('hyphenate', 
286  function (head,tail)
287     return main1_suppress_hyphenate_ja(head)
288  end,'ltj.hyphenate')
289
290
291 ------------------------------------------------------------------------
292 -- MAIN PROCESS STEP 4: width of japanese chars (prefix: main4)
293 ------------------------------------------------------------------------
294
295 -- TeX's \hss
296 local function main4_get_hss()
297    local hss = node_new(id_glue)
298    local fil_spec = node_new(id_glue_spec)
299    fil_spec.width = 0
300    fil_spec.stretch = 65536
301    fil_spec.stretch_order = 2
302    fil_spec.shrink = 65536
303    fil_spec.shrink_order = 2
304    hss.spec = fil_spec
305    return hss
306 end
307
308 local function main4_set_ja_width(head)
309    local p = head
310    local met_tb, t, s, g, q, a, h
311    local m = false -- is in math mode?
312    while p do
313       local v=has_attr(p,attr_yablshift) or 0
314       if p.id==id_glyph then
315          p.yoffset = p.yoffset-v
316          if is_japanese_glyph_node(p) then
317             met_tb = ltj.font_metric_table[p.font]
318             t = ltj.metrics[met_tb.jfm]
319             s = t.char_type[has_attr(p,attr_jchar_class)]
320             if s.width ~= 'prop' and
321                not(s.left==0.0 and s.down==0.0 and s.align=='left' 
322                    and round(s.width*met_tb.size)==p.width) then
323                -- must be encapsuled by a \hbox
324                head, q = node.remove(head,p)
325                p.next=nil
326                p.yoffset=round(p.yoffset-met_tb.size*s.down)
327                p.xoffset=round(p.xoffset-met_tb.size*s.left)
328                if s.align=='middle' or s.align=='right' then
329                   h = node_insert_before(p, p, main4_get_hss())
330                else h=p end
331                if s.align=='middle' or s.align=='left' then
332                   node_insert_after(h, p, main4_get_hss())
333                end
334                g = node_hpack(h, round(met_tb.size*s.width), 'exactly')
335                g.height = round(met_tb.size*s.height)
336                g.depth = round(met_tb.size*s.depth)
337                head, p = node_insert_before(head, q, g)
338                p = q
339             else p=node_next(p)
340             end
341          else p=node_next(p)
342          end
343       elseif p.id==id_math then
344          m=(p.subtype==0); p=node_next(p)
345       else
346          if m then
347             if p.id==id_hlist or p.id==id_vlist then
348                p.shift=p.shift+v
349             elseif p.id==id_rule then
350                p.height=p.height-v; p.depth=p.depth+v 
351             end
352          end
353          p=node_next(p)
354       end
355    end
356 return head
357 end
358
359 -- main process
360 -- mode = true iff main_process is called from pre_linebreak_filter
361 local function main_process(head, mode)
362    local p = head
363    p = ltj.int_insert_jfm_glue(p,mode)
364    p = ltj.int_insert_kanji_skip(p) 
365    -- off because we write the code of step 2
366    p = main4_set_ja_width(p)
367    return p
368 end
369
370
371 -- debug
372 local debug_depth
373 function ltj.ext_show_node_list(head,depth,print_fn)
374    debug_depth = depth
375    if head then
376       debug_show_node_list_X(head, print_fn)
377    else
378       print_fn(debug_depth .. ' (null list)')
379    end
380 end
381 function debug_show_node_list_X(p,print_fn)
382    debug_depth=debug_depth.. '.'
383    local k = debug_depth
384    local s
385    while p do
386       local pt=node_type(p.id)
387       if pt == 'glyph' then
388          print_fn(debug_depth.. ' GLYPH  ', p.subtype, utf.char(p.char), p.font)
389       elseif pt=='hlist' then
390          print_fn(debug_depth.. ' hlist  ', p.subtype, '(' .. print_scaled(p.height)
391             .. '+' .. print_scaled(p.depth)
392          .. ')x' .. print_scaled(p.width) )
393          debug_show_node_list_X(p.head,print_fn)
394          debug_depth=k
395       elseif pt == 'whatsit' then
396          print_fn(debug_depth.. ' whatsit', p.subtype)
397       elseif pt == 'glue' then
398          s = debug_depth.. ' glue   ' ..  p.subtype 
399             .. ' ' ..  print_spec(p.spec)
400          if has_attr(p, attr_icflag)==2 then
401             s = s .. ' (might be replaced)'
402          elseif has_attr(p, attr_icflag)==3 then
403             s = s .. ' (from JFM)'
404          end
405          print_fn(s)
406       elseif pt == 'kern' then
407          s = debug_depth.. ' kern   ' ..  p.subtype
408             .. ' ' .. print_scaled(p.kern) .. 'pt'
409          if has_attr(p, attr_icflag)==ITALIC then
410             s = s .. ' (italic correction)'
411          elseif has_attr(p, attr_icflag)==TEMPORARY then
412             s = s .. ' (might be replaced)'
413          elseif has_attr(p, attr_icflag)==FROM_JFM then
414             s = s .. ' (from JFM)'
415          elseif has_attr(p, attr_icflag)==LINE_END then
416             s = s .. " (from 'lineend' in JFM)"
417          end
418          print_fn(s)
419       elseif pt == 'penalty' then
420          s = debug_depth.. ' penalty ' ..  tostring(p.penalty)
421          if has_attr(p, attr_icflag)==KINSOKU then
422             s = s .. ' (for kinsoku)'
423          end
424          print_fn(s)
425       else
426          print_fn(debug_depth.. ' ' .. node.type(p.id), p.subtype)
427       end
428       p=node_next(p)
429    end
430 end
431
432
433 -- callbacks
434 luatexbase.add_to_callback('pre_linebreak_filter', 
435    function (head,groupcode)
436      return main_process(head, true)
437    end,'ltj.pre_linebreak_filter',2)
438 luatexbase.add_to_callback('hpack_filter', 
439   function (head,groupcode,size,packtype)
440      return main_process(head, false)
441   end,'ltj.hpack_filter',2)