OSDN Git Service

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