OSDN Git Service

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