-- -- src/ltj-direction.lua -- luatexja.load_module 'base'; local ltjb = luatexja.base luatexja.load_module 'stack'; local ltjs = luatexja.stack luatexja.direction = {} local attr_dir = luatexbase.attributes['ltj@dir'] local attr_icflag = luatexbase.attributes['ltj@icflag'] local dnode = node.direct local cat_lp = luatexbase.catcodetables['latex-package'] local to_node = dnode.tonode local to_direct = dnode.todirect local get_attr = dnode.get_attribute local set_attr = dnode.set_attribute local insert_before = dnode.insert_before local insert_after = dnode.insert_after local getid = dnode.getid local getsubtype = dnode.getsubtype local getlist = dnode.getlist local getfield = dnode.getfield local getwhd = dnode.getwhd local getvalue = node.direct.getdata local setfield = dnode.setfield local setwhd = dnode.setwhd local setnext = dnode.setnext local setlist = dnode.setlist local setvalue = node.direct.setdata local node_new = dnode.new local node_free = dnode.flush_node or dnode.free local node_remove = dnode.remove local node_next = dnode.getnext local traverse = dnode.traverse local traverse_id = dnode.traverse_id local start_time_measure, stop_time_measure = ltjb.start_time_measure, ltjb.stop_time_measure local abs = math.abs local id_kern = node.id 'kern' local id_hlist = node.id 'hlist' local id_vlist = node.id 'vlist' local id_whatsit = node.id 'whatsit' local sid_save = node.subtype 'pdf_save' local sid_user = node.subtype 'user_defined' local getnest = tex.getnest local tex_nest = tex.nest local getcount = tex.getcount local ensure_tex_attr = ltjb.ensure_tex_attr local PROCESSED = luatexja.icflag_table.PROCESSED local PROCESSED_BEGIN_FLAG = luatexja.icflag_table.PROCESSED_BEGIN_FLAG local PACKED = luatexja.icflag_table.PACKED local DIR = luatexja.userid_table.DIR local dir_tate = luatexja.dir_table.dir_tate local dir_yoko = luatexja.dir_table.dir_yoko local dir_dtou = luatexja.dir_table.dir_dtou local dir_utod = luatexja.dir_table.dir_utod local dir_math_mod = luatexja.dir_table.dir_math_mod local dir_node_auto = luatexja.dir_table.dir_node_auto local dir_node_manual = luatexja.dir_table.dir_node_manual local function get_attr_icflag(p) return (get_attr(p, attr_icflag) or 0) % PROCESSED_BEGIN_FLAG end local page_direction -- local dir_pool do local node_copy = dnode.copy dir_pool = {} for _,i in pairs({dir_tate, dir_yoko, dir_dtou, dir_utod}) do local w = node_new(id_whatsit, sid_user) dnode.setattributelist(w, nil) set_attr(w, attr_dir, i); set_attr(w, attr_icflag, 0) setfield(w, 'user_id', DIR) setfield(w, 'type', 110); setnext(w, nil) dir_pool[i] = function () return node_copy(w) end end end -- local function adjust_badness(hd) if not node_next(hd) and getid(hd)==id_whatsit and getsubtype(hd)==sid_user and getfield(hd, 'user_id')==DIR then -- avoid double whatsit luatexja.global_temp=tex.globaldefs; tex.globaldefs=0 luatexja.hbadness_temp=tex.hbadness; tex.hbadness=10000 luatexja.vbadness_temp=tex.vbadness; tex.vbadness=10000 else luatexja.global_temp = nil luatexja.hbadness_temp=nil luatexja.vbadness_temp=nil end end local get_dir_count, get_adjust_dir_count do local node_attr = node.get_attribute local function get_dir_count_inner(h) if h then if h.id==id_whatsit and h.subtype==sid_user and h.user_id==DIR then return ((node_attr(h, attr_icflag) or 0)=1 and abs(getnest(lv-1).mode) == ltjs.mmode and v == dir_tate then v = dir_utod end elseif v=='adj' then v,name = get_adjust_dir_count(), nil end local current_nest = getnest() if tex.currentgrouptype==6 then ltjb.package_error( 'luatexja', "You can't use `\\" .. name .. "' in an align", "To change the direction in an align, \n" .. "you shold use \\hbox or \\vbox.") elseif current_nest.mode == ltjs.hmode or abs(current_nest.mode) == ltjs.mmode then ltjb.package_error( 'luatexja', "Improper `\\" .. name .. "'", 'You cannot change the direction in unrestricted horizontal mode \n' .. 'nor math modes.') else local h = (lv==0) and tex.lists.page_head or current_nest.head.next local flag,w = test_list(h,lv) if flag==0 then if lv==0 and not page_direction then page_direction = v -- for first call of \yoko (in luatexja-core.sty) else if luatexja.debug then luatexja.ext_show_node_list(dnode.tonode(h),'>> ', texio.write_nl) end ltjb.package_error( 'luatexja', "Use `\\" .. tostring(name) .. "' at top of list", 'Direction change command by LuaTeX-ja is available\n' .. 'only when the current list is null.') end elseif flag==1 then node_set_attr(w, attr_dir, v) if lv==0 then page_direction = v end elseif lv==0 then page_direction = v else -- flag == 2: need to create dir whatsit. local h = current_nest.head local hn = node.next(h) hn = (hn and hn.id==id_local) and hn or h local w = to_node(dir_pool[v]()) insert_after_node(h,hn,w) current_nest.tail = node_tail_node(w) end ensure_tex_attr(attr_icflag, 0) end ensure_tex_attr(attr_dir, 0) end luatexja.direction.set_list_direction = set_list_direction end -- ボックスに dir whatsit を追加 local function create_dir_whatsit(hd, gc, new_dir) if getid(hd)==id_whatsit and getsubtype(hd)==sid_user and getfield(hd, 'user_id')==DIR then set_attr(hd, attr_icflag, get_attr_icflag(hd) + PROCESSED_BEGIN_FLAG) local n =node_next(hd) if n then set_attr(n, attr_icflag, get_attr_icflag(n) + PROCESSED_BEGIN_FLAG) end ensure_tex_attr(attr_icflag, 0) return hd else local w = dir_pool[new_dir]() set_attr(w, attr_icflag, PROCESSED_BEGIN_FLAG) set_attr(hd, attr_icflag, get_attr_icflag(hd) + PROCESSED_BEGIN_FLAG) ensure_tex_attr(attr_icflag, 0) ensure_tex_attr(attr_dir, 0) return insert_before(hd, hd, w) end end -- hpack_filter, vpack_filter, post_line_break_filter -- の結果を組方向を明示するため,先頭に dir_node を設置 local get_box_dir do local function create_dir_whatsit_hpack(h, gc) local hd = to_direct(h) if gc=='fin_row' then if hd then for p in traverse_id(15, hd) do -- unset if get_box_dir(p, 0)==0 then setfield(p, 'head', create_dir_whatsit(getfield(p, 'head'), 'fin_row', ltjs.list_dir)) -- We cannot use setlist and getlist, since they don't support unset_node end end set_attr(hd, attr_icflag, PROCESSED_BEGIN_FLAG) ensure_tex_attr(attr_icflag, 0) end return h elseif gc == 'preamble' then else adjust_badness(hd) return to_node(create_dir_whatsit(hd, gc, ltjs.list_dir)) end end ltjb.add_to_callback('hpack_filter', create_dir_whatsit_hpack, 'ltj.create_dir_whatsit', 10000) end do local function create_dir_whatsit_parbox(h, gc) stop_time_measure 'tex_linebreak'; -- start 側は ltj-debug.lua に local new_dir = ltjs.list_dir for line in traverse_id(id_hlist, to_direct(h)) do setlist(line, create_dir_whatsit(getlist(line), gc, new_dir) ) end ensure_tex_attr(attr_dir, 0) return h end ltjb.add_to_callback('post_linebreak_filter', create_dir_whatsit_parbox, 'ltj.create_dir_whatsit', 10000) end local create_dir_whatsit_vbox do local wh = {} local id_glue = node.id 'glue' create_dir_whatsit_vbox = function (hd, gc) ltjs.list_dir = get_dir_count() -- remove dir whatsit for x in traverse_id(id_whatsit, hd) do if getsubtype(x)==sid_user and getfield(x, 'user_id')==DIR then wh[#wh+1]=x end end if hd==wh[1] then ltjs.list_dir = get_attr(hd, attr_dir) local x = node_next(hd) while x and getid(x)==id_glue and getsubtype(x)==3 do node_remove(hd,x); node_free(x); x = node_next(hd) end --if gc~='vtop' then -- if #wh==1 then wh[1]=nil else wh[#wh], wh[1]=nil, wh[#wh] end --end end for i=1,#wh do hd = node_remove(hd, wh[i]); node_free(wh[i]); wh[i] = nil end if gc=='fin_row' then -- gc == 'preamble' case is treated in dir_adjust_vpack if hd then set_attr(hd, attr_icflag, PROCESSED_BEGIN_FLAG) ensure_tex_attr(attr_icflag, 0) end return hd elseif gc=='vtop' then local n = node_next(hd) local w = create_dir_whatsit(hd, gc, ltjs.list_dir) -- move dir whatsit after hd setnext(hd, w); setnext(w, n) return hd else return create_dir_whatsit(hd, gc, ltjs.list_dir) end end end -- dir_node に包む方法を書いたテーブル local dir_node_aux do local setkern = dnode.setkern local setshift = dnode.setshift local sid_restore= node.subtype 'pdf_restore' local sid_matrix = node.subtype 'pdf_setmatrix' local floor = math.floor local get_h =function (w,h,d) return h end local get_d =function (w,h,d) return d end local get_h_d =function (w,h,d) return h+d end local get_h_d_neg =function (w,h,d) return -h-d end local get_d_neg =function (w,h,d) return -d end local get_w_half =function (w,h,d) return floor(0.5*w) end local get_w_half_rem =function (w,h,d) return w-floor(0.5*w) end local get_w_neg =function (w,h,d) return -w end local get_w =function (w,h,d) return w end local zero = function() return 0 end local function gen_kern(arg, b, w,h,d,dw,dh,dd) local nn = node_new(id_kern) setkern(nn, arg(w, h, d, dw, dh, dd)); return nn end local function gen_whatsit(arg) return node_new(id_whatsit, arg) end local function gen_rotate(arg) local nn = node_new(id_whatsit, sid_matrix) setfield(nn, 'data', arg); return nn end local function gen_box(arg, b, w,h,d,dw,dh,dd) local nn = b; setnext(b, nil) setshift(nn, arg(w, h, d, dw, dh, dd)); return nn end dir_node_aux = { [dir_yoko] = { -- yoko を [dir_tate] = { -- tate 中で組む width = get_h_d, height = get_w_half, depth = get_w_half_rem, [id_hlist] = { { gen_whatsit, sid_save }, { gen_rotate, '0 1 -1 0' }, { gen_kern, function(w,h,d,nw,nh,nd) return -nd end }, { gen_box , get_h}, { gen_kern, function(w,h,d,nw,nh,nd) return nd-w end }, { gen_whatsit, sid_restore }, }, [id_vlist] = { { gen_whatsit, sid_save }, { gen_rotate, '0 1 -1 0' }, { gen_kern , zero }, { gen_box , function(w,h,d,nw,nh,nd) return -nh-nd end }, { gen_kern, get_h_d_neg}, { gen_whatsit, sid_restore }, }, }, [dir_dtou] = { -- dtou 中で組む width = get_h_d, height = get_w, depth = zero, [id_hlist] = { { gen_whatsit, sid_save }, { gen_rotate, '0 -1 1 0' }, { gen_kern, function(w,h,d,nw,nh,nd) return -nh end }, { gen_box, get_d_neg }, { gen_kern, function(w,h,d,nw,nh,nd) return nh-w end }, { gen_whatsit, sid_restore }, }, [id_vlist] = { { gen_whatsit, sid_save }, { gen_rotate, '0 -1 1 0' }, { gen_kern, get_h_d_neg }, { gen_box, zero }, { gen_whatsit, sid_restore }, }, }, }, [dir_tate] = { -- tate を [dir_yoko] = { -- yoko 中で組む width = get_h_d, height = get_w, depth = zero, [id_hlist] = { { gen_whatsit, sid_save }, { gen_rotate, '0 -1 1 0' }, { gen_kern, function (w,h,d,nw,nh,nd) return -nh end }, { gen_box , get_d_neg }, { gen_kern, function (w,h,d,nw,nh,nd) return nh-w end }, { gen_whatsit, sid_restore }, }, [id_vlist] = { { gen_whatsit, sid_save }, { gen_rotate, '0 -1 1 0' }, { gen_kern, get_h_d_neg }, { gen_box, zero }, { gen_whatsit, sid_restore }, }, }, [dir_dtou] = { -- dtou 中で組む width = get_w, height = get_d, depth = get_h, [id_hlist] = { { gen_whatsit, sid_save }, { gen_rotate, '-1 0 0 -1' }, { gen_kern, get_w_neg }, { gen_box, function (w,h,d,nw,nh,nd) return h-nd end }, { gen_whatsit, sid_restore }, }, [id_vlist] = { { gen_whatsit, sid_save }, { gen_rotate, '-1 0 0 -1' }, { gen_kern, get_h_d_neg }, { gen_box, get_w_neg }, { gen_whatsit, sid_restore }, }, }, }, [dir_dtou] = { -- dtou を [dir_yoko] = { -- yoko 中で組む width = get_h_d, height = get_w, depth = zero, [id_hlist] = { { gen_whatsit, sid_save }, { gen_rotate, '0 1 -1 0' }, { gen_kern, function (w,h,d,nw,nh,nd) return -nd end }, { gen_box, get_h }, { gen_kern, function (w,h,d,nw,nh,nd) return nd-w end }, { gen_whatsit, sid_restore }, }, [id_vlist] = { { gen_kern, zero }, { gen_whatsit, sid_save }, { gen_rotate, '0 1 -1 0' }, { gen_box, function (w,h,d,nw,nh,nd) return -nd-nh end }, { gen_kern, get_h_d_neg }, { gen_whatsit, sid_restore }, }, }, [dir_tate] = { -- tate 中で組む width = get_w, height = get_d, depth = get_h, [id_hlist] = { { gen_whatsit, sid_save }, { gen_rotate, '-1 0 0 -1' }, { gen_kern, get_w_neg }, { gen_box, function (w,h,d,nw,nh,nd) return h-nd end }, { gen_whatsit, sid_restore }, }, [id_vlist] = { { gen_whatsit, sid_save }, { gen_rotate, ' -1 0 0 -1' }, { gen_kern, function (w,h,d,nw,nh,nd) return -nh-nd end }, { gen_box, get_w_neg }, { gen_kern, function (w,h,d,nw,nh,nd) return nh+nd-h-d end }, { gen_whatsit, sid_restore }, }, }, }, } end -- 1st ret val: b の組方向 -- 2nd ret val はその DIR whatsit function get_box_dir(b, default) start_time_measure 'get_box_dir' local dir = get_attr(b, attr_dir) or 0 local bh = getfield(b, 'head') -- We cannot use getlist since b may be an unset_node. local c if bh~=0 then -- bh != nil for bh in traverse_id(id_whatsit, bh) do if getsubtype(bh)==sid_user and getfield(bh, 'user_id')==DIR then c = bh; dir = (dir==0) and get_attr(bh, attr_dir) or dir end end end stop_time_measure 'get_box_dir' return (dir==0 and default or dir), c end do local ltj_tempcnta = luatexbase.registernumber 'ltj@tempcnta' local getbox = tex.getbox local dir_backup function luatexja.direction.unbox_check_dir() start_time_measure 'box_primitive_hook' local list_dir = get_dir_count()%dir_math_mod local b = getbox(getcount(ltj_tempcnta)) if b and getlist(to_direct(b)) then local box_dir = get_box_dir(to_direct(b), dir_yoko) if box_dir%dir_math_mod ~= list_dir then ltjb.package_error( 'luatexja', "Incompatible direction list can't be unboxed", 'I refuse to unbox a box in differrent direction.') tex.sprint(cat_lp, '\\@gobbletwo') else dir_backup = nil local bd = to_direct(b) local hd = getlist(bd) local nh = hd while hd do if getid(hd)==id_whatsit and getsubtype(hd)==sid_user and getfield(hd, 'user_id')==DIR then local d = hd nh, hd = node_remove(nh, hd); node_free(d) else hd = node_next(hd) end end setlist(bd, nh) end end if luatexja.global_temp and tex.globaldefs~=luatexja.global_temp then tex.globaldefs = luatexja.global_temp end stop_time_measure 'box_primitive_hook' end function luatexja.direction.uncopy_check_dir() start_time_measure 'box_primitive_hook' local list_dir = get_dir_count()%dir_math_mod local b = getbox(getcount(ltj_tempcnta)) if b and getlist(to_direct(b)) then local box_dir = get_box_dir(to_direct(b), dir_yoko) if box_dir%dir_math_mod ~= list_dir then ltjb.package_error( 'luatexja', "Incompatible direction list can't be unboxed", 'I refuse to unbox a box in differrent direction.') tex.sprint(cat_lp, '\\@gobbletwo') else dir_backup = nil local bd = to_direct(b) local hd = getlist(bd) local nh = hd while hd do if getid(hd)==id_whatsit and getsubtype(hd)==sid_user and getfield(hd, 'user_id')==DIR then local d = hd nh, hd = node_remove(nh, hd) if not dir_backup then dir_backup = d; setnext(dir_backup, nil) else node_free(d) end else hd = node_next(hd) end end setlist(bd, nh) end end if luatexja.global_temp and tex.globaldefs~=luatexja.global_temp then tex.globaldefs = luatexja.global_temp end stop_time_measure 'box_primitive_hook' end function luatexja.direction.uncopy_restore_whatsit() local b = getbox(getcount(ltj_tempcnta)) if b then local bd = to_direct(b) if dir_backup then setnext(dir_backup, getlist(bd)) setlist(bd, dir_backup) dir_backup = nil end end end end -- dir_node に包まれている「本来の中身」を取り出し, -- dir_node を全部消去 local function unwrap_dir_node(b, head, box_dir) -- b: dir_node, head: the head of list, box_dir: -- return values are (new head), (next of b), (contents), (dir of contents) local bh = getlist(b) local nh, nb if head then nh = insert_before(head, b, bh) nh, nb = node_remove(nh, b) setnext(b, nil); node_free(b) end local shift_old, b_dir, wh = nil, get_box_dir(bh, 0) if wh then dnode.flush_list(getvalue(wh)); setvalue(wh, nil) end return nh, nb, bh, b_dir end -- is_manual: 寸法変更に伴うものか? local create_dir_node do local getdir = dnode.getdir local setdir = dnode.setdir local setshift = dnode.setshift create_dir_node = function(b, b_dir, new_dir, is_manual) local info = dir_node_aux[b_dir%dir_math_mod][new_dir%dir_math_mod] local w, h, d = getwhd(b) local db = node_new(getid(b)) -- dir_node set_attr(db, attr_dir, new_dir + (is_manual and dir_node_manual or dir_node_auto)) set_attr(db, attr_icflag, PROCESSED) set_attr(b, attr_icflag, PROCESSED) ensure_tex_attr(attr_dir, 0) ensure_tex_attr(attr_icflag, 0) setdir(db, getdir(b)); setshift(db, 0) setwhd(db, info.width(w,h,d), info.height(w,h,d), info.depth(w,h,d)) return db end end -- 異方向のボックスの処理 local make_dir_whatsit, process_dir_node do make_dir_whatsit = function (head, b, new_dir, origin) new_dir = new_dir%dir_math_mod -- head: list head, b: box -- origin: コール元 (for debug) -- return value: (new head), (next of b), (new b), (is_b_dir_node) -- (new b): b か dir_node に被せられた b local bh = getlist(b) local box_dir, dn = get_box_dir(b, ltjs.list_dir) -- 既に b の中身にあるwhatsit if (box_dir=dir_node_auto then -- dir_node としてカプセル化されている local _, dnc = get_box_dir(b, 0) if dnc then -- free all other dir_node dnode.flush_list(getvalue(dnc)); setvalue(dnc, nil) end set_attr(b, attr_dir, box_dir%dir_math_mod + dir_node_auto) return head, node_next(b), b, true else -- 組方向が一緒 (up to math dir) のボックスなので,何もしなくて良い return head, node_next(b), b, false end else -- 組方向を合わせる必要あり local nh, nb, ret, flag if box_dir>= dir_node_auto then -- unwrap local b_dir head, nb, b, b_dir = unwrap_dir_node(b, head, box_dir) bh = getlist(b) if b_dir%dir_math_mod==new_dir then -- dir_node の中身が周囲の組方向とあっている return head, nb, b, false else box_dir = b_dir end end box_dir = box_dir%dir_math_mod local db local dnh = getvalue(dn) for x in traverse(dnh) do if get_attr(x, attr_dir)%dir_math_mod == new_dir then setvalue(dn, to_node(node_remove(dnh, x))) db=x; break end end dnode.flush_list(getvalue(dn)); setvalue(dn, nil) db = db or create_dir_node(b, box_dir, new_dir, false) local w, h, d = getwhd(b) nh, nb = insert_before(head, b, db), nil nh, nb = node_remove(nh, b) setnext(b, nil); setlist(db, b) ret, flag = db, true return nh, nb, ret, flag end end process_dir_node = function (hd, gc) local x, new_dir = hd, ltjs.list_dir or dir_yoko while x do local xid = getid(x) if (xid==id_hlist and get_attr_icflag(x)~=PACKED) or xid==id_vlist then hd, x = make_dir_whatsit(hd, x, new_dir, 'process_dir_node:' .. gc) else x = node_next(x) end end return hd end -- lastbox local node_prev = dnode.getprev local id_glue = node.id 'glue' local function lastbox_hook() start_time_measure 'box_primitive_hook' local bn = getnest().tail if bn then local b, head = to_direct(bn), to_direct(getnest().head) local bid = getid(b) if bid==id_hlist or bid==id_vlist then local p = getlist(b) -- alignment の各行の中身が入ったボックス if p and getid(p)==id_glue and getsubtype(p)==12 then -- tabskip local np = node_next(p); local npid = getid(np) if npid==id_hlist or npid==id_vlist then setlist(b, create_dir_whatsit(p, 'align', get_box_dir(np, 0))) end end local box_dir = get_box_dir(b, 0) if box_dir>= dir_node_auto then -- unwrap dir_node local p = node_prev(b) local dummy1, dummy2, nb = unwrap_dir_node(b, nil, box_dir) setnext(p, nb); getnest().tail = to_node(nb) setnext(b, nil); setlist(b, nil) node_free(b); b = nb end local _, wh = get_box_dir(b, 0) -- clean dir_node attached to the box if wh then dnode.flush_list(getvalue(wh)); setvalue(wh, nil) end end end stop_time_measure 'box_primitive_hook' end luatexja.direction.make_dir_whatsit = make_dir_whatsit luatexja.direction.lastbox_hook = lastbox_hook end -- \wd, \ht, \dp の代わり do local getbox, setdimen = tex.getbox, tex.setdimen local ltj_tempdima = luatexbase.registernumber 'ltj@tempdima' local function get_box_dim_common(key, s, l_dir) -- s: not dir_node. local s_dir, wh = get_box_dir(s, dir_yoko) s_dir = s_dir%dir_math_mod if s_dir ~= l_dir then local not_found = true for x in traverse(getvalue(wh)) do if l_dir == get_attr(x, attr_dir)%dir_node_auto then setdimen(ltj_tempdima, getfield(x, key)) not_found = false; break end end if not_found then local w, h, d = getwhd(s) setdimen(ltj_tempdima, dir_node_aux[s_dir][l_dir][key](w,h,d)) end else setdimen(ltj_tempdima, getfield(s, key)) end end local function get_box_dim(key, n) local gt = tex.globaldefs; tex.globaldefs = 0 local s = getbox(n) if s then local l_dir = (get_dir_count())%dir_math_mod s = to_direct(s) local b_dir = get_box_dir(s,dir_yoko) if b_dir=dir_node_auto then -- n is dir_node finalize_dir_node(n, ndir%dir_math_mod) else finalize_inner(n) end end end end local copy = dnode.copy function luatexja.direction.shipout_lthook (head) start_time_measure 'box_primitive_hook' local a = to_direct(head) local a_dir = get_box_dir(a, dir_yoko) if a_dir~=dir_yoko then local b = create_dir_node(a, a_dir, dir_yoko, false) setlist(b, a); a = b end setlist(shipout_temp, a); finalize_inner(shipout_temp) a = copy(getlist(shipout_temp)); setlist(shipout_temp, nil) stop_time_measure 'box_primitive_hook' return to_node(a) end end