OSDN Git Service

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