OSDN Git Service

Use everysel package for patching \selectfont.
[luatex-ja/luatexja.git] / src / ltj-otf.lua
1 --
2 -- luatexja/ltj-otf.lua
3 --
4 require('unicode')
5 require('lualibs')
6
7 luatexja.load_module('base');      local ltjb = luatexja.base
8 luatexja.load_module('jfont');     local ltjf = luatexja.jfont
9 luatexja.load_module('rmlgbm');    local ltjr = luatexja.rmlgbm
10 luatexja.load_module('charrange'); local ltjc = luatexja.charrange
11
12 local id_glyph = node.id('glyph')
13 local id_whatsit = node.id('whatsit')
14 local sid_user = node.subtype('user_defined')
15
16 local Dnode = node.direct or node
17
18 local setfield = (Dnode ~= node) and Dnode.setfield or function(n, i, c) n[i] = c end
19 local getfield = (Dnode ~= node) and Dnode.getfield or function(n, i) return n[i] end
20 local getid = (Dnode ~= node) and Dnode.getid or function(n) return n.id end
21 local getfont = (Dnode ~= node) and Dnode.getfont or function(n) return n.font end
22 --local getlist = (Dnode ~= node) and Dnode.getlist or function(n) return n.head end
23 local getchar = (Dnode ~= node) and Dnode.getchar or function(n) return n.char end
24 local getsubtype = (Dnode ~= node) and Dnode.getsubtype or function(n) return n.subtype end
25
26 local nullfunc = function(n) return n end
27 local to_node = (Dnode ~= node) and Dnode.tonode or nullfunc
28 local to_direct = (Dnode ~= node) and Dnode.todirect or nullfunc
29
30 local node_new = Dnode.new
31 local node_remove = luatexja.Dnode_remove -- Dnode.remove
32 local node_next = (Dnode ~= node) and Dnode.getnext or node.next
33 local node_free = Dnode.free
34 local has_attr = Dnode.has_attribute
35 local set_attr = Dnode.set_attribute
36 local unset_attr = Dnode.unset_attribute
37 local node_insert_after = Dnode.insert_after 
38 local node_write = Dnode.write
39 local node_traverse_id = Dnode.traverse_id
40
41 local identifiers = fonts.hashes.identifiers
42
43 local attr_curjfnt = luatexbase.attributes['ltj@curjfnt']
44 local attr_yablshift = luatexbase.attributes['ltj@yablshift']
45 local attr_ykblshift = luatexbase.attributes['ltj@ykblshift']
46
47 local ltjf_font_metric_table = ltjf.font_metric_table
48 local ltjf_find_char_class = ltjf.find_char_class
49 local ltjr_cidfont_data = ltjr.cidfont_data
50 local ltjc_is_ucs_in_japanese_char = ltjc.is_ucs_in_japanese_char
51
52 luatexja.userid_table.OTF = luatexbase.newuserwhatsitid('char_by_cid',  'luatexja')
53 luatexja.userid_table.VSR = luatexbase.newuserwhatsitid('replace_vs',  'luatexja') 
54 local OTF, VSR = luatexja.userid_table.OTF, luatexja.userid_table.VSR
55
56 local function get_ucs_from_rmlgbm(c)
57    local v = ltjr_cidfont_data["Adobe-Japan1"].resources.unicodes["Japan1." .. tostring(c)]
58    if not v then -- AJ1 範囲外
59       return 0
60    elseif v<0xF0000 then -- 素直に Unicode にマップ可能
61       return v
62    else
63       local w = ltjr_cidfont_data["Adobe-Japan1"].characters[v]. tounicode
64       -- must be non-nil!
65       local i = string.len(w)
66       if i==4 then -- UCS2
67          return tonumber(w,16)
68       elseif i==8 then 
69          i,w = tonumber(string.sub(w,1,4),16), tonumber(string.sub(w,-4),16)
70          if (w>=0xD800) and (w<=0xDB7F) and (i>=0xDC00) and (i<=0xDFFF) then -- Surrogate pair
71             return (w-0xD800)*0x400 + (i-0xDC00)
72          else
73             return 0
74          end
75       end
76    end
77 end
78
79 -- Append a whatsit node to the list.
80 -- This whatsit node will be extracted to a glyph_node
81 local function append_jglyph(char)
82    local p = node_new(id_whatsit,sid_user)
83    local v = tex.attribute[attr_curjfnt]
84    setfield(p, 'user_id', OTF)
85    setfield(p, 'type', 100)
86    setfield(p, 'value', char)
87    set_attr(p, attr_yablshift, tex.attribute[attr_ykblshift])
88    node_write(p)
89 end
90
91 local function cid(key)
92    if key==0 then return append_jglyph(char) end
93    local curjfnt = identifiers[tex.attribute[attr_curjfnt]]
94    if not curjfnt.cidinfo or 
95       curjfnt.cidinfo.ordering ~= "Japan1" and
96       curjfnt.cidinfo.ordering ~= "GB1" and
97       curjfnt.cidinfo.ordering ~= "CNS1" and
98       curjfnt.cidinfo.ordering ~= "Korea1" then
99 --      ltjb.package_warning('luatexja-otf',
100 --                         'Current Japanese font (or other CJK font) "'
101 --                            ..curjfnt.psname..'" is not a CID-Keyed font (Adobe-Japan1 etc.)')
102       return append_jglyph(get_ucs_from_rmlgbm(key))
103    end
104    local char = curjfnt.resources.unicodes[curjfnt.cidinfo.ordering..'.'..tostring(key)]
105    if not char then
106       ltjb.package_warning('luatexja-otf',
107                            'Current Japanese font (or other CJK font) "'
108                               ..curjfnt.psname..'" does not have the specified CID character ('
109                               ..tostring(key)..')', 
110                            'Use a font including the specified CID character.')
111       char = 0
112    end
113    return append_jglyph(char)
114 end
115
116 local function extract(head)
117    head = to_direct(head)
118    local p = head
119    local v
120    while p do
121       if getid(p)==id_whatsit then
122          if getsubtype(p)==sid_user then
123             local puid = getfield(p, 'user_id')
124             if puid==OTF or puid==VSR then
125                local g = node_new(id_glyph)
126                setfield(g, 'subtype', 0)
127                setfield(g, 'char', getfield(p, 'value'))
128                v = has_attr(p, attr_curjfnt); setfield(g, 'font',v)
129                set_attr(g, attr_curjfnt, puid==OTF and v or -1)
130                -- VSR yields ALchar
131                v = has_attr(p, attr_yablshift)
132                if v then 
133                   set_attr(g, attr_yablshift, v)
134                else
135                   unset_attr(g, attr_yablshift)
136                end
137                head = node_insert_after(head, p, g)
138                head = node_remove(head, p)
139                node_free(p); p = g
140             end
141          end
142       end
143       p = node_next(p)
144    end
145    return to_node(head)
146 end
147
148 luatexbase.add_to_callback('hpack_filter', extract,
149                            'ltj.hpack_filter_otf',
150    luatexbase.priority_in_callback('pre_linebreak_filter',
151                                    'ltj.pre_linebreak_filter'))
152 luatexbase.add_to_callback('pre_linebreak_filter', extract,
153                            'ltj.pre_linebreak_filter_otf',
154    luatexbase.priority_in_callback('pre_linebreak_filter',
155                                    'ltj.pre_linebreak_filter'))
156
157
158 -- additional callbacks
159 -- 以下は,LuaTeX-ja に用意された callback のサンプルになっている.
160 --   JFM の文字クラスの指定の所で,"AJ1-xxx" 形式での指定を可能とした.
161 --   これらの文字指定は,和文フォント定義ごとに,それぞれのフォントの
162 --   CID <-> グリフ 対応状況による変換テーブルが用意される.
163
164 -- 和文フォント読み込み時に,CID -> unicode 対応をとっておく.
165 local function cid_to_char(fmtable, fn)
166    local fi = identifiers[fn]
167    if fi.cidinfo and fi.cidinfo.ordering == "Japan1" then
168       fmtable.cid_char_type = {}
169       for i, v in pairs(fmtable.chars) do
170          local j = string.match(i, "^AJ1%-([0-9]*)")
171          if j then
172             j = tonumber(fi.resources.unicodes['Japan1.'..tostring(j)])
173             if j then
174                fmtable.cid_char_type[j] = v 
175             end
176          end
177       end
178    end
179    return fmtable
180 end
181 luatexbase.add_to_callback("luatexja.define_jfont", 
182                            cid_to_char, "ltj.otf.define_jfont", 1)
183 --  既に読み込まれているフォントに対しても,同じことをやらないといけない
184 for fn, v in pairs(ltjf_font_metric_table) do
185    ltjf_font_metric_table[fn] = cid_to_char(v, fn)
186 end
187
188
189 local function cid_set_char_class(arg, fmtable, char)
190    if arg~=0 then return arg
191    elseif fmtable.cid_char_type then
192       return fmtable.cid_char_type[char] or 0
193    else return 0
194    end
195 end
196 luatexbase.add_to_callback("luatexja.find_char_class", 
197                            cid_set_char_class, "ltj.otf.find_char_class", 1)
198
199 -------------------- IVS
200 local font_ivs_table = {} -- key: fontnumber
201 local enable_ivs
202 do
203    local is_ivs_enabled = false
204    local ivs -- temp table
205    local sort = table.sort
206    local uniq_flag
207    local function add_ivs_table(tg, unitable, glyphmax)
208       for i = 0, glyphmax-1 do
209          if tg[i] then
210             local gv = tg[i]
211             if gv.altuni then
212                for _,at in pairs(gv.altuni) do
213                   local bu, vsel = at.unicode, at.variant
214                   if vsel then
215                      if vsel>=0xE0100 then vsel = vsel - 0xE0100 end
216                      if not ivs[bu] then ivs[bu] = {} end
217                      uniq_flag = true
218                      for i,_ in pairs(ivs[bu]) do
219                         if i==vs then uniq_flag = false; break end
220                      end
221                      if uniq_flag then 
222                         ivs[bu][vsel] = unitable[gv.name]
223                      end
224                   end
225                end
226             end
227          end
228       end
229    end
230    local function make_ivs_table(id, fname)
231       ivs = {}
232       local fl = fontloader.open(fname)
233       local unicodes = id.resources.unicodes
234       if fl.glyphs then
235          add_ivs_table(fl.glyphs, id.resources.unicodes, fl.glyphmax)
236       end
237       if fl.subfonts then
238          for _,v in pairs(fl.subfonts) do
239             add_ivs_table(v.glyphs, id.resources.unicodes, v.glyphmax)
240          end
241       end
242       fontloader.close(fl)
243       return ivs
244    end
245
246 -- loading and saving
247    local font_ivs_basename = {} -- key: basename
248    local cache_ver = 4
249    local checksum = file.checksum
250
251    local function prepare_ivs_data(n, id)
252       -- test if already loaded
253       if type(id)=='number' then -- sometimes id is an integer
254          font_ivs_table[n] = font_ivs_table[id]; return
255       elseif not id then return
256       end
257       local fname = id.filename
258       local bname = file.basename(fname)
259       if not fname then 
260          font_ivs_table[n] = {}; return
261       elseif font_ivs_basename[bname] then 
262          font_ivs_table[n] = font_ivs_basename[bname]; return
263       end
264       
265       -- if the cache is present, read it
266       local newsum = checksum(fname) -- MD5 checksum of the fontfile
267       local v = "ivs_" .. string.lower(file.nameonly(fname))
268       local dat = ltjb.load_cache(v, 
269          function (t) return (t.version~=cache_ver) or (t.chksum~=newsum) end
270       )
271       -- if the cache is not found or outdated, save the cache
272       if dat then 
273          font_ivs_basename[bname] = dat[1] or {}
274       else
275          dat = make_ivs_table(id, fname)
276          font_ivs_basename[bname] = dat or {}
277          ltjb.save_cache( v,
278                           {
279                              chksum = checksum(fname), 
280                              version = cache_ver,
281                              dat,
282                           })
283       end
284       font_ivs_table[n] = font_ivs_basename[bname]
285    end
286
287 -- 組版時
288    local function ivs_jglyph(char, bp, pf, uid)
289       local p = node_new(id_whatsit,sid_user)
290       setfield(p, 'user_id', uid)
291       setfield(p, 'type', 100)
292       setfield(p, 'value', char)
293       set_attr(p, attr_curjfnt, pf)
294       set_attr(p, attr_yablshift, has_attr(bp, attr_ykblshift) or 0)
295       return p
296    end
297
298    local function do_ivs_repr(head)
299       head = to_direct(head)
300       local p, r = head
301       while p do
302          local pid = getid(p)
303          if pid==id_glyph then
304             local pf = getfont(p)
305             local q = node_next(p) -- the next node of p
306             if q and getid(q)==id_glyph then
307                local qc = getchar(q)
308                if (qc>=0xFE00 and qc<=0xFE0F) or (qc>=0xE0100 and qc<0xE01F0) then 
309                   -- q is a variation selector
310                   if qc>=0xE0100 then qc = qc - 0xE0100 end
311                   local pt = font_ivs_table[pf]
312                   pt = pt and pt[getchar(p)];  pt = pt and  pt[qc]
313                   head, r = node_remove(head,q)
314                   node_free(q)
315                   if pt then
316                      local np = ivs_jglyph(pt, p, pf,
317                                            (has_attr(p,attr_curjfnt) or 0)==pf and OTF or VSR)
318                      head = node_insert_after(head, p, np) 
319                      head = node_remove(head,p)
320                      node_free(p)
321                   end
322                   p = r
323                else 
324                   p = q
325                end
326             else
327                p = node_next(p)
328             end
329          else
330             p = node_next(p)
331          end
332       end
333       return to_node(head)
334    end
335
336    -- font define
337    local function font_callback(name, size, id, fallback)
338       local d = fallback(name, size, id)
339       prepare_ivs_data(id, d)
340       return d
341    end
342
343    enable_ivs = function ()
344       if is_ivs_enabled then
345          ltjb.package_warning('luatexja-otf',
346                               'luatexja.otf.enable_ivs() was already called, so this call is ignored', '')
347       else
348          luatexbase.add_to_callback('hpack_filter', 
349                                     do_ivs_repr,'do_ivs', 1)
350          luatexbase.add_to_callback('pre_linebreak_filter', 
351                                     do_ivs_repr, 'do_ivs', 1)
352          local ivs_callback = function (name, size, id)
353             return font_callback(
354                name, size, id, 
355                function (name, size, id) return luatexja.font_callback(name, size, id) end
356             )
357          end
358          luatexbase.add_to_callback('define_font',ivs_callback,"luatexja.ivs_font_callback", 1)
359          for i=1,font.nextid()-1 do
360             if identifiers[i] then prepare_ivs_data(i, identifiers[i]) end
361          end
362          is_ivs_enabled = true
363       end
364    end
365 end
366
367 luatexja.otf = {
368   append_jglyph = append_jglyph,
369   enable_ivs = enable_ivs,  -- 隠し機能: IVS
370   font_ivs_table = font_ivs_table,
371   cid = cid,
372 }
373
374 -- EOF