summaryrefslogtreecommitdiff
path: root/luaotfload-legacy-database.lua
diff options
context:
space:
mode:
authorPhilipp Gesang <phg42.2a@gmail.com>2013-05-11 16:14:36 +0200
committerPhilipp Gesang <phg42.2a@gmail.com>2013-05-11 16:14:36 +0200
commit3da43df46bb3ddc265ddedcf6bbb732d788ab269 (patch)
tree4c836735fb8e2c7d7c0d7a61521a4a2b60cb8680 /luaotfload-legacy-database.lua
parent0004b213f99a77411c03811e3324c9a1e934e31a (diff)
parent78b43966b0e7f28137d971b3f6921beca473e966 (diff)
downloadluaotfload-3da43df46bb3ddc265ddedcf6bbb732d788ab269.tar.gz
Merge ../luaotfload-compat
Diffstat (limited to 'luaotfload-legacy-database.lua')
-rw-r--r--luaotfload-legacy-database.lua724
1 files changed, 724 insertions, 0 deletions
diff --git a/luaotfload-legacy-database.lua b/luaotfload-legacy-database.lua
new file mode 100644
index 0000000..4af05f5
--- /dev/null
+++ b/luaotfload-legacy-database.lua
@@ -0,0 +1,724 @@
+if not modules then modules = { } end modules ['font-nms'] = {
+ version = "old",
+ comment = "companion to luaotfload.lua",
+ author = "Khaled Hosny and Elie Roux",
+ copyright = "Luaotfload Development Team",
+ license = "GNU GPL v2"
+}
+
+fonts = fonts or { }
+fonts.names = fonts.names or { }
+
+local names = fonts.names
+local names_dir = "luatex-cache/generic/names"
+names.version = "old" -- not the same as in context
+names.data = nil
+names.path = {
+ basename = "otfl-names.lua", --- different from current
+ localdir = file.join(kpse.expand_var("$TEXMFVAR"), names_dir),
+ systemdir = file.join(kpse.expand_var("$TEXMFSYSVAR"), names_dir),
+}
+
+
+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, 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
+ end
+end
+
+local function fontnames_init()
+ return {
+ mappings = { },
+ status = { },
+ version = names.version,
+ }
+end
+
+local function load_names()
+ local localpath = file.join(names.path.localdir, names.path.basename)
+ local systempath = file.join(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
+ logs.info("Font names database loaded: " .. foundname)
+ else
+ logs.info([[Font names database not found, generating new one.
+ This can take several minutes; please be patient.]])
+ data = names.update(fontnames_init())
+ names.save(data)
+ end
+ return data
+end
+
+local synonyms = {
+ regular = { "normal", "roman", "plain", "book", "medium" },
+ -- 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
+
+function names.resolve(specification)
+ 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
+ end
+
+ local data = names.data
+ if type(data) == "table" and data.version == names.version then
+ if data.mappings then
+ local found = { }
+ for _,face in next, data.mappings do
+ local family, subfamily, fullname, psname
+ local optsize, dsnsize, maxsize, minsize
+
+ if face.names then
+ family = sanitize(face.names.family)
+ subfamily = sanitize(face.names.subfamily)
+ fullname = sanitize(face.names.fullname)
+ psname = sanitize(face.names.psname)
+ end
+ local fontname = sanitize(face.fontname)
+ local pfullname = sanitize(face.fullname)
+ if #face.size > 0 then
+ optsize = face.size
+ dsnsize = optsize[1] and optsize[1] / 10
+ -- 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
+ found[1] = face
+ break
+ else
+ found[#found+1] = face
+ end
+ else
+ found[1] = face
+ break
+ end
+ elseif synonyms[style] and
+ table.contains(synonyms[style], subfamily) then
+ if optsize then
+ if dsnsize == size
+ or (size > minsize and size <= maxsize) then
+ found[1] = face
+ break
+ else
+ found[#found+1] = face
+ end
+ else
+ found[1] = face
+ break
+ end
+ elseif subfamily == "regular" or
+ table.contains(synonyms.regular, subfamily) then
+ found.fallback = face
+ elseif 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
+ found[1] = face
+ break
+ else
+ found[#found+1] = face
+ end
+ else
+ found[1] = face
+ break
+ end
+ end
+ else
+ 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
+ found[1] = face
+ break
+ else
+ found[#found+1] = face
+ end
+ else
+ found[1] = face
+ break
+ end
+ end
+ end
+ 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]
+ elseif lfs.isfile(found[1].found_at) then
+ logs.report("load font",
+ "font family='%s', subfamily='%s' found: %s",
+ name, style, found[1].found_at)
+ return found[1].found_at, found[1].filename[2]
+ end
+ elseif #found > 1 then
+ -- we found matching font(s) but not in the requested optical
+ -- sizes, so we loop through the matches to find the one with
+ -- least difference from the requested size.
+ local closest
+ 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)
+ 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]
+ elseif lfs.isfile(closest.found_at) then
+ logs.report("load font",
+ "font family='%s', subfamily='%s' found: %s",
+ name, style, closest.found_at)
+ return closest.found_at, closest.filename[2]
+ end
+ elseif found.fallback then
+ return found.fallback.filename[1], found.fallback.filename[2]
+ 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)
+ 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
+ end
+end
+
+names.resolvespec = names.resolve
+
+function names.set_log_level(level)
+ if level == 2 then
+ trace_loading = true
+ elseif level >= 3 then
+ trace_loading = true
+ trace_search = true
+ end
+end
+
+local lastislog = 0
+
+local function log(fmt, ...)
+ lastislog = 1
+ texio.write_nl(format("luaotfload | %s", format(fmt,...)))
+ 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 found_at = filename
+ 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
+ return
+ end
+ local m = fontloader.to_table(f)
+ fontloader.close(f)
+ 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
+ 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
+ }
+ end
+ end
+ else
+ -- no names table, propably a broken font
+ if trace_loading then
+ logs.report("broken font rejected: %s", basefile)
+ end
+ 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
+ -- 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,
+ }
+ -- rather, waste space on paths
+ t.found_at = found_at
+ return t
+end
+
+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
+ if table.contains(names.blacklist, filename) or
+ table.contains(names.blacklist, basename(filename)) then
+ if trace_search then
+ logs.report("ignoring font '%s'", filename)
+ end
+ 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)])
+ 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 next, 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", basefile)
+ end
+ return
+ end
+ local info = fontloader.info(filename)
+ if info then
+ if type(info) == "table" and #info > 1 then
+ for i in next, info do
+ local fullinfo = font_fullinfo(filename, i-1, texmf)
+ if not fullinfo then
+ return
+ end
+ local index = newstatus[basefile].index[i]
+ if not index then
+ index = #newmappings+1
+ end
+ newmappings[index] = fullinfo
+ newstatus[basefile].index[i] = index
+ end
+ else
+ local fullinfo = font_fullinfo(filename, false, texmf)
+ if not fullinfo then
+ return
+ end
+ local index = newstatus[basefile].index[1]
+ if not index then
+ index = #newmappings+1
+ end
+ newmappings[index] = fullinfo
+ newstatus[basefile].index[1] = index
+ end
+ else
+ if trace_loading then
+ logs.report("failed to load %s", basefile)
+ end
+ end
+ end
+end
+
+local function path_normalize(path)
+ --[[
+ 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 = path:gsub('\\', '/')
+ path = path:lower()
+ 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
+
+names.blacklist = { }
+
+local function read_blacklist()
+ local files = {
+ kpse.lookup("otfl-blacklist.cnf", {all=true, format="tex"})
+ }
+ local blacklist = names.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
+ -- comment or empty line
+ else
+ line = line:split("%")[1]
+ line = line:strip()
+ if trace_search then
+ logs.report("blacklisted file: %s", line)
+ end
+ blacklist[#blacklist+1] = line
+ end
+ end
+ end
+ end
+end
+
+local font_extensions = { "otf", "ttf", "ttc", "dfont" }
+
+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
+ - texmf is a boolean saying if we are scanning a texmf directory
+ ]]
+ local list, found = { }, { }
+ local nbfound = 0
+ if trace_search then
+ logs.report("scanning '%s'", dirname)
+ end
+ for _,i in next, font_extensions do
+ for _,ext in next, { i, upper(i) } do
+ found = glob(format("%s/**.%s$", dirname, ext))
+ -- note that glob fails silently on broken symlinks, which happens
+ -- sometimes in TeX Live.
+ if trace_search then
+ logs.report("%s '%s' fonts found", #found, ext)
+ end
+ nbfound = nbfound + #found
+ table.append(list, found)
+ end
+ end
+ if trace_search then
+ logs.report("%d fonts found in '%s'", nbfound, dirname)
+ end
+
+ for _,file in next, list do
+ file = path_normalize(file)
+ if trace_loading then
+ logs.report("loading font: %s", file)
+ end
+ load_font(file, fontnames, newfontnames, texmf)
+ end
+end
+
+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...")
+ else
+ logs.info("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
+ scan_dir(d, fontnames, newfontnames, true)
+ end
+ end
+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.
+ - in addition:
+ - 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
+
+ This means that if you have fonts in fancy directories, you need to set them
+ in OSFONTDIR if they cannot be found by fontconfig.
+]]
+
+local function read_fonts_conf(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.
+ ]]
+ local f = io.open(path)
+ table.insert(passed_paths, path)
+ if not f then
+ logs.info("Warning: unable to read "..path.. ", skipping...")
+ return results
+ end
+ local incomments = false
+ for line in f:lines() do
+ while line and line ~= "" do
+ -- spaghetti code... hmmm...
+ if incomments then
+ local tmp = find(line, '-->')
+ if tmp then
+ incomments = false
+ line = sub(line, tmp+3)
+ else
+ line = nil
+ end
+ else
+ local tmp = find(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)
+ -- and we loop again with the comment
+ incomments = true
+ line = sub(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
+ -- now we need to replace ~ by kpse.expand_path('~')
+ if sub(dir, 1, 1) == '~' then
+ dir = file.join(kpse.expand_path('~'), sub(dir, 2))
+ end
+ -- we exclude paths with texmf in them, as they should be
+ -- found anyway
+ if not find(dir, 'texmf') then
+ results[#results+1] = dir
+ end
+ end
+ for include in gmatch(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))
+ -- 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)
+ end
+ if lfs.isfile(include) and kpse.readable_file(include) and not table.contains(passed_paths, include) then
+ -- 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
+ if not table.contains(passed_paths, f) then
+ read_fonts_conf(f, results, passed_paths)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ f:close()
+ return results
+end
+
+-- for testing purpose
+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"),
+ "/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 read_fonts_conf("/etc/fonts/fonts.conf", {}, {})
+ end
+end
+
+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
+ ]]
+ logs.info("Scanning OS fonts...")
+ if trace_search then
+ logs.info("Searching in static system directories...")
+ end
+ for _,d in next, get_os_dirs() do
+ scan_dir(d, fontnames, newfontnames, false)
+ end
+end
+
+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
+ ]]
+ logs.info("Updating the font names database:")
+
+ if force then
+ fontnames = fontnames_init()
+ else
+ if not fontnames then
+ fontnames = names.load()
+ 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
+ end
+ end
+ local newfontnames = fontnames_init()
+ read_blacklist()
+ scan_texmf_fonts(fontnames, newfontnames)
+ scan_os_fonts(fontnames, newfontnames)
+ return newfontnames
+end
+
+local function save_names(fontnames)
+ local savepath = names.path.localdir
+ if not lfs.isdir(savepath) then
+ dir.mkdirs(savepath)
+ end
+ savepath = file.join(savepath, names.path.basename)
+ if file.iswritable(savepath) then
+ table.tofile(savepath, fontnames, true)
+ logs.info("Font names database saved: %s \n", savepath)
+ return savepath
+ else
+ logs.info("Failed to save names database\n")
+ return nil
+ end
+end
+
+local function scan_external_dir(dir)
+ local old_names, new_names
+ if loaded then
+ old_names = names.data
+ else
+ old_names = names.load()
+ loaded = true
+ end
+ new_names = table.copy(old_names)
+ scan_dir(dir, old_names, new_names)
+ names.data = new_names
+end
+
+names.scan = scan_external_dir
+names.load = load_names
+names.update = update_names
+names.save = save_names
+
+-- vim:ft=lua:sw=4:ts=4:expandtab