OSDN Git Service

03c5f696394b28149199f95b6a25f78e56d24712
[luatex-ja/luatexja.git] / src / luatexja-jfmglue.lua
1 ------------------------------------------------------------------------
2 -- MAIN PROCESS STEP 2: insert glue/kerns from JFM (prefix: none)
3 ------------------------------------------------------------------------
4
5 local node_type = node.type
6 local node_new = node.new
7 local node_remove = node.remove
8 local node_prev = node.prev
9 local node_next = node.next
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 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_whatsit = node.id('whatsit')
23 local sid_user = node.subtype('user_defined')
24
25 local TEMPORARY = 2
26 local FROM_JFM = 3
27 local KINSOKU = 4
28 local LINE_END = 5
29
30
31 local attr_jchar_class = luatexbase.attributes['ltj@charclass']
32 local attr_curjfnt = luatexbase.attributes['ltj@curjfnt']
33 local attr_icflag = luatexbase.attributes['ltj@icflag']
34 -- attr_icflag: 1: kern from \/, 2: 'lineend' kern from JFM
35
36 -- 
37 local cstb_get_penalty_table = ltj.int_get_penalty_table
38 local ljfm_find_char_class = ltj.int_find_char_class
39
40 -- arithmetic with penalty.
41 -- p += e
42 local function add_penalty(p,e)
43    local i = p
44    if i>=10000 then
45       if e<=-10000 then i = 0 end
46    elseif i<=-10000 then
47       if e>=10000 then i = 0 end
48    else
49       i = i + e
50       if i>=10000 then i = 10000
51       elseif i<=-10000 then i = -10000 end
52    end
53    return i
54 end
55
56 -- return true if and only if p is a Japanese character node
57 local function is_japanese_glyph_node(p)
58    return (p.id==id_glyph) and (p.font==has_attr(p,attr_curjfnt))
59 end
60
61 -- EXT: for \inhibitglue
62 function ltj.ext_create_inhibitglue_node()
63    local g=node_new(id_whatsit, sid_user)
64    g.user_id=30111; g.type=100; g.value=1; node.write(g)
65 end
66
67 -- In the beginning of a hlist created by line breaking, there are the followings:
68 --   - a hbox by \parindent
69 --   - a whatsit node which contains local paragraph materials.
70 -- When we insert jfm glues, we ignore these nodes.
71 local function is_parindent_box(p)
72    if p.id==id_hlist then 
73       return (p.subtype==3)
74       -- hlist (subtype=3) is a box by \parindent
75    elseif p.id==id_whatsit then 
76       return (p.subtype==node.subtype('local_par'))
77    end
78 end
79
80 local function find_size_metric(px)
81    if is_japanese_glyph_node(px) then
82       return { ltj.font_metric_table[px.font].size, 
83                ltj.font_metric_table[px.font].jfm, ltj.font_metric_table[px.font].var }
84    else 
85       return nil
86    end
87 end
88
89 local function new_jfm_glue(size,mt,bc,ac)
90 -- mt: metric key, bc, ac: char classes
91    local g=nil
92    local z = ltj.metrics[mt].char_type[bc]
93    if z.glue and z.glue[ac] then
94       local h = node_new(id_glue_spec)
95       h.width   = round(size*z.glue[ac][1])
96       h.stretch = round(size*z.glue[ac][2])
97       h.shrink  = round(size*z.glue[ac][3])
98       h.stretch_order=0; h.shrink_order=0
99       g = node_new(id_glue)
100       g.subtype = 0; g.spec = h
101    elseif z.kern and z.kern[ac] then
102       g = node_new(id_kern)
103       g.subtype = 1; g.kern = round(size*z.kern[ac])
104    end
105    return g
106 end
107
108 -- Insert jfm glue: main routine
109 -- local variables
110 local p
111 local q = nil  -- the previous node of p
112 local q_post   -- the postbreakpenalty of q
113 local ps, qs
114 local widow_node -- 最後の「句読点扱いでない」和文文字
115 local widow_bp -- 挿入位置 (a penalty)
116 local last -- the sentinel 
117 local chain = false -- is q a Japanese character?
118 local ihb_flag = false -- is \inhibitglue specified?
119 local head -- the head of current list
120 local mode -- true iff insert_jfm_glue is called from pre_linebreak_filter
121
122 -- initialize (insert the sentinel, etc.)
123 local function init_var()
124    p = head;  q = nil; widow_node = nil; widow_bp = nil
125    chain = false; ihb_flag = false; 
126    if mode then
127       while p and is_parindent_box(p) do p=node_next(p) end
128       last=node.tail(head)
129       if last and last.id==id_glue and last.subtype==15 then
130          last=node.prev(last)
131          while (last and last.id==id_penalty) do last=node.prev(last) end
132       end
133       if last then last=node_next(last) end
134    else -- 番人を挿入
135       last=node.tail(head); local g = node_new('kern')
136       node_insert_after(head, last, g); last = g
137    end
138 end
139
140 -- Insert JFM glue before the first node (which is a Japanese glyph node)
141 local function ins_gk_head()
142    if is_japanese_glyph_node(p) then
143       ps = find_size_metric(p)
144       local g = new_jfm_glue(ps[1], ps[2],
145                              ljfm_find_char_class('boxbdd',ps[2]),
146                              has_attr(p,attr_jchar_class))
147       if g then
148          set_attr(g, attr_icflag, FROM_JFM)
149          head = node_insert_before(head, p, g)
150       end
151       q_post = cstb_get_penalty_table('post', p.char, 0, ltj.box_stack_level); chain = true
152    elseif p.id==id_glyph then
153       q_post = cstb_get_penalty_table('post', p.char, 0, ltj.box_stack_level)
154    end
155    qs = ps; q = p; p = node_next(p)
156 end
157
158 -- The real insertion process is handled in this procedure.
159 local function real_insert(g, w, pen, always_penalty_ins)
160    -- g: glur/kern from JFM
161    -- w: the width of kern that will be inserted between q and the end of a line
162    -- pen: penalty
163    -- always_penalty_ins: true iff we insert a penalty,
164    --   for the linebreak between q and p.
165    if w~=0 then
166       if not g then
167          g = node_new(id_kern); g.kern = -w; g.subtype = 1
168          set_attr(g, attr_icflag, TEMPORARY)
169          -- this g might be replaced by \[x]kanjiskip in step 3.
170       else 
171          set_attr(g, attr_icflag, FROM_JFM)
172          if g.id==id_kern then w=0
173          else g.spec.width = round(g.spec.width - w) 
174          end
175       end
176    end
177    if w~=0 then
178       local h = node_new(id_kern)
179       set_attr(h, attr_icflag, LINE_END)
180       h.kern = w; h.subtype = 0; node_insert_before(head, p, h)
181    elseif g then
182       set_attr(g, attr_icflag, FROM_JFM)
183       if g.id==id_kern  then
184          pen=0; always_penalty_ins = false
185       end
186    end
187    if w~=0  or pen~=0 or ((not g) and always_penalty_ins) then
188       local h = node_new(id_penalty)
189       h.penalty = pen; set_attr(h, attr_icflag, KINSOKU)
190       node_insert_before(head, p, h)
191    end
192    if g then
193       node_insert_before(head, p, g); 
194    end
195 end
196
197 -- This is a variant of real_insert (the case which a kern is related)
198 local function real_insert_kern(g)
199    if g then
200       if g.id==id_glue then
201          local h = node_new(id_penalty)
202          h.penalty = 10000; set_attr(h, attr_icflag, KINSOKU)
203          node_insert_before(head, p, h)
204       end
205       set_attr(g, attr_icflag, FROM_JFM)
206       node_insert_before(head, p, g)
207    end
208 end
209
210 ltj.ja_diffmet_rule = math.two_average
211
212 local function calc_ja_ja_aux(gb,ga)
213    if not gb then 
214       return ga
215    else
216       if not ga then return gb end
217       local k = node.type(gb.id) .. node.type(ga.id)
218       if k == 'glueglue' then 
219          -- 両方とも glue.
220          gb.spec.width   = round(ltj.ja_diffmet_rule(gb.spec.width, ga.spec.width))
221          gb.spec.stretch = round(ltj.ja_diffmet_rule(gb.spec.stretch,ga.spec.shrink))
222          gb.spec.shrink  = -round(ltj.ja_diffmet_rule(-gb.spec.shrink, -ga.spec.shrink))
223          return gb
224       elseif k == 'kernkern' then
225          -- 両方とも kern.
226          gb.kern = round(ltj.ja_diffmet_rule(gb.kern, ga.kern))
227          return gb
228       elseif k == 'kernglue' then 
229          -- gb: kern, ga: glue
230          ga.spec.width   = round(ltj.ja_diffmet_rule(gb.kern,ga.spec.width))
231          ga.spec.stretch = round(ltj.ja_diffmet_rule(ga.spec.stretch, 0))
232          ga.spec.shrink  = -round(ltj.ja_diffmet_rule(-ga.spec.shrink, 0))
233          return ga
234       else
235          -- gb: glue, ga: kern
236          gb.spec.width   = round(ltj.ja_diffmet_rule(ga.kern, gb.spec.width))
237          gb.spec.stretch = round(ltj.ja_diffmet_rule(gb.spec.stretch, 0))
238          gb.spec.shrink  = -round(ltj.ja_diffmet_rule(-gb.spec.shrink, 0))
239          return gb
240       end
241    end
242 end
243
244 -- Calc the glue between two Japanese characters
245 local function calc_ja_ja_glue()
246    if ihb_flag then return nil
247    elseif table.are_equal(qs,ps) then
248       return new_jfm_glue(ps[1],ps[2],
249                           has_attr(q,attr_jchar_class),
250                           has_attr(p,attr_jchar_class))
251    else
252       local g = new_jfm_glue(qs[1],qs[2],
253                              has_attr(q,attr_jchar_class),
254                              ljfm_find_char_class('diffmet',qs[2]))
255       local h = new_jfm_glue(ps[1],ps[2],
256                              ljfm_find_char_class('diffmet',ps[2]),
257                              has_attr(p,attr_jchar_class))
258       return calc_ja_ja_aux(g,h)
259    end
260 end
261
262 local function ins_gk_any_JA()
263    ps = find_size_metric(p)
264    if chain then -- (q,p): JA-JA
265       local g = calc_ja_ja_glue()
266       local w = 0; local x = ljfm_find_char_class('lineend', qs[2])
267       if (not ihb_flag) and x~=0  then
268          local h = ltj.metrics[qs[2]].char_type[has_attr(q, attr_jchar_class)]
269          if h.kern and h.kern[x] then w = round(qs[1]*h.kern[x]) end
270       end
271       q_post = add_penalty(q_post, cstb_get_penalty_table('pre', p.char, 0, ltj.box_stack_level))
272       real_insert(g, w, q_post, false)
273    elseif q.id==id_glyph then -- (q,p): AL-JA
274       local g = nil
275       if not ihb_flag then
276          g = new_jfm_glue(ps[1], ps[2],
277                           ljfm_find_char_class('jcharbdd',ps[2]),
278                           has_attr(p,attr_jchar_class))
279       end
280       q_post = add_penalty(q_post, cstb_get_penalty_table('pre', p.char, 0, ltj.box_stack_level))
281       real_insert(g, 0, q_post, true)
282    elseif q.id==id_kern then -- (q,p): kern-JA
283       local g = nil
284       if not ihb_flag then
285          g = new_jfm_glue(ps[1], ps[2],
286                           ljfm_find_char_class('jcharbdd',ps[2]),
287                           has_attr(p,attr_jchar_class))
288       end
289       real_insert_kern(g)
290    else
291       local g = nil
292       if not ihb_flag then
293          g = new_jfm_glue(ps[1], ps[2],
294                           ljfm_find_char_class('jcharbdd',ps[2]),
295                           has_attr(p,attr_jchar_class))
296       end
297       real_insert(g, 0, q_post, true)
298    end
299    q, qs, q_post = p, ps, cstb_get_penalty_table('post', p.char, 0, ltj.box_stack_level)
300    if cstb_get_penalty_table('kcat', p.char, 0, ltj.box_stack_level)%2~=1 then
301       widow_node = p
302    end
303    p = node_next(p); chain = true
304 end
305
306 local function ins_gk_JA_any()
307    -- the case (q,p): JA-JA is treated in ins_gk_any_JA()
308    local g = nil
309    if not ihb_flag then
310       g = new_jfm_glue(qs[1], qs[2],
311                        has_attr(q,attr_jchar_class),
312                        ljfm_find_char_class('jcharbdd',qs[2]))
313    end
314    if p.id==id_glyph then -- (q,p): JA-AL
315       local w = 0; local x = ljfm_find_char_class('lineend', qs[2])
316       if (not ihb_flag) and x~=0  then
317          local h = ltj.metrics[qs[2]].char_type[has_attr(q,attr_jchar_class)]
318          if h.kern and h.kern[x] then w = round(qs[1]*h.kern[x]) end
319       end
320       q_post = add_penalty(q_post, cstb_get_penalty_table('pre', p.char, 0, ltj.box_stack_level))
321       real_insert(g, w, q_post, true)
322    elseif p.id==id_kern then -- (q,p): JA-kern
323       real_insert_kern(g)
324    else
325       local w = 0; local x = ljfm_find_char_class('lineend', qs[2])
326       if (not ihb_flag) and x~=0  then
327          local h = ltj.metrics[qs[2]].char_type[has_attr(q,attr_jchar_class)]
328          if h.kern and h.kern[x] then w = round(qs[1]*h.kern[x]) end
329       end
330       if p.id~=penalty then
331          real_insert(g, w, q_post, true)
332       else
333          real_insert(g, w, q_post, false)
334       end
335    end
336    chain = false; q, qs, q_post = p, nil, 0; p = node_next(p)
337 end
338
339 -- Insert JFM glue after thr last node
340 local function ins_gk_tail()
341    local g
342    if is_japanese_glyph_node(p) then
343       if mode then
344          local w = 0; local x = ljfm_find_char_class('lineend', qs[2])
345          if (not ihb_flag) and x~=0  then
346             local h = ltj.metrics[qs[2]].char_type[has_attr(q,attr_jchar_class)]
347             if h.kern and h.kern[x] then w = round(qs[1]*h.kern[x]) end
348          end
349          if w~=0 then
350             g = node_new(id_kern); g.subtype = 0; g.kern = w
351             set_attr(g, attr_icflag, LINE_END)
352             node_insert_before(head, last, g)
353          end
354       end
355    end
356 end
357
358 local function add_widow_penalty()
359    -- widoe_node: must be 
360    if not widow_node then return end
361    local a = node_prev(widow_node)
362    local i = has_attr(a, attr_icflag) or 0
363    local wp = cstb_get_penalty_table('jwp', 0, 0, ltj.box_stack_level)
364    if i==4 then
365       a.penalty=add_penalty(a.penalty, wp)
366    elseif i>=2 then
367       local b = node_prev(a)
368       if i==4 then
369          b.penalty=add_penalty(b.penalty, wp)
370       else
371          local g = node_new(id_penalty)
372          g.penalty = wp; head = node_insert_before(head,a,g)
373       end
374    else
375       local g = node_new(id_penalty)
376       g.penalty = wp; head = node_insert_before(head,widow_node,g)
377    end
378 end
379
380 -- Finishing: add \jcharwidowpenalty or remove the sentinel
381 local function finishing()
382    if mode then
383       -- Insert \jcharwidowpenalty
384       add_widow_penalty()
385    else
386       head = node_remove(head, last)
387    end
388 end
389
390 -- The interface
391 function ltj.int_insert_jfm_glue(ahead, amode)
392    if not ahead then return ahead end
393    head = ahead; mode = amode; init_var(); 
394    while p~=last and p.id==id_whatsit and p.subtype==sid_user and p.user_id==30111 do
395       local g = p; p = node_next(p); ihb_flag = true; head, p = node.remove(head, g)
396    end
397    if p~=last then ins_gk_head() else finishing() return head end
398    while p~=last do
399       if p.id==id_whatsit and p.subtype==sid_user and p.user_id==30111 then
400          local g = p; p = node_next(p)
401          ihb_flag = true; head, p = node.remove(head, g)
402       else
403          if is_japanese_glyph_node(p) then -- p: JAchar
404             ins_gk_any_JA()
405          elseif chain then -- q: JAchar
406             ins_gk_JA_any()
407          else 
408             q, qs, q_post = p, nil, 0; p = node_next(p)
409          end
410          ihb_flag = false
411       end
412    end
413    ins_gk_tail(); finishing(); return head
414 end
415