diff options
Diffstat (limited to 'Master/texmf-dist/tex/luatex/luaotfload/luaotfload-database.lua')
-rw-r--r-- | Master/texmf-dist/tex/luatex/luaotfload/luaotfload-database.lua | 579 |
1 files changed, 272 insertions, 307 deletions
diff --git a/Master/texmf-dist/tex/luatex/luaotfload/luaotfload-database.lua b/Master/texmf-dist/tex/luatex/luaotfload/luaotfload-database.lua index b1d264f9b78..8f0ef41fc73 100644 --- a/Master/texmf-dist/tex/luatex/luaotfload/luaotfload-database.lua +++ b/Master/texmf-dist/tex/luatex/luaotfload/luaotfload-database.lua @@ -5,8 +5,8 @@ do -- block to avoid to many local variables error assert(luaotfload_module, "This is a part of luaotfload and should not be loaded independently") { name = "luaotfload-database", - version = "3.18", --TAGVERSION - date = "2021-05-21", --TAGDATE + version = "3.19", --TAGVERSION + date = "2022-02-24", --TAGDATE description = "luaotfload submodule / database", license = "GPL v2.0", author = "Khaled Hosny, Elie Roux, Philipp Gesang, Marcel Krüger", @@ -142,8 +142,7 @@ do end local otfhandler = fonts.handlers.otf or { } -local gzipload = gzip.load -local gzipsave = gzip.save +local gzipopen = gzip.open local iolines = io.lines local ioopen = io.open local kpseexpand_path = kpse.expand_path @@ -159,6 +158,7 @@ local mathmin = math.min local osgetenv = os.getenv local osgettimeofday = os.gettimeofday local osremove = os.remove +local dump = string.dump local stringfind = string.find local stringformat = string.format local stringgmatch = string.gmatch @@ -486,42 +486,47 @@ local function initialize_namedata (formats, created) } end +-- A helper to load a file which might be gziped +local function load_maybe_gzip (path, binary) + local gzippath = path .. '.gz' + local f = ioopen (gzippath, 'rb') + if f then + path = gzippath + local result = f:read'*a' + result = gzip.decompress(result, 31) + f:close() + return path, result + else + f, msg = ioopen (path, binary and 'rb' or 'r') + if f then + local result = f:read'*a' + f:close() + return path, result + end + end + + return nil, msg +end + --- 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. ---- string -> (string * table) -local function load_lua_file (path) - local foundname = filereplacesuffix (path, "luc") - local code = nil - - 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 --- probe gzipped file - foundname = filereplacesuffix (path, "lua.gz") - local chunk = gzipload (foundname) - if chunk then - code = load (chunk, "t") +--- string -> string -> (string * table) +local function load_lua_file (path_lua, path_luc) + local foundname, chunk = load_maybe_gzip (path_luc, true) + if foundname then + chunk = assert (load (chunk, 'b')) + else + foundname, chunk = load_maybe_gzip (path_lua, false) + if foundname then + chunk = assert (load (chunk, 't')) + else + return nil end end - if not code then return nil, nil end - return foundname, code () + return foundname, chunk() end --- define locals in scope @@ -543,7 +548,8 @@ local fuzzy_limit = 1 --- display closest only --- bool? -> -> bool? -> dbobj option local function load_names (dry_run, no_rebuild) local starttime = osgettimeofday () - local foundname, data = load_lua_file (config.luaotfload.paths.index_path_lua) + local foundname, data = load_lua_file (config.luaotfload.paths.index_path_lua, + config.luaotfload.paths.index_path_luc) if data then logreport ("log", 0, "db", @@ -624,7 +630,8 @@ end --- unit -> unit local function load_lookups ( ) - local foundname, data = load_lua_file(config.luaotfload.paths.lookup_path_lua) + local foundname, data = load_lua_file(config.luaotfload.paths.lookup_path_lua, + config.luaotfload.paths.lookup_path_luc) if data then logreport ("log", 0, "cache", "Lookup cache loaded from %s.", foundname) logreport ("term", 3, "cache", @@ -638,11 +645,12 @@ local function load_lookups ( ) end local regular_synonym = { - book = true, - normal = true, - plain = true, - regular = true, - roman = true, + book = 0, + normal = 0, + plain = 0, + regular = 0, + roman = 0, + medium = 1000, -- Some font authors haven't got the memo that medium is not regular } local style_synonym = { @@ -1577,8 +1585,6 @@ local function organize_namedata (rawinfo, or nametable.family or rawinfo.familyname or info.familyname --- local default_modifier = nametable.typographicsubfamily --- or nametable.subfamily local fontnames = { --- see --- https://developer.apple.com/fonts/TTRefMan/RM06/Chap6name.html @@ -1642,7 +1648,7 @@ local function split_fontname (fontname) end end -local function organize_styledata (metadata, rawinfo, info) +local function organize_styledata (metadata, rawinfo) local pfminfo = metadata.pfminfo local names = rawinfo.names return { @@ -1686,8 +1692,7 @@ local function ot_fullinfo (filename, basename, info) local style = organize_styledata (metadata, - rawinfo, - info) + rawinfo) local res = { file = { base = basename, full = filename, @@ -1929,7 +1934,7 @@ local function read_font_names (fullname, if insert_fullinfo (fullname, basename, n_font, loader, format, location, targetmappings, targetentrystatus, - info) + info[n_font]) then success = true end @@ -2368,7 +2373,7 @@ local function collect_font_filenames_texmf () fontdirs = fontdirs .. path_separator .. expanded_path "type1 fonts" fontdirs = fontdirs .. path_separator .. expanded_path "afm" - fontdirs = filesplitpath (fontdirs) or { } + fontdirs = filesplitpath (fontdirs) or { } local tasks = filter_out_pwd (fontdirs) logreport ("both", 3, "db", @@ -2725,95 +2730,51 @@ local function generate_filedata (mappings) return files end -local bold_spectrum_low = 501 --- 500 is medium, 900 heavy/black -local normal_weight = 400 -local bold_weight = 700 -local normal_width = 5 - -local pick_style -local pick_fallback_style -local check_regular - -do - function pick_style (typographicsubfamily, subfamily) - return style_synonym [typographicsubfamily or subfamily or ""] - end - - function pick_fallback_style (italicangle, pfmweight, width) - --[[-- - More aggressive, but only to determine bold faces. - Note: Before you make this test more inclusive, ensure - no fonts are matched in the bold synonym spectrum over - a literally “bold[italic]” one. In the past, heuristics - been tried but ultimately caused unwanted modifiers - polluting the lookup table. What doesn’t work is, e. g. - treating weights > 500 as bold or allowing synonyms like - “heavy”, “black”. - --]]-- - if width == normal_width then - if pfmweight == bold_weight then - --- bold spectrum matches - if italicangle == 0 then - return "b" - end - return "bi" - elseif pfmweight == normal_weight then - if italicangle ~= 0 then - return "i" - end - end - return tostring(pfmweight) .. (italicangle == 0 and "" or "i") - end - return false - end - - --- we use only exact matches here since there are constructs - --- like “regularitalic” (Cabin, Bodoni Old Fashion) - - function check_regular (typographicsubfamily, - subfamily, - italicangle, - weight, - width, - pfmweight) - local plausible_weight = false - --[[-- - This filters out undesirable candidates that specify their - typographicsubfamily or subfamily as “regular” but are actually of - “semibold” or other weight—another drawback of the - oversimplifying classification into only three styles (r, i, - b, bi). - --]]-- - if italicangle == 0 then - if pfmweight == 400 then - --[[-- - Some fonts like Dejavu advertise an undistinguished - regular and a “condensed” version with the same - weight whilst also providing the style info in the - typographic subfamily instead of the subfamily (i. e. - the converse of what Adobe’s doing). The only way to - weed out the undesired pseudo-regular shape is to - peek at its advertised width (4 vs. 5). - --]]-- - if width then - plausible_weight = width == normal_width - else - plausible_weight = true - end - elseif weight and regular_synonym [weight] then - plausible_weight = true - end - end - - if plausible_weight then - if subfamily then - if regular_synonym [subfamily] then return "r" end - elseif typographicsubfamily then - if regular_synonym [typographicsubfamily] then return "r" end - end - end - return false - end +local function regular_score(entry) + return 10000 * (entry.italicangle or 0)^2 -- We really don't want italic fonts here (italic font have an angle around 10) + + .01 * ((entry.pfmweight or 400) - 400)^2 -- weights are normally multiples of 100, so they are still quite large after .01 + + ((entry.width or 5) - 5)^2 + + (regular_synonym[entry.subfamily or entry.typographicsubfamily] or 1000000) + + (entry.pfmweight > 500 and 1000 or 0) +end +local function italic_score(entry, regular_entry) + local regularangle = regular_entry.italicangle or 0 + local angle = entry.italicangle or 0 + if (angle == 0 or angle == regularangle) + and (entry.subfamily == regular_entry.subfamily or style_synonym[entry.subfamily] ~= 'i') then + return -- This font is not italic in any way + end + return .1 * (angle - regularangle - 10)^2 -- Should there ever be multiple levels of italicness... + + 0.1 * ((entry.pfmweight or 400) - (regular_entry.pfmweight or 400))^2 -- weights are normally multiples of 100, so they are still quite large after .01 + + ((entry.width or 5) - regular_entry.width)^2 + + (style_synonym[entry.subfamily or entry.typographicsubfamily] == 'i' and 0 or 1000000) +end +local function bold_score(entry, regular_entry) + local regularweight = regular_entry.pfmweight or 400 + local weight = entry.pfmweight + if weight < regularweight + 100 + and (entry.subfamily == regular_entry.subfamily or style_synonym[entry.subfamily] ~= 'b') then + return -- This font is not bold in any way + end + return 10000 * (entry.italicangle or 0)^2 -- We really don't want italic fonts here (italic font have an angle around 10) + + .01 * ((entry.pfmweight or 400) - (regularweight + 200))^2 -- weights are normally multiples of 100, so they are still quite large after .01 + + ((entry.width or 5) - regular_entry.width)^2 + + (style_synonym[entry.subfamily or entry.typographicsubfamily] == 'b' and 0 or 1000000) + + (entry.pfmweight > 500 and 0 or 10000) +end +local function bolditalic_score(entry, bold_entry, italic_entry) + local italicangle = italic_entry.italicangle or 0 + local angle = entry.italicangle or 0 + local boldweight = bold_entry.pfmweight or 400 + local weight = entry.pfmweight or 400 + if (angle == 0 or weight < boldweight) + and (entry.subfamily == bold_entry.subfamily or entry.subfamily == italic_entry.subfamily or style_synonym[entry.subfamily] ~= 'bi') then + return -- This font is not italic in any way + end + return 100 * (angle - italicangle)^2 + + (weight - boldweight)^2 + + ((entry.width or 5) - bold_entry.width)^2 + + (style_synonym[entry.subfamily or entry.typographicsubfamily] == 'bi' and 0 or 1000000) end local function pull_values (entry) @@ -2854,7 +2815,7 @@ local function pull_values (entry) end end -local function add_family (name, subtable, modifier, entry) +local function add_family (name, subtable, entry) if not name then --- probably borked font return end @@ -2866,22 +2827,9 @@ local function add_family (name, subtable, modifier, entry) familytable [#familytable + 1] = { index = entry.index, - modifier = modifier, } end -local function add_lastresort_regular (name, subtable, entry) - if not name then --- probably borked font - return - end - local familytable = subtable [name] - if not familytable then - familytable = { } - subtable [name] = familytable - end - familytable.fallback = entry.index -end - local function get_subtable (families, entry) local location = entry.location local format = entry.format @@ -2919,27 +2867,7 @@ local function collect_families (mappings) local width = entry.width local pfmweight = entry.pfmweight local italicangle = entry.italicangle - local modifier = pick_style (typographicsubfamily, subfamily) - - if not modifier then --- regular, exact only - modifier = check_regular (typographicsubfamily, - subfamily, - italicangle, - weight, - width, - pfmweight) - end - - if not modifier then - modifier = pick_fallback_style (italicangle, pfmweight, width) - end - - if modifier then - add_family (familyname, subtable, modifier, entry) - end - if modifier ~= 'r' and regular_synonym[typographicsubfamily or subfamily or ''] then - add_lastresort_regular (familyname, subtable, entry) - end + add_family (familyname, subtable, entry) end collectgarbage "collect" @@ -2983,99 +2911,135 @@ end local style_categories = { "r", "b", "i", "bi" } local bold_categories = { "b", "bi" } +local huge = math.huge local function group_modifiers (mappings, families) logreport ("info", 2, "db", "Analyzing shapes, weights, and styles.") for location, location_data in next, families do for format, format_data in next, location_data do for familyname, collected in next, format_data do - local styledata = { } --- will replace the “collected” table - local lastresort_regular = collected.fallback - collected.fallback = nil - --- First, fill in the ordinary style data that - --- fits neatly into the four relevant modifier - --- categories. - for _, modifier in next, style_categories do - local entries - for key, info in next, collected do - if info.modifier == modifier then - if not entries then - entries = { } - end - local index = info.index - local entry = mappings [index] - local size = entry.size + local best_score = huge + local best_match + for i=1,#collected do + local v = collected[i] + local entry = mappings[v.index] + local score = regular_score(entry) + if score <= best_score then + v.prev = best_score == score and best_match or nil + best_score = score + best_match = v + end + end + local regular = {} + repeat + local index = best_match.index + local entry = mappings[index] + local size = entry.size + if size then + regular [#regular + 1] = { + size [1], + size [2], + size [3], + index, + } + else + regular.default = index + end + best_match = best_match.prev + until not best_match + local regular_entry = mappings[regular.default or regular[1][4]] + local best_match_i, best_match_b + local best_score_i, best_score_b = 10000000000, 10000000000 + for i=1,#collected do + local v = collected[i] + local entry = mappings[v.index] + local score_i = italic_score(entry, regular_entry) + local score_b = bold_score(entry, regular_entry) + if score_i and score_i <= best_score_i then + v.prev = best_score_i == score_i and best_match_i or nil + best_score_i = score_i + best_match_i = v + end + if score_b and score_b <= best_score_b then + v.prev = best_score_b == score_b and best_match_b or nil + best_score_b = score_b + best_match_b = v + end + end + local italic, bold + if best_match_i then + italic = {} + repeat + local index = best_match_i.index + local entry = mappings[index] + local size = entry.size + if size then + italic [#italic + 1] = { + size [1], + size [2], + size [3], + index, + } + else + italic.default = index + end + best_match_i = best_match_i.prev + until not best_match_i + end + if best_match_b then + bold = {} + repeat + local index = best_match_b.index + local entry = mappings[index] + local size = entry.size + if size then + bold [#bold + 1] = { + size [1], + size [2], + size [3], + index, + } + else + bold.default = index + end + best_match_b = best_match_b.prev + until not best_match_b + end + local bolditalic + if bold and italic then + best_score = 1000000000000 + local bold_entry = mappings[bold.default or bold[1][4]] + local italic_entry = mappings[italic.default or italic[1][4]] + for i=1,#collected do + local v = collected[i] + local entry = mappings[v.index] + local score = bolditalic_score(entry, bold_entry, italic_entry) + if score and score <= best_score then + v.prev = best_score == score and best_match or nil + best_score = score + best_match = v + end + end + if best_match then + bolditalic = {} + repeat + local index = best_match.index + local entry = mappings[index] + local size = entry.size if size then - entries [#entries + 1] = { + bolditalic [#bolditalic + 1] = { size [1], size [2], size [3], index, } else - entries.default = index + bolditalic.default = index end - collected [key] = nil - end - styledata [modifier] = entries + best_match = best_match.prev + until not best_match end end - if not styledata.r and lastresort_regular then - styledata.r = {default = lastresort_regular} - end - - --- At this point the family set may still lack - --- entries for bold or bold italic. We will fill - --- those in using the modifier with the numeric - --- weight that is closest to bold (700). - if next (collected) then --- there are uncategorized entries - for _, modifier in next, bold_categories do - if not styledata [modifier] then - local closest - local minimum = 2^51 - for key, info in next, collected do - local info_modifier = tonumber (info.modifier) and "b" or "bi" - if modifier == info_modifier then - local index = info.index - local entry = mappings [index] - local weight = entry.pfmweight - local diff = weight < 700 and 700 - weight or weight - 700 - if weight > 500 and diff < minimum then - minimum = diff - closest = weight - end - end - end - if closest then - --- We know there is a substitute face for the modifier. - --- Now we scan the list again to extract the size data - --- in case the shape is available at multiple sizes. - local entries = { } - for key, info in next, collected do - local info_modifier = tonumber (info.modifier) and "b" or "bi" - if modifier == info_modifier then - local index = info.index - local entry = mappings [index] - local size = entry.size - if entry.pfmweight == closest then - if size then - entries [#entries + 1] = { - size [1], - size [2], - size [3], - index, - } - else - entries.default = index - end - end - end - end - styledata [modifier] = entries - end - end - end - end - format_data [familyname] = styledata + format_data [familyname] = { r = regular, b = bold, i = italic, bi = bolditalic } end end end @@ -3514,28 +3478,62 @@ function update_names (currentnames, force, dry_run) return targetnames end +local function compress_dummy(data) return data end +--- string -> string -> (string * table) +local function save_lua_table (data, path_lua, path_luc, compress) + if compress then + osremove(path_lua) + osremove(path_luc) + path_lua, path_luc = path_lua .. '.gz', path_luc .. '.gz' + else + osremove(path_lua .. '.gz') + osremove(path_luc .. '.gz') + end + local file_lua, msg = ioopen(path_lua, compress and 'wb' or 'w') + if not file_lua then + logreport ("info", 0, "cache", "Failed to write %q: %s", path_lua, msg) + end + local file_luc file_luc, msg = ioopen(path_luc, 'wb') + if not file_luc then + logreport ("info", 0, "cache", "Failed to write %q: %s", path_luc, msg) + end + if not (file_lua or file_luc) then + return + end + -- If we can only write one of the files, try to remove the other + -- one to avoid them beiing inconsistent. This will probably fail, + -- but in some situations we might be allowed to delete a file we + -- can't write to. + if not file_lua then + osremove(path_lua) + elseif not file_luc then + osremove(path_luc) + end + local serialized = tableserialize (data, true) + if file_lua then + file_lua:write(compress and gzip.compress(serialized, 31) or serialized) + file_lua:close() + end + if file_luc then + local compiled = dump(assert(load(serialized, 't')), true) + file_luc:write(compress and gzip.compress(compiled, 31) or compiled) + file_luc:close() + end + -- Even if we could write one file but not the other one it's still an + -- error since reloading is then unreliable. + return file_lua and path_lua or nil, file_luc and path_luc or nil +end + --- unit -> bool function save_lookups ( ) local paths = config.luaotfload.paths local luaname, lucname = paths.lookup_path_lua, paths.lookup_path_luc - if fileiswritable (luaname) and fileiswritable (lucname) then - tabletofile (luaname, lookup_cache, true) - osremove (lucname) - caches.compile (lookup_cache, luaname, lucname) - --- double check ... - if lfsisfile (luaname) and lfsisfile (lucname) then - logreport ("both", 3, "cache", "Lookup cache saved.") - return true - end - logreport ("info", 0, "cache", "Could not compile lookup cache.") - return false - end - logreport ("info", 0, "cache", "Lookup cache file not writable.") - if not fileiswritable (luaname) then - logreport ("info", 0, "cache", "Failed to write %s.", luaname) - end - if not fileiswritable (lucname) then - logreport ("info", 0, "cache", "Failed to write %s.", lucname) + luaname, lucname = save_lua_table(lookup_cache, luaname, lucname) + if luaname and lucname then + logreport ("both", 3, "cache", "Lookup cache saved.") + return true + else + logreport ("info", 0, "cache", "Lookup cache file not writable.") end return false end @@ -3553,45 +3551,15 @@ function save_names (currentnames) end local paths = config.luaotfload.paths local luaname, lucname = paths.index_path_lua, paths.index_path_luc - if fileiswritable (luaname) and fileiswritable (lucname) then - osremove (lucname) - local gzname = luaname .. ".gz" - if config.luaotfload.db.compress then - local serialized = tableserialize (currentnames, true) - gzipsave (gzname, serialized) - caches.compile (currentnames, "", lucname) - else - tabletofile (luaname, currentnames, true) - caches.compile (currentnames, luaname, lucname) - end + local compress = config.luaotfload.db.compress + luaname, lucname = save_lua_table(currentnames, luaname, lucname, compress) + if luaname and lucname then logreport ("info", 2, "db", "Font index saved at ...") - local success = false - if lfsisfile (luaname) then - logreport ("info", 2, "db", "Text: " .. luaname) - success = true - end - if lfsisfile (gzname) then - logreport ("info", 2, "db", "Gzip: " .. gzname) - success = true - end - if lfsisfile (lucname) then - logreport ("info", 2, "db", "Byte: " .. lucname) - success = true - end - if success then - return true - else - logreport ("info", 0, "db", "Could not compile font index.") - return false - end + logreport ("info", 2, "db", "Text: " .. luaname) + logreport ("info", 2, "db", "Byte: " .. lucname) + return true end logreport ("info", 0, "db", "Index file not writable") - if not fileiswritable (luaname) then - logreport ("info", 0, "db", "Failed to write %s.", luaname) - end - if not fileiswritable (lucname) then - logreport ("info", 0, "db", "Failed to write %s.", lucname) - end return false end @@ -3829,10 +3797,7 @@ return function () fonts.definers = fonts.definers or { resolvers = { } } names.blacklist = blacklist - -- MK Changed to rebuild with case insensitive fallback. - -- Negative version to indicate generation by modified code. - names.version = -2 --- decrease monotonically - -- /MK + names.version = 6 --- increase monotonically names.data = nil --- contains the loaded database names.lookups = nil --- contains the lookup cache |