You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
948 lines
30 KiB
948 lines
30 KiB
-----------------------------------------------------------------------
|
|
-- FILE: luaotfload-auxiliary.lua
|
|
-- DESCRIPTION: part of luaotfload
|
|
-- AUTHOR: Khaled Hosny, Élie Roux, Philipp Gesang
|
|
-----------------------------------------------------------------------
|
|
|
|
local ProvidesLuaModule = {
|
|
name = "luaotfload-auxiliary",
|
|
version = "3.14", --TAGVERSION
|
|
date = "2020-05-06", --TAGDATE
|
|
description = "luaotfload submodule / auxiliary functions",
|
|
license = "GPL v2.0"
|
|
}
|
|
|
|
if luatexbase and luatexbase.provides_module then
|
|
luatexbase.provides_module (ProvidesLuaModule)
|
|
end
|
|
|
|
|
|
luaotfload = luaotfload or { }
|
|
local log = luaotfload.log
|
|
local logreport = log.report
|
|
local fonthashes = fonts.hashes
|
|
local encodings = fonts.encodings
|
|
local identifiers = fonthashes.identifiers
|
|
local fontnames = fonts.names
|
|
|
|
local fontid = font.id
|
|
local texsprint = tex.sprint
|
|
|
|
local dofile = dofile
|
|
local getmetatable = getmetatable
|
|
local setmetatable = setmetatable
|
|
local utfcodepoint = utf8.codepoint
|
|
local stringlower = string.lower
|
|
local stringupper = string.upper
|
|
local stringformat = string.format
|
|
local stringgsub = string.gsub
|
|
local stringbyte = string.byte
|
|
local stringfind = string.find
|
|
local tablecopy = table.copy
|
|
|
|
local harf = luaotfload.harfbuzz
|
|
local GSUBtag, GPOStag
|
|
if harf then
|
|
GSUBtag = harf.Tag.new("GSUB")
|
|
GPOStag = harf.Tag.new("GPOS")
|
|
end
|
|
|
|
local aux = { }
|
|
local luaotfload_callbacks = { }
|
|
|
|
-----------------------------------------------------------------------
|
|
--- font patches
|
|
-----------------------------------------------------------------------
|
|
|
|
--- https://github.com/khaledhosny/luaotfload/issues/54
|
|
|
|
local function rewrite_fontname(tfmdata, specification)
|
|
local format = tfmdata.format or tfmdata.properties.format
|
|
if stringfind (specification, " ") then
|
|
tfmdata.name = stringformat ("%q", specification)
|
|
else
|
|
--- other specs should parse just fine
|
|
tfmdata.name = specification
|
|
end
|
|
end
|
|
|
|
local rewriting = false
|
|
|
|
function aux.start_rewrite_fontname()
|
|
if rewriting == false then
|
|
luatexbase.add_to_callback (
|
|
"luaotfload.patch_font",
|
|
rewrite_fontname,
|
|
"luaotfload.rewrite_fontname")
|
|
rewriting = true
|
|
logreport ("log", 1, "aux",
|
|
"start rewriting tfmdata.name field")
|
|
end
|
|
end
|
|
|
|
function aux.stop_rewrite_fontname()
|
|
if rewriting == true then
|
|
luatexbase.remove_from_callback
|
|
("luaotfload.patch_font", "luaotfload.rewrite_fontname")
|
|
rewriting = false
|
|
logreport ("log", 1, "aux",
|
|
"stop rewriting tfmdata.name field")
|
|
end
|
|
end
|
|
|
|
|
|
--[[doc--
|
|
This sets two dimensions apparently relied upon by the unicode-math
|
|
package.
|
|
--doc]]--
|
|
|
|
local function set_sscale_dimens(fontdata)
|
|
local resources = fontdata.resources if not resources then return end
|
|
local mathconstants = resources.MathConstants if not mathconstants then return end
|
|
local parameters = fontdata.parameters if not parameters then return end
|
|
--- the default values below are complete crap
|
|
parameters [10] = mathconstants.ScriptPercentScaleDown or 70
|
|
parameters [11] = mathconstants.ScriptScriptPercentScaleDown or 50
|
|
end
|
|
|
|
luaotfload_callbacks [#luaotfload_callbacks + 1] = {
|
|
"patch_font", set_sscale_dimens, "set_sscale_dimens",
|
|
}
|
|
|
|
-- Starting with LuaTeX 1.11.2, this is a more reliable way of selecting the
|
|
-- right font than relying on psnames
|
|
luaotfload_callbacks [#luaotfload_callbacks + 1] = {
|
|
"patch_font", function(fontdata)
|
|
fontdata.subfont = fontdata.specification.sub or 1
|
|
end, "set_font_index",
|
|
}
|
|
|
|
local default_units = 1000
|
|
|
|
--- fontobj -> int
|
|
local function lookup_units(fontdata)
|
|
local units = fontdata.units
|
|
if units and units > 0 then return units end
|
|
local shared = fontdata.shared if not shared then return default_units end
|
|
local rawdata = shared.rawdata if not rawdata then return default_units end
|
|
local metadata = rawdata.metadata if not metadata then return default_units end
|
|
local capheight = metadata.capheight if not capheight then return default_units end
|
|
local units = metadata.units or fontdata.units
|
|
if not units or units == 0 then
|
|
return default_units
|
|
end
|
|
return units
|
|
end
|
|
|
|
--[[doc--
|
|
This callback corrects some values of the Cambria font.
|
|
--doc]]--
|
|
--- fontobj -> unit
|
|
local function patch_cambria_domh(fontdata)
|
|
local mathconstants = fontdata.MathConstants
|
|
if mathconstants and fontdata.psname == "CambriaMath" then
|
|
--- my test Cambria has 2048
|
|
local units = fontdata.units or lookup_units(fontdata)
|
|
local sz = fontdata.parameters.size or fontdata.size
|
|
local mh = 2800 / units * sz
|
|
if mathconstants.DisplayOperatorMinHeight < mh then
|
|
mathconstants.DisplayOperatorMinHeight = mh
|
|
end
|
|
end
|
|
end
|
|
|
|
luaotfload_callbacks [#luaotfload_callbacks + 1] = {
|
|
"patch_font", patch_cambria_domh, "patch_cambria_domh",
|
|
}
|
|
|
|
|
|
--[[doc--
|
|
|
|
Add missing field to fonts that lack it. Addresses issue
|
|
https://github.com/lualatex/luaotfload/issues/253
|
|
|
|
This is considered a hack, especially since importing the
|
|
unicode-math package fixes the problem quite nicely.
|
|
|
|
--doc]]--
|
|
|
|
--- fontobj -> unit
|
|
local function fixup_fontdata(data)
|
|
local t = type (data)
|
|
--- Some OT fonts like Libertine R lack the resources table, causing
|
|
--- the fontloader to nil-index.
|
|
if t == "table" then
|
|
if data and not data.resources then data.resources = { } end
|
|
end
|
|
end
|
|
|
|
luaotfload_callbacks [#luaotfload_callbacks + 1] = {
|
|
"patch_font_unsafe", fixup_fontdata, "fixup_fontdata",
|
|
}
|
|
|
|
|
|
--[[doc--
|
|
|
|
Comment from fontspec:
|
|
|
|
“Here we patch fonts tfm table to emulate \XeTeX's \cs{fontdimen8},
|
|
which stores the caps-height of the font. (Cf.\ \cs{fontdimen5} which
|
|
stores the x-height.)
|
|
|
|
Falls back to measuring the glyph if the font doesn't contain the
|
|
necessary information.
|
|
This needs to be extended for fonts that don't contain an `X'.”
|
|
|
|
--doc]]--
|
|
|
|
local capheight_reference_chars = { "X", "M", "Ж", "ξ", }
|
|
local capheight_reference_codepoints do
|
|
capheight_reference_codepoints = { }
|
|
for i = 1, #capheight_reference_chars do
|
|
local chr = capheight_reference_chars [i]
|
|
capheight_reference_codepoints [i] = utfcodepoint (chr)
|
|
end
|
|
end
|
|
|
|
local function determine_capheight(fontdata)
|
|
local parameters = fontdata.parameters if not parameters then return false end
|
|
local characters = fontdata.characters if not characters then return false end
|
|
--- Pretty simplistic but it does return *some* value for most fonts;
|
|
--- we could also refine the approach to return some kind of average
|
|
--- of all capital letters or a user-provided subset.
|
|
for i = 1, #capheight_reference_codepoints do
|
|
local refcp = capheight_reference_codepoints [i]
|
|
local refchar = characters [refcp]
|
|
if refchar then
|
|
logreport ("both", 4, "aux",
|
|
"picked height of character '%s' (U+%04X) as \\fontdimen8 \z
|
|
candidate",
|
|
capheight_reference_chars [i], refcp)
|
|
return refchar.height
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function query_ascender(fontdata)
|
|
local parameters = fontdata.parameters if not parameters then return false end
|
|
local ascender = parameters.ascender
|
|
if ascender then
|
|
return ascender --- pre-scaled
|
|
end
|
|
|
|
local shared = fontdata.shared if not shared then return false end
|
|
local rawdata = shared.rawdata if not rawdata then return false end
|
|
local metadata = rawdata.metadata if not metadata then return false end
|
|
ascender = metadata.ascender if not ascender then return false end
|
|
local size = parameters.size if not size then return false end
|
|
local units = lookup_units (fontdata)
|
|
if not units or units == 0 then return false end
|
|
return ascender * size / units --- scaled
|
|
end
|
|
|
|
local function query_capheight(fontdata)
|
|
local parameters = fontdata.parameters if not parameters then return false end
|
|
local shared = fontdata.shared if not shared then return false end
|
|
local rawdata = shared.rawdata if not rawdata then return false end
|
|
local metadata = rawdata.metadata if not metadata then return false end
|
|
local capheight = metadata.capheight if not capheight then return false end
|
|
local size = parameters.size if not size then return false end
|
|
local units = lookup_units (fontdata)
|
|
if not units or units == 0 then return false end
|
|
return capheight * size / units
|
|
end
|
|
|
|
local function query_fontdimen8(fontdata)
|
|
local parameters = fontdata.parameters if not parameters then return false end
|
|
local fontdimen8 = parameters [8]
|
|
if fontdimen8 then return fontdimen8 end
|
|
return false
|
|
end
|
|
|
|
local function caphtfmt(ref, ht)
|
|
if not ht then return "<none>" end
|
|
if not ref then return tostring (ht) end
|
|
return stringformat ("%s(δ=%s)", ht, ht - ref)
|
|
end
|
|
|
|
local function set_capheight(fontdata)
|
|
if not fontdata then
|
|
logreport ("both", 0, "aux",
|
|
"error: set_capheight() received garbage")
|
|
return
|
|
end
|
|
local capheight_dimen8 = query_fontdimen8 (fontdata)
|
|
local capheight_alleged = query_capheight (fontdata)
|
|
local capheight_ascender = query_ascender (fontdata)
|
|
local capheight_measured = determine_capheight (fontdata)
|
|
logreport ("term", 4, "aux",
|
|
"capht: param[8]=%s advertised=%s ascender=%s measured=%s",
|
|
tostring (capheight_dimen8),
|
|
caphtfmt (capheight_dimen8, capheight_alleged),
|
|
caphtfmt (capheight_dimen8, capheight_ascender),
|
|
caphtfmt (capheight_dimen8, capheight_measured))
|
|
if capheight_dimen8 then --- nothing to do
|
|
return
|
|
end
|
|
|
|
local capheight = capheight_alleged or capheight_ascender or capheight_measured
|
|
if capheight then
|
|
fontdata.parameters [8] = capheight
|
|
end
|
|
end
|
|
|
|
luaotfload_callbacks [#luaotfload_callbacks + 1] = {
|
|
"patch_font", set_capheight, "set_capheight",
|
|
}
|
|
|
|
-- Of course there are also fonts with no sensible x-height, so let's add a
|
|
-- fallback there:
|
|
local function set_xheight(tfmdata)
|
|
local parameters = tfmdata.parameters
|
|
if not parameters then return end
|
|
if not ((parameters.x_height or parameters[5] or 0) == 0) then return end
|
|
if tfmdata.characters and tfmdata.characters[120] then
|
|
parameters.x_height = tfmdata.characters[120].height
|
|
else
|
|
parameters.x_height = (parameters.ascender or 0)/2
|
|
end
|
|
end
|
|
luaotfload_callbacks [#luaotfload_callbacks + 1] = {
|
|
"patch_font", set_xheight, "set_xheight",
|
|
}
|
|
-----------------------------------------------------------------------
|
|
--- glyphs and characters
|
|
-----------------------------------------------------------------------
|
|
|
|
--- int -> int -> bool
|
|
function aux.font_has_glyph(font_id, codepoint)
|
|
local fontdata = fonts.hashes.identifiers[font_id]
|
|
if fontdata then
|
|
if fontdata.characters[codepoint] ~= nil then return true end
|
|
end
|
|
return false
|
|
end
|
|
|
|
--- undocumented
|
|
|
|
local function raw_slot_of_name(font_id, glyphname)
|
|
local fontdata = font.fonts[font_id]
|
|
if fontdata.type == "virtual" then --- get base font for glyph idx
|
|
local codepoint = encodings.agl.unicodes[glyphname]
|
|
local glyph = fontdata.characters[codepoint]
|
|
if fontdata.characters[codepoint] then
|
|
return codepoint
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
--[[doc--
|
|
|
|
This one is approximately “name_to_slot” from the microtype package;
|
|
note that it is all about Adobe Glyph names and glyph slots in the
|
|
font. The names and values may diverge from actual Unicode.
|
|
|
|
http://www.adobe.com/devnet/opentype/archives/glyph.html
|
|
|
|
The “unsafe” switch triggers a fallback lookup in the raw fonts
|
|
table. As some of the information is stored as references, this may
|
|
have unpredictable side-effects.
|
|
|
|
--doc]]--
|
|
|
|
--- int -> string -> bool -> (int | false)
|
|
function aux.slot_of_name(font_id, glyphname, unsafe)
|
|
if not font_id or type (font_id) ~= "number"
|
|
or not glyphname or type (glyphname) ~= "string"
|
|
then
|
|
logreport ("both", 0, "aux",
|
|
"invalid parameters to slot_of_name (%s, %s)",
|
|
tostring (font_id), tostring (glyphname))
|
|
return false
|
|
end
|
|
|
|
local tfmdata = identifiers [font_id]
|
|
if not tfmdata then return raw_slot_of_name (font_id, glyphname) end
|
|
local hbdata = tfmdata.hb
|
|
if hbdata then
|
|
local hbshared = hbdata.shared
|
|
local nominals = hbshared.nominals
|
|
local hbfont = hbshared.font
|
|
|
|
local gid = hbfont:get_glyph_from_name(glyphname)
|
|
if gid ~= nil then
|
|
return nominals[gid] or gid + hbshared.gid_offset
|
|
end
|
|
else
|
|
local resources = tfmdata.resources if not resources then return false end
|
|
local unicodes = resources.unicodes if not unicodes then return false end
|
|
|
|
local unicode = unicodes [glyphname]
|
|
if unicode then
|
|
if type (unicode) == "number" then
|
|
return unicode
|
|
else
|
|
return unicode [1] --- for multiple components
|
|
end
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
--[[doc--
|
|
|
|
Inverse of above; not authoritative as to my knowledge the official
|
|
inverse of the AGL is the AGLFN. Maybe this whole issue should be
|
|
dealt with in a separate package that loads char-def.lua and thereby
|
|
solves the problem for the next couple decades.
|
|
|
|
http://partners.adobe.com/public/developer/en/opentype/aglfn13.txt
|
|
|
|
--doc]]--
|
|
|
|
local indices
|
|
|
|
--- int -> (string | false)
|
|
function aux.name_of_slot(codepoint)
|
|
if not codepoint or type (codepoint) ~= "number" then
|
|
logreport ("both", 0, "aux",
|
|
"invalid parameters to name_of_slot (%s)",
|
|
tostring (codepoint))
|
|
return false
|
|
end
|
|
|
|
if not indices then --- this will load the glyph list
|
|
local unicodes = encodings.agl.unicodes
|
|
if not unicodes or not next (unicodes)then
|
|
logreport ("both", 0, "aux",
|
|
"name_of_slot: failed to load the AGL.")
|
|
end
|
|
indices = table.swapped (unicodes)
|
|
end
|
|
|
|
local glyphname = indices [codepoint]
|
|
if glyphname then
|
|
return glyphname
|
|
end
|
|
return false
|
|
end
|
|
|
|
--[[doc--
|
|
|
|
Get the GID of the glyph associated with a given name.
|
|
|
|
--doc]]--
|
|
function aux.gid_of_name(font_id, glyphname)
|
|
local slot = aux.slot_of_name(font_id, glyphname)
|
|
if not slot then return end
|
|
local tfmdata = identifiers[font_id] or font.fonts[font_id]
|
|
-- assert(tfmdata) -- Otherwise slot_of_name would have failed already
|
|
return tfmdata.characters[slot].index or slot
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
--- features / scripts / languages
|
|
-----------------------------------------------------------------------
|
|
--- lots of arrowcode ahead
|
|
|
|
local function get_features(tfmdata)
|
|
local resources = tfmdata.resources if not resources then return false end
|
|
local features = resources.features if not features then return false end
|
|
return features
|
|
end
|
|
|
|
local function get_hbface(tfmdata)
|
|
if not tfmdata.hb then return end
|
|
return tfmdata.hb.shared.face
|
|
end
|
|
|
|
--[[doc--
|
|
This function, modeled after “check_script()” from fontspec, returns
|
|
true if in the given font, the script “asked_script” is accounted for in at
|
|
least one feature.
|
|
--doc]]--
|
|
|
|
--- int -> string -> bool
|
|
function aux.provides_script(font_id, asked_script)
|
|
if not font_id or type (font_id) ~= "number"
|
|
or not asked_script or type (asked_script) ~= "string"
|
|
then
|
|
logreport ("both", 0, "aux",
|
|
"invalid parameters to provides_script(%s, %s)",
|
|
tostring (font_id), tostring (asked_script))
|
|
return false
|
|
end
|
|
asked_script = stringlower(asked_script)
|
|
local tfmdata = identifiers[font_id]
|
|
if not tfmdata then
|
|
logreport ("log", 0, "aux", "no font with id %d", font_id)
|
|
return false
|
|
end
|
|
local hbface = get_hbface(tfmdata)
|
|
if hbface then
|
|
local script = harf.Tag.new(asked_script == "dflt" and "DFLT"
|
|
or asked_script)
|
|
for _, tag in next, { GSUBtag, GPOStag } do
|
|
if hbface:ot_layout_find_script(tag, script) then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
else
|
|
local features = get_features (tfmdata)
|
|
if features == false then
|
|
logreport ("log", 1, "aux", "font no %d lacks a features table", font_id)
|
|
return false
|
|
end
|
|
for method, featuredata in next, features do
|
|
--- where method: "gpos" | "gsub"
|
|
for feature, data in next, featuredata do
|
|
if data[asked_script] then
|
|
logreport ("log", 1, "aux",
|
|
"font no %d (%s) defines feature %s for script %s",
|
|
font_id, fontname, feature, asked_script)
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
logreport ("log", 0, "aux",
|
|
"font no %d (%s) defines no feature for script %s",
|
|
font_id, fontname, asked_script)
|
|
end
|
|
return false
|
|
end
|
|
|
|
--[[doc--
|
|
This function, modeled after “check_language()” from fontspec, returns
|
|
true if in the given font, the language with tage “asked_language” is
|
|
accounted for in the script with tag “asked_script” in at least one
|
|
feature.
|
|
--doc]]--
|
|
|
|
--- int -> string -> string -> bool
|
|
function aux.provides_language(font_id, asked_script, asked_language)
|
|
if not font_id or type (font_id) ~= "number"
|
|
or not asked_script or type (asked_script) ~= "string"
|
|
or not asked_language or type (asked_language) ~= "string"
|
|
then
|
|
logreport ("both", 0, "aux",
|
|
"invalid parameters to provides_language(%s, %s, %s)",
|
|
tostring (font_id),
|
|
tostring (asked_script),
|
|
tostring (asked_language))
|
|
return false
|
|
end
|
|
asked_script = stringlower(asked_script)
|
|
local tfmdata = identifiers[font_id]
|
|
if not tfmdata then
|
|
logreport ("log", 0, "aux", "no font with id %d", font_id)
|
|
return false
|
|
end
|
|
local hbface = get_hbface(tfmdata)
|
|
if hbface then
|
|
asked_language = stringupper(asked_language)
|
|
if asked_language == "DFLT" then
|
|
return aux.provides_script(font_id, asked_script)
|
|
end
|
|
local script = harf.Tag.new(asked_script == "dflt" and "DFLT"
|
|
or asked_script)
|
|
local language = harf.Tag.new(asked_language == "DFLT" and "dflt"
|
|
or asked_language)
|
|
for _, tag in next, { GSUBtag, GPOStag } do
|
|
local _, script_idx = hbface:ot_layout_find_script(tag, script)
|
|
if hbface:ot_layout_find_language(tag, script_idx, language) then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
else
|
|
asked_language = stringlower(asked_language)
|
|
local tfmdata = identifiers[font_id]
|
|
if not tfmdata then return false end
|
|
local features = get_features (tfmdata)
|
|
if features == false then
|
|
logreport ("log", 1, "aux", "font no %d lacks a features table", font_id)
|
|
return false
|
|
end
|
|
for method, featuredata in next, features do
|
|
--- where method: "gpos" | "gsub"
|
|
for feature, data in next, featuredata do
|
|
local scriptdata = data[asked_script]
|
|
if scriptdata and scriptdata[asked_language] then
|
|
logreport ("log", 1, "aux",
|
|
"font no %d (%s) defines feature %s "
|
|
.. "for script %s with language %s",
|
|
font_id, fontname, feature,
|
|
asked_script, asked_language)
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
logreport ("log", 0, "aux",
|
|
"font no %d (%s) defines no feature "
|
|
.. "for script %s with language %s",
|
|
font_id, fontname, asked_script, asked_language)
|
|
return false
|
|
end
|
|
end
|
|
|
|
--[[doc--
|
|
We strip the syntax elements from feature definitions (shouldn’t
|
|
actually be there in the first place, but who cares ...)
|
|
--doc]]--
|
|
|
|
local lpeg = require"lpeg"
|
|
local C, P, S = lpeg.C, lpeg.P, lpeg.S
|
|
local lpegmatch = lpeg.match
|
|
|
|
local sign = S"+-"
|
|
local rhs = P"=" * P(1)^0 * P(-1)
|
|
local strip_garbage = sign^-1 * C((1 - rhs)^1)
|
|
|
|
--s = "+foo" --> foo
|
|
--ss = "-bar" --> bar
|
|
--sss = "baz" --> baz
|
|
--t = "foo=bar" --> foo
|
|
--tt = "+bar=baz" --> bar
|
|
--ttt = "-baz=true" --> baz
|
|
--
|
|
--print(lpeg.match(strip_garbage, s))
|
|
--print(lpeg.match(strip_garbage, ss))
|
|
--print(lpeg.match(strip_garbage, sss))
|
|
--print(lpeg.match(strip_garbage, t))
|
|
--print(lpeg.match(strip_garbage, tt))
|
|
--print(lpeg.match(strip_garbage, ttt))
|
|
|
|
--[[doc--
|
|
This function, modeled after “check_feature()” from fontspec, returns
|
|
true if in the given font, the language with tag “asked_language” is
|
|
accounted for in the script with tag “asked_script” in feature
|
|
“asked_feature”.
|
|
--doc]]--
|
|
|
|
--- int -> string -> string -> string -> bool
|
|
function aux.provides_feature(font_id, asked_script,
|
|
asked_language, asked_feature)
|
|
if not font_id or type (font_id) ~= "number"
|
|
or not asked_script or type (asked_script) ~= "string"
|
|
or not asked_language or type (asked_language) ~= "string"
|
|
or not asked_feature or type (asked_feature) ~= "string"
|
|
then
|
|
logreport ("both", 0, "aux",
|
|
"invalid parameters to provides_feature(%s, %s, %s, %s)",
|
|
tostring (font_id), tostring (asked_script),
|
|
tostring (asked_language), tostring (asked_feature))
|
|
return false
|
|
end
|
|
asked_script = stringlower(asked_script)
|
|
asked_feature = lpegmatch(strip_garbage, asked_feature)
|
|
|
|
local tfmdata = identifiers[font_id]
|
|
if not tfmdata then
|
|
logreport ("log", 0, "aux", "no font with id %d", font_id)
|
|
return false
|
|
end
|
|
local hbface = get_hbface(tfmdata)
|
|
if hbface then
|
|
asked_language = stringupper(asked_language)
|
|
local script = harf.Tag.new(asked_script == "dflt" and "DFLT"
|
|
or asked_script)
|
|
local language = harf.Tag.new(asked_language == "DFLT" and "dflt"
|
|
or asked_language)
|
|
local feature = harf.Tag.new(asked_feature)
|
|
|
|
for _, tag in next, { GSUBtag, GPOStag } do
|
|
local _, script_idx = hbface:ot_layout_find_script(tag, script)
|
|
local _, language_idx = hbface:ot_layout_find_language(tag, script_idx, language)
|
|
if hbface:ot_layout_find_feature(tag, script_idx, language_idx, feature) then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
else
|
|
asked_language = stringlower(asked_language)
|
|
local features = get_features (tfmdata)
|
|
if features == false then
|
|
logreport ("log", 1, "aux", "font no %d lacks a features table", font_id)
|
|
return false
|
|
end
|
|
for method, featuredata in next, features do
|
|
--- where method: "gpos" | "gsub"
|
|
local feature = featuredata[asked_feature]
|
|
if feature then
|
|
local scriptdata = feature[asked_script]
|
|
if scriptdata and scriptdata[asked_language] then
|
|
logreport ("log", 1, "aux",
|
|
"font no %d (%s) defines feature %s "
|
|
.. "for script %s with language %s",
|
|
font_id, fontname, asked_feature,
|
|
asked_script, asked_language)
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
logreport ("log", 0, "aux",
|
|
"font no %d (%s) does not define feature %s for script %s with language %s",
|
|
font_id, fontname, asked_feature, asked_script, asked_language)
|
|
end
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
--- font dimensions
|
|
-----------------------------------------------------------------------
|
|
|
|
--- int -> string -> int
|
|
local function get_math_dimension(font_id, dimenname)
|
|
if type(font_id) == "string" then
|
|
font_id = fontid(font_id) --- safeguard
|
|
end
|
|
local fontdata = identifiers[font_id]
|
|
local mathdata = fontdata.mathparameters
|
|
if mathdata then
|
|
return mathdata[dimenname] or 0
|
|
end
|
|
return 0
|
|
end
|
|
|
|
aux.get_math_dimension = get_math_dimension
|
|
|
|
--- int -> string -> unit
|
|
function aux.sprint_math_dimension(font_id, dimenname)
|
|
if type(font_id) == "string" then
|
|
font_id = fontid(font_id)
|
|
end
|
|
local dim = get_math_dimension(font_id, dimenname)
|
|
texsprint(luatexbase.catcodetables["latex-package"], dim, "sp")
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
--- extra database functions
|
|
-----------------------------------------------------------------------
|
|
|
|
--[====[-- TODO -> port this to new db model
|
|
|
|
--- local directories -------------------------------------------------
|
|
|
|
--- migrated from luaotfload-database.lua
|
|
--- https://github.com/lualatex/luaotfload/pull/61#issuecomment-17776975
|
|
|
|
--- string -> (int * int)
|
|
local function scan_external_dir (dir)
|
|
local old_names, new_names = names.data()
|
|
if not old_names then
|
|
old_names = load_names()
|
|
end
|
|
new_names = tablecopy(old_names)
|
|
local n_scanned, n_new = scan_dir(dir, old_names, new_names)
|
|
--- FIXME
|
|
--- This doesn’t seem right. If a db update is triggered after this
|
|
--- point, then the added fonts will be saved along with it --
|
|
--- which is not as “temporarily” as it should be. (This should be
|
|
--- addressed during a refactoring of names_resolve().)
|
|
names.data = new_names
|
|
return n_scanned, n_new
|
|
end
|
|
|
|
aux.scan_external_dir = scan_external_dir
|
|
|
|
--]====]--
|
|
|
|
function aux.scan_external_dir()
|
|
print "ERROR: scan_external_dir() is not implemented"
|
|
end
|
|
|
|
--- db queries --------------------------------------------------------
|
|
|
|
--- https://github.com/lualatex/luaotfload/issues/74
|
|
--- string -> (string * int)
|
|
local function resolve_fontname(name)
|
|
local foundname, subfont = luaotfload.resolvers.name {
|
|
name = name,
|
|
specification = "name:" .. name,
|
|
}
|
|
if foundname then
|
|
return foundname, subfont
|
|
end
|
|
return false, false
|
|
end
|
|
|
|
aux.resolve_fontname = resolve_fontname
|
|
|
|
--- string list -> (string * int)
|
|
function aux.resolve_fontlist(names)
|
|
for n = 1, #names do
|
|
local foundname, subfont = resolve_fontname(this)
|
|
if foundname then
|
|
return foundname, subfont
|
|
end
|
|
end
|
|
return false, false
|
|
end
|
|
|
|
--- index access ------------------------------------------------------
|
|
|
|
--- Based on a discussion on the Luatex mailing list:
|
|
--- http://tug.org/pipermail/luatex/2014-June/004881.html
|
|
|
|
--[[doc--
|
|
|
|
aux.read_font_index -- Read the names index from the canonical
|
|
location and return its contents. This does not affect the behavior
|
|
of Luaotfload: The returned table is independent of what the font
|
|
resolvers use internally. Access is raw: each call to the function
|
|
will result in the entire table being re-read from disk.
|
|
|
|
--doc]]--
|
|
|
|
local load_names = fontnames.load
|
|
local access_font_index = fontnames.access_font_index
|
|
|
|
function aux.read_font_index()
|
|
return load_names (true) or { }
|
|
end
|
|
|
|
--[[doc--
|
|
|
|
aux.font_index -- Access Luaotfload’s internal database. If the
|
|
database hasn’t been loaded yet this will cause it to be loaded, with
|
|
all the possible side-effects like for instance creating the index
|
|
file if it doesn’t exist, reading all font files, &c.
|
|
|
|
--doc]]--
|
|
|
|
function aux.font_index() return access_font_index () end
|
|
|
|
|
|
--- loaded fonts ------------------------------------------------------
|
|
|
|
--- just a proof of concept
|
|
|
|
--- fontobj -> string list -> (string list) list
|
|
local function get_font_data (tfmdata, keys)
|
|
local acc = {}
|
|
for n = 1, #keys do
|
|
acc[n] = tfmdata[keys[n]] or false
|
|
end
|
|
return acc
|
|
end
|
|
|
|
--[[doc--
|
|
|
|
The next one operates on the fonts.hashes.identifiers table.
|
|
It returns a list containing tuples of font ids and the
|
|
contents of the fields specified in the first argument.
|
|
Font table entries that were created indirectly -- e.g. by
|
|
\letterspacefont or during font expansion -- will not be
|
|
listed.
|
|
|
|
--doc]]--
|
|
|
|
local default_keys = { "fullname" }
|
|
|
|
--- string list -> (int * string list) list
|
|
function aux.get_loaded_fonts (keys)
|
|
keys = keys or default_keys
|
|
local acc = {}
|
|
for id, tfmdata in pairs(identifiers) do
|
|
local data = get_font_data(tfmdata, keys)
|
|
acc[#acc+1] = { id, data }
|
|
end
|
|
return acc
|
|
end
|
|
|
|
--- Raw access to the font.* namespace is unsafe so no documentation on
|
|
--- this one.
|
|
function aux.get_raw_fonts ()
|
|
local res = { }
|
|
for i, v in font.each() do
|
|
if v.filename then
|
|
res[#res+1] = { i, v }
|
|
end
|
|
end
|
|
return res
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
--- font parameters
|
|
-----------------------------------------------------------------------
|
|
--- analogy of font-hsh
|
|
|
|
fonthashes.parameters = fonthashes.parameters or { }
|
|
fonthashes.quads = fonthashes.quads or { }
|
|
|
|
local parameters = fonthashes.parameters or { }
|
|
local quads = fonthashes.quads or { }
|
|
|
|
setmetatable(parameters, { __index = function (t, font_id)
|
|
local tfmdata = identifiers[font_id]
|
|
if not tfmdata then --- unsafe; avoid
|
|
tfmdata = font.fonts[font_id]
|
|
end
|
|
if tfmdata and type(tfmdata) == "table" then
|
|
local fontparameters = tfmdata.parameters
|
|
t[font_id] = fontparameters
|
|
return fontparameters
|
|
end
|
|
return nil
|
|
end})
|
|
|
|
--[[doc--
|
|
|
|
Note that the reason as to why we prefer functions over table indices
|
|
is that functions are much safer against unintended manipulation.
|
|
This justifies the overhead they cost.
|
|
|
|
--doc]]--
|
|
|
|
--- int -> (number | false)
|
|
function aux.get_quad(font_id)
|
|
local quad = quads[font_id]
|
|
if quad then
|
|
return quad
|
|
end
|
|
local fontparameters = parameters[font_id]
|
|
if fontparameters then
|
|
local quad = fontparameters.quad or 0
|
|
quads[font_id] = quad
|
|
return quad
|
|
end
|
|
return false
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
--- initialization
|
|
-----------------------------------------------------------------------
|
|
|
|
local function inject_callbacks (lst)
|
|
if not lst and next (lst) then return false end
|
|
|
|
local function inject (def)
|
|
local cb, fn, id = unpack (def)
|
|
cb = tostring (cb)
|
|
id = tostring (id)
|
|
if not cb or not fn or not id or not type (fn) == "function" then
|
|
logreport ("both", 0, "aux", "Invalid callback requested (%q, %s, %q).",
|
|
cb, tostring (fn), id)
|
|
return false
|
|
end
|
|
cb = stringformat ("luaotfload.%s", cb)
|
|
id = stringformat ("luaotfload.aux.%s", id)
|
|
logreport ("log", 5, "aux", "Installing callback %q->%q.", cb, id)
|
|
luatexbase.add_to_callback (cb, fn, id)
|
|
return true
|
|
end
|
|
|
|
local ret = true
|
|
for i = 1, #lst do ret = inject (lst [i]) end
|
|
return ret
|
|
end
|
|
|
|
return function ()
|
|
luaotfload.aux = aux
|
|
return inject_callbacks (luaotfload_callbacks)
|
|
end
|
|
|
|
-- vim:tw=79:sw=2:ts=8:et
|
|
|
|
|