OSDN Git Service

9d650f2020ab2ad3ec95aeef60a9b2f9f9a58b8e
[luatex-ja/luatexja.git] / src / ltj-direction.lua
1 --
2 -- src/ltj-direction.lua
3 --
4
5 luatexja.load_module('base');      local ltjb = luatexja.base
6 luatexja.load_module('stack');     local ltjs = luatexja.stack
7 luatexja.direction = {}
8
9 local attr_dir = luatexbase.attributes['ltj@dir']
10 local attr_icflag = luatexbase.attributes['ltj@icflag']
11
12 local cat_lp = luatexbase.catcodetables['latex-package']
13 local to_node = node.direct.tonode
14 local to_direct = node.direct.todirect
15 local has_attr = node.direct.has_attribute
16 local set_attr = node.direct.set_attribute
17 local insert_before = node.direct.insert_before
18 local insert_after = node.direct.insert_after
19 local getid = node.direct.getid
20 local getsubtype = node.direct.getsubtype
21 local getlist = node.direct.getlist
22 local setfield = node.direct.setfield
23 local getfield = node.direct.getfield
24 local node_new = node.direct.new
25 local node_tail = node.direct.tail
26 local node_free = node.direct.free
27 local node_remove = node.direct.remove
28 local node_next = node.direct.getnext
29 local traverse = node.direct.traverse
30 local traverse_id = node.direct.traverse_id
31 local start_time_measure, stop_time_measure
32    = ltjb.start_time_measure, ltjb.stop_time_measure
33 local abs = math.abs
34
35 local id_kern = node.id('kern')
36 local id_hlist = node.id('hlist')
37 local id_vlist = node.id('vlist')
38 local id_whatsit = node.id('whatsit')
39 local sid_save = node.subtype('pdf_save')
40 local sid_restore = node.subtype('pdf_restore')
41 local sid_matrix = node.subtype('pdf_setmatrix')
42 local sid_user = node.subtype('user_defined')
43
44 local tex_nest = tex.nest
45 local tex_getcount = tex.getcount
46 local ensure_tex_attr = ltjb.ensure_tex_attr
47 local PROCESSED    = luatexja.icflag_table.PROCESSED
48 local PROCESSED_BEGIN_FLAG = luatexja.icflag_table.PROCESSED_BEGIN_FLAG
49 local PACKED       = luatexja.icflag_table.PACKED
50 local DIR  = luatexja.userid_table.DIR
51 local dir_tate = luatexja.dir_table.dir_tate
52 local dir_yoko = luatexja.dir_table.dir_yoko
53 local dir_dtou = luatexja.dir_table.dir_dtou
54 local dir_utod = luatexja.dir_table.dir_utod
55 local dir_math_mod    = luatexja.dir_table.dir_math_mod
56 local dir_node_auto   = luatexja.dir_table.dir_node_auto
57 local dir_node_manual = luatexja.dir_table.dir_node_manual
58 local function get_attr_icflag(p)
59    return (has_attr(p, attr_icflag) or 0) % PROCESSED_BEGIN_FLAG
60 end
61
62 local page_direction
63 --
64 local dir_pool
65 do
66    local node_copy = node.direct.copy
67    dir_pool = {}
68    for _,i in pairs({dir_tate, dir_yoko, dir_dtou, dir_utod}) do
69       local w = node_new(id_whatsit, sid_user)
70       set_attr(w, attr_dir, i)
71       setfield(w, 'user_id', DIR)
72       setfield(w, 'type', 110)
73       setfield(w, 'next', nil)
74       dir_pool[i] = function () return node_copy(w) end
75    end
76 end
77
78 --
79 local function adjust_badness(hd)
80    if not node_next(hd) and getid(hd)==id_whatsit and getsubtype(hd)==sid_user
81    and getfield(hd, 'user_id')==DIR then
82       -- avoid double whatsit
83       luatexja.global_temp=tex.globaldefs; tex.globaldefs=0
84       luatexja.hbadness_temp=tex.hbadness; tex.hbadness=10000
85       luatexja.vbadness_temp=tex.vbadness; tex.vbadness=10000
86    else
87       luatexja.global_temp = nil
88       luatexja.hbadness_temp=nil
89       luatexja.vbadness_temp=nil
90    end
91 end
92
93 local get_dir_count, get_adjust_dir_count
94 do
95    local function get_dir_count_inner(h)
96       if h then
97          if h.id==id_whatsit and h.subtype==sid_user and h.user_id==DIR then
98                local ic = node.has_attribute(h, attr_icflag) or 0
99                return (ic<PROCESSED_BEGIN_FLAG)
100                   and (node.has_attribute(h,attr_dir)%dir_node_auto) or 0
101          else
102             return 0
103          end
104       else
105          return 0
106       end
107    end
108    function get_dir_count()
109        for i=tex_nest.ptr, 1, -1 do
110            local h = tex_nest[i].head.next
111            if h then
112                local t = get_dir_count_inner(h)
113                if t~=0 then return t end
114            end
115        end
116        return page_direction
117    end
118    function get_adjust_dir_count()
119       for i=tex_nest.ptr, 1, -1 do
120          local v = tex_nest[i]
121          local h, m = v.head.next, v.mode
122          if abs(m)== ltjs.vmode and h then
123             local t = get_dir_count_inner(h)
124             if t~=0 then return t end
125          end
126       end
127       return page_direction
128    end
129    luatexja.direction.get_dir_count = get_dir_count
130    luatexja.direction.get_adjust_dir_count = get_adjust_dir_count
131 end
132
133
134 -- \tate, \yoko,\dtou, \utod
135 do
136    local node_next = node.next
137    local node_set_attr = node.set_attribute
138    local node_traverse = node.traverse
139    local STCK = luatexja.userid_table.STCK
140    local IHB = luatexja.userid_table.IHB
141    local id_local = node.id('local_par')
142
143    local function test_list(h, lv)
144       if not h then
145          return 2 -- need to create dir_whatsit
146       else
147          local flag = 2 -- need to create dir_whatsit
148          local w
149          for p in node_traverse(h) do
150             if p.id==id_whatsit then
151                local ps = p.subtype
152                if ps==sid_user then
153                   local uid= p.user_id
154                   if uid==DIR then
155                      flag = 1; w = w or p -- found
156                   elseif not(uid==IHB or uid==STCK) then
157                      flag = 0; break -- error
158                   end
159                end
160             elseif p.id~=id_local then
161                flag = 0; break
162             end
163          end
164          if flag==1 then -- dir_whatsit already exists
165             return 1,w
166          else
167             return flag
168          end
169       end
170    end
171    local node_next_node, node_tail_node = node.next, node.tail
172    local insert_after_node = node.insert_after
173    function luatexja.direction.set_list_direction_hook(v)
174       local lv = tex_nest.ptr -- must be >= 1
175       if not v then
176          v = get_dir_count()
177          if abs(tex_nest[lv-1].mode) == ltjs.mmode and v == dir_tate then
178             v = dir_utod
179          end
180       elseif v=='adj' then
181          v = get_adjust_dir_count()
182       end
183       local h = tex_nest[lv].head
184       local hn = node.next(h)
185       hn = (hn and hn.id==id_local) and hn or h
186       local w = to_node(dir_pool[v]())
187       insert_after_node(h, hn, w)
188       tex_nest[lv].tail = node_tail_node(w)
189       ensure_tex_attr(attr_icflag, 0)
190       ensure_tex_attr(attr_dir, 0)
191    end
192
193    local function set_list_direction(v, name)
194       local lv = tex_nest.ptr
195       if not v then
196          v,name  = get_dir_count(), nil
197          if lv>=1 and abs(tex_nest[lv-1].mode) == ltjs.mmode and v == dir_tate then
198             v = dir_utod
199          end
200       elseif v=='adj' then
201          v,name = get_adjust_dir_count(), nil
202       end
203       if tex.currentgrouptype==6 then
204          ltjb.package_error(
205                  'luatexja',
206                  "You can't use `\\" .. name .. "' in an align",
207                  "To change direction in an align, \n"
208                     .. "you shold use \\hbox or \\vbox.")
209       else
210          local h = (lv==0) and tex.lists.page_head or tex_nest[lv].head.next
211          local flag,w = test_list(h,lv)
212          if flag==0 then
213             if lv==0 and not page_direction then
214                page_direction = v -- for first call of \yoko (in luatexja-core.sty)
215             else
216               ltjb.package_error(
217                  'luatexja',
218                  "Use `\\" .. tostring(name) .. "' at top of list",
219                  'Direction change command by LuaTeX-ja is available\n'
220                     .. 'only when the current list is null.')
221             end
222          elseif flag==1 then
223             node_set_attr(w, attr_dir, v)
224             if lv==0 then page_direction = v end
225          elseif lv==0 then
226             page_direction = v
227          else -- flag == 2: need to create dir whatsit.
228             local h = tex_nest[lv].head
229             local hn = node.next(h)
230             hn = (hn and hn.id==id_local) and hn or h
231             local w = to_node(dir_pool[v]())
232             insert_after_node(h,hn,w)
233             tex_nest[lv].tail = node_tail_node(w)
234          end
235          ensure_tex_attr(attr_icflag, 0)
236       end
237       ensure_tex_attr(attr_dir, 0)
238    end
239    luatexja.direction.set_list_direction = set_list_direction
240 end
241
242 -- ボックスに dir whatsit を追加
243 local function create_dir_whatsit(hd, gc, new_dir)
244    if getid(hd)==id_whatsit and
245             getsubtype(hd)==sid_user and getfield(hd, 'user_id')==DIR then
246       set_attr(hd, attr_icflag,
247                get_attr_icflag(hd) + PROCESSED_BEGIN_FLAG)
248       local n =node_next(hd)
249       if n then
250          set_attr(n, attr_icflag,
251                   get_attr_icflag(n) + PROCESSED_BEGIN_FLAG)
252       end
253       ensure_tex_attr(attr_icflag, 0)
254       return hd
255    else
256       local w = dir_pool[new_dir]()
257       setfield(w, 'next', hd)
258       set_attr(w, attr_icflag, PROCESSED_BEGIN_FLAG)
259       set_attr(hd, attr_icflag,
260                get_attr_icflag(hd) + PROCESSED_BEGIN_FLAG)
261       ensure_tex_attr(attr_icflag, 0)
262       ensure_tex_attr(attr_dir, 0)
263       return w
264    end
265 end
266
267 -- hpack_filter, vpack_filter, post_line_break_filter
268 -- の結果を組方向を明示するため,先頭に dir_node を設置
269 local get_box_dir
270 do
271    local function create_dir_whatsit_hpack(h, gc)
272       local hd = to_direct(h)
273       if gc=='fin_row' then
274          if hd  then
275             for p in traverse_id(15, hd) do -- unset
276                if get_box_dir(p, 0)==0 then
277                   setfield(p, 'head', create_dir_whatsit(getlist(p), 'fin_row', ltjs.list_dir))
278                end
279             end
280             set_attr(hd, attr_icflag, PROCESSED_BEGIN_FLAG)
281             ensure_tex_attr(attr_icflag, 0)
282          end
283          return h
284       elseif gc == 'preamble'  then
285       else
286          adjust_badness(hd)
287          return to_node(create_dir_whatsit(hd, gc, ltjs.list_dir))
288       end
289    end
290
291    ltjb.add_to_callback('hpack_filter',
292                               create_dir_whatsit_hpack, 'ltj.create_dir_whatsit', 10000)
293 end
294
295 do
296    local function create_dir_whatsit_parbox(h, gc)
297       stop_time_measure('tex_linebreak');
298       -- start 側は ltj-debug.lua に
299       local new_dir = ltjs.list_dir
300       for line in traverse_id(id_hlist, to_direct(h)) do
301          setfield(line, 'head', create_dir_whatsit(getlist(line), gc, new_dir) )
302       end
303       ensure_tex_attr(attr_dir, 0)
304       return h
305    end
306    ltjb.add_to_callback('post_linebreak_filter',
307                               create_dir_whatsit_parbox, 'ltj.create_dir_whatsit', 10000)
308 end
309
310 local create_dir_whatsit_vbox
311 do
312    local wh = {}
313    local id_glue, sid_parskip = node.id('glue'), 3
314    create_dir_whatsit_vbox = function (hd, gc)
315       ltjs.list_dir = get_dir_count()
316       -- remove dir whatsit
317       for x in traverse_id(id_whatsit, hd) do
318          if getsubtype(x)==sid_user and getfield(x, 'user_id')==DIR then
319             wh[#wh+1]=x
320          end
321       end
322       if hd==wh[1] then
323          ltjs.list_dir =has_attr(hd,attr_dir)
324          local x = node_next(hd)
325          if getid(x)==id_glue and getsubtype(x)==sid_parskip then
326             node_remove(hd,x); node_free(x)
327          end
328       end
329       for i=1,#wh do
330          hd = node_remove(hd, wh[i]); node_free(wh[i]); wh[i] = nil
331       end
332       if gc=='fin_row' then -- gc == 'preamble' case is treated in dir_adjust_vpack
333          if hd  then
334             set_attr(hd, attr_icflag, PROCESSED_BEGIN_FLAG)
335             ensure_tex_attr(attr_icflag, 0)
336          end
337          return hd
338       else
339          local n =node_next(hd)
340          if gc=='vtop' then
341             local w = create_dir_whatsit(hd, gc, ltjs.list_dir)
342             -- move  dir whatsit after hd
343             setfield(hd, 'next', w); setfield(w, 'next', n)
344             return hd
345          else
346             hd = create_dir_whatsit(hd, gc, ltjs.list_dir)
347             return hd
348          end
349       end
350    end
351 end
352
353 -- dir_node に包む方法を書いたテーブル
354 local dir_node_aux
355 do
356    local floor = math.floor
357    local get_h =function (w,h,d) return h end
358    local get_d =function (w,h,d) return d end
359    local get_h_d =function (w,h,d) return h+d end
360    local get_h_d_neg =function (w,h,d) return -h-d end
361    local get_d_neg =function (w,h,d) return -d end
362    local get_w_half =function (w,h,d) return floor(0.5*w) end
363    local get_w_half_rem =function (w,h,d) return w-floor(0.5*w) end
364    local get_w_neg =function (w,h,d) return -w end
365    local get_w =function (w,h,d) return w end
366    local zero = function() return 0 end
367    dir_node_aux = {
368       [dir_yoko] = { -- yoko を
369          [dir_tate] = { -- tate 中で組む
370             width  = get_h_d,
371             height = get_w_half,
372             depth  = get_w_half_rem,
373             [id_hlist] = {
374                { 'whatsit', sid_save },
375                { 'rotate', '0 1 -1 0' },
376                { 'kern', function(w,h,d,nw,nh,nd) return -nd end },
377                { 'box' , get_h},
378                { 'kern', function(w,h,d,nw,nh,nd) return nd-w end },
379                { 'whatsit', sid_restore },
380             },
381             [id_vlist] = {
382                { 'whatsit', sid_save },
383                { 'rotate', '0 1 -1 0' },
384                { 'kern' , zero },
385                { 'box' , function(w,h,d,nw,nh,nd) return -nh-nd end },
386                { 'kern', get_h_d_neg},
387                { 'whatsit', sid_restore },
388             },
389          },
390          [dir_dtou] = { -- dtou 中で組む
391             width  = get_h_d,
392             height = get_w,
393             depth  = zero,
394             [id_hlist] = {
395                { 'whatsit', sid_save },
396                { 'rotate', '0 -1 1 0' },
397                { 'kern', function(w,h,d,nw,nh,nd) return -nh end },
398                { 'box', get_d_neg },
399                { 'kern', function(w,h,d,nw,nh,nd) return nh-w end },
400                { 'whatsit', sid_restore },
401             },
402             [id_vlist] = {
403                { 'whatsit', sid_save },
404                { 'rotate', '0 -1 1 0' },
405                { 'kern', get_h_d_neg },
406                { 'box', zero },
407                { 'whatsit', sid_restore },
408             },
409          },
410       },
411       [dir_tate] = { -- tate を
412          [dir_yoko] = { -- yoko 中で組む
413             width  = get_h_d,
414             height = get_w,
415             depth  = zero,
416             [id_hlist] = {
417                { 'whatsit', sid_save },
418                { 'rotate', '0 -1 1 0' },
419                { 'kern', function (w,h,d,nw,nh,nd) return -nh end },
420                { 'box' , get_d_neg },
421                { 'kern', function (w,h,d,nw,nh,nd) return nh-w end },
422                { 'whatsit', sid_restore },
423             },
424             [id_vlist] = {
425                { 'whatsit', sid_save },
426                { 'rotate', '0 -1 1 0' },
427                { 'kern', get_h_d_neg },
428                { 'box', zero },
429                { 'whatsit', sid_restore },
430             },
431          },
432          [dir_dtou] = { -- dtou 中で組む
433             width  = get_w,
434             height = get_d,
435             depth  = get_h,
436             [id_hlist] = {
437                { 'whatsit', sid_save },
438                { 'rotate', '-1 0 0 -1' },
439                { 'kern', get_w_neg },
440                { 'box',  function (w,h,d,nw,nh,nd) return h-nd end },
441                { 'whatsit', sid_restore },
442             },
443             [id_vlist] = {
444                { 'whatsit', sid_save },
445                { 'rotate', '-1 0 0 -1' },
446                { 'kern', get_h_d_neg },
447                { 'box', get_w_neg },
448                { 'whatsit', sid_restore },
449             },
450          },
451       },
452       [dir_dtou] = { -- dtou を
453          [dir_yoko] = { -- yoko 中で組む
454             width  = get_h_d,
455             height = get_w,
456             depth  = zero,
457             [id_hlist] = {
458                { 'whatsit', sid_save },
459                { 'rotate', '0 1 -1 0' },
460                { 'kern', function (w,h,d,nw,nh,nd) return -nd end },
461                { 'box', get_h },
462                { 'kern', function (w,h,d,nw,nh,nd) return nd-w end },
463                { 'whatsit', sid_restore },
464             },
465             [id_vlist] = {
466                { 'kern', zero },
467                { 'whatsit', sid_save },
468                { 'rotate', '0 1 -1 0' },
469                { 'box', function (w,h,d,nw,nh,nd) return -nd-nh end },
470                { 'kern', get_h_d_neg },
471                { 'whatsit', sid_restore },
472             },
473          },
474          [dir_tate] = { -- tate 中で組む
475             width  = get_w,
476             height = get_d,
477             depth  = get_h,
478             [id_hlist] = {
479                { 'whatsit', sid_save },
480                { 'rotate', '-1 0 0 -1' },
481                { 'kern', get_w_neg },
482                { 'box', function (w,h,d,nw,nh,nd) return h-nd end },
483                { 'whatsit', sid_restore },
484             },
485             [id_vlist] = {
486                { 'whatsit', sid_save },
487                { 'rotate', ' -1 0 0 -1' },
488                { 'kern', function (w,h,d,nw,nh,nd) return -nh-nd end },
489                { 'box', get_w_neg },
490                { 'kern', function (w,h,d,nw,nh,nd) return nh+nd-h-d end },
491                { 'whatsit', sid_restore },
492             },
493          },
494       },
495    }
496 end
497
498 -- 1st ret val: b の組方向
499 -- 2nd ret val はその DIR whatsit
500 function get_box_dir(b, default)
501    start_time_measure('get_box_dir')
502    local dir = has_attr(b, attr_dir) or 0
503    local bh = getfield(b,'head')
504    -- b は insert node となりうるので getlist() は使えない
505    local c
506    if bh~=0 then -- bh != nil
507       for bh in traverse_id(id_whatsit, bh) do
508          if getsubtype(bh)==sid_user and getfield(bh, 'user_id')==DIR then
509             c = bh
510            dir = (dir==0) and has_attr(bh, attr_dir) or dir
511          end
512       end
513    end
514    -- for i=1,2 do
515    --    if bh and getid(bh)==id_whatsit
516    --    and getsubtype(bh)==sid_user and getfield(bh, 'user_id')==DIR then
517    --    c = bh
518    --    dir = (dir==0) and has_attr(bh, attr_dir) or dir
519    --    end
520    --    bh = node_next(bh)
521    -- end
522    stop_time_measure('get_box_dir')
523    return (dir==0 and default or dir), c
524 end
525
526 do
527    local getbox = tex.getbox
528    local dir_backup
529    function luatexja.direction.unbox_check_dir(is_copy)
530       start_time_measure('box_primitive_hook')
531       local list_dir = get_dir_count()%dir_math_mod
532       local b = getbox(tex_getcount('ltj@tempcnta'))
533       if b and getlist(to_direct(b)) then
534          local box_dir = get_box_dir(to_direct(b), dir_yoko)
535          if box_dir%dir_math_mod ~= list_dir then
536             ltjb.package_error(
537                'luatexja',
538                "Incompatible direction list can't be unboxed",
539                'I refuse to unbox a box in differrent direction.')
540             tex.sprint(cat_lp, '\\@gobbletwo')
541          else
542             dir_backup = nil
543             local bd = to_direct(b)
544             local hd = getlist(bd)
545             local nh = hd
546             while hd do
547                if getid(hd)==id_whatsit and getsubtype(hd)==sid_user
548                   and getfield(hd, 'user_id')==DIR then
549                      local d = hd
550                      nh, hd = node_remove(nh, hd)
551                      if is_copy and (not dir_backup) then
552                         dir_backup = d
553                         setfield(dir_backup, 'next', nil)
554                      else
555                         node_free(d)
556                      end
557                else
558                   hd = node_next(hd)
559                end
560             end
561             setfield(bd, 'head', nh)
562          end
563       end
564       if luatexja.global_temp and tex.globaldefs~=luatexja.global_temp then
565          tex.globaldefs = luatexja.global_temp
566       end
567       stop_time_measure('box_primitive_hook')
568    end
569    function luatexja.direction.uncopy_restore_whatsit()
570       local b = getbox(tex_getcount('ltj@tempcnta'))
571       if b then
572          local bd = to_direct(b)
573          if dir_backup then
574             setfield(dir_backup, 'next', getlist(bd))
575             setfield(bd, 'head', dir_backup)
576             dir_backup = nil
577          end
578       end
579    end
580 end
581
582 -- dir_node に包まれている「本来の中身」を取り出し,
583 -- dir_node を全部消去
584 local function unwrap_dir_node(b, head, box_dir)
585    -- b: dir_node, head: the head of list, box_dir:
586    -- return values are (new head), (next of b), (contents), (dir of contents)
587    local bh = getlist(b)
588    local nh, nb
589    if head then
590       nh = insert_before(head, b, bh)
591       nh, nb = node_remove(nh, b)
592       setfield(b, 'next', nil)
593       node_free(b)
594    end
595    local shift_old, b_dir, wh = nil, get_box_dir(bh, 0)
596    if wh then
597       node.direct.flush_list(getfield(wh, 'value'))
598       setfield(wh, 'value', nil)
599    end
600    return nh, nb, bh, b_dir
601 end
602
603 -- is_manual: 寸法変更に伴うものか?
604 local function create_dir_node(b, b_dir, new_dir, is_manual)
605    local info = dir_node_aux[b_dir%dir_math_mod][new_dir%dir_math_mod]
606    local w = getfield(b, 'width')
607    local h = getfield(b, 'height')
608    local d = getfield(b, 'depth')
609    local db = node_new(getid(b)) -- dir_node
610    set_attr(db, attr_dir,
611             new_dir + (is_manual and dir_node_manual or dir_node_auto))
612    set_attr(db, attr_icflag, PROCESSED)
613    set_attr(b, attr_icflag, PROCESSED)
614    ensure_tex_attr(attr_dir, 0)
615    ensure_tex_attr(attr_icflag, 0)
616    setfield(db, 'dir', getfield(b, 'dir'))
617    setfield(db, 'shift', 0)
618    setfield(db, 'width',  info.width(w,h,d))
619    setfield(db, 'height', info.height(w,h,d))
620    setfield(db, 'depth',  info.depth(w,h,d))
621    return db
622 end
623
624 -- 異方向のボックスの処理
625 local make_dir_whatsit, process_dir_node
626 do
627    make_dir_whatsit = function (head, b, new_dir, origin)
628       new_dir = new_dir%dir_math_mod
629       -- head: list head, b: box
630       -- origin: コール元 (for debug)
631       -- return value: (new head), (next of b), (new b), (is_b_dir_node)
632       -- (new b): b か dir_node に被せられた b
633       local bh = getlist(b)
634       local box_dir, dn =  get_box_dir(b, ltjs.list_dir)
635       -- 既に b の中身にあるwhatsit
636
637       if box_dir%dir_math_mod==new_dir then
638          if box_dir>=dir_node_auto then
639             -- dir_node としてカプセル化されている
640             local _, dnc = get_box_dir(b, 0)
641             if dnc then -- free all other dir_node
642                node.direct.flush_list(getfield(dnc, 'value'))
643                setfield(dnc, 'value', nil)
644             end
645             set_attr(b, attr_dir, box_dir%dir_math_mod + dir_node_auto)
646             return head, node_next(b), b, true
647          else
648             -- 組方向が一緒 (up to math dir) のボックスなので,何もしなくて良い
649             return head, node_next(b), b, false
650          end
651       else
652          -- 組方向を合わせる必要あり
653          local nh, nb, ret, flag
654          if box_dir>= dir_node_auto then -- unwrap
655             local b_dir
656             head, nb, b, b_dir = unwrap_dir_node(b, head, box_dir)
657             bh = getlist(b)
658             if b_dir%dir_math_mod==new_dir then
659                -- dir_node の中身が周囲の組方向とあっている
660                return head, nb, b, false
661             else box_dir = b_dir end
662          end
663          box_dir = box_dir%dir_math_mod
664          local db
665          local dnh = getfield(dn, 'value')
666          for x in traverse(dnh) do
667             if has_attr(x, attr_dir)%dir_math_mod == new_dir then
668                setfield(dn, 'value', to_node(node_remove(dnh, x)))
669                db=x; break
670             end
671          end
672          node.direct.flush_list(getfield(dn, 'value'))
673          setfield(dn, 'value', nil)
674          db = db or create_dir_node(b, box_dir, new_dir, false)
675          local w = getfield(b, 'width')
676          local h = getfield(b, 'height')
677          local d = getfield(b, 'depth')
678          local dn_w = getfield(db, 'width')
679          local dn_h = getfield(db, 'height')
680          local dn_d = getfield(db, 'depth')
681          nh, nb =  insert_before(head, b, db), nil
682          nh, nb = node_remove(nh, b)
683          setfield(b, 'next', nil); setfield(db, 'head', b)
684          ret, flag = db, true
685          return nh, nb, ret, flag
686       end
687    end
688    process_dir_node = function (hd, gc)
689       local x, new_dir = hd, ltjs.list_dir or dir_yoko
690       while x do
691          local xid = getid(x)
692          if (xid==id_hlist and get_attr_icflag(x)~=PACKED)
693          or xid==id_vlist then
694             hd, x = make_dir_whatsit(hd, x, new_dir, 'process_dir_node:' .. gc)
695          else
696             x = node_next(x)
697          end
698       end
699       return hd
700    end
701
702    -- lastbox
703    local node_prev = (node.direct~=node) and node.direct.getprev or node.prev
704    local id_glue = node.id('glue')
705    local function lastbox_hook()
706       start_time_measure('box_primitive_hook')
707       local bn = tex_nest[tex_nest.ptr].tail
708       if bn then
709          local b, head = to_direct(bn), to_direct(tex_nest[tex_nest.ptr].head)
710          local bid = getid(b)
711          if bid==id_hlist or bid==id_vlist then
712             local p = getlist(b)
713             -- alignment の各行の中身が入ったボックス
714             if p and getid(p)==id_glue and getsubtype(p)==12 then -- tabskip
715                local np = node_next(p); local npid = getid(np)
716                if npid==id_hlist or npid==id_vlist then
717                   setfield(b, 'head', create_dir_whatsit(p, 'align', get_box_dir(np, 0)))
718                end
719             end
720             local box_dir =  get_box_dir(b, 0)
721             if box_dir>= dir_node_auto then -- unwrap dir_node
722                local p = node_prev(b)
723                local dummy1, dummy2, nb = unwrap_dir_node(b, nil, box_dir)
724                setfield(p, 'next', nb);  tex_nest[tex_nest.ptr].tail = to_node(nb)
725                setfield(b, 'next', nil); setfield(b, 'head', nil)
726                node_free(b); b = nb
727             end
728             local _, wh =  get_box_dir(b, 0) -- clean dir_node attached to the box
729             if wh then
730                node.direct.flush_list(getfield('value', wh))
731                setfield(wh, 'value', nil)
732             end
733          end
734       end
735       stop_time_measure('box_primitive_hook')
736    end
737
738    luatexja.direction.make_dir_whatsit = make_dir_whatsit
739    luatexja.direction.lastbox_hook = lastbox_hook
740 end
741
742 -- \wd, \ht, \dp の代わり
743 do
744    local getbox, setdimen = tex.getbox, tex.setdimen
745    local function get_box_dim_common(key, s, l_dir)
746       -- s: not dir_node.
747       local s_dir, wh = get_box_dir(s, dir_yoko)
748       s_dir = s_dir%dir_math_mod
749       if s_dir ~= l_dir then
750          local not_found = true
751          for x in traverse(getfield(wh, 'value')) do
752             if l_dir == has_attr(x, attr_dir)%dir_node_auto then
753                setdimen('ltj@tempdima', getfield(x, key))
754                not_found = false; break
755             end
756          end
757          if not_found then
758             local w = getfield(s, 'width')
759             local h = getfield(s, 'height')
760             local d = getfield(s, 'depth')
761             setdimen('ltj@tempdima',
762                          dir_node_aux[s_dir][l_dir][key](w,h,d))
763          end
764       else
765          setdimen('ltj@tempdima', getfield(s, key))
766       end
767    end
768    local function get_box_dim(key, n)
769       local gt = tex.globaldefs; tex.globaldefs = 0
770       local s = getbox(n)
771       if s then
772          local l_dir = (get_dir_count())%dir_math_mod
773          s = to_direct(s)
774          local b_dir = get_box_dir(s,dir_yoko)
775          if b_dir<dir_node_auto then
776             get_box_dim_common(key, s, l_dir)
777          elseif b_dir%dir_math_mod==l_dir then
778             setdimen('ltj@tempdima', getfield(s, key))
779          else
780             get_box_dim_common(key, getlist(s), l_dir)
781          end
782       else
783          setdimen('ltj@tempdima', 0)
784       end
785       tex.sprint(cat_lp, '\\ltj@tempdima')
786       tex.globaldefs = gt
787    end
788    luatexja.direction.get_box_dim = get_box_dim
789
790    -- return value: (changed dimen of box itself?)
791    local scan_dimen, scan_int = token.scan_dimen, token.scan_int
792    local scan_keyword = token.scan_keyword
793    local function set_box_dim_common(key, s, l_dir)
794       local s_dir, wh = get_box_dir(s, dir_yoko)
795       s_dir = s_dir%dir_math_mod
796       if s_dir ~= l_dir then
797          if not wh then
798             wh = create_dir_whatsit(getlist(s), 'set_box_dim', s_dir)
799             setfield(s, 'head', wh)
800          end
801          local db
802          local dnh = getfield(wh, 'value')
803          for x in traverse(dnh) do
804             if has_attr(x, attr_dir)%dir_node_auto==l_dir then
805                db = x; break
806             end
807          end
808          if not db then
809             db = create_dir_node(s, s_dir, l_dir, true)
810             setfield(db, 'next', dnh)
811             setfield(wh, 'value',to_node(db))
812          end
813          setfield(db, key, scan_dimen())
814          return false
815       else
816          setfield(s, key, scan_dimen())
817          if wh then
818             -- change dimension of dir_nodes which are created "automatically"
819                local bw, bh, bd
820                   = getfield(s,'width'), getfield(s, 'height'), getfield(s, 'depth')
821             for x in traverse(getfield(wh, 'value')) do
822                local x_dir = has_attr(x, attr_dir)
823                if x_dir<dir_node_manual then
824                   local info = dir_node_aux[s_dir][x_dir%dir_node_auto]
825                   setfield(x, 'width',  info.width(bw,bh,bd))
826                   setfield(x, 'height', info.height(bw,bh,bd))
827                   setfield(x, 'depth',  info.depth(bw,bh,bd))
828                end
829             end
830          end
831          return true
832       end
833    end
834    local function set_box_dim(key)
835       local s = getbox(scan_int()); scan_keyword('=')
836       if s then
837          local l_dir = (get_dir_count())%dir_math_mod
838          s = to_direct(s)
839          local b_dir = get_box_dir(s,dir_yoko)
840          if b_dir<dir_node_auto then
841             set_box_dim_common(key, s, l_dir)
842          elseif b_dir%dir_math_mod == l_dir then
843             -- s is dir_node
844             setfield(s, key, scan_dimen())
845             if b_dir<dir_node_manual then
846                set_attr(s, attr_dir, b_dir%dir_node_auto + dir_node_manual)
847             end
848          else
849             local sid, b = getid(s), getlist(s)
850             local info = dir_node_aux[get_box_dir(b,dir_yoko)%dir_math_mod][b_dir%dir_node_auto]
851             local bw, bh, bd
852                = getfield(b,'width'), getfield(b, 'height'), getfield(b, 'depth')
853             local sw, sh, sd
854                = getfield(s,'width'), getfield(s, 'height'), getfield(s, 'depth')
855             if set_box_dim_common(key, b, l_dir) and b_dir<dir_node_manual then
856                -- re-calculate dimension of s, if s is created "automatically"
857                if b_dir<dir_node_manual then
858                   setfield(s, 'width',  info.width(bw,bh,bd))
859                   setfield(s, 'height', info.height(bw,bh,bd))
860                   setfield(s, 'depth',  info.depth(bw,bh,bd))
861                end
862             end
863          end
864       end
865    end
866    luatexja.direction.set_box_dim = set_box_dim
867 end
868
869 do
870    local getbox = tex.getbox
871    local function get_register_dir(n)
872       local s = getbox(n)
873       if s then
874          s = to_direct(s)
875          local b_dir = get_box_dir(s, dir_yoko)
876          if b_dir<dir_node_auto then
877             return b_dir
878          else
879             local b_dir = get_box_dir(
880                node_next(node_next(node_next(getlist(s)))), dir_yoko)
881             return b_dir
882          end
883       else
884          return 0
885       end
886    end
887    luatexja.direction.get_register_dir = get_register_dir
888 end
889
890 do
891    local getbox, setbox, copy_list = tex.getbox, tex.setbox, node.direct.copy_list
892    -- raise, lower
893    function luatexja.direction.raise_box()
894       start_time_measure('box_primitive_hook')
895       local list_dir = get_dir_count()
896       local s = getbox('ltj@afbox')
897       if s then
898          local sd = to_direct(s)
899          local box_dir = get_box_dir(sd, dir_yoko)
900          if box_dir%dir_math_mod ~= list_dir then
901             setbox(
902                'ltj@afbox',
903                to_node(copy_list(make_dir_whatsit(sd, sd, list_dir, 'box_move')))
904                -- copy_list しないとリストの整合性が崩れる……?
905             )
906          end
907       end
908       stop_time_measure('box_primitive_hook')
909    end
910 end
911
912 -- PACKED の hbox から文字を取り出す
913 -- luatexja.jfmglue.check_box などで使用
914 do
915    local function glyph_from_packed(h)
916       local b = getlist(h)
917       return (getid(b)==id_kern or (getid(b)==id_whatsit and getsubtype(b)==sid_save) )
918          and node_next(node_next(node_next(b))) or b
919    end
920    luatexja.direction.glyph_from_packed = glyph_from_packed
921 end
922
923 -- adjust
924 do
925    local id_adjust = node.id('adjust')
926    function luatexja.direction.check_adjust_direction()
927       start_time_measure('box_primitive_hook')
928       local list_dir = get_adjust_dir_count()
929       local a = tex_nest[tex_nest.ptr].tail
930       local ad = to_direct(a)
931       if a and getid(ad)==id_adjust then
932          local adj_dir = get_box_dir(ad)
933          if list_dir~=adj_dir then
934             ltjb.package_error(
935                'luatexja',
936                'Direction Incompatible',
937                "\\vadjust's argument and outer vlist must have same direction.")
938             node.direct.last_node()
939          end
940       end
941       stop_time_measure('box_primitive_hook')
942    end
943 end
944
945 -- insert
946 do
947    local id_ins = node.id('ins')
948    local id_rule = node.id('rule')
949    function luatexja.direction.populate_insertion_dir_whatsit()
950       start_time_measure('box_primitive_hook')
951       local list_dir = get_dir_count()
952       local a = tex_nest[tex_nest.ptr].tail
953       local ad = to_direct(a)
954       if (not a) or getid(ad)~=id_ins then
955           a = node.tail(tex.lists.page_head); ad = to_direct(a)
956       end
957       if a and getid(ad)==id_ins then
958          local h = getfield(ad, 'head')
959          if getid(h)==id_whatsit and
960             getsubtype(h)==sid_user and getfield(h, 'user_id')==DIR then
961                local n = h; h = node_remove(h,h)
962                node_free(n)
963          end
964          for box_rule in traverse(h) do
965             if getid(box_rule)<id_rule then
966                h = insert_before(h, box_rule, dir_pool[list_dir]())
967             end
968          end
969          ensure_tex_attr(attr_dir, 0)
970          setfield(ad, 'head', h)
971       end
972       stop_time_measure('box_primitive_hook')
973    end
974 end
975
976 -- vsplit
977 do
978    local split_dir_whatsit, split_dir_head
979    local cat_lp = luatexbase.catcodetables['latex-package']
980    local sprint, scan_int, tex_getbox = tex.sprint, token.scan_int, tex.getbox
981    function luatexja.direction.vsplit()
982       local n = scan_int();
983       local p = to_direct(tex_getbox(n))
984       split_dir_head = nil
985       if p then
986          local bh = getlist(p)
987          if getid(bh)==id_whatsit and getsubtype(bh)==sid_user and getfield(bh, 'user_id')==DIR 
988             and node_next(bh) then
989             ltjs.list_dir = has_attr(bh, attr_dir)
990             local q = node_next(p)
991             setfield(p, 'head', node_remove(bh,bh,bh))
992             split_dir_head = bh
993          end
994       end
995       sprint(cat_lp, '\\ltj@@orig@vsplit' .. tostring(n))
996    end  
997    local function dir_adjust_vpack(h, gc)
998       start_time_measure('direction_vpack')
999       local hd = to_direct(h)
1000       if gc=='split_keep' then
1001          -- supply dir_whatsit
1002          hd = create_dir_whatsit_vbox(hd, gc)
1003          split_dir_whatsit = hd
1004       elseif gc=='split_off'  then
1005          if split_dir_head then
1006             list_dir = has_attr(split_dir_head, attr_dir)
1007             hd = insert_before(hd, hd, split_dir_head)
1008             split_dir_head=nil
1009          end
1010          if split_dir_whatsit then
1011             -- adjust direction of 'split_keep'
1012             set_attr(split_dir_whatsit, attr_dir, ltjs.list_dir)
1013          end
1014          split_dir_whatsit=nil
1015       elseif gc=='preamble' then
1016          split_dir_whatsit=nil
1017       else
1018          adjust_badness(hd)
1019          -- hd = process_dir_node(create_dir_whatsit_vbox(hd, gc), gc)
1020          -- done in append_to_vpack callback
1021          hd = create_dir_whatsit_vbox(hd, gc)
1022          split_dir_whatsit=nil
1023       end
1024       stop_time_measure('direction_vpack')
1025       return to_node(hd)
1026    end
1027    ltjb.add_to_callback('vpack_filter',
1028                               dir_adjust_vpack,
1029                               'ltj.direction', 10000)
1030 end
1031
1032 do
1033    -- supply direction whatsit to the main vertical list "of the next page"
1034    local function dir_adjust_pre_output(h, gc)
1035       return to_node(create_dir_whatsit_vbox(to_direct(h), gc))
1036    end
1037    ltjb.add_to_callback('pre_output_filter',
1038                               dir_adjust_pre_output,
1039                               'ltj.direction', 10000)
1040
1041    function luatexja.direction.remove_end_whatsit()
1042       local h=tex.lists.page_head
1043       if h and (not h.next) and
1044          h.id==id_whatsit and h.subtype==sid_user and
1045          h.user_id == DIR then
1046             tex.lists.page_head = nil
1047             node.free(h)
1048       end
1049    end
1050 end
1051
1052 -- append_to_vlist filter
1053 do
1054    local id_glue = node.id('glue')
1055    local getglue = node.direct.getglue or
1056       function(g)
1057          return getfield(g,'width'), getfield(g,'stretch'), getfield(g,'shrink'),
1058          getfield(g,'stretch_order'), getfield(g,'shrink_order')
1059       end
1060 local setglue = luatexja.setglue
1061    local function copy_glue (new_glue, old_glue, subtype, new_w)
1062       setfield(new_glue, 'subtype', subtype)
1063       local w,st,sp,sto,spo = getglue(to_direct(old_glue))
1064       setglue(new_glue, new_w or w, st, sp, sto, spo)
1065    end
1066    local node_write = node.direct.write
1067    local function dir_adjust_append_vlist(b, loc, prev, mirrored)
1068       local old_b = to_direct(b)
1069       local new_b = loc=='box' and 
1070          make_dir_whatsit(old_b, old_b, get_dir_count(), 'append_vlist') or old_b
1071       
1072       if prev > -65536000 then
1073          local d = tex.baselineskip.width - prev 
1074             - getfield(new_b, mirrored and 'depth' or 'height')
1075          local g = node_new(id_glue)
1076          if d < tex.lineskiplimit then
1077             copy_glue(g, tex.lineskip, 1)
1078          else
1079             copy_glue(g, tex.baselineskip, 2, d);
1080          end
1081          node_write(g)
1082       end
1083       node_write(new_b)
1084       tex.prevdepth = getfield(new_b, mirrored and 'height' or 'depth')
1085       return nil -- do nothing on tex side
1086    end
1087    ltjb.add_to_callback('append_to_vlist_filter',
1088                         dir_adjust_append_vlist,
1089                         'ltj.direction', 10000)
1090 end
1091
1092 -- finalize (executed just before \shipout)
1093 -- we supply correct pdfsavematrix nodes etc. inside dir_node
1094 do
1095    local finalize_inner
1096    local function finalize_dir_node(db,new_dir)
1097       local b = getlist(db)
1098       finalize_inner(b)
1099       local w = getfield(b, 'width')
1100       local h = getfield(b, 'height')
1101       local d = getfield(b, 'depth')
1102       local dn_w = getfield(db, 'width')
1103       local dn_h = getfield(db, 'height')
1104       local dn_d = getfield(db, 'depth')
1105       local db_head, db_tail
1106       for _,v in ipairs(dir_node_aux
1107                         [get_box_dir(b, dir_yoko)%dir_math_mod][new_dir][getid(b)]) do
1108          local cmd, arg, nn = v[1], v[2]
1109          if cmd=='kern' then
1110             nn = node_new(id_kern, 1)
1111             setfield(nn, 'kern', arg(w, h, d, dn_w, dn_h, dn_d))
1112          elseif cmd=='whatsit' then
1113             nn = node_new(id_whatsit, arg)
1114          elseif cmd=='rotate' then
1115             nn = node_new(id_whatsit, sid_matrix)
1116             setfield(nn, 'data', arg)
1117          elseif cmd=='box' then
1118             nn = b; setfield(b, 'next', nil)
1119             setfield(nn, 'shift', arg(w, h, d, dn_w, dn_h, dn_d))
1120          end
1121          if db_head then
1122             insert_after(db_head, db_tail, nn)
1123             db_tail = nn
1124          else
1125             setfield(db, 'head', nn)
1126             db_head, db_tail = nn, nn
1127          end
1128       end
1129    end
1130
1131    tex.setattribute(attr_dir, dir_yoko)
1132    local shipout_temp =  node_new(id_hlist)
1133    tex.setattribute(attr_dir, 0)
1134
1135    finalize_inner = function (box)
1136       for n in traverse(getlist(box)) do
1137          local nid = getid(n)
1138          if (nid==id_hlist or nid==id_vlist) then
1139             local ndir = get_box_dir(n, dir_yoko)
1140             if ndir>=dir_node_auto then -- n is dir_node
1141                finalize_dir_node(n, ndir%dir_math_mod)
1142             else
1143                finalize_inner(n)
1144             end
1145          end
1146       end
1147    end
1148    local getbox = tex.getbox
1149    local setbox, copy = node.direct.setbox, node.direct.copy
1150    local lua_mem_kb = 0
1151    function luatexja.direction.finalize()
1152       local a = to_direct(tex.getbox("AtBeginShipoutBox"))
1153       local a_dir = get_box_dir(a, dir_yoko)
1154       if a_dir~=dir_yoko then
1155          local b = create_dir_node(a, a_dir, dir_yoko, false)
1156          setfield(b, 'head', a); a = b
1157       end
1158       setfield(shipout_temp, 'head', a)
1159       finalize_inner(shipout_temp)
1160       setbox('global', "AtBeginShipoutBox", copy(getlist(shipout_temp)))
1161       setfield(shipout_temp, 'head',nil)
1162       -- garbage collect
1163       --local m = collectgarbage('count')
1164       --if m>lua_mem_kb+20480 then
1165       --   collectgarbage(); lua_mem_kb = collectgarbage('count')
1166       --end
1167       --print('Lua Memory Usage', lua_mem_kb)
1168    end
1169 end