diff options
Diffstat (limited to 'otfl-font-nms.lua')
-rw-r--r-- | otfl-font-nms.lua | 307 |
1 files changed, 195 insertions, 112 deletions
diff --git a/otfl-font-nms.lua b/otfl-font-nms.lua index 8e89ea5..13921a2 100644 --- a/otfl-font-nms.lua +++ b/otfl-font-nms.lua @@ -6,6 +6,43 @@ if not modules then modules = { } end modules ['font-nms'] = { license = "GNU GPL v2" } +--- Luatex builtins +local dofile = dofile +local load = load +local next = next +local pcall = pcall +local require = require +local tonumber = tonumber + +local iolines = io.lines +local ioopen = io.open +local kpseexpand_path = kpse.expand_path +local mathabs = math.abs +local stringfind = string.find +local stringformat = string.format +local stringgmatch = string.gmatch +local stringgsub = string.gsub +local stringlower = string.lower +local stringsub = string.sub +local stringupper = string.upper +local tableinsert = table.insert +local texiowrite_nl = texio.write_nl +local utf8gsub = unicode.utf8.gsub +local utf8lower = unicode.utf8.lower + +--- these come from Lualibs/Context +local dirglob = dir.glob +local filebasename = file.basename +local filecollapsepath = file.collapsepath +local fileextname = file.extname +local filejoin = file.join +local filereplacesuffix = file.replacesuffix +local filesplitpath = file.splitpath +local stringis_empty = string.is_empty +local stringsplit = string.split +local stringstrip = string.strip + +--- the font loader namespace is “fonts”, same as in Context fonts = fonts or { } fonts.names = fonts.names or { } @@ -15,12 +52,13 @@ names.version = 2.2 -- not the same as in context names.data = nil names.path = { basename = "otfl-names.lua", - dir = file.join(kpse.expand_var("$TEXMFVAR"), names_dir), + dir = filejoin(kpse.expand_var("$TEXMFVAR"), names_dir), } local success = pcall(require, "luatexbase.modutils") if success then - success = pcall(luatexbase.require_module, "lualatex-platform", "2011/03/30") + success = pcall(luatexbase.require_module, + "lualatex-platform", "2011/03/30") end local get_installed_fonts if success then @@ -30,27 +68,21 @@ else end end -local splitpath, collapsepath = file.splitpath, file.collapsepath -local expandpath = kpse.expand_path -local glob, basename = dir.glob, file.basename -local extname = file.extname -local upper, lower, format = string.upper, string.lower, string.format -local gsub, match, rpadd = string.gsub, string.match, string.rpadd -local gmatch, sub, find = string.gmatch, string.sub, string.find -local utfgsub = unicode.utf8.gsub -local tableinsert = table.insert +--[[doc-- +Auxiliary functions +--doc]]-- + local report = logs.names_report -local function sanitize(str) - if str then - return utfgsub(lower(str), "[^%a%d]", "") - else - return str -- nil +local sanitize_string = function (str) + if str ~= nil then + return utf8gsub(utf8lower(str), "[^%a%d]", "") end + return nil end -local function fontnames_init() +local fontnames_init = function ( ) return { mappings = { }, status = { }, @@ -58,22 +90,61 @@ local function fontnames_init() } end -local function make_name(path) - return file.replacesuffix(path, "lua"), file.replacesuffix(path, "luc") +local make_name = function (path) + return filereplacesuffix(path, "lua"), filereplacesuffix(path, "luc") end -local function load_names() - local path = file.join(names.path.dir, names.path.basename) - local luaname, lucname = make_name(path) - local foundname - local data - if file.isreadable(lucname) then - data = dofile(lucname) - foundname = lucname - elseif file.isreadable(luaname) then - data = dofile(luaname) - foundname = luaname +--- When loading a lua file we try its binary complement first, which +--- is assumed to be located at an identical path, carrying the suffix +--- .luc. +--- Furthermore, we memoize loaded files along the way to avoid +--- duplication. + +local code_cache = { } + +--- string -> (string * table) +local load_lua_file = function (path) + local code = code_cache[path] + if code then return path, code() end + + local foundname = filereplacesuffix(path, "luc") + + local fh = ioopen(foundname, "rb") -- try bin first + if fh then + local chunk = fh:read"*all" + fh:close() + code = load(chunk, "b") end + + if not code then --- fall back to text file + foundname = filereplacesuffix(path, "lua") + fh = ioopen(foundname, "rb") + if fh then + local chunk = fh:read"*all" + fh:close() + code = load(chunk, "t") + end + end + + if not code then return nil, nil end + + code_cache[path] = code --- insert into memo + return foundname, code() +end + +--- define locals in scope +local load_names +local save_names +local scan_external_dir +local update_names +local read_fonts_conf +local resolve + + +load_names = function ( ) + local path = filejoin(names.path.dir, names.path.basename) + local foundname, data = load_lua_file(path) + if data then report("info", 0, "Font names database loaded", "%s", foundname) else @@ -83,22 +154,27 @@ local function load_names() data = names.update(fontnames_init()) names.save(data) end - texio.write_nl("") + texiowrite_nl("") return data end local synonyms = { - regular = { "normal", "roman", "plain", "book", "medium" }, + regular = { "normal", "roman", + "plain", "book", + "medium" }, --- TODO note from Élie Roux --- boldregular was for old versions of Linux Libertine, is it still useful? --- semibold is in new versions of Linux Libertine, but there is also a bold, --- not sure it's useful here... - bold = { "demi", "demibold", "semibold", "boldregular" }, - italic = { "regularitalic", "normalitalic", "oblique", "slanted" }, + bold = { "demi", "demibold", + "semibold", "boldregular", }, + italic = { "regularitalic", "normalitalic", + "oblique", "slanted", }, bolditalic = { - "boldoblique", "boldslanted", "demiitalic", "demioblique", - "demislanted", "demibolditalic", "semibolditalic" - }, + "boldoblique", "boldslanted", + "demiitalic", "demioblique", + "demislanted", "demibolditalic", + "semibolditalic", }, } local loaded = false @@ -114,10 +190,9 @@ font database created by the mkluatexfontdb script. --doc]]-- -local resolve resolve = function (_,_,specification) -- the 1st two parameters are used by ConTeXt - inspect(specification) - local name = sanitize(specification.name) - local style = sanitize(specification.style) or "regular" +resolve = function (_,_,specification) -- the 1st two parameters are used by ConTeXt + local name = sanitize_string(specification.name) + local style = sanitize_string(specification.style) or "regular" local size if specification.optsize then @@ -128,7 +203,7 @@ local resolve resolve = function (_,_,specification) -- the 1st two parameters a if not loaded then - names.data = names.load() + names.data = load_names() loaded = true end @@ -137,12 +212,12 @@ local resolve resolve = function (_,_,specification) -- the 1st two parameters a if data.mappings then local found = { } for _,face in next, data.mappings do - local family = sanitize(face.names and face.names.family) - local subfamily = sanitize(face.names and face.names.subfamily) - local fullname = sanitize(face.names and face.names.fullname) - local psname = sanitize(face.names and face.names.psname) - local fontname = sanitize(face.fontname) - local pfullname = sanitize(face.fullname) + local family = sanitize_string(face.names and face.names.family) + local subfamily = sanitize_string(face.names and face.names.subfamily) + local fullname = sanitize_string(face.names and face.names.fullname) + local psname = sanitize_string(face.names and face.names.psname) + local fontname = sanitize_string(face.fontname) + local pfullname = sanitize_string(face.fullname) local optsize, dsnsize, maxsize, minsize if #face.size > 0 then optsize = face.size @@ -219,7 +294,7 @@ local resolve resolve = function (_,_,specification) -- the 1st two parameters a local least = math.huge -- initial value is infinity for i,face in next, found do local dsnsize = face.size[1]/10 - local difference = math.abs(dsnsize-size) + local difference = mathabs(dsnsize-size) if difference < least then closest = face least = difference @@ -261,9 +336,6 @@ local resolve resolve = function (_,_,specification) -- the 1st two parameters a end end -names.resolve = resolve --- replace the resolver from luatex-fonts -names.resolvespec = resolve - local function font_fullinfo(filename, subfont, texmf) local t = { } local f = fontloader.open(filename, subfont) @@ -304,7 +376,7 @@ local function font_fullinfo(filename, subfont, texmf) t.fontname = m.fontname t.fullname = m.fullname t.familyname = m.familyname - t.filename = { texmf and basename(filename) or filename, subfont } + t.filename = { texmf and filebasename(filename) or filename, subfont } t.weight = m.pfminfo.weight t.width = m.pfminfo.width t.slant = m.italicangle @@ -322,10 +394,11 @@ local function load_font(filename, fontnames, newfontnames, texmf) local newstatus = newfontnames.status local mappings = fontnames.mappings local status = fontnames.status - local basefile = texmf and basename(filename) or filename + local basename = filebasename(filename) + local basefile = texmf and basename or filename if filename then if names.blacklist[filename] or - names.blacklist[basename(filename)] then + names.blacklist[basename] then report("log", 2, "ignoring font", "%s", filename) return end @@ -333,7 +406,7 @@ local function load_font(filename, fontnames, newfontnames, texmf) db_timestamp = status[basefile] and status[basefile].timestamp timestamp = lfs.attributes(filename, "modification") - local index_status = newstatus[basefile] or (not texmf and newstatus[basename(filename)]) + local index_status = newstatus[basefile] or (not texmf and newstatus[basename]) if index_status and index_status.timestamp == timestamp then -- already indexed this run return @@ -399,23 +472,23 @@ local function path_normalize(path) - using kpse.readable_file on Win32 ]] if os.type == "windows" or os.type == "msdos" or os.name == "cygwin" then - path = path:gsub('\\', '/') - path = path:lower() - path = path:gsub('^/cygdrive/(%a)/', '%1:/') + path = stringgsub(path, '\\', '/') + path = stringlower(path) + path = stringgsub(path, '^/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) + elseif kpse.readable_file(filejoin(file.dirname(path), dest)) then + path = filejoin(file.dirname(path), dest) else -- broken symlink? end end end - path = collapsepath(path) + path = filecollapsepath(path) return path end @@ -432,15 +505,16 @@ local function read_blacklist() if files and type(files) == "table" then for _,v in next, files do - for line in io.lines(v) do - line = line:strip() -- to get rid of lines like " % foo" - if line:find("^%%") or line:is_empty() then + for line in iolines(v) do + line = stringstrip(line) -- to get rid of lines like " % foo" + local first_chr = stringsub(line, 1, 1) --- faster than find + if first_chr == "%" or stringis_empty(line) then -- comment or empty line else - line = line:split("%")[1] - line = line:strip() - if string.sub(line,1,1) == "-" then - whitelist[string.sub(line,2,-1)] = true + line = stringsplit(line, "%")[1] + line = stringstrip(line) + if stringsub(line, 1, 1) == "-" then + whitelist[stringsub(line, 2, -1)] = true else report("log", 2, "blacklisted file", "%s", line) blacklist[line] = true @@ -473,7 +547,7 @@ local function scan_installed_fonts(fontnames, newfontnames) for key, value in next, fonts do local file = value.path if file then - local ext = extname(file) + local ext = fileextname(file) if ext and font_extensions_set[ext] then file = path_normalize(file) report("log", 1, "loading font", "%s", file) @@ -498,8 +572,8 @@ local function scan_dir(dirname, fontnames, newfontnames, texmf) local nbfound = 0 report("log", 2, "scanning", "%s", dirname) for _,i in next, font_extensions do - for _,ext in next, { i, upper(i) } do - found = glob(format("%s/**.%s$", dirname, ext)) + for _,ext in next, { i, stringupper(i) } do + found = dirglob(stringformat("%s/**.%s$", dirname, ext)) -- note that glob fails silently on broken symlinks, which happens -- sometimes in TeX Live. report("log", 2, "fonts found", "%s '%s' fonts found", #found, ext) @@ -521,15 +595,15 @@ local function scan_texmf_fonts(fontnames, newfontnames) This function scans all fonts in the texmf tree, through kpathsea variables OPENTYPEFONTS and TTFONTS of texmf.cnf ]] - if expandpath("$OSFONTDIR"):is_empty() then + if stringis_empty(kpseexpand_path("$OSFONTDIR")) then report("info", 0, "Scanning TEXMF fonts...") else report("info", 0, "Scanning TEXMF and OS fonts...") end - local fontdirs = expandpath("$OPENTYPEFONTS"):gsub("^%.", "") - fontdirs = fontdirs .. expandpath("$TTFONTS"):gsub("^%.", "") - if not fontdirs:is_empty() then - for _,d in next, splitpath(fontdirs) do + local fontdirs = stringgsub(kpseexpand_path("$OPENTYPEFONTS"), "^%.", "") + fontdirs = fontdirs .. stringgsub(kpseexpand_path("$TTFONTS"), "^%.", "") + if not stringis_empty(fontdirs) then + for _,d in next, filesplitpath(fontdirs) do scan_dir(d, fontnames, newfontnames, true) end end @@ -548,71 +622,76 @@ end in OSFONTDIR. ]] -local read_fonts_conf read_fonts_conf = function (path, results, passed_paths) +--- (string -> tab -> tab -> tab) +read_fonts_conf = function (path, results, passed_paths) --[[ - 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. + 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 f = io.open(path) + local fh = ioopen(path) tableinsert(passed_paths, path) - if not f then + if not fh then report("log", 2, "cannot open file", "%s", path) return results end local incomments = false - for line in f:lines() do + for line in fh:lines() do while line and line ~= "" do -- spaghetti code... hmmm... if incomments then - local tmp = find(line, '-->') + local tmp = stringfind(line, '-->') --- wtf? if tmp then incomments = false - line = sub(line, tmp+3) + line = stringsub(line, tmp+3) else line = nil end else - local tmp = find(line, '<!--') + local tmp = stringfind(line, '<!--') local newline = line if tmp then -- for the analysis, we take everything that is before the -- comment sign - newline = sub(line, 1, tmp-1) + newline = stringsub(line, 1, tmp-1) -- and we loop again with the comment incomments = true - line = sub(line, tmp+4) + line = stringsub(line, tmp+4) else -- if there is no comment start, the block after that will -- end the analysis, we exit the while loop line = nil end - for dir in gmatch(newline, '<dir>([^<]+)</dir>') do + for dir in stringgmatch(newline, '<dir>([^<]+)</dir>') do -- now we need to replace ~ by kpse.expand_path('~') - if sub(dir, 1, 1) == '~' then - dir = file.join(kpse.expand_path('~'), sub(dir, 2)) + if stringsub(dir, 1, 1) == '~' then + dir = filejoin(kpseexpand_path('~'), stringsub(dir, 2)) end -- we exclude paths with texmf in them, as they should be -- found anyway - if not find(dir, 'texmf') then + if not stringfind(dir, 'texmf') then results[#results+1] = dir end end - for include in gmatch(newline, '<include[^<]*>([^<]+)</include>') do + for include in stringgmatch(newline, '<include[^<]*>([^<]+)</include>') do -- include here can be four things: a directory or a file, -- in absolute or relative path. - if sub(include, 1, 1) == '~' then - include = file.join(kpse.expand_path('~'),sub(include, 2)) + if stringsub(include, 1, 1) == '~' then + include = filejoin(kpseexpand_path('~'),stringsub(include, 2)) -- First if the path is relative, we make it absolute: elseif not lfs.isfile(include) and not lfs.isdir(include) then - include = file.join(file.dirname(path), include) + include = filejoin(file.dirname(path), include) end - if lfs.isfile(include) and kpse.readable_file(include) and not table.contains(passed_paths, include) then + if lfs.isfile(include) + and kpse.readable_file(include) + and not table.contains(passed_paths, include) + then -- maybe we should prevent loops here? -- we exclude path with texmf in them, as they should -- be found otherwise read_fonts_conf(include, results, passed_paths) elseif lfs.isdir(include) then - for _,f in next, glob(file.join(include, "*.conf")) do + for _,f in next, dirglob(filejoin(include, "*.conf")) do read_fonts_conf(f, results, passed_paths) end end @@ -620,7 +699,7 @@ local read_fonts_conf read_fonts_conf = function (path, results, passed_paths) end end end - f:close() + fh:close() return results end @@ -630,14 +709,14 @@ names.read_fonts_conf = read_fonts_conf local function get_os_dirs() if os.name == 'macosx' then return { - file.join(kpse.expand_path('~'), "Library/Fonts"), + filejoin(kpseexpand_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 { file.join(windir, 'Fonts') } + return { filejoin(windir, 'Fonts') } else for _,p in next, {"/usr/local/etc/fonts/fonts.conf", "/etc/fonts/fonts.conf"} do if lfs.isfile(p) then @@ -661,7 +740,7 @@ local function scan_os_fonts(fontnames, newfontnames) end end -local function update_names(fontnames, force) +update_names = function (fontnames, force) --[[ The main function, scans everything - fontnames is the final table to return @@ -673,7 +752,7 @@ local function update_names(fontnames, force) fontnames = fontnames_init() else if not fontnames then - fontnames = names.load() + fontnames = load_names() end if fontnames.version ~= names.version then fontnames = fontnames_init() @@ -686,18 +765,18 @@ local function update_names(fontnames, force) installed_font_scanned = false scan_installed_fonts(fontnames, newfontnames) scan_texmf_fonts(fontnames, newfontnames) - if not installed_fonts_scanned and expandpath("$OSFONTDIR"):is_empty() then + if not installed_fonts_scanned and stringis_empty(kpseexpand_path("$OSFONTDIR")) then scan_os_fonts(fontnames, newfontnames) end return newfontnames end -local function save_names(fontnames) +save_names = function (fontnames) local path = names.path.dir if not lfs.isdir(path) then dir.mkdirs(path) end - path = file.join(path, names.path.basename) + path = filejoin(path, names.path.basename) if file.iswritable(path) then local luaname, lucname = make_name(path) table.tofile(luaname, fontnames, true) @@ -710,12 +789,12 @@ local function save_names(fontnames) end end -local function scan_external_dir(dir) +scan_external_dir = function (dir) local old_names, new_names if loaded then old_names = names.data else - old_names = names.load() + old_names = load_names() loaded = true end new_names = table.copy(old_names) @@ -723,13 +802,17 @@ local function scan_external_dir(dir) names.data = new_names end +--- export functionality to the namespace “fonts.names” names.scan = scan_external_dir names.load = load_names names.update = update_names names.save = save_names --- dummy -function fonts.names.getfilename(askedname,suffix) -- only supported in mkiv - return "" -end +names.resolve = resolve --- replace the resolver from luatex-fonts +names.resolvespec = resolve + +--- dummy required by luatex-fonts (cf. luatex-fonts-syn.lua) + +fonts.names.getfilename = function (askedname,suffix) return "" end + -- vim:tw=71:sw=4:ts=4:expandtab |