OSDN Git Service

ltj-rmlgbm.lua: support vertical form.
[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.load_module('rmlgbm');    local ltjr = luatexja.rmlgbm
8 luatexja.direction = {}
9
10 local attr_dir = luatexbase.attributes['ltj@dir']
11 local attr_icflag = luatexbase.attributes['ltj@icflag']
12
13 local Dnode = node.direct or node
14 local nullfunc = function (n) return n end
15 local to_node = (Dnode ~= node) and Dnode.tonode or nullfunc
16 local to_direct = (Dnode ~= node) and Dnode.todirect or nullfunc
17 local has_attr = Dnode.has_attribute
18 local set_attr = Dnode.set_attribute
19 local insert_before = Dnode.insert_before
20 local insert_after = Dnode.insert_after
21 local getid = (Dnode ~= node) and Dnode.getid or function(n) return n.id end
22 local getsubtype = (Dnode ~= node) and Dnode.getsubtype or function(n) return n.subtype end
23 local getlist = (Dnode ~= node) and Dnode.getlist or function(n) return n.head end
24 local setfield = (Dnode ~= node) and Dnode.setfield or function(n, i, c) n[i] = c end
25 local getfield = (Dnode ~= node) and Dnode.getfield or function(n, i) return n[i] end
26 local node_new = Dnode.new
27 local node_tail = Dnode.tail
28 local node_free = Dnode.free
29 local node_remove = Dnode.remove
30 local node_next = (Dnode ~= node) and Dnode.getnext or node.next
31 local traverse_id = Dnode.traverse_id
32
33 local id_kern = node.id('kern')
34 local id_hlist = node.id('hlist')
35 local id_vlist = node.id('vlist')
36 local id_whatsit = node.id('whatsit')
37 local sid_save = node.subtype('pdf_save')
38 local sid_restore = node.subtype('pdf_restore')
39 local sid_matrix = node.subtype('pdf_setmatrix')
40 local sid_user = node.subtype('user_defined')
41
42 local PROCESSED    = luatexja.icflag_table.PROCESSED
43 local PACKED       = luatexja.icflag_table.PACKED
44 local DIR = luatexja.stack_table_index.DIR
45 local wh_DIR = luatexja.userid_table.DIR
46 local dir_tate = 3
47 local dir_yoko = 4
48
49 do
50   local node_next = node.next
51   local function set_list_direction(v, name)
52     if node.next(tex.nest[tex.nest.ptr].head) then
53       ltjb.package_error('luatexja',
54                          "Use `\\" .. name .. "' at top of list", 
55                          'Direction change command by LuaTeX-ja is available\n'
56                             .. 'only while current list is null.')
57     else
58        ltjs.set_stack_table(luatexja.stack_table_index.DIR, v, true)
59     end
60   end
61   luatexja.direction.set_list_direction = set_list_direction
62 end
63
64 do 
65    local tex_getcount = tex.getcount
66    local function set_dir_flag(h, gc)
67       local hd, new_dir = to_direct(h), ltjs.table_current_stack[DIR]
68       local w
69       if hd and getid(hd)==id_whatsit and getsubtype(hd)==sid_user 
70       and getfield(hd, 'user_id')==wh_DIR then
71          w = hd
72       else
73          w = node_new(id_whatsit, sid_user)
74          setfield(w, 'next', hd)
75       end
76       setfield(w, 'user_id', wh_DIR)
77       setfield(w, 'type', 100)
78       setfield(w, 'value', new_dir)
79       return to_node(w)
80    end
81    luatexbase.add_to_callback('hpack_filter', set_dir_flag, 'ltj.set_dir_flag', 10000)
82    luatexbase.add_to_callback('vpack_filter', 
83                               function (h, gc)
84                                  local box_set, cl = 0, tex.currentgrouplevel + 1
85                                  for w in traverse_id(id_whatsit, to_direct(h)) do
86                                     if getfield(w, 'value')==cl then box_set = 1; break end
87                                  end
88                                  ltjs.report_stack_level(tex_getcount('ltj@@stack') + box_set)
89                                  return set_dir_flag(h, gc)
90                               end, 'ltj.set_dir_flag', 1)
91    luatexbase.add_to_callback('post_linebreak_filter', 
92                               function (h)
93                                  local new_dir = ltjs.table_current_stack[DIR]
94                                  for line in traverse_id(id_hlist, to_direct(h)) do
95                                     set_attr(line, attr_dir, new_dir)
96                                  end
97                                  return set_dir_flag(h, gc)
98                               end, 'ltj.set_dir_flag', 100)
99
100 end
101
102 local make_dir_node
103 do
104    local get_h =function (w,h,d) return h end 
105    local get_d =function (w,h,d) return d end 
106    local get_h_d =function (w,h,d) return h+d end 
107    local get_h_neg =function (w,h,d) return -h end 
108    local get_d_neg =function (w,h,d) return -d end 
109    local get_w_half =function (w,h,d) return 0.5*w end 
110    local get_w_neg_half =function (w,h,d) return -0.5*w end 
111    local get_w_neg =function (w,h,d) return -w end
112    local get_w =function (w,h,d) return w end
113    local dir_node_aux = {
114       [dir_yoko] = { -- yoko を tate 中で組む
115          width  = get_h_d,
116          height = get_w_half,
117          depth  = get_w_half,
118          [id_hlist] = {
119             { 'kern', get_h }, 
120             { 'whatsit', sid_save },
121             { 'rotate', '0 1 -1 0' },
122             { 'kern', get_w_neg_half },
123             { 'box' },
124             { 'kern', get_w_neg_half },
125             { 'whatsit', sid_restore },
126          },
127          [id_vlist] = {
128             { 'kern', get_w},
129             { 'whatsit', sid_save },
130             { 'rotate', '0 1 -1 0' },
131             { 'box' },
132             { 'kern', get_h_neg},
133             { 'whatsit', sid_restore },
134          },
135       }, 
136       [dir_tate] = { -- tate を yoko 中で組む
137          width  = get_h_d,
138          height = get_w,
139          depth  = function() return 0 end,
140          [id_hlist] = {
141             { 'kern', get_d }, 
142             { 'whatsit', sid_save },
143             { 'rotate', '0 -1 1 0' },
144             { 'kern', get_w_neg },
145             { 'box' },
146             { 'whatsit', sid_restore },
147          }, 
148          [id_vlist] = {
149             { 'whatsit', sid_save },
150             { 'rotate', '0 -1 1 0' },
151             { 'kern', get_h_neg },
152             { 'kern', get_d_neg },
153             { 'box' },
154             { 'whatsit', sid_restore },
155          }, 
156       },
157    }
158
159    make_dir_node = function (head, b, new_dir, origin)
160       -- head: list head, b: box
161       -- origin: コール元 (for debug)
162       -- return value: (new head), (next of b), (new b), (is_b_dir_node)
163       -- (new b): b か dir_node に被せられた b
164       local old_dir
165       local bh = getlist(b)
166       if bh and getid(bh)==id_whatsit and getsubtype(bh)==sid_user
167           and getfield(bh, 'user_id')==wh_DIR then
168              old_dir = getfield(bh, 'value')
169              setfield(b, 'head', node_next(bh))
170              set_attr(b, attr_icflag, PROCESSED)
171              node_free(bh)
172       else
173          old_dir = has_attr(b, attr_dir) or dir_yoko
174          if old_dir==0 then old_dir =dir_yoko end
175       end
176       --print('make_dir_node', origin, old_dir,new_dir)
177       if old_dir==new_dir then 
178          set_attr(b, attr_icflag, PROCESSED)
179          return head, node_next(b), b, false
180       elseif  -old_dir == new_dir  then 
181          return head, node_next(b), b, true
182       else
183          local nh, nb, ret, flag
184          if old_dir < 0 then 
185             -- b itself is a dir node; just unwrap
186             local bc = node_next(node_next(
187                                     node_next(node_next(bh))))
188             node_remove(bh, bc); 
189             nh, nb =  insert_before(head, b, bc), nil
190             nh, nb = node_remove(head, b)
191             setfield(b, 'next', nil); Dnode.flush_list(b)
192             ret, flag = bc, false
193          else
194             local bid = getid(b)
195             local db = node_new(bid) -- dir node
196             nh, nb =  insert_before(head, b, db), nil
197             nh, nb = node_remove(nh, b)
198             local w = getfield(b, 'width')
199             local h = getfield(b, 'height')
200             local d = getfield(b, 'depth')
201             local info = dir_node_aux[old_dir]
202             set_attr(db, attr_dir, -new_dir)
203             set_attr(b, attr_icflag, PROCESSED)
204             set_attr(db, attr_icflag, PROCESSED)
205             setfield(db, 'dir', getfield(b, 'dir'))
206             setfield(db, 'shift', 0)
207             setfield(db, 'width',  info.width(w,h,d))
208             setfield(db, 'height', info.height(w,h,d))
209             setfield(db, 'depth',  info.depth(w,h,d))
210             local db_head, db_tail  = nil
211             for _,v in ipairs(info[bid]) do
212                local cmd, arg, nn = v[1], v[2]
213                if cmd=='kern' then
214                   nn = node_new(id_kern)
215                   setfield(nn, 'kern', arg(w, h, d))
216                elseif cmd=='whatsit' then
217                   nn = node_new(id_whatsit, arg)
218                elseif cmd=='rotate' then
219                   nn = node_new(id_whatsit, sid_matrix)
220                   setfield(nn, 'data', arg)
221                elseif cmd=='box' then
222                   nn = b; setfield(b, 'next', nil)
223                end
224                if db_head then 
225                   insert_after(db_head, db_tail, nn)
226                   db_tail = nn
227                else
228                   db_head, db_tail = nn, nn
229                end
230             end
231             setfield(db, 'head', db_head)
232             ret, flag = db, true
233          end
234          return nh, nb, ret, flag
235       end
236    end
237    local function process_dir_node(head, gc)
238       local h = to_direct(head)
239       local x, new_dir = h, ltjs.table_current_stack[DIR] or dir_yoko 
240       while x do
241          local xid = getid(x)
242          if (xid==id_hlist and has_attr(x, attr_icflag)~=PACKED) 
243          or xid==id_vlist then
244             h, x = make_dir_node(h, x, new_dir, 'process_dir_node:' .. gc)
245          else
246             x = node_next(x)
247          end
248       end
249       return to_node(h)
250    end
251    luatexja.direction.make_dir_node = make_dir_node
252    luatexbase.add_to_callback('vpack_filter', 
253                               process_dir_node, 'ltj.dir_node', 10001)
254 end
255
256 local font_vert_table = {} -- key: fontnumber
257 do
258    local font_vert_basename = {} -- key: basename
259    local function add_feature_table(tname, src, dest)
260       for i,v in pairs(src) do
261          if type(v.slookups)=='table' then
262             local s = v.slookups[tname]
263             if s and not dest[i] then
264                dest[i] = s
265             end
266          end
267       end
268    end
269
270    local function prepare_vert_data(n, id)
271       -- test if already loaded
272       if type(id)=='number' then -- sometimes id is an integer
273          font_vert_table[n] = font_vert_table[id]; return
274       elseif (not id) or font_vert_table[n]  then return
275       end
276
277       local fname = id.filename
278       local bname = file.basename(fname)
279       if not fname then 
280          font_vert_table[n] = {}; return
281       elseif font_vert_basename[bname] then 
282          font_vert_table[n] = font_vert_basename[bname]; return
283       end
284
285       local vtable = {}
286       local a = id.resources.sequences
287       if a then
288          local s = id.shared.rawdata.descriptions
289          for i,v in pairs(a) do
290             if v.features.vert or v.features.vrt2 then 
291                add_feature_table(v.subtables[1], s, vtable)
292             end
293          end
294       end
295       font_vert_basename[bname] = vtable
296       font_vert_table[n] = vtable
297    end
298    
299    function luatexja.direction.get_vert_glyph(n, chr)
300       local fn = font_vert_table[n]
301       return fn and fn[chr] or chr
302    end
303
304    luatexbase.add_to_callback('luatexja.define_font',
305                               function (res, name, size, id)
306                                  prepare_vert_data(id, res)
307                               end,
308                               'prepare_vert_data', 1)
309
310    local function a (n, dat) font_vert_table[n] = dat end
311    luatexja.rmlgbm.vert_addfunc = a
312    
313 end