diff options
Diffstat (limited to 'Master/texmf-dist/tex/luatex/luaotfload/otfl-font-nms.lua')
-rw-r--r-- | Master/texmf-dist/tex/luatex/luaotfload/otfl-font-nms.lua | 562 |
1 files changed, 401 insertions, 161 deletions
diff --git a/Master/texmf-dist/tex/luatex/luaotfload/otfl-font-nms.lua b/Master/texmf-dist/tex/luatex/luaotfload/otfl-font-nms.lua index fd31bac0832..e75b6c94b18 100644 --- a/Master/texmf-dist/tex/luatex/luaotfload/otfl-font-nms.lua +++ b/Master/texmf-dist/tex/luatex/luaotfload/otfl-font-nms.lua @@ -3,15 +3,29 @@ if not modules then modules = { } end modules ['font-nms'] = { comment = "companion to luaotfload.lua", author = "Khaled Hosny and Elie Roux", copyright = "Luaotfload Development Team", - license = "GPL" + license = "GNU GPL v2" } +-- This is a patch for otfl-font-def.lua, that defines a reader for ofm fonts, +-- this is necessary if we set the forced field of the specification to 'ofm' +-- we use it only when using luaotfload, not mkluatexfontdb. +if fonts and fonts.tfm and fonts.tfm.readers then + fonts.tfm.readers.ofm = fonts.tfm.readers.tfm +end + +-- This is a necessary initalization in order not to rebuild an existing font. +-- Maybe 600 should be replaced by \pdfpkresolution +-- or texconfig.pk_dpi (and it should be replaced dynamically), but we don't +-- have access (yet) to the texconfig table, so we let it be 600. Anyway, it +-- does still work fine even if \pdfpkresolution is changed. +kpse.init_prog('', 600, '/') + fonts = fonts or { } fonts.names = fonts.names or { } local names = fonts.names local names_dir = "/luatex/generic/luaotfload/names/" -names.version = 2.007 -- not the same as in context +names.version = 2.009 -- not the same as in context names.data = nil names.path = { basename = "otfl-names.lua", @@ -20,15 +34,30 @@ names.path = { } -local splitpath, expandpath, glob, basename = file.split_path, kpse.expand_path, dir.glob, file.basename -local upper, lower, format, gsub, match = string.upper, string.lower, string.format, string.gsub, string.match -local rpadd = string.rpadd -local utfgsub = unicode.utf8.gsub +local splitpath, expandpath = file.split_path, kpse.expand_path +local glob, basename = dir.glob, file.basename +local upper, lower, format = string.upper, string.lower, string.format +local gsub, match, rpadd = string.gsub, string.match, string.rpadd +local gmatch, sub = string.gmatch, string.sub +local utfgsub = unicode.utf8.gsub +local trace_short = false --tracing adapted to rebuilding of the database inside a document local trace_progress = true --trackers.register("names.progress", function(v) trace_progress = v end) local trace_search = false --trackers.register("names.search", function(v) trace_search = v end) local trace_loading = false --trackers.register("names.loading", function(v) trace_loading = v end) + +-- Basic function from <http://stackoverflow.com/questions/2282444/how-to-check-if-a-table-contains-an-element-in-lua> +function table.contains(table, element) + for _, value in pairs(table) do + if value == element then + return true + end + end + return false +end + + local function sanitize(str) if str then return utfgsub(lower(str), "[^%a%d]", "") @@ -37,54 +66,127 @@ local function sanitize(str) end end +local function fontnames_init() + return { + mappings = { }, + status = { }, + version = names.version, + } +end + function names.load() + -- this sets the output of the database building accordingly. + names.set_log_level(-1) local localpath = names.path.localdir .. names.path.basename local systempath = names.path.systemdir .. names.path.basename local kpsefound = kpse.find_file(names.path.basename) + local foundname local data if kpsefound and file.isreadable(kpsefound) then data = dofile(kpsefound) + foundname = kpsefound elseif file.isreadable(localpath) then data = dofile(localpath) + foundname = localpath elseif file.isreadable(systempath) then data = dofile(systempath) + foundname = systempath end if data then - if trace_loading then - logs.report("load font", "loaded font names database: %s", foundname) - end - return data + logs.report("load font", + "loaded font names database: %s", foundname) + else + logs.report("load font", + "no font names database found, generating new one") + data = names.update() + names.save(data) end + return data end -local loaded = false - local synonyms = { - regular = {normal=true, roman=true, plain=true, book=true, medium=true}, - italic = {regularitalic=true, normalitalic=true, oblique=true, slant=true}, - bolditalic = {boldoblique=true, boldslant=true}, + regular = { + normal = true, + roman = true, + plain = true, + book = true, + medium = true, + }, + italic = { + regularitalic = true, + normalitalic = true, + oblique = true, + slant = true, + }, + bolditalic = { + boldoblique = true, + boldslant = true, + }, } --- a small hack to get the log message "no font names database loaded" only --- once in the log -local log_message_shown = false +local loaded = false +local reloaded = false function names.resolve(specification) - local tfm = resolvers.find_file(specification.name, "ofm") - local name = sanitize(specification.name) - local style = sanitize(specification.style) or "regular" - local size = tonumber(specification.optsize) or specification.size and specification.size / 65536 + local tfm = resolvers.find_file(specification.name, "tfm") + local ext = lower(file.extname(specification.name)) + if tfm then -- is a tfm font, skip names database - return specification.name, false + if ext == 'tfm' then + return specification.name, false + else + return specification.name..'.tfm', false + end + elseif resolvers.find_file(specification.name, "ofm") then + if ext == 'ofm' then + return specification.name, false + else + return specification.name..'.ofm', false + end + end + + local name = sanitize(specification.name) + local style = sanitize(specification.style) or "regular" + + local size + if specification.optsize then + size = tonumber(specification.optsize) + elseif specification.size then + size = specification.size / 65536 end + + if not loaded then - names.data = names.load() - loaded = true + names.data = names.load() + loaded = true end - local data = names.data + + local data = names.data if type(data) == "table" and data.version == names.version then if data.mappings then + -- if the specified name ends like a font file, we first look for + -- it through kpse, and if we don't find it, we look for it in + -- the database. + if table.contains(font_extensions_lc, ext) then + local fname = specification.name + -- for absolute and relative path, kpse will find them, so + -- there shouldn't be any problem + local path = resolvers.find_file(fname, "opentype fonts") + if not path then + path = resolvers.find_file(fname, "truetype fonts") + end + if path then + return path -- or fname ? + else + for _,face in ipairs(data.mappings) do + if basename(face.filename[1]) == fname then + return face.filename[1], face.filename[2] + end + end + end + return specification.name + end local found = { } for _,face in ipairs(data.mappings) do local family = sanitize(face.names.family) @@ -97,13 +199,15 @@ function names.resolve(specification) if #face.size > 0 then optsize = face.size dsnsize = optsize[1] and optsize[1] / 10 - maxsize = optsize[2] and optsize[2] / 10 or dsnsize -- can be nil - minsize = optsize[3] and optsize[3] / 10 or dsnsize -- can be nil + -- can be nil + maxsize = optsize[2] and optsize[2] / 10 or dsnsize + minsize = optsize[3] and optsize[3] / 10 or dsnsize end if name == family then if subfamily == style then if optsize then - if dsnsize == size or (size > minsize and size <= maxsize) then + if dsnsize == size + or (size > minsize and size <= maxsize) then found[1] = face break else @@ -115,7 +219,8 @@ function names.resolve(specification) end elseif synonyms[style] and synonyms[style][subfamily] then if optsize then - if dsnsize == size or (size > minsize and size <= maxsize) then + if dsnsize == size + or (size > minsize and size <= maxsize) then found[1] = face break else @@ -129,9 +234,13 @@ function names.resolve(specification) found[1] = face end else - if name == fullname or name == pfullname or name == fontname or name == psname then + if name == fullname + or name == pfullname + or name == fontname + or name == psname then if optsize then - if dsnsize == size or (size > minsize and size <= maxsize) then + if dsnsize == size + or (size > minsize and size <= maxsize) then found[1] = face break else @@ -172,19 +281,37 @@ function names.resolve(specification) return closest.filename[1], closest.filename[2] end end - -- no font found so far, fallback to filename + -- no font found so far + if not reloaded then + -- try reloading the database + names.data = names.update(names.data) + names.save(names.data) + reloaded = true + return names.resolve(specification) + else + -- else, fallback to filename + return specification.name, false + end + end + else + if not reloaded then + names.data = names.update() + names.save(names.data) + reloaded = true + return names.resolve(specification) + else return specification.name, false end - elseif not log_message_shown then - logs.report("load font", "no font names database loaded") - log_message_shown = true end end -names.resolvespec = names.resolve -- only supported in mkiv +names.resolvespec = names.resolve function names.set_log_level(level) - if level == 2 then + if level == -1 then + trace_progress = false + trace_short = true + elseif level == 2 then trace_progress = false trace_loading = true elseif level >= 3 then @@ -193,7 +320,7 @@ function names.set_log_level(level) trace_search = true end end - + local lastislog = 0 function log(fmt, ...) @@ -203,6 +330,7 @@ end logs = logs or { } logs.report = logs.report or log +logs.info = logs.info or log local log = names.log @@ -229,7 +357,13 @@ end local function font_fullinfo(filename, subfont, texmf) local t = { } local f = fontloader.open(filename, subfont) - local m = f and fontloader.to_table(f) + if not f then + if trace_loading then + logs.report("error: failed to open %s", filename) + end + return nil + end + local m = fontloader.to_table(f) fontloader.close(f) collectgarbage('collect') -- see http://www.microsoft.com/typography/OTSPEC/features_pt.htm#size @@ -244,11 +378,13 @@ local function font_fullinfo(filename, subfont, texmf) for _,v in pairs(m.names) do if v.lang == "English (US)" then t.names = { - -- see http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html - fullname = v.names.compatfull or v.names.fullname, -- 18, 4 - family = v.names.preffamilyname or v.names.family, -- 17, 1 - subfamily = t.fontstyle_name or v.names.prefmodifiers or v.names.subfamily, -- opt. style, 16, 2 - psname = v.names.postscriptname --or t.fontname + -- see + -- http://developer.apple.com/textfonts/ + -- TTRefMan/RM06/Chap6name.html + fullname = v.names.compatfull or v.names.fullname, + family = v.names.preffamilyname or v.names.family, + subfamily= t.fontstyle_name or v.names.prefmodifiers or v.names.subfamily, + psname = v.names.postscriptname } end end @@ -266,49 +402,72 @@ local function font_fullinfo(filename, subfont, texmf) m.design_range_top ~= 0 and m.design_range_top or nil, m.design_range_bottom ~= 0 and m.design_range_bottom or nil, } - return t end -local function load_font(filename, fontnames, status, newfontnames, newstatus, texmf) - local mappings = newfontnames and newfontnames.mappings or { } - local oldmappings = fontnames.mappings or { } +local function load_font(filename, fontnames, newfontnames, texmf) + local newmappings = newfontnames.mappings + local newstatus = newfontnames.status + local mappings = fontnames.mappings + local status = fontnames.status + local basefile = texmf and basename(filename) or filename if filename then local timestamp, db_timestamp - db_timestamp = status[filename] and status[filename].timestamp + db_timestamp = status[basefile] and status[basefile].timestamp timestamp = lfs.attributes(filename, "modification") - newstatus[filename] = { } - newstatus[filename].timestamp = timestamp - newstatus[filename].index = {} - if db_timestamp == timestamp then - for _,v in ipairs(status[filename].index) do - mappings[#mappings+1] = oldmappings[v] - newstatus[filename].index[#newstatus[filename].index+1] = #mappings + + local index_status = newstatus[basefile] or (not texmf and newstatus[basename(filename)]) + if index_status and index_status.timestamp == timestamp then + -- already indexed this run + return + end + + newstatus[basefile] = newstatus[basefile] or { } + newstatus[basefile].timestamp = timestamp + newstatus[basefile].index = newstatus[basefile].index or { } + + if db_timestamp == timestamp and not newstatus[basefile].index[1] then + for _,v in ipairs(status[basefile].index) do + local index = #newstatus[basefile].index + newmappings[#newmappings+1] = mappings[v] + newstatus[basefile].index[index+1] = #newmappings end if trace_loading then - logs.report("font already indexed: %s", filename) + logs.report("font already indexed: %s", basefile) end return end if trace_loading then - logs.report("loading font: %s", filename) + logs.report("loading font: %s", basefile) end local info = fontloader.info(filename) if info then if type(info) == "table" and #info > 1 then for i in ipairs(info) do local fullinfo = font_fullinfo(filename, i-1, texmf) - mappings[#mappings+1] = fullinfo - newstatus[filename].index[#newstatus[filename].index+1] = #mappings + local index = newstatus[basefile].index[i] + if newstatus[basefile].index[i] then + index = newstatus[basefile].index[i] + else + index = #newmappings+1 + end + newmappings[index] = fullinfo + newstatus[basefile].index[i] = index end else local fullinfo = font_fullinfo(filename, false, texmf) - mappings[#mappings+1] = fullinfo - newstatus[filename].index[#newstatus[filename].index+1] = #mappings + local index + if newstatus[basefile].index[1] then + index = newstatus[basefile].index[1] + else + index = #newmappings+1 + end + newmappings[index] = fullinfo + newstatus[basefile].index[1] = index end else if trace_loading then - logs.report("failed to load %s", filename) + logs.report("failed to load %s", basefile) end end end @@ -316,66 +475,108 @@ end local function path_normalize(path) --[[ - path normalization: - - a\b\c -> a/b/c - - a/../b -> b - - /cygdrive/a/b -> a:/b + path normalization: + - a\b\c -> a/b/c + - a/../b -> b + - /cygdrive/a/b -> a:/b + - reading symlinks under non-Win32 + - using kpse.readable_file on Win32 --]] if os.type == "windows" or os.type == "msdos" or os.name == "cygwin" then + path = kpse.readable_file(path) path = path:gsub('\\', '/') path = path:lower() - -- for cygwin cases... path = path:gsub('^/cygdrive/(%a)/', '%1:/') end + if os.type ~= "windows" and os.type ~= "msdos" then + local dest = lfs.readlink(path) + if dest then + if kpse.readable_file(dest) then + path = dest + elseif kpse.readable_file(file.join(file.dirname(path), dest)) then + path = file.join(file.dirname(path), dest) + else + -- broken symlink? + end + end + end path = file.collapse_path(path) return path end fonts.path_normalize = path_normalize -local function scan_dir(dirname, fontnames, status, newfontnames, newstatus, recursive, texmf) +if os.name == "macosx" then + -- While Mac OS X 10.6 has a problem with TTC files, ignore them globally: + font_extensions = { + "otf", "ttf", "dfont", "OTF", "TTF", "DFONT" + } + font_extensions_lc = { "otf", "ttf", "dfont" } +else + font_extensions = { + "otf", "ttf", "ttc", "dfont", "OTF", "TTF", "TTC", "DFONT" + } + font_extensions_lc = { "otf", "ttf", "ttc", "dfont" } +end + +local function scan_dir(dirname, fontnames, newfontnames, texmf) --[[ this function scans a directory and populates the list of fonts with all the fonts it finds. - dirname is the name of the directory to scan - names is the font database to fill - - recursive is whether we scan all directories recursively (always false - in this script) - - texmf is a boolean saying if we are scanning a texmf directory (always - true in this script) + - texmf is a boolean saying if we are scanning a texmf directory --]] local list, found = { }, { } local nbfound = 0 - for _,ext in ipairs { "otf", "ttf", "ttc", "dfont" } do - if recursive then pat = "/**." else pat = "/*." end - if trace_search then - logs.report("scanning '%s' for '%s' fonts", dirname, ext) - end - found = glob(dirname .. pat .. ext) + if trace_search then + logs.report("scanning '%s'", dirname) + end + for _,ext in ipairs(font_extensions) do + found = glob(dirname .. "/**." .. ext) -- note that glob fails silently on broken symlinks, which happens -- sometimes in TeX Live. if trace_search then - logs.report("%s fonts found", #found) + logs.report("%s '%s' fonts found", #found, ext) end nbfound = nbfound + #found table.append(list, found) - if trace_search then - logs.report("scanning '%s' for '%s' fonts", dirname, upper(ext)) - end - found = glob(dirname .. pat .. upper(ext)) - table.append(list, found) - nbfound = nbfound + #found end if trace_search then logs.report("%d fonts found in '%s'", nbfound, dirname) end + list = remove_ignore_fonts(list) -- fixme: general solution required for _,fnt in ipairs(list) do fnt = path_normalize(fnt) - load_font(fnt, fontnames, status, newfontnames, newstatus, texmf) + load_font(fnt, fontnames, newfontnames, texmf) end end -local function scan_texmf_tree(fontnames, status, newfontnames, newstatus) +-- Temporary until a general solution is implemented: +if os.name == "macosx" then + ignore_fonts = { + -- this font kills the indexer: + "/System/Library/Fonts/LastResort.ttf" + } + function remove_ignore_fonts(fonts) + for N,fnt in ipairs(fonts) do + if table.contains(ignore_fonts,fnt) then + if trace_search then + logs.report("ignoring font '%s'", fnt) + end + table.remove(fonts,N) + end + end + return fonts + end +-- This function is only necessary, for now, on Mac OS X. +else + function remove_ignore_fonts(fonts) + return fonts + end +end + +local function scan_texmf_fonts(fontnames, newfontnames) --[[ The function that scans all fonts in the texmf tree, through kpathsea variables OPENTYPEFONTS and TTFONTS of texmf.cnf @@ -386,110 +587,149 @@ local function scan_texmf_tree(fontnames, status, newfontnames, newstatus) else logs.report("scanning TEXMF and OS fonts:") end + elseif trace_short then + if expandpath("$OSFONTDIR"):is_empty() then + logs.info("scanning TEXMF fonts...") + else + logs.info("scanning TEXMF and OS fonts...") + end end - local fontdirs = expandpath("$OPENTYPEFONTS") - fontdirs = fontdirs .. gsub(expandpath("$TTFONTS"), "^\.", "") + local fontdirs = expandpath("$OPENTYPEFONTS"):gsub("^\.", "") + fontdirs = fontdirs .. expandpath("$TTFONTS"):gsub("^\.", "") if not fontdirs:is_empty() then - local explored_dirs = {} fontdirs = splitpath(fontdirs) - -- hack, don't scan current dir - table.remove(fontdirs, 1) count = 0 for _,d in ipairs(fontdirs) do - if not explored_dirs[d] then - count = count + 1 - progress(count, #fontdirs) - scan_dir(d, fontnames, status, newfontnames, newstatus, false, true) - explored_dirs[d] = true - end + count = count + 1 + progress(count, #fontdirs) + scan_dir(d, fontnames, newfontnames, true) end end end -local function read_fcdata(data) - --[[ - this function takes raw data returned by fc-list, parses it, normalizes the - paths and makes a list out of it. - --]] - local list = { } - for line in data:lines() do - line = line:gsub(": ", "") - local ext = lower(match(line,"^.+%.([^/\\]-)$")) - if ext == "otf" or ext == "ttf" or ext == "ttc" or ext == "dfont" then - list[#list+1] = path_normalize(line:gsub(": ", "")) - end - end - return list -end +--[[ + For the OS fonts, there are several options: + - if OSFONTDIR is set (which is the case under windows by default but + not on the other OSs), it scans it at the same time as the texmf tree, + in the scan_texmf_fonts. + - if not: + - under Windows and Mac OSX, we take a look at some hardcoded directories + - under Unix, we read /etc/fonts/fonts.conf and read the directories in it -local function scan_os_fonts(fontnames, status, newfontnames, newstatus) - --[[ - This function scans the OS fonts through fontcache (fc-list), it executes - only if OSFONTDIR is empty (which is the case under most Unix by default). - If OSFONTDIR is non-empty, this means that the system fonts it contains have - already been scanned, and thus we don't scan them again. - --]] - if expandpath("$OSFONTDIR"):is_empty() then - if trace_progress then - logs.report("scanning OS fonts:") - end - if trace_search then - logs.report("executing 'fc-list : file' and parsing its result...") - end - local data = io.popen("fc-list : file", 'r') - local list = read_fcdata(data) - data:close() - if trace_search then - logs.report("%d fonts found", #list) - end - count = 0 - for _,fnt in ipairs(list) do - count = count + 1 - progress(count, #list) - load_font(fnt, fontnames, status, newfontnames, newstatus, false) + This means that if you have fonts in fancy directories, you need to set them + in OSFONTDIR. +]] + +--[[ + This function parses /etc/fonts/fonts.conf and returns all the dir it finds. + The code is minimal, please report any error it may generate. +]] + +local function read_fonts_conf() + local f = io.open("/etc/fonts/fonts.conf") + if not f then + error("Cannot open the file /etc/fonts/fonts.conf") + end + local results = {} + local incomments = false + for line in f:lines() do + -- spaghetti code... hmmm... + if incomments and sub(line, 1, 3) == '-->' then + incomments = false + elseif sub(line, 1, 4) == '<!--' then + incomments = true + else + for dir in gmatch(line, '<dir>([^<]+)</dir>') do + -- now we need to replace ~ by kpse.expand_path('~') + if sub(dir, 1, 1) == '~' then + dir = kpse.expand_path('~') .. sub(dir, 2) + end + results[#results+1] = dir + end end end + f:close() + return results end -local function fontnames_init() - return { - mappings = { }, - version = names.version, - } +local function get_os_dirs() + if os.name == 'macosx' then + return { + kpse.expand_path('~') .. "/Library/Fonts", + "/Library/Fonts", + "/System/Library/Fonts", + "/Network/Library/Fonts", + } + elseif os.type == "windows" or os.type == "msdos" or os.name == "cygwin" then + local windir = os.getenv("WINDIR") + return {windir..'\\Fonts',} + else + return read_fonts_conf() + end end -local function status_init() - return { - version = names.version, - } +local function scan_os_fonts(fontnames, newfontnames) + --[[ + This function scans the OS fonts through + - fontcache for Unix (reads the fonts.conf file and scans the directories) + - a static set of directories for Windows and MacOSX + --]] + if trace_progress then + logs.report("scanning OS fonts:") + elseif trace_short then + logs.info("scanning OS fonts...") + end + if trace_search then + logs.info("searching in static system directories...") + end + count = 0 + local os_dirs = get_os_dirs() + for _,d in ipairs(os_dirs) do + count = count + 1 + progress(count, #os_dirs) + scan_dir(d, fontnames, newfontnames, false) + end end -local function update(fontnames, status, force) +local function update_names(fontnames, force) --[[ The main function, scans everything - fontnames is the final table to return - force is whether we rebuild it from scratch or not - - status is a table containing the current status of the database --]] + if trace_short then + logs.info("Updating the font names database:") + end if force then fontnames = fontnames_init() - status = status_init() else - if not fontnames or not fontnames.version or fontnames.version ~= names.version - or not status or not status.version or status.version ~= names.version then + if not fontnames + or not fontnames.version + or fontnames.version ~= names.version then fontnames = fontnames_init() - status = status_init() if trace_search then - logs.report("no font names database or old one found, generating new one") + logs.report("no font names database or old one found, " + .."generating new one") end end end local newfontnames = fontnames_init() - local newstatus = status_init() - scan_texmf_tree(fontnames, status, newfontnames, newstatus) - scan_os_fonts (fontnames, status, newfontnames, newstatus) - return newfontnames, newstatus + scan_texmf_fonts(fontnames, newfontnames) + if expandpath("$OSFONTDIR"):is_empty() then + scan_os_fonts(fontnames, newfontnames) + end + return newfontnames +end + +local function save_names(fontnames) + local savepath = names.path.localdir + if not lfs.isdir(savepath) then + dir.mkdirs(savepath) + end + io.savedata(savepath .. names.path.basename, + table.serialize(fontnames, true)) end names.scan = scan_dir -names.update = update +names.update = update_names +names.save = save_names |