OSDN Git Service

Rewrited process_input_buffer callback (ticket #25231).
[luatex-ja/luatexja.git] / src / luatexja-xkanji.lua
1 ------------------------------------------------------------------------
2 -- MAIN PROCESS STEP 3: insert \xkanjiskip (prefix: none)
3 ------------------------------------------------------------------------
4
5 local node_type = node.type
6 local node_new = node.new
7 local node_prev = node.prev
8 local node_next = node.next
9 local node_copy = node.copy
10 local has_attr = node.has_attribute
11 local set_attr = node.set_attribute
12 local node_insert_before = node.insert_before
13 local node_insert_after = node.insert_after
14 local node_hpack = node.hpack
15 local round = tex.round
16
17 local id_penalty = node.id('penalty')
18 local id_glyph = node.id('glyph')
19 local id_glue = node.id('glue')
20 local id_glue_spec = node.id('glue_spec')
21 local id_kern = node.id('kern')
22 local id_hlist = node.id('hlist')
23 local id_ins = node.id('ins')
24 local id_mark = node.id('mark')
25 local id_adjust = node.id('adjust')
26 local id_math = node.id('math')
27 local id_whatsit = node.id('whatsit')
28
29 local attr_icflag = luatexbase.attributes['ltj@icflag']
30 local attr_autospc = luatexbase.attributes['ltj@autospc']
31 local attr_autoxspc = luatexbase.attributes['ltj@autoxspc']
32 local attr_curjfnt = luatexbase.attributes['ltj@curjfnt']
33 local max_dimen = 1073741823
34
35 local ITALIC = 1
36 local TEMPORARY = 2
37 local FROM_JFM = 3
38 local KINSOKU = 4
39 local LINE_END = 5
40 local KANJI_SKIP = 6
41 local XKANJI_SKIP = 7
42 local PACKED = 8
43
44 local kanji_skip
45 local xkanji_skip
46
47 -- (glyph_node nr) ... (node nq) <GLUE> ,,, (node np)
48 local np, nq, nrc, nrf
49 local np_spc, nr_spc
50 local no_skip = 0 
51 local after_schar = 1
52 local after_wchar = 2 -- nr is a Japanese glyph_node
53 local insert_skip = no_skip
54 local head
55
56 local function is_japanese_glyph_node(p)
57    return p and (p.id==id_glyph) 
58    and (p.font==has_attr(p,attr_curjfnt))
59 end
60
61 local function get_zero_glue()
62    local g = node_new(id_glue_spec)
63    g.width = 0; g.stretch_order = 0; g.stretch = 0
64    g.shrink_order = 0; g.shrink = 0
65    return g
66 end
67
68 local function skip_table_to_spec(n)
69    local g = node_new(id_glue_spec)
70    local st = luatexja.stack.get_skip_table(n, ltj.box_stack_level)
71    g.width = st.width; g.stretch = st.stretch; g.shrink = st.shrink
72    g.stretch_order = st.stretch_order; g.shrink_order = st.shrink_order
73    return g
74 end
75
76 local function add_glue_spec(g,h)
77    -- g := g + h
78    g.width = g.width + h.width
79    if g.stretch_order<h.stretch_order then
80       g.stretch_order = h.stretch_order
81       g.stretch = h.stretch
82    elseif g.stretch_order==h.stretch_order then
83       g.stretch = g.stretch + h.stretch
84    end
85    if g.shrink_order<h.shrink_order then
86       g.shrink_order = h.shrink_order
87       g.shrink = h.shrink
88    elseif g.shrink_order==h.shrink_order then
89       g.shrink = g.shrink + h.shrink
90    end
91 end
92
93 -- lowest part of \xkanjiskip 
94 local function get_xkanji_skip_from_jfm(pf)
95    if pf then
96       local px = { ltj.font_metric_table[pf].size, 
97                    ltj.font_metric_table[pf].jfm }
98       local i = ltj.metrics[px[2]].xkanjiskip
99       if i then
100          return { round(i[1]*px[1]), round(i[2]*px[1]), round(i[3]*px[1]) }
101       else return nil
102       end
103    else return nil
104    end
105 end
106
107 local function insert_xkanjiskip_node(q, f, p)
108    if nr_spc[2] or np_spc[2] then
109       local g = node_new(id_glue); g.subtype = 0
110       if xkanji_skip.width==max_dimen then -- use xkanjiskip from JFM
111          local gx = node_new(id_glue_spec)
112          gx.stretch_order = 0; gx.shrink_order = 0
113          local ak = get_xkanji_skip_from_jfm(f)
114          if ak then
115             gx.width = ak[1]; gx.stretch = ak[2]; gx.shrink = ak[3]
116          else gx = get_zero_glue() -- fallback
117          end
118          g.spec = gx
119       else g.spec=node_copy(xkanji_skip)
120       end
121       local h = node_prev(p)
122       if h  and has_attr(h, attr_icflag)==TEMPORARY then
123          if h.id==id_kern then
124             g.spec.width = g.spec.width + h.kern
125             set_attr(g,attr_icflag,XKANJI_SKIP)
126             node_insert_after(head, q, g)
127             head = node.remove(head, h)
128             node.free(h)
129          else
130             add_glue_spec(h.spec, g.spec)
131             node.free(g.spec); node.free(g)
132          end
133       else
134          set_attr(g,attr_icflag,XKANJI_SKIP)
135          node_insert_after(head, q, g)
136       end
137    end
138 end
139
140 local function insert_ascii_kanji_xkskip(q, p)
141    if luatexja.stack.get_penalty_table('xsp', p.char, 3, ltj.box_stack_level)<=1 then return end
142    insert_xkanjiskip_node(q, p.font, p)
143 end
144
145 local function insert_kanji_ascii_xkskip(q, p)
146    local g = true
147    local c = p.char
148    while p.components and p.subtype 
149       and math.floor(p.subtype/2)%2==1 do
150       p = p.components; c = p.char
151    end
152    if luatexja.stack.get_penalty_table('xsp', c, 3, ltj.box_stack_level)%2 == 1 then
153       if luatexja.stack.get_penalty_table('xsp', nrc, 3, ltj.box_stack_level)%2 == 0 then g = false end
154    else g = false
155    end
156    if g then insert_xkanjiskip_node(q, nrf, p) end
157 end
158
159 local function set_insert_skip_after_achar(p)
160    local c = p.char
161    while p.components and p.subtype 
162       and math.floor(p.subtype/2)%2 == 1 do
163       p=node.tail(p.components); c = p.char
164    end
165   if luatexja.stack.get_penalty_table('xsp', c, 3, ltj.box_stack_level)>=2 then
166      insert_skip = after_schar
167   else
168      insert_skip = no_skip
169   end
170 end
171
172 -- lowest part of \kanjiskip 
173 local function get_kanji_skip_from_jfm(pf)
174    if pf then
175       local px = { ltj.font_metric_table[pf].size, 
176                    ltj.font_metric_table[pf].jfm }
177       local i = ltj.metrics[px[2]].kanjiskip
178       if i then
179          return { round(i[1]*px[1]), round(i[2]*px[1]), round(i[3]*px[1]) }
180       else return nil
181       end
182    else return nil
183    end
184 end
185
186 local function insert_kanji_skip(ope, p)
187    local g = node_new(id_glue); g.subtype=0
188    if nr_spc[1] or np_spc[1] then
189       if kanji_skip.width==max_dimen then -- use kanjiskip from JFM
190          local gx = node_new(id_glue_spec);
191          gx.stretch_order = 0; gx.shrink_order = 0
192          local bk = get_kanji_skip_from_jfm(nrf)
193          local ak = get_kanji_skip_from_jfm(p.font)
194          if bk then
195             if ak then
196                gx.width = round(ltj.ja_diffmet_rule(bk[1], ak[1]))
197                gx.stretch = round(ltj.ja_diffmet_rule(bk[2], ak[2]))
198                gx.shrink = -round(ltj.ja_diffmet_rule(-bk[3], -ak[3]))
199             else
200                gx.width = bk[1]; gx.stretch = bk[2]; gx.shrink = bk[3]
201             end
202          elseif ak then
203             gx.width = ak[1]; gx.stretch = ak[2]; gx.shrink = ak[3]
204          else gx = get_zero_glue() -- fallback
205          end
206          g.spec = gx
207       else g.spec=node_copy(kanji_skip)
208       end
209    else g.spec = get_zero_glue()
210    end
211    local h = node_prev(p)
212    if h  and has_attr(h, attr_icflag)==TEMPORARY then
213       if h.id==id_kern then
214          g.spec.width = g.spec.width + h.kern
215          head = node.remove(head, h)
216          node.free(h)
217          set_attr(g,attr_icflag,KANJI_SKIP)
218          ope(head, p, g)
219       else
220          add_glue_spec(h.spec, g.spec)
221          node.free(g.spec); node.free(g)
222       end
223    else
224       set_attr(g,attr_icflag,KANJI_SKIP)
225       ope(head, p, g)
226    end
227 end
228
229 -- When p is a glyph_node ...
230 local function insks_around_char()
231    if is_japanese_glyph_node(np) then
232       if insert_skip==after_wchar then
233          insert_kanji_skip(node_insert_before, np)
234       elseif insert_skip==after_schar then
235          insert_ascii_kanji_xkskip(nq, np)
236       end
237       insert_skip=after_wchar
238       nrc = np.char; nrf = np.font; nr_spc = np_spc
239    else
240       if insert_skip==after_wchar then
241          insert_kanji_ascii_xkskip(nq, np)
242       end
243       set_insert_skip_after_achar(np); nr_spc = np_spc
244    end
245    nq = np
246 end
247
248 -- Return first and last glyph nodes in a hbox
249 local first_char = nil
250 local last_char = nil
251 local find_first_char = nil
252 local function check_box(box_ptr)
253    local p = box_ptr; local found_visible_node = false
254    if not p then 
255       find_first_char = false; first_char = nil; last_char = nil
256       return true
257    end
258    while p do
259       if p.id==id_glyph then
260          repeat 
261             if find_first_char then
262                first_char = p; find_first_char = false
263             end
264             last_char = p; found_visible_node = true; p=node_next(p)
265             if not p then return found_visible_node end
266          until p.id~=id_glyph
267       end
268       if p.id==id_hlist then
269          if has_attr(p, attr_icflag)==PACKED then
270             for q in node.traverse_id(id_glyph, p.head) do
271                if find_first_char then
272                   first_char = q; find_first_char = false
273                end
274                last_char = q; found_visible_node = true
275             end
276          else
277             if p.shift==0 then
278                if check_box(p.head) then found_visible_node = true end
279             else if find_first_char then 
280                   find_first_char = false
281                else 
282                   last_char = nil
283                end
284             end
285          end
286       elseif p.id==id_ins    or p.id==id_mark
287           or p.id==id_adjust or p.id==id_whatsit
288           or p.id==id_penalty then
289          p=p
290       else
291          found_visible_node = true
292          if find_first_char then 
293             find_first_char = false
294          else 
295             last_char = nil
296          end
297       end
298       p = node_next(p)
299    end
300    return found_visible_node
301 end 
302
303 -- When np is a hlist_node ...
304 local function insks_around_hbox()
305    if np.shift==0 then
306       find_first_char = true; first_char = nil; last_char = nil
307       if check_box(np.head) then
308          -- first char
309          if is_japanese_glyph_node(first_char) then
310             nrc = first_char.char; nrf = first_char.font
311             if insert_skip==after_schar then 
312                insert_ascii_kanji_xkskip(nq, first_char)
313             elseif insert_skip==after_wchar then
314                np_spc = { has_attr(first_char, attr_autospc)==1, 
315                           has_attr(first_char, attr_autoxspc)==1 }
316                insert_kanji_skip(node_insert_before, np)
317             end
318             insert_skip = after_wchar
319          elseif first_char then
320             if insert_skip==after_wchar then
321                insert_kanji_ascii_xkskip(nq, first_char)
322             end
323             set_insert_skip_after_achar(first_char)
324          end
325          -- last char
326          if is_japanese_glyph_node(last_char) then
327             insert_skip = after_wchar
328             nrc = last_char.char; nrf = last_char.font
329             nr_spc = { has_attr(last_char, attr_autospc)==1, 
330                        has_attr(last_char, attr_autoxspc)==1 }
331             if is_japanese_glyph_node(node_next(np)) then
332                insert_kanji_skip(node_insert_after, np)
333             end
334          elseif last_char then
335             set_insert_skip_after_achar(last_char)
336             nr_spc = { has_attr(last_char, attr_autospc)==1, 
337                        has_attr(last_char, attr_autoxspc)==1 }
338          else insert_skip = no_skip
339          end
340       else insert_skip = no_skip
341       end
342    else insert_skip = no_skip
343    end
344    nq = np
345 end
346
347 -- When np is a penalty ...
348 local function insks_around_penalty()
349    nq = np
350 end
351
352 -- When np is a kern ...
353 -- 
354 local function insks_around_kern()
355    if np.subtype==1 then -- \kern or \/
356       local i = has_attr(np, attr_icflag)
357       if not i or i==FROM_JFM then -- \kern
358          insert_skip = no_skip
359       elseif i==ITALIC or i==LINE_END or i==TEMPORARY then
360          nq = np
361       end
362    elseif np.subtype==2 then 
363       -- (np = kern from \accent) .. (accent char) .. (kern from \accent) .. (glyph)
364       np = node_next(node_next(np))
365    else  -- kern from TFM
366       nq = np
367    end
368 end
369
370 -- When np is a math_node ...
371 local function insks_around_math()
372    local g = { char = -1 }
373    if (np.subtype==0) and (insert_skip==after_wchar) then
374       insert_kanji_ascii_xkskip(nq, g)
375       insert_skip = no_skip
376    else
377       nq = np; set_insert_skip_after_achar(g); nr_spc = np_spc
378    end
379 end
380
381 function ltj.int_insert_kanji_skip(ahead)
382    kanji_skip=skip_table_to_spec('kanjiskip')
383    xkanji_skip=skip_table_to_spec('xkanjiskip')
384    head = ahead
385    np = head; nq = nil; insert_skip = no_skip
386    while np do
387       np_spc = { (has_attr(np, attr_autospc)==1), 
388                  (has_attr(np, attr_autoxspc)==1) }
389       if np.id==id_glyph then
390          repeat 
391             np_spc = { has_attr(np, attr_autospc)==1, 
392                        has_attr(np, attr_autoxspc)==1 }
393             insks_around_char(); np=node_next(np)
394          until (not np) or np.id~=id_glyph
395       else
396          if np.id==id_hlist then
397             insks_around_hbox()
398          elseif np.id==id_penalty then
399             insks_around_penalty()
400          elseif np.id==id_kern then
401             insks_around_kern()
402          elseif np.id==id_math then
403             insks_around_math()
404          elseif np.id==id_ins    or np.id==id_mark
405              or np.id==id_adjust or np.id==id_whatsit then
406             -- do nothing
407             np = np
408          else
409             -- rule, disc, glue, margin_kern
410             insert_skip = no_skip
411          end
412          np = node_next(np)
413       end
414    end
415    return head
416 end