diff options
author | Elie Roux <elie.roux@telecom-bretagne.eu> | 2013-04-18 18:09:20 +0200 |
---|---|---|
committer | Elie Roux <elie.roux@telecom-bretagne.eu> | 2013-04-18 18:09:20 +0200 |
commit | a9510469b60edeceedf72811e7737ea0f3c56dc5 (patch) | |
tree | 5c7a80ce3a0c2f1d22c12bff36f84355df554c46 /otfl-font-nms.lua | |
parent | e67643a60422ed265dc5ad8955a83140598385f1 (diff) | |
parent | b0c22678d1f776f991ffef67694451b8bb5f9e20 (diff) | |
download | luaotfload-a9510469b60edeceedf72811e7737ea0f3c56dc5.tar.gz |
Merge branch 'experimental' of phi-gamma/luaotfload into phi-gamma-experimental
Conflicts:
mkluatexfontdb.lua
otfl-basics-gen.lua
Diffstat (limited to 'otfl-font-nms.lua')
-rw-r--r-- | otfl-font-nms.lua | 869 |
1 files changed, 559 insertions, 310 deletions
diff --git a/otfl-font-nms.lua b/otfl-font-nms.lua index 094ebec..c987c3a 100644 --- a/otfl-font-nms.lua +++ b/otfl-font-nms.lua @@ -1,56 +1,107 @@ if not modules then modules = { } end modules ['font-nms'] = { - version = 1.002, + version = 2.2, comment = "companion to luaotfload.lua", author = "Khaled Hosny and Elie Roux", copyright = "Luaotfload Development Team", license = "GNU GPL v2" } +--- Luatex builtins +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 mathmin = math.min +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 tableconcat = table.concat +local tablecopy = table.copy +local tablesort = table.sort +local tabletofile = table.tofile +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 dirmkdirs = dir.mkdirds +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 +local tableappend = table.append +local tabletohash = table.tohash + +--- the font loader namespace is “fonts”, same as in Context fonts = fonts or { } fonts.names = fonts.names or { } local names = fonts.names local names_dir = "luatex-cache/generic/names" -names.version = 2.010 -- not the same as in context +names.version = 2.2 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") -end -local get_installed_fonts -if success then - get_installed_fonts = lualatex.platform.get_installed_fonts -else - function get_installed_fonts() - end -end -local splitpath, expandpath = file.split_path, 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 trace_short = false --tracing adapted to rebuilding of the database inside a document -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) - -local function sanitize(str) - if str then - return utfgsub(lower(str), "[^%a%d]", "") - else - return str -- nil +---- <FIXME> +--- +--- these lines load some binary module called “lualatex-platform” +--- that doesn’t appear to build with Lua 5.2. I’m going ahead and +--- disable it for the time being until someone clarifies what it +--- is supposed to do and whether we should care to fix it. +--- +--local success = pcall(require, "luatexbase.modutils") +--if success then +-- success = pcall(luatexbase.require_module, +-- "lualatex-platform", "2011/03/30") +-- print(success) +--end + +--local get_installed_fonts +--if success then +-- get_installed_fonts = lualatex.platform.get_installed_fonts +--else +-- function get_installed_fonts() +-- end +--end +---- </FIXME> + +local get_installed_fonts = nil + +--[[doc-- +Auxiliary functions +--doc]]-- + + +local report = logs.names_report + +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,47 +109,143 @@ 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. + +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 find_closest +local font_fullinfo +local load_names +local read_fonts_conf +local reload_db +local resolve +local save_names +local scan_external_dir +local update_names + +load_names = function ( ) + local path = filejoin(names.path.dir, names.path.basename) + local foundname, data = load_lua_file(path) + if data then - logs.info("Font names database loaded", "%s", foundname) + report("info", 0, "Font names database loaded", "%s", foundname) else - logs.info([[Font names database not found, generating new one. + report("info", 0, + [[Font names database not found, generating new one. This can take several minutes; please be patient.]]) - data = names.update(fontnames_init()) - names.save(data) + data = update_names(fontnames_init()) + save_names(data) end - texio.write_nl("") + texiowrite_nl"" return data end -local synonyms = { - regular = { "normal", "roman", "plain", "book", "medium" }, - bold = { "boldregular", "demi", "demibold" }, - italic = { "regularitalic", "normalitalic", "oblique", "slanted" }, - bolditalic = { "boldoblique", "boldslanted", "demiitalic", "demioblique", "demislanted", "demibolditalic" }, -} +local fuzzy_limit = 1 --- display closest only + +local style_synonyms = { set = { } } +do + style_synonyms.list = { + 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", }, + bolditalic = { "boldoblique", "boldslanted", + "demiitalic", "demioblique", + "demislanted", "demibolditalic", + "semibolditalic", }, + } -local loaded = false -local reloaded = false + for category, synonyms in next, style_synonyms.list do + style_synonyms.set[category] = tabletohash(synonyms, true) + end +end -function names.resolve(_,_,specification) -- the 1st two parameters are used by ConTeXt - local name = sanitize(specification.name) - local style = sanitize(specification.style) or "regular" +--- state of the database +local fonts_loaded = false +local fonts_reloaded = false + +--[[doc-- + +Luatex-fonts, the font-loader package luaotfload imports, comes with +basic file location facilities (see luatex-fonts-syn.lua). +However, the builtin functionality is too limited to be of more than +basic use, which is why we supply our own resolver that accesses the +font database created by the mkluatexfontdb script. + +--doc]]-- + + +--- +--- the request specification has the fields: +--- +--- · features: table +--- · normal: set of { ccmp clig itlc kern liga locl mark mkmk rlig } +--- · ??? +--- · forced: string +--- · lookup: "name" | "file" +--- · method: string +--- · name: string +--- · resolved: string +--- · size: int +--- · specification: string (== <lookup> ":" <name>) +--- · sub: string +--- +--- the return value of “resolve” is the file name of the requested +--- font + +--- 'a -> 'a -> table -> (string * string | bool * bool) +--- +--- note by phg: I added a third return value that indicates a +--- successful lookup as this cannot be inferred from the other +--- values. +--- +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 @@ -107,23 +254,33 @@ function names.resolve(_,_,specification) -- the 1st two parameters are used by size = specification.size / 65536 end - - if not loaded then - names.data = names.load() - loaded = true + if not fonts_loaded then + names.data = load_names() + fonts_loaded = true end local data = names.data - if type(data) == "table" and data.version == names.version then + if type(data) == "table" then + local db_version, nms_version = data.version, names.version + if data.version ~= names.version then + report("log", 0, "db", + [[version mismatch; expected %4.3f, got %4.3f]], + nms_version, db_version + ) + return reload_db(resolve, nil, nil, specification) + end if data.mappings then local found = { } + local synonym_set = style_synonyms.set 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) + --- TODO we really should store those in dedicated + --- .sanitized field + 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 @@ -146,8 +303,8 @@ function names.resolve(_,_,specification) -- the 1st two parameters are used by found[1] = face break end - elseif synonyms[style] and - table.contains(synonyms[style], subfamily) then + elseif synonym_set[style] and + synonym_set[style][subfamily] then if optsize then if dsnsize == size or (size > minsize and size <= maxsize) then @@ -161,7 +318,7 @@ function names.resolve(_,_,specification) -- the 1st two parameters are used by break end elseif subfamily == "regular" or - table.contains(synonyms.regular, subfamily) then + synonym_set.regular[subfamily] then found.fallback = face end else @@ -186,10 +343,11 @@ function names.resolve(_,_,specification) -- the 1st two parameters are used by end if #found == 1 then if kpse.lookup(found[1].filename[1]) then - logs.report("load font", - "font family='%s', subfamily='%s' found: %s", - name, style, found[1].filename[1]) - return found[1].filename[1], found[1].filename[2] + report("log", 0, "load font", + "font family='%s', subfamily='%s' found: %s", + name, style, found[1].filename[1] + ) + return found[1].filename[1], found[1].filename[2], true end elseif #found > 1 then -- we found matching font(s) but not in the requested optical @@ -199,152 +357,242 @@ function names.resolve(_,_,specification) -- the 1st two parameters are used by 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 end end if kpse.lookup(closest.filename[1]) then - logs.report("load font", - "font family='%s', subfamily='%s' found: %s", - name, style, closest.filename[1]) - return closest.filename[1], closest.filename[2] + report("log", 0, "load font", + "font family='%s', subfamily='%s' found: %s", + name, style, closest.filename[1] + ) + return closest.filename[1], closest.filename[2], true end elseif found.fallback then - return found.fallback.filename[1], found.fallback.filename[2] + return found.fallback.filename[1], found.fallback.filename[2], true end - -- 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) + --- no font found so far + if not fonts_reloaded then + --- last straw: try reloading the database + return reload_db(resolve, nil, nil, specification) else - -- else, fallback to filename - -- XXX: specification.name is empty with absolute paths, looks - -- like a bug in the specification parser - return specification.name, false + --- else, fallback to requested name + --- XXX: specification.name is empty with absolute paths, looks + --- like a bug in the specification parser + return specification.name, false, 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 + + else --- no db or outdated; reload names and retry + if not fonts_reloaded then + return reload_db(resolve, nil, nil, specification) + else --- unsucessfully reloaded; bail + return specification.name, false, false end end -end +end --- resolve() -names.resolvespec = names.resolve +--- when reload is triggered we update the database +--- and then re-run the caller with the arg list -function names.set_log_level(level) - if level == 2 then - trace_loading = true - elseif level >= 3 then - trace_loading = true - trace_search = true +--- ('a -> 'a) -> 'a list -> 'a +reload_db = function (caller, ...) + report("log", 1, "db", "reload initiated") + names.data = update_names() + save_names(names.data) + fonts_reloaded = true + return caller(...) +end + +--- string -> string -> int +local iterative_levenshtein = function (s1, s2) + + local costs = { } + local len1, len2 = #s1, #s2 + + for i = 0, len1 do + local last = i + for j = 0, len2 do + if i == 0 then + costs[j] = j + else + if j > 0 then + local current = costs[j-1] + if stringsub(s1, i, i) ~= stringsub(s2, j, j) then + current = mathmin(current, last, costs[j]) + 1 + end + costs[j-1] = last + last = current + end + end end + if i > 0 then costs[len2] = last end + end + + return costs[len2]--- lower right has the distance end -local lastislog = 0 +--- string -> int -> bool +find_closest = function (name, limit) + local name = sanitize_string(name) + limit = limit or fuzzy_limit -local function log(category, fmt, ...) - lastislog = 1 - if fmt then - texio.write_nl(format("luaotfload | %s: %s", category, format(fmt, ...))) - elseif category then - texio.write_nl(format("luaotfload | %s", category)) - else - texio.write_nl(format("luaotfload |")) + if not fonts_loaded then + names.data = load_names() + fonts_loaded = true end - io.flush() -end -logs = logs or { } -logs.report = logs.report or log -logs.info = logs.info or log - -local function font_fullinfo(filename, subfont, texmf) - local t = { } - local f = fontloader.open(filename, subfont) - if not f then - if trace_loading then - logs.report("error", "failed to open %s", filename) - end + local data = names.data + + if type(data) == "table" then + local by_distance = { } --- (int, string list) dict + local distances = { } --- int list + local cached = { } --- (string, int) dict + local mappings = data.mappings + local n_fonts = #mappings + + for n = 1, n_fonts do + local current = mappings[n] + local cnames = current.names + --[[ + This is simplistic but surpisingly fast. + Matching is performed against the “family” name + of a db record. We then store its “fullname” at + it edit distance. + We should probably do some weighting over all the + font name categories as well as whatever agrep + does. + --]] + if cnames then + local fullname, family = cnames.fullname, cnames.family + family = sanitize_string(family) + + local dist = cached[family]--- maybe already calculated + if not dist then + dist = iterative_levenshtein(name, family) + cached[family] = dist + end + local namelst = by_distance[dist] + if not namelst then --- first entry + namelst = { fullname } + distances[#distances+1] = dist + else --- append + namelst[#namelst+1] = fullname + end + by_distance[dist] = namelst + end + end + + --- print the matches according to their distance + local n_distances = #distances + if n_distances > 0 then --- got some data + tablesort(distances) + limit = mathmin(n_distances, limit) + report(false, 1, "query", + "displaying %d distance levels", limit) + + for i = 1, limit do + local dist = distances[i] + local namelst = by_distance[dist] + report(false, 0, "query", + "distance from “" .. name .. "”: " .. dist + .. "\n " .. tableconcat(namelst, "\n ") + ) + end + + return true + end + return false + else --- need reload + return reload_db(find_closest, name) + end + return false +end --- find_closest() + +--[[doc-- +The data inside an Opentype font file can be quite heterogeneous. +Thus in order to get the relevant information, parts of the original +table as returned by the font file reader need to be relocated. +--doc]]-- +font_fullinfo = function (filename, subfont, texmf) + local tfmdata = { } + local rawfont = fontloader.open(filename, subfont) + if not rawfont then + report("log", 1, "error", "failed to open %s", filename) return end - local m = fontloader.to_table(f) - fontloader.close(f) - collectgarbage('collect') + local metadata = fontloader.to_table(rawfont) + fontloader.close(rawfont) + collectgarbage("collect") -- see http://www.microsoft.com/typography/OTSPEC/features_pt.htm#size - if m.fontstyle_name then - for _,v in next, m.fontstyle_name do - if v.lang == 1033 then - t.fontstyle_name = v.name + if metadata.fontstyle_name then + for _, name in next, metadata.fontstyle_name do + if name.lang == 1033 then --- I hate magic numbers + tfmdata.fontstyle_name = name.name end end end - if m.names then - for _,v in next, 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, - family = v.names.preffamilyname or v.names.family, - subfamily= t.fontstyle_name or v.names.prefmodifiers or v.names.subfamily, - psname = v.names.postscriptname + if metadata.names then + for _, namedata in next, metadata.names do + if namedata.lang == "English (US)" then + tfmdata.names = { + --- see + --- https://developer.apple.com/fonts/TTRefMan/RM06/Chap6name.html + fullname = namedata.names.compatfull + or namedata.names.fullname, + family = namedata.names.preffamilyname + or namedata.names.family, + subfamily= tfmdata.fontstyle_name + or namedata.names.prefmodifiers + or namedata.names.subfamily, + psname = namedata.names.postscriptname } end end else -- no names table, propably a broken font - if trace_loading then - logs.report("broken font rejected", "%s", basefile) - end + report("log", 1, "broken font rejected", "%s", basefile) return end - t.fontname = m.fontname - t.fullname = m.fullname - t.familyname = m.familyname - t.filename = { texmf and basename(filename) or filename, subfont } - t.weight = m.pfminfo.weight - t.width = m.pfminfo.width - t.slant = m.italicangle + tfmdata.fontname = metadata.fontname + tfmdata.fullname = metadata.fullname + tfmdata.familyname = metadata.familyname + tfmdata.filename = { + texmf and filebasename(filename) or filename, + subfont + } + tfmdata.weight = metadata.pfminfo.weight + tfmdata.width = metadata.pfminfo.width + tfmdata.slant = metadata.italicangle -- don't waste the space with zero values - t.size = { - m.design_size ~= 0 and m.design_size or nil, - m.design_range_top ~= 0 and m.design_range_top or nil, - m.design_range_bottom ~= 0 and m.design_range_bottom or nil, + tfmdata.size = { + metadata.design_size ~= 0 and metadata.design_size or nil, + metadata.design_range_top ~= 0 and metadata.design_range_top or nil, + metadata.design_range_bottom ~= 0 and metadata.design_range_bottom or nil, } - return t + return tfmdata end -local function load_font(filename, fontnames, newfontnames, texmf) +local load_font = function (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 + local basename = filebasename(filename) + local basefile = texmf and basename or filename if filename then if names.blacklist[filename] or - names.blacklist[basename(filename)] then - if trace_search then - logs.report("ignoring font", "%s", filename) - end + names.blacklist[basename] then + report("log", 2, "ignoring font", "%s", filename) return end local timestamp, db_timestamp 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 @@ -360,9 +608,7 @@ local function load_font(filename, fontnames, newfontnames, texmf) newmappings[#newmappings+1] = mappings[v] newstatus[basefile].index[index+1] = #newmappings end - if trace_loading then - logs.report("font already indexed", "%s", basefile) - end + report("log", 1, "font already indexed", "%s", basefile) return end local info = fontloader.info(filename) @@ -397,9 +643,7 @@ local function load_font(filename, fontnames, newfontnames, texmf) newstatus[basefile].index[1] = index end else - if trace_loading then - logs.report("failed to load", "%s", basefile) - end + report("log", 1, "failed to load", "%s", basefile) end end end @@ -414,23 +658,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 = file.collapse_path(path) + path = filecollapsepath(path) return path end @@ -447,20 +691,20 @@ 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 + --- this is highly inefficient + line = stringsplit(line, "%")[1] + line = stringstrip(line) + if stringsub(line, 1, 1) == "-" then + whitelist[stringsub(line, 2, -1)] = true else - if trace_search then - logs.report("blacklisted file", "%s", line) - end - blacklist[line] = true + report("log", 2, "blacklisted file", "%s", line) + blacklist[line] = true end end end @@ -477,37 +721,34 @@ for key, value in next, font_extensions do font_extensions_set[value] = true end -local installed_fonts_scanned = false - -local function scan_installed_fonts(fontnames, newfontnames) - -- Try to query and add font list from operating system. - -- This uses the lualatex-platform module. - logs.info("Scanning fonts known to operating system...") - local fonts = get_installed_fonts() - if fonts and #fonts > 0 then - installed_fonts_scanned = true - if trace_search then - logs.report("operating system fonts found", "%d", #fonts) - end - for key, value in next, fonts do - local file = value.path - if file then - local ext = extname(file) - if ext and font_extensions_set[ext] then - file = path_normalize(file) - if trace_loading then - logs.report("loading font", "%s", file) - end - load_font(file, fontnames, newfontnames, false) - end - end - end - else - if trace_search then - logs.report("Could not retrieve list of installed fonts") - end - end -end +--local installed_fonts_scanned = false --- ugh + +--- we already have scan_os_fonts don’t we? + +--local function scan_installed_fonts(fontnames, newfontnames) +-- --- Try to query and add font list from operating system. +-- --- This uses the lualatex-platform module. +-- --- <phg>what for? why can’t we do this in Lua?</phg> +-- report("info", 0, "Scanning fonts known to operating system...") +-- local fonts = get_installed_fonts() +-- if fonts and #fonts > 0 then +-- installed_fonts_scanned = true +-- report("log", 2, "operating system fonts found", "%d", #fonts) +-- for key, value in next, fonts do +-- local file = value.path +-- if file then +-- local ext = fileextname(file) +-- if ext and font_extensions_set[ext] then +-- file = path_normalize(file) +-- report("log", 1, "loading font", "%s", file) +-- load_font(file, fontnames, newfontnames, false) +-- end +-- end +-- end +-- else +-- report("log", 2, "Could not retrieve list of installed fonts") +-- end +--end local function scan_dir(dirname, fontnames, newfontnames, texmf) --[[ @@ -519,30 +760,22 @@ local function scan_dir(dirname, fontnames, newfontnames, texmf) ]] local list, found = { }, { } local nbfound = 0 - if trace_search then - logs.report("scanning", "%s", dirname) - end + 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. - if trace_search then - logs.report("fonts found", "%s '%s' fonts found", #found, ext) - end + report("log", 2, "fonts found", "%s '%s' fonts found", #found, ext) nbfound = nbfound + #found - table.append(list, found) + tableappend(list, found) end end - if trace_search then - logs.report("fonts found", "%d fonts found in '%s'", nbfound, dirname) - end + report("log", 2, "fonts found", "%d fonts found in '%s'", nbfound, dirname) for _,file in next, list do file = path_normalize(file) - if trace_loading then - logs.report("loading font", "%s", file) - end + report("log", 1, "loading font", "%s", file) load_font(file, fontnames, newfontnames, texmf) end end @@ -552,15 +785,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 - logs.info("Scanning TEXMF fonts...") + if stringis_empty(kpseexpand_path("$OSFONTDIR")) then + report("info", 0, "Scanning TEXMF fonts...") else - logs.info("Scanning TEXMF and OS fonts...") + 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 @@ -579,101 +812,112 @@ end in OSFONTDIR. ]] -local function read_fonts_conf(path, results) +--- (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. + + TODO fonts.conf are some kind of XML so in theory the following + is totally inappropriate. Maybe a future version of the + lualibs will include the lxml-* files from Context so we + can write something presentable instead. ]] - local f = io.open(path) - if not f then - if trace_search then - logs.report("cannot open file", "%s", path) - end + local fh = ioopen(path) + passed_paths[#passed_paths+1] = path + passed_paths_set = tabletohash(passed_paths, true) + 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) then + if lfs.isfile(include) + and kpse.readable_file(include) + and not passed_paths_set[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) + read_fonts_conf(include, results, passed_paths) elseif lfs.isdir(include) then - for _,f in next, glob(file.join(include, "*.conf")) do - read_fonts_conf(f, results) + for _,f in next, dirglob(filejoin(include, "*.conf")) do + read_fonts_conf(f, results, passed_paths) end end end end end end - f:close() + fh:close() return results end -- for testing purpose names.read_fonts_conf = read_fonts_conf +--- TODO stuff those paths into some writable table 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') } - else + return { filejoin(windir, 'Fonts') } + else --- TODO what about ~/config/fontconfig/fonts.conf etc? for _,p in next, {"/usr/local/etc/fonts/fonts.conf", "/etc/fonts/fonts.conf"} do if lfs.isfile(p) then - return read_fonts_conf("/etc/fonts/fonts.conf", {}) + return read_fonts_conf("/etc/fonts/fonts.conf", {}, {}) end end end @@ -686,85 +930,90 @@ local function scan_os_fonts(fontnames, newfontnames) - fontcache for Unix (reads the fonts.conf file and scans the directories) - a static set of directories for Windows and MacOSX ]] - logs.info("Scanning OS fonts...") - if trace_search then - logs.info("Searching in static system directories...") - end + report("info", 0, "Scanning OS fonts...") + report("info", 2, "Searching in static system directories...") for _,d in next, get_os_dirs() do scan_dir(d, fontnames, newfontnames, false) 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 - force is whether we rebuild it from scratch or not ]] - logs.info("Updating the font names database") + report("info", 0, "Updating the font names database") if force then fontnames = fontnames_init() else if not fontnames then - fontnames = names.load() + fontnames = load_names() end if fontnames.version ~= names.version then fontnames = fontnames_init() - if trace_search then - logs.report("No font names database or old one found; " - .."generating new one") - end + report("log", 0, "No font names database or old one found; " + .."generating new one") end end local newfontnames = fontnames_init() read_blacklist() - installed_font_scanned = false - scan_installed_fonts(fontnames, newfontnames) + --installed_fonts_scanned = false + --scan_installed_fonts(fontnames, newfontnames) --- see fixme above 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")) + if 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) + dirmkdirs(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) + tabletofile(luaname, fontnames, true) caches.compile(fontnames,luaname,lucname) - logs.info("Font names database saved") + report("info", 0, "Font names database saved") return path else - logs.info("Failed to save names database") + report("info", 0, "Failed to save names database") return nil end end -local function scan_external_dir(dir) +scan_external_dir = function (dir) local old_names, new_names - if loaded then + if fonts_loaded then old_names = names.data else - old_names = names.load() - loaded = true + old_names = load_names() + fonts_loaded = true end - new_names = table.copy(old_names) + new_names = tablecopy(old_names) scan_dir(dir, old_names, new_names) 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 +names.find_closest = find_closest + +--- 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 |