OSDN Git Service

e9265e2dc32de6007532607b1b2f49c9b5ce264d
[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 get_node_font = function(p)
293       return (ltjc_is_ucs_in_japanese_char(p) and (has_attr(p, attr_curjfnt) or 0) or p.font)
294    end
295    local function do_ivs_repr(head)
296       local p = head
297       while p do
298          local pid = p.id
299          if pid==id_glyph then
300             local pt = font_ivs_table[get_node_font(p)]
301             local q = node_next(p) -- the next node of p
302             if q and q.id==id_glyph then
303                local qc = q.char
304                if qc>=0xE0100 and qc<0xE01F0 then -- q is an IVS selector
305                   pt = pt and pt[p.char];  pt = pt and  pt[qc-0xE0100]
306                   if pt then
307                      p.char = pt or p.char
308                   end
309                   head = node_remove(head,q)
310                end
311             end
312          end
313          p = node_next(p)
314       end
315       return head
316    end
317
318    -- font define
319    local function font_callback(name, size, id, fallback)
320       local d = fallback(name, size, id)
321       prepare_ivs_data(id, d)
322       return d
323    end
324
325    enable_ivs = function ()
326       luatexbase.add_to_callback('hpack_filter', 
327                                  function (head) return do_ivs_repr(head) end,'do_ivs', 1)
328       luatexbase.add_to_callback('pre_linebreak_filter', 
329                                  function (head) return do_ivs_repr(head) end, 'do_ivs', 1)
330       local ivs_callback = function (name, size, id)
331          return font_callback(
332             name, size, id, 
333             function (name, size, id) return luatexja.font_callback(name, size, id) end
334          )
335       end
336       luatexbase.add_to_callback('define_font',ivs_callback,"luatexja.ivs_font_callback", 1)
337       for i=1,font.nextid()-1 do
338          if identifiers[i] then prepare_ivs_data(i, identifiers[i]) end
339       end
340    end
341 end
342
343 -------------------- all done
344 luatexja.otf = {
345   append_jglyph = append_jglyph,
346   enable_ivs = enable_ivs,  -- 隠し機能: IVS
347   font_ivs_table = font_ivs_table,
348   cid = cid,
349 }
350
351 -- EOF