OSDN Git Service

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