diff options
author | Philipp Gesang <phg42.2a@gmail.com> | 2013-05-10 21:11:34 +0200 |
---|---|---|
committer | Philipp Gesang <phg42.2a@gmail.com> | 2013-05-10 21:11:34 +0200 |
commit | c79bb2abad019324e8a1701c0310e09867cf1c1b (patch) | |
tree | 84c3168a005ad25b2c09f328e83b3c1fda6b56f2 | |
parent | e6b84660875c89de138ebc0e9c9e9e9dbaa6db76 (diff) | |
download | luaotfload-c79bb2abad019324e8a1701c0310e09867cf1c1b.tar.gz |
import legacy files
-rw-r--r-- | luaotfload-legacy-attributes.lua | 27 | ||||
-rw-r--r-- | luaotfload-legacy-database.lua | 705 | ||||
-rw-r--r-- | luaotfload-legacy-merged.lua | 8157 | ||||
-rwxr-xr-x | luaotfload-legacy-tool.lua | 105 | ||||
-rw-r--r-- | luaotfload-legacy.lua | 154 |
5 files changed, 9148 insertions, 0 deletions
diff --git a/luaotfload-legacy-attributes.lua b/luaotfload-legacy-attributes.lua new file mode 100644 index 0000000..c6130b4 --- /dev/null +++ b/luaotfload-legacy-attributes.lua @@ -0,0 +1,27 @@ +----------------------------------------------------------------------- +-- FILE: otfl-luat-att.lua +-- USAGE: with old luaotfload +-- DESCRIPTION: setting attributes abide luatexbase rules +-- REQUIREMENTS: some old luatex +-- AUTHOR: Philipp Gesang (Phg), <phg42.2a@gmail.com> +-- CREATED: 2013-05-10 20:37:19+0200 +----------------------------------------------------------------------- +-- + +if not modules then modules = { } end modules ['otfl-luat-att'] = { + version = math.pi/42, + comment = "companion to luaotfload.lua", + author = "Philipp Gesang", + copyright = "Luaotfload Development Team", + license = "GNU GPL v2" +} + +function attributes.private(name) + local attr = "otfl@" .. name + local number = luatexbase.attributes[attr] + if not number then + number = luatexbase.new_attribute(attr) + end + return number +end + diff --git a/luaotfload-legacy-database.lua b/luaotfload-legacy-database.lua new file mode 100644 index 0000000..2edb615 --- /dev/null +++ b/luaotfload-legacy-database.lua @@ -0,0 +1,705 @@ +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 = sanitize(face.names.family) + local subfamily = sanitize(face.names.subfamily) + local fullname = sanitize(face.names.fullname) + local psname = sanitize(face.names.psname) + local fontname = sanitize(face.fontname) + local pfullname = sanitize(face.fullname) + local optsize, dsnsize, maxsize, minsize + 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] + 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] + 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 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, + } + 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 diff --git a/luaotfload-legacy-merged.lua b/luaotfload-legacy-merged.lua new file mode 100644 index 0000000..9bec298 --- /dev/null +++ b/luaotfload-legacy-merged.lua @@ -0,0 +1,8157 @@ +-- merged file : luaotfload-legacy-merged.lua +-- parent file : luaotfload-legacy.lua +-- merge date : Fri May 10 20:57:35 2013 + +do -- begin closure to overcome local limits and interference + +if not modules then modules={} end modules ['luat-dum']={ + version=1.100, + comment="companion to luatex-*.tex", + author="Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright="PRAGMA ADE / ConTeXt Development Team", + license="see context related readme files" +} +local dummyfunction=function() end +statistics={ + register=dummyfunction, + starttiming=dummyfunction, + stoptiming=dummyfunction, +} +directives={ + register=dummyfunction, + enable=dummyfunction, + disable=dummyfunction, +} +trackers={ + register=dummyfunction, + enable=dummyfunction, + disable=dummyfunction, +} +experiments={ + register=dummyfunction, + enable=dummyfunction, + disable=dummyfunction, +} +storage={ + register=dummyfunction, + shared={}, +} +logs={ + report=dummyfunction, + simple=dummyfunction, +} +tasks={ + new=dummyfunction, + actions=dummyfunction, + appendaction=dummyfunction, + prependaction=dummyfunction, +} +callbacks={ + register=function(n,f) return callback.register(n,f) end, +} +texconfig.kpse_init=true +resolvers=resolvers or {} +local remapper={ + otf="opentype fonts", + ttf="truetype fonts", + ttc="truetype fonts", + dfont="truetype fonts", + cid="cid maps", + fea="font feature files", +} +function resolvers.find_file(name,kind) + name=string.gsub(name,"\\","/") + kind=string.lower(kind) + return kpse.find_file(name,(kind and kind~="" and (remapper[kind] or kind)) or file.extname(name,"tex")) +end +function resolvers.findbinfile(name,kind) + if not kind or kind=="" then + kind=file.extname(name) + end + return resolvers.find_file(name,(kind and remapper[kind]) or kind) +end +caches={} +local writable,readables=nil,{} +if not caches.namespace or caches.namespace=="" or caches.namespace=="context" then + caches.namespace='generic' +end +do + local cachepaths + if kpse.expand_var('$TEXMFCACHE')~='$TEXMFCACHE' then + cachepaths=kpse.expand_var('$TEXMFCACHE') + elseif kpse.expand_var('$TEXMFVAR')~='$TEXMFVAR' then + cachepaths=kpse.expand_var('$TEXMFVAR') + end + if not cachepaths then + cachepaths="." + end + cachepaths=string.split(cachepaths,os.type=="windows" and ";" or ":") + for i=1,#cachepaths do + local done + writable=file.join(cachepaths[i],"luatex-cache") + writable=file.join(writable,caches.namespace) + writable,done=dir.mkdirs(writable) + if done then + break + end + end + for i=1,#cachepaths do + if file.isreadable(cachepaths[i]) then + readables[#readables+1]=file.join(cachepaths[i],"luatex-cache",caches.namespace) + end + end + if not writable then + texio.write_nl("quiting: fix your writable cache path\n") + os.exit() + elseif #readables==0 then + texio.write_nl("quiting: fix your readable cache path\n") + os.exit() + elseif #readables==1 and readables[1]==writable then + texio.write(string.format("(using cache: %s)",writable)) + else + texio.write(string.format("(using write cache: %s)",writable)) + texio.write(string.format("(using read cache: %s)",table.concat(readables," "))) + end +end +function caches.getwritablepath(category,subcategory) + local path=file.join(writable,category) + lfs.mkdir(path) + path=file.join(path,subcategory) + lfs.mkdir(path) + return path +end +function caches.getreadablepaths(category,subcategory) + local t={} + for i=1,#readables do + t[i]=file.join(readables[i],category,subcategory) + end + return t +end +local function makefullname(path,name) + if path and path~="" then + name="temp-"..name + return file.addsuffix(file.join(path,name),"lua") + end +end +function caches.iswritable(path,name) + local fullname=makefullname(path,name) + return fullname and file.iswritable(fullname) +end +function caches.loaddata(paths,name) + for i=1,#paths do + local fullname=makefullname(paths[i],name) + if fullname then + texio.write(string.format("(load: %s)",fullname)) + local data=loadfile(fullname) + return data and data() + end + end +end +function caches.savedata(path,name,data) + local fullname=makefullname(path,name) + if fullname then + texio.write(string.format("(save: %s)",fullname)) + table.tofile(fullname,data,'return',false,true,false) + end +end + +end -- closure + +do -- begin closure to overcome local limits and interference + +if not modules then modules={} end modules ['luat-ovr']={ + version=1.001, + comment="companion to luatex-*.tex", + author="Khaled Hosny and Elie Roux", + copyright="Luaotfload Development Team", + license="GNU GPL v2" +} +local write_nl,format,name=texio.write_nl,string.format,"luaotfload" +local dummyfunction=function() end +callbacks={ + register=dummyfunction, +} +function logs.report(category,fmt,...) + if fmt then + write_nl('log',format("%s | %s: %s",name,category,format(fmt,...))) + elseif category then + write_nl('log',format("%s | %s",name,category)) + else + write_nl('log',format("%s |",name)) + end +end +function logs.info(category,fmt,...) + if fmt then + write_nl(format("%s | %s: %s",name,category,format(fmt,...))) + elseif category then + write_nl(format("%s | %s",name,category)) + else + write_nl(format("%s |",name)) + end + io.flush() +end +function logs.simple(fmt,...) + if fmt then + write_nl('log',format("%s | %s",name,format(fmt,...))) + else + write_nl('log',format("%s |",name)) + end +end +tex.attribute[0]=0 +tex.ctxcatcodes=luatexbase.catcodetables.latex + +end -- closure + +do -- begin closure to overcome local limits and interference + +if not modules then modules={} end modules ['data-con']={ + version=1.100, + comment="companion to luat-lib.mkiv", + author="Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright="PRAGMA ADE / ConTeXt Development Team", + license="see context related readme files" +} +local format,lower,gsub=string.format,string.lower,string.gsub +local trace_cache=false trackers.register("resolvers.cache",function(v) trace_cache=v end) +local trace_containers=false trackers.register("resolvers.containers",function(v) trace_containers=v end) +local trace_storage=false trackers.register("resolvers.storage",function(v) trace_storage=v end) +containers=containers or {} +containers.usecache=true +local function report(container,tag,name) + if trace_cache or trace_containers then + logs.report(format("%s cache",container.subcategory),"%s: %s",tag,name or 'invalid') + end +end +local allocated={} +local mt={ + __index=function(t,k) + if k=="writable" then + local writable=caches.getwritablepath(t.category,t.subcategory) or { "." } + t.writable=writable + return writable + elseif k=="readables" then + local readables=caches.getreadablepaths(t.category,t.subcategory) or { "." } + t.readables=readables + return readables + end + end +} +function containers.define(category,subcategory,version,enabled) + if category and subcategory then + local c=allocated[category] + if not c then + c={} + allocated[category]=c + end + local s=c[subcategory] + if not s then + s={ + category=category, + subcategory=subcategory, + storage={}, + enabled=enabled, + version=version or math.pi, + trace=false, + } + setmetatable(s,mt) + c[subcategory]=s + end + return s + end +end +function containers.is_usable(container,name) + return container.enabled and caches and caches.iswritable(container.writable,name) +end +function containers.is_valid(container,name) + if name and name~="" then + local storage=container.storage[name] + return storage and storage.cache_version==container.version + else + return false + end +end +function containers.read(container,name) + local storage=container.storage + local stored=storage[name] + if not stored and container.enabled and caches and containers.usecache then + stored=caches.loaddata(container.readables,name) + if stored and stored.cache_version==container.version then + report(container,"loaded",name) + else + stored=nil + end + storage[name]=stored + elseif stored then + report(container,"reusing",name) + end + return stored +end +function containers.write(container,name,data) + if data then + data.cache_version=container.version + if container.enabled and caches then + local unique,shared=data.unique,data.shared + data.unique,data.shared=nil,nil + caches.savedata(container.writable,name,data) + report(container,"saved",name) + data.unique,data.shared=unique,shared + end + report(container,"stored",name) + container.storage[name]=data + end + return data +end +function containers.content(container,name) + return container.storage[name] +end +function containers.cleanname(name) + return (gsub(lower(name),"[^%w%d]+","-")) +end + +end -- closure + +do -- begin closure to overcome local limits and interference + +if not modules then modules={} end modules ['font-ini']={ + version=1.001, + comment="companion to font-ini.mkiv", + author="Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright="PRAGMA ADE / ConTeXt Development Team", + license="see context related readme files" +} +local utf=unicode.utf8 +local format,serialize=string.format,table.serialize +local write_nl=texio.write_nl +local lower=string.lower +if not fontloader then fontloader=fontforge end +fontloader.totable=fontloader.to_table +fonts=fonts or {} +fonts.ids=fonts.ids or {} fonts.identifiers=fonts.ids +fonts.chr=fonts.chr or {} fonts.characters=fonts.chr +fonts.qua=fonts.qua or {} fonts.quads=fonts.qua +fonts.tfm=fonts.tfm or {} +fonts.mode='base' +fonts.private=0xF0000 +fonts.verbose=false +fonts.ids[0]={ + characters={}, + descriptions={}, + name="nullfont", +} +fonts.chr[0]={} +fonts.methods=fonts.methods or { + base={ tfm={},afm={},otf={},vtf={},fix={} }, + node={ tfm={},afm={},otf={},vtf={},fix={} }, +} +fonts.initializers=fonts.initializers or { + base={ tfm={},afm={},otf={},vtf={},fix={} }, + node={ tfm={},afm={},otf={},vtf={},fix={} } +} +fonts.triggers=fonts.triggers or { + 'mode', + 'language', + 'script', + 'strategy', +} +fonts.processors=fonts.processors or {} +fonts.manipulators=fonts.manipulators or {} +fonts.define=fonts.define or {} +fonts.define.specify=fonts.define.specify or {} +fonts.define.specify.synonyms=fonts.define.specify.synonyms or {} +if not fonts.color then + fonts.color={ + set=function() end, + reset=function() end, + } +end +fonts.formats={} +function fonts.fontformat(filename,default) + local extname=lower(file.extname(filename)) + local format=fonts.formats[extname] + if format then + return format + else + logs.report("fonts define","unable to determine font format for '%s'",filename) + return default + end +end + +end -- closure + +do -- begin closure to overcome local limits and interference + +if not modules then modules={} end modules ['node-dum']={ + version=1.001, + comment="companion to luatex-*.tex", + author="Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright="PRAGMA ADE / ConTeXt Development Team", + license="see context related readme files" +} +nodes=nodes or {} +fonts=fonts or {} +attributes=attributes or {} +local traverse_id=node.traverse_id +local free_node=node.free +local remove_node=node.remove +local new_node=node.new +local glyph=node.id('glyph') +local fontdata=fonts.ids or {} +function nodes.simple_font_handler(head) + head=nodes.process_characters(head) + nodes.inject_kerns(head) + nodes.protect_glyphs(head) + head=node.ligaturing(head) + head=node.kerning(head) + return head +end +if tex.attribute[0]~=0 then + texio.write_nl("log","!") + texio.write_nl("log","! Attribute 0 is reserved for ConTeXt's font feature management and has to be") + texio.write_nl("log","! set to zero. Also, some attributes in the range 1-255 are used for special") + texio.write_nl("log","! purposed so setting them at the TeX end might break the font handler.") + texio.write_nl("log","!") + tex.attribute[0]=0 +end +nodes.protect_glyphs=node.protect_glyphs +nodes.unprotect_glyphs=node.unprotect_glyphs +function nodes.process_characters(head) + local usedfonts,done,prevfont={},false,nil + for n in traverse_id(glyph,head) do + local font=n.font + if font~=prevfont then + prevfont=font + local used=usedfonts[font] + if not used then + local tfmdata=fontdata[font] + if tfmdata then + local shared=tfmdata.shared + if shared then + local processors=shared.processes + if processors and #processors>0 then + usedfonts[font]=processors + done=true + end + end + end + end + end + end + if done then + for font,processors in next,usedfonts do + for i=1,#processors do + local h,d=processors[i](head,font,0) + head,done=h or head,done or d + end + end + end + return head,true +end +function nodes.kern(k) + local n=new_node("kern",1) + n.kern=k + return n +end +function nodes.remove(head,current,free_too) + local t=current + head,current=remove_node(head,current) + if t then + if free_too then + free_node(t) + t=nil + else + t.next,t.prev=nil,nil + end + end + return head,current,t +end +function nodes.delete(head,current) + return nodes.remove(head,current,true) +end +nodes.before=node.insert_before +nodes.after=node.insert_after +attributes.unsetvalue=-0x7FFFFFFF +local numbers,last={},127 +function attributes.private(name) + local number=numbers[name] + if not number then + if last<255 then + last=last+1 + end + number=last + numbers[name]=number + end + return number +end + +end -- closure + +do -- begin closure to overcome local limits and interference + +if not modules then modules={} end modules ['node-inj']={ + version=1.001, + comment="companion to node-ini.mkiv", + author="Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright="PRAGMA ADE / ConTeXt Development Team", + license="see context related readme files" +} +local next=next +local trace_injections=false trackers.register("nodes.injections",function(v) trace_injections=v end) +fonts=fonts or {} +fonts.tfm=fonts.tfm or {} +fonts.ids=fonts.ids or {} +local fontdata=fonts.ids +local glyph=node.id('glyph') +local kern=node.id('kern') +local traverse_id=node.traverse_id +local unset_attribute=node.unset_attribute +local has_attribute=node.has_attribute +local set_attribute=node.set_attribute +local insert_node_before=node.insert_before +local insert_node_after=node.insert_after +local newkern=nodes.kern +local markbase=attributes.private('markbase') +local markmark=attributes.private('markmark') +local markdone=attributes.private('markdone') +local cursbase=attributes.private('cursbase') +local curscurs=attributes.private('curscurs') +local cursdone=attributes.private('cursdone') +local kernpair=attributes.private('kernpair') +local cursives={} +local marks={} +local kerns={} +function nodes.set_cursive(start,nxt,factor,rlmode,exit,entry,tfmstart,tfmnext) + local dx,dy=factor*(exit[1]-entry[1]),factor*(exit[2]-entry[2]) + local ws,wn=tfmstart.width,tfmnext.width + local bound=#cursives+1 + set_attribute(start,cursbase,bound) + set_attribute(nxt,curscurs,bound) + cursives[bound]={ rlmode,dx,dy,ws,wn } + return dx,dy,bound +end +function nodes.set_pair(current,factor,rlmode,r2lflag,spec,tfmchr) + local x,y,w,h=factor*spec[1],factor*spec[2],factor*spec[3],factor*spec[4] + if x~=0 or w~=0 or y~=0 or h~=0 then + local bound=has_attribute(current,kernpair) + if bound then + local kb=kerns[bound] + kb[2],kb[3],kb[4],kb[5]=(kb[2] or 0)+x,(kb[3] or 0)+y,(kb[4] or 0)+w,(kb[5] or 0)+h + else + bound=#kerns+1 + set_attribute(current,kernpair,bound) + kerns[bound]={ rlmode,x,y,w,h,r2lflag,tfmchr.width } + end + return x,y,w,h,bound + end + return x,y,w,h +end +function nodes.set_kern(current,factor,rlmode,x,tfmchr) + local dx=factor*x + if dx~=0 then + local bound=#kerns+1 + set_attribute(current,kernpair,bound) + kerns[bound]={ rlmode,dx } + return dx,bound + else + return 0,0 + end +end +function nodes.set_mark(start,base,factor,rlmode,ba,ma,index) + local dx,dy=factor*(ba[1]-ma[1]),factor*(ba[2]-ma[2]) + local bound=has_attribute(base,markbase) + if bound then + local mb=marks[bound] + if mb then + if not index then index=#mb+1 end + mb[index]={ dx,dy } + set_attribute(start,markmark,bound) + set_attribute(start,markdone,index) + return dx,dy,bound + else + logs.report("nodes mark","possible problem, U+%04X is base without data (id: %s)",base.char,bound) + end + end + index=index or 1 + bound=#marks+1 + set_attribute(base,markbase,bound) + set_attribute(start,markmark,bound) + set_attribute(start,markdone,index) + marks[bound]={ [index]={ dx,dy,rlmode } } + return dx,dy,bound +end +function nodes.trace_injection(head) + local function dir(n) + return (n and n<0 and "r-to-l") or (n and n>0 and "l-to-r") or ("unset") + end + local function report(...) + logs.report("nodes finisher",...) + end + report("begin run") + for n in traverse_id(glyph,head) do + if n.subtype<256 then + local kp=has_attribute(n,kernpair) + local mb=has_attribute(n,markbase) + local mm=has_attribute(n,markmark) + local md=has_attribute(n,markdone) + local cb=has_attribute(n,cursbase) + local cc=has_attribute(n,curscurs) + report("char U+%05X, font=%s",n.char,n.font) + if kp then + local k=kerns[kp] + if k[3] then + report(" pairkern: dir=%s, x=%s, y=%s, w=%s, h=%s",dir(k[1]),k[2] or "?",k[3] or "?",k[4] or "?",k[5] or "?") + else + report(" kern: dir=%s, dx=%s",dir(k[1]),k[2] or "?") + end + end + if mb then + report(" markbase: bound=%s",mb) + end + if mm then + local m=marks[mm] + if mb then + local m=m[mb] + if m then + report(" markmark: bound=%s, index=%s, dx=%s, dy=%s",mm,md or "?",m[1] or "?",m[2] or "?") + else + report(" markmark: bound=%s, missing index",mm) + end + else + m=m[1] + report(" markmark: bound=%s, dx=%s, dy=%s",mm,m[1] or "?",m[2] or "?") + end + end + if cb then + report(" cursbase: bound=%s",cb) + end + if cc then + local c=cursives[cc] + report(" curscurs: bound=%s, dir=%s, dx=%s, dy=%s",cc,dir(c[1]),c[2] or "?",c[3] or "?") + end + end + end + report("end run") +end +function nodes.inject_kerns(head,where,keep) + local has_marks,has_cursives,has_kerns=next(marks),next(cursives),next(kerns) + if has_marks or has_cursives then + if trace_injections then + nodes.trace_injection(head) + end + local done,ky,rl,valid,cx,wx,mk=false,{},{},{},{},{},{} + if has_kerns then + local nf,tm=nil,nil + for n in traverse_id(glyph,head) do + if n.subtype<256 then + valid[#valid+1]=n + if n.font~=nf then + nf=n.font + tm=fontdata[nf].marks + end + mk[n]=tm[n.char] + local k=has_attribute(n,kernpair) + if k then + local kk=kerns[k] + if kk then + local x,y,w,h=kk[2] or 0,kk[3] or 0,kk[4] or 0,kk[5] or 0 + local dy=y-h + if dy~=0 then + ky[n]=dy + end + if w~=0 or x~=0 then + wx[n]=kk + end + rl[n]=kk[1] + end + end + end + end + else + local nf,tm=nil,nil + for n in traverse_id(glyph,head) do + if n.subtype<256 then + valid[#valid+1]=n + if n.font~=nf then + nf=n.font + tm=fontdata[nf].marks + end + mk[n]=tm[n.char] + end + end + end + if #valid>0 then + local cx={} + if has_kerns and next(ky) then + for n,k in next,ky do + n.yoffset=k + end + end + if has_cursives then + local p_cursbase,p=nil,nil + local t,d,maxt={},{},0 + for i=1,#valid do + local n=valid[i] + if not mk[n] then + local n_cursbase=has_attribute(n,cursbase) + if p_cursbase then + local n_curscurs=has_attribute(n,curscurs) + if p_cursbase==n_curscurs then + local c=cursives[n_curscurs] + if c then + local rlmode,dx,dy,ws,wn=c[1],c[2],c[3],c[4],c[5] + if rlmode>=0 then + dx=dx-ws + else + dx=dx+wn + end + if dx~=0 then + cx[n]=dx + rl[n]=rlmode + end + dy=-dy + maxt=maxt+1 + t[maxt]=p + d[maxt]=dy + else + maxt=0 + end + end + elseif maxt>0 then + local ny=n.yoffset + for i=maxt,1,-1 do + ny=ny+d[i] + local ti=t[i] + ti.yoffset=ti.yoffset+ny + end + maxt=0 + end + if not n_cursbase and maxt>0 then + local ny=n.yoffset + for i=maxt,1,-1 do + ny=ny+d[i] + local ti=t[i] + ti.yoffset=ny + end + maxt=0 + end + p_cursbase,p=n_cursbase,n + end + end + if maxt>0 then + local ny=n.yoffset + for i=maxt,1,-1 do + ny=ny+d[i] + local ti=t[i] + ti.yoffset=ny + end + maxt=0 + end + if not keep then + cursives={} + end + end + if has_marks then + for i=1,#valid do + local p=valid[i] + local p_markbase=has_attribute(p,markbase) + if p_markbase then + local mrks=marks[p_markbase] + for n in traverse_id(glyph,p.next) do + local n_markmark=has_attribute(n,markmark) + if p_markbase==n_markmark then + local index=has_attribute(n,markdone) or 1 + local d=mrks[index] + if d then + local rlmode=d[3] + if rlmode and rlmode>0 then + local k=wx[p] + if k then + n.xoffset=p.xoffset-(p.width-d[1])-k[2] + else + n.xoffset=p.xoffset-(p.width-d[1]) + end + else + local k=wx[p] + if k then + n.xoffset=p.xoffset-d[1]-k[2] + else + n.xoffset=p.xoffset-d[1] + end + end + if mk[p] then + n.yoffset=p.yoffset+d[2] + else + n.yoffset=n.yoffset+p.yoffset+d[2] + end + end + else + break + end + end + end + end + if not keep then + marks={} + end + end + if next(wx) then + for n,k in next,wx do + local rl,x,w,r2l=k[1],k[2] or 0,k[4] or 0,k[6] + local wx=w-x + if r2l then + if wx~=0 then + insert_node_before(head,n,newkern(wx)) + end + if x~=0 then + insert_node_after (head,n,newkern(x)) + end + else + if x~=0 then + insert_node_before(head,n,newkern(x)) + end + if wx~=0 then + insert_node_after(head,n,newkern(wx)) + end + end + end + end + if next(cx) then + for n,k in next,cx do + if k~=0 then + local rln=rl[n] + if rln and rln<0 then + insert_node_before(head,n,newkern(-k)) + else + insert_node_before(head,n,newkern(k)) + end + end + end + end + if not keep then + kerns={} + end + return head,true + elseif not keep then + kerns,cursives,marks={},{},{} + end + elseif has_kerns then + if trace_injections then + nodes.trace_injection(head) + end + for n in traverse_id(glyph,head) do + if n.subtype<256 then + local k=has_attribute(n,kernpair) + if k then + local kk=kerns[k] + if kk then + local rl,x,y,w=kk[1],kk[2] or 0,kk[3],kk[4] + if y and y~=0 then + n.yoffset=y + end + if w then + local r2l=kk[6] + local wx=w-x + if r2l then + if wx~=0 then + insert_node_before(head,n,newkern(wx)) + end + if x~=0 then + insert_node_after (head,n,newkern(x)) + end + else + if x~=0 then + insert_node_before(head,n,newkern(x)) + end + if wx~=0 then + insert_node_after(head,n,newkern(wx)) + end + end + else + if x~=0 then + insert_node_before(head,n,newkern(x)) + end + end + end + end + end + end + if not keep then + kerns={} + end + return head,true + else + end + return head,false +end + +end -- closure + +do -- begin closure to overcome local limits and interference + + +if not modules then modules={} end modules ['otfl-luat-att']={ + version=math.pi/42, + comment="companion to luaotfload.lua", + author="Philipp Gesang", + copyright="Luaotfload Development Team", + license="GNU GPL v2" +} +function attributes.private(name) + local attr="otfl@"..name + local number=luatexbase.attributes[attr] + if not number then + number=luatexbase.new_attribute(attr) + end + return number +end + +end -- closure + +do -- begin closure to overcome local limits and interference + +if not modules then modules={} end modules ['font-tfm']={ + version=1.001, + comment="companion to font-ini.mkiv", + author="Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright="PRAGMA ADE / ConTeXt Development Team", + license="see context related readme files" +} +local utf=unicode.utf8 +local next,format,match,lower,gsub=next,string.format,string.match,string.lower,string.gsub +local concat,sortedkeys,utfbyte,serialize=table.concat,table.sortedkeys,utf.byte,table.serialize +local trace_defining=false trackers.register("fonts.defining",function(v) trace_defining=v end) +local trace_scaling=false trackers.register("fonts.scaling",function(v) trace_scaling=v end) +fonts=fonts or {} +fonts.tfm=fonts.tfm or {} +fonts.ids=fonts.ids or {} +local tfm=fonts.tfm +fonts.loaded=fonts.loaded or {} +fonts.dontembed=fonts.dontembed or {} +fonts.triggers=fonts.triggers or {} +fonts.initializers=fonts.initializers or {} +fonts.initializers.common=fonts.initializers.common or {} +local fontdata=fonts.ids +local disc=node.id('disc') +local glyph=node.id('glyph') +local set_attribute=node.set_attribute +tfm.resolve_vf=true +tfm.share_base_kerns=false +tfm.mathactions={} +tfm.fontname_mode="fullpath" +tfm.enhance=tfm.enhance or function() end +fonts.formats.tfm="type1" +function tfm.read_from_tfm(specification) + local fname,tfmdata=specification.filename or "",nil + if fname~="" then + if trace_defining then + logs.report("define font","loading tfm file %s at size %s",fname,specification.size) + end + tfmdata=font.read_tfm(fname,specification.size) + if tfmdata then + tfmdata.descriptions=tfmdata.descriptions or {} + if tfm.resolve_vf then + fonts.logger.save(tfmdata,file.extname(fname),specification) + fname=resolvers.findbinfile(specification.name,'ovf') + if fname and fname~="" then + local vfdata=font.read_vf(fname,specification.size) + if vfdata then + local chars=tfmdata.characters + for k,v in next,vfdata.characters do + chars[k].commands=v.commands + end + tfmdata.type='virtual' + tfmdata.fonts=vfdata.fonts + end + end + end + tfm.enhance(tfmdata,specification) + end + elseif trace_defining then + logs.report("define font","loading tfm with name %s fails",specification.name) + end + return tfmdata +end +local factors={ + pt=65536.0, + bp=65781.8, +} +function tfm.setfactor(f) + tfm.factor=factors[f or 'pt'] or factors.pt +end +tfm.setfactor() +function tfm.scaled(scaledpoints,designsize) + if scaledpoints<0 then + if designsize then + if designsize>tfm.factor then + return (- scaledpoints/1000)*designsize + else + return (- scaledpoints/1000)*designsize*tfm.factor + end + else + return (- scaledpoints/1000)*10*tfm.factor + end + else + return scaledpoints + end +end +function tfm.get_virtual_id(tfmdata) + if not tfmdata.fonts then + tfmdata.type="virtual" + tfmdata.fonts={ { id=0 } } + return 1 + else + tfmdata.fonts[#tfmdata.fonts+1]={ id=0 } + return #tfmdata.fonts + end +end +function tfm.check_virtual_id(tfmdata,id) + if tfmdata and tfmdata.type=="virtual" then + if not tfmdata.fonts or #tfmdata.fonts==0 then + tfmdata.type,tfmdata.fonts="real",nil + else + local vfonts=tfmdata.fonts + for f=1,#vfonts do + local fnt=vfonts[f] + if fnt.id and fnt.id==0 then + fnt.id=id + end + end + end + end +end +fonts.trace_scaling=false +local charactercache={} +function tfm.calculate_scale(tfmtable,scaledpoints,relativeid) + if scaledpoints<0 then + scaledpoints=(- scaledpoints/1000)*tfmtable.designsize + end + local units=tfmtable.units or 1000 + local delta=scaledpoints/units + return scaledpoints,delta,units +end +function tfm.do_scale(tfmtable,scaledpoints,relativeid) + local t={} + local scaledpoints,delta,units=tfm.calculate_scale(tfmtable,scaledpoints,relativeid) + t.units_per_em=units or 1000 + local hdelta,vdelta=delta,delta + for k,v in next,tfmtable do + if type(v)=="table" then + else + t[k]=v + end + end + local extend_factor=tfmtable.extend_factor or 0 + if extend_factor~=0 and extend_factor~=1 then + hdelta=hdelta*extend_factor + t.extend=extend_factor*1000 + else + t.extend=1000 + end + local slant_factor=tfmtable.slant_factor or 0 + if slant_factor~=0 then + t.slant=slant_factor*1000 + else + t.slant=0 + end + local isvirtual=tfmtable.type=="virtual" or tfmtable.virtualized + local hasmath=(tfmtable.math_parameters~=nil and next(tfmtable.math_parameters)~=nil) or (tfmtable.MathConstants~=nil and next(tfmtable.MathConstants)~=nil) + local nodemode=tfmtable.mode=="node" + local hasquality=tfmtable.auto_expand or tfmtable.auto_protrude + local hasitalic=tfmtable.has_italic + t.parameters={} + t.characters={} + t.MathConstants={} + local descriptions=tfmtable.descriptions or {} + t.unicodes=tfmtable.unicodes + t.indices=tfmtable.indices + t.marks=tfmtable.marks +t.goodies=tfmtable.goodies +t.colorscheme=tfmtable.colorscheme + t.descriptions=descriptions + if tfmtable.fonts then + t.fonts=table.fastcopy(tfmtable.fonts) + end + local tp=t.parameters + local mp=t.math_parameters + local tfmp=tfmtable.parameters + tp.slant=(tfmp.slant or tfmp[1] or 0) + tp.space=(tfmp.space or tfmp[2] or 0)*hdelta + tp.space_stretch=(tfmp.space_stretch or tfmp[3] or 0)*hdelta + tp.space_shrink=(tfmp.space_shrink or tfmp[4] or 0)*hdelta + tp.x_height=(tfmp.x_height or tfmp[5] or 0)*vdelta + tp.quad=(tfmp.quad or tfmp[6] or 0)*hdelta + tp.extra_space=(tfmp.extra_space or tfmp[7] or 0)*hdelta + local protrusionfactor=(tp.quad~=0 and 1000/tp.quad) or 0 + local tc=t.characters + local characters=tfmtable.characters + local nameneeded=not tfmtable.shared.otfdata + local changed=tfmtable.changed or {} + local ischanged=changed and next(changed) + local indices=tfmtable.indices + local luatex=tfmtable.luatex + local tounicode=luatex and luatex.tounicode + local defaultwidth=luatex and luatex.defaultwidth or 0 + local defaultheight=luatex and luatex.defaultheight or 0 + local defaultdepth=luatex and luatex.defaultdepth or 0 + local scaledwidth=defaultwidth*hdelta + local scaledheight=defaultheight*vdelta + local scaleddepth=defaultdepth*vdelta + local stackmath=tfmtable.ignore_stack_math~=true + local private=fonts.private + local sharedkerns={} + for k,v in next,characters do + local chr,description,index + if ischanged then + local c=changed[k] + if c then + description=descriptions[c] or v + v=characters[c] or v + index=(indices and indices[c]) or c + else + description=descriptions[k] or v + index=(indices and indices[k]) or k + end + else + description=descriptions[k] or v + index=(indices and indices[k]) or k + end + local width=description.width + local height=description.height + local depth=description.depth + if width then width=hdelta*width else width=scaledwidth end + if height then height=vdelta*height else height=scaledheight end + if depth and depth~=0 then + depth=delta*depth + if nameneeded then + chr={ + name=description.name, + index=index, + height=height, + depth=depth, + width=width, + } + else + chr={ + index=index, + height=height, + depth=depth, + width=width, + } + end + else + if nameneeded then + chr={ + name=description.name, + index=index, + height=height, + width=width, + } + else + chr={ + index=index, + height=height, + width=width, + } + end + end + if tounicode then + local tu=tounicode[index] + if tu then + chr.tounicode=tu + end + end + if hasquality then + local ve=v.expansion_factor + if ve then + chr.expansion_factor=ve*1000 + end + local vl=v.left_protruding + if vl then + chr.left_protruding=protrusionfactor*width*vl + end + local vr=v.right_protruding + if vr then + chr.right_protruding=protrusionfactor*width*vr + end + end + if hasitalic then + local vi=description.italic or v.italic + if vi and vi~=0 then + chr.italic=vi*hdelta + end + end + if hasmath then + local vn=v.next + if vn then + chr.next=vn + else + local vv=v.vert_variants + if vv then + local t={} + for i=1,#vv do + local vvi=vv[i] + t[i]={ + ["start"]=(vvi["start"] or 0)*vdelta, + ["end"]=(vvi["end"] or 0)*vdelta, + ["advance"]=(vvi["advance"] or 0)*vdelta, + ["extender"]=vvi["extender"], + ["glyph"]=vvi["glyph"], + } + end + chr.vert_variants=t + else + local hv=v.horiz_variants + if hv then + local t={} + for i=1,#hv do + local hvi=hv[i] + t[i]={ + ["start"]=(hvi["start"] or 0)*hdelta, + ["end"]=(hvi["end"] or 0)*hdelta, + ["advance"]=(hvi["advance"] or 0)*hdelta, + ["extender"]=hvi["extender"], + ["glyph"]=hvi["glyph"], + } + end + chr.horiz_variants=t + end + end + end + local vt=description.top_accent + if vt then + chr.top_accent=vdelta*vt + end + if stackmath then + local mk=v.mathkerns + if mk then + local kerns={} + local v=mk.top_right if v then local k={} for i=1,#v do local vi=v[i] + k[i]={ height=vdelta*vi.height,kern=vdelta*vi.kern } + end kerns.top_right=k end + local v=mk.top_left if v then local k={} for i=1,#v do local vi=v[i] + k[i]={ height=vdelta*vi.height,kern=vdelta*vi.kern } + end kerns.top_left=k end + local v=mk.bottom_left if v then local k={} for i=1,#v do local vi=v[i] + k[i]={ height=vdelta*vi.height,kern=vdelta*vi.kern } + end kerns.bottom_left=k end + local v=mk.bottom_right if v then local k={} for i=1,#v do local vi=v[i] + k[i]={ height=vdelta*vi.height,kern=vdelta*vi.kern } + end kerns.bottom_right=k end + chr.mathkern=kerns + end + end + end + if not nodemode then + local vk=v.kerns + if vk then + local s=sharedkerns[vk] + if not s then + s={} + for k,v in next,vk do s[k]=v*hdelta end + sharedkerns[vk]=s + end + chr.kerns=s + end + local vl=v.ligatures + if vl then + if true then + chr.ligatures=vl + else + local tt={} + for i,l in next,vl do + tt[i]=l + end + chr.ligatures=tt + end + end + end + if isvirtual then + local vc=v.commands + if vc then + local ok=false + for i=1,#vc do + local key=vc[i][1] + if key=="right" or key=="down" then + ok=true + break + end + end + if ok then + local tt={} + for i=1,#vc do + local ivc=vc[i] + local key=ivc[1] + if key=="right" then + tt[#tt+1]={ key,ivc[2]*hdelta } + elseif key=="down" then + tt[#tt+1]={ key,ivc[2]*vdelta } + elseif key=="rule" then + tt[#tt+1]={ key,ivc[2]*vdelta,ivc[3]*hdelta } + else + tt[#tt+1]=ivc + end + end + chr.commands=tt + else + chr.commands=vc + end + end + end + tc[k]=chr + end + t.size=scaledpoints + t.factor=delta + t.hfactor=hdelta + t.vfactor=vdelta + if t.fonts then + t.fonts=table.fastcopy(t.fonts) + end + if hasmath then + local ma=tfm.mathactions + for i=1,#ma do + ma[i](t,tfmtable,delta,hdelta,vdelta) + end + end + local tpx=tp.x_height + if hasmath then + if not tp[13] then tp[13]=.86*tpx end + if not tp[14] then tp[14]=.86*tpx end + if not tp[15] then tp[15]=.86*tpx end + if not tp[16] then tp[16]=.48*tpx end + if not tp[17] then tp[17]=.48*tpx end + if not tp[22] then tp[22]=0 end + if t.MathConstants then t.MathConstants.AccentBaseHeight=nil end + end + t.tounicode=1 + t.cidinfo=tfmtable.cidinfo + if hasmath then + if trace_defining then + logs.report("define font","math enabled for: name '%s', fullname: '%s', filename: '%s'",t.name or "noname",t.fullname or "nofullname",t.filename or "nofilename") + end + else + if trace_defining then + logs.report("define font","math disabled for: name '%s', fullname: '%s', filename: '%s'",t.name or "noname",t.fullname or "nofullname",t.filename or "nofilename") + end + t.nomath,t.MathConstants=true,nil + end + if not t.psname then + t.psname=t.fontname or (t.fullname and fonts.names.cleanname(t.fullname)) + end + if trace_defining then + logs.report("define font","used for accesing subfont: '%s'",t.psname or "nopsname") + logs.report("define font","used for subsetting: '%s'",t.fontname or "nofontname") + end + return t,delta +end +tfm.auto_cleanup=true +local lastfont=nil +function tfm.cleanup_table(tfmdata) + if tfm.auto_cleanup then + if tfmdata.type=='virtual' or tfmdata.virtualized then + for k,v in next,tfmdata.characters do + if v.commands then v.commands=nil end + end + else + end + end +end +function tfm.cleanup(tfmdata) +end +function tfm.scale(tfmtable,scaledpoints,relativeid) + local t,factor=tfm.do_scale(tfmtable,scaledpoints,relativeid) + t.factor=factor + t.ascender=factor*(tfmtable.ascender or 0) + t.descender=factor*(tfmtable.descender or 0) + t.shared=tfmtable.shared or {} + t.unique=table.fastcopy(tfmtable.unique or {}) + tfm.cleanup(t) + return t +end +fonts.analyzers=fonts.analyzers or {} +fonts.analyzers.aux=fonts.analyzers.aux or {} +fonts.analyzers.methods=fonts.analyzers.methods or {} +fonts.analyzers.initializers=fonts.analyzers.initializers or {} +local state=attributes.private('state') +function fonts.analyzers.aux.setstate(head,font) + local tfmdata=fontdata[font] + local characters=tfmdata.characters + local descriptions=tfmdata.descriptions + local first,last,current,n,done=nil,nil,head,0,false + while current do + local id=current.id + if id==glyph and current.font==font then + local d=descriptions[current.char] + if d then + if d.class=="mark" then + done=true + set_attribute(current,state,5) + elseif n==0 then + first,last,n=current,current,1 + set_attribute(current,state,1) + else + last,n=current,n+1 + set_attribute(current,state,2) + end + else + if first and first==last then + set_attribute(last,state,4) + elseif last then + set_attribute(last,state,3) + end + first,last,n=nil,nil,0 + end + elseif id==disc then + set_attribute(current,state,2) + last=current + else + if first and first==last then + set_attribute(last,state,4) + elseif last then + set_attribute(last,state,3) + end + first,last,n=nil,nil,0 + end + current=current.next + end + if first and first==last then + set_attribute(last,state,4) + elseif last then + set_attribute(last,state,3) + end + return head,done +end +function tfm.replacements(tfm,value) + tfm.characters[0x0027]=tfm.characters[0x2019] +end +function tfm.checked_filename(metadata,whatever) + local foundfilename=metadata.foundfilename + if not foundfilename then + local askedfilename=metadata.filename or "" + if askedfilename~="" then + foundfilename=resolvers.findbinfile(askedfilename,"") or "" + if foundfilename=="" then + logs.report("fonts","source file '%s' is not found",askedfilename) + foundfilename=resolvers.findbinfile(file.basename(askedfilename),"") or "" + if foundfilename~="" then + logs.report("fonts","using source file '%s' (cache mismatch)",foundfilename) + end + end + elseif whatever then + logs.report("fonts","no source file for '%s'",whatever) + foundfilename="" + end + metadata.foundfilename=foundfilename + end + return foundfilename +end +statistics.register("fonts load time",function() + return statistics.elapsedseconds(fonts) +end) + +end -- closure + +do -- begin closure to overcome local limits and interference + +if not modules then modules={} end modules ['font-cid']={ + version=1.001, + comment="companion to font-otf.lua (cidmaps)", + author="Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright="PRAGMA ADE / ConTeXt Development Team", + license="see context related readme files" +} +local format,match,lower=string.format,string.match,string.lower +local tonumber=tonumber +local lpegmatch=lpeg.match +local trace_loading=false trackers.register("otf.loading",function(v) trace_loading=v end) +fonts=fonts or {} +fonts.cid=fonts.cid or {} +fonts.cid.map=fonts.cid.map or {} +fonts.cid.max=fonts.cid.max or 10 +local number=lpeg.C(lpeg.R("09","af","AF")^1) +local space=lpeg.S(" \n\r\t") +local spaces=space^0 +local period=lpeg.P(".") +local periods=period*period +local name=lpeg.P("/")*lpeg.C((1-space)^1) +local unicodes,names={},{} +local function do_one(a,b) + unicodes[tonumber(a)]=tonumber(b,16) +end +local function do_range(a,b,c) + c=tonumber(c,16) + for i=tonumber(a),tonumber(b) do + unicodes[i]=c + c=c+1 + end +end +local function do_name(a,b) + names[tonumber(a)]=b +end +local grammar=lpeg.P { "start", + start=number*spaces*number*lpeg.V("series"), + series=(spaces*(lpeg.V("one")+lpeg.V("range")+lpeg.V("named")) )^1, + one=(number*spaces*number)/do_one, + range=(number*periods*number*spaces*number)/do_range, + named=(number*spaces*name)/do_name +} +function fonts.cid.load(filename) + local data=io.loaddata(filename) + if data then + unicodes,names={},{} + lpegmatch(grammar,data) + local supplement,registry,ordering=match(filename,"^(.-)%-(.-)%-()%.(.-)$") + return { + supplement=supplement, + registry=registry, + ordering=ordering, + filename=filename, + unicodes=unicodes, + names=names + } + else + return nil + end +end +local template="%s-%s-%s.cidmap" +local function locate(registry,ordering,supplement) + local filename=format(template,registry,ordering,supplement) + local hashname=lower(filename) + local cidmap=fonts.cid.map[hashname] + if not cidmap then + if trace_loading then + logs.report("load otf","checking cidmap, registry: %s, ordering: %s, supplement: %s, filename: %s",registry,ordering,supplement,filename) + end + local fullname=resolvers.find_file(filename,'cid') or "" + if fullname~="" then + cidmap=fonts.cid.load(fullname) + if cidmap then + if trace_loading then + logs.report("load otf","using cidmap file %s",filename) + end + fonts.cid.map[hashname]=cidmap + cidmap.usedname=file.basename(filename) + return cidmap + end + end + end + return cidmap +end +function fonts.cid.getmap(registry,ordering,supplement) + local supplement=tonumber(supplement) + if trace_loading then + logs.report("load otf","needed cidmap, registry: %s, ordering: %s, supplement: %s",registry,ordering,supplement) + end + local cidmap=locate(registry,ordering,supplement) + if not cidmap then + local cidnum=nil + if supplement<fonts.cid.max then + for supplement=supplement+1,fonts.cid.max do + local c=locate(registry,ordering,supplement) + if c then + cidmap,cidnum=c,supplement + break + end + end + end + if not cidmap and supplement>0 then + for supplement=supplement-1,0,-1 do + local c=locate(registry,ordering,supplement) + if c then + cidmap,cidnum=c,supplement + break + end + end + end + if cidmap and cidnum>0 then + for s=0,cidnum-1 do + filename=format(template,registry,ordering,s) + if not fonts.cid.map[filename] then + fonts.cid.map[filename]=cidmap + end + end + end + end + return cidmap +end + +end -- closure + +do -- begin closure to overcome local limits and interference + +if not modules then modules={} end modules ['font-otf']={ + version=1.001, + comment="companion to font-otf.lua (tables)", + author="Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright="PRAGMA ADE / ConTeXt Development Team", + license="see context related readme files" +} +local type,next,tonumber,tostring=type,next,tonumber,tostring +local gsub,lower=string.gsub,string.lower +fonts=fonts or {} +fonts.otf=fonts.otf or {} +local otf=fonts.otf +otf.tables=otf.tables or {} +otf.meanings=otf.meanings or {} +otf.tables.scripts={ + ['dflt']='Default', + ['arab']='Arabic', + ['armn']='Armenian', + ['bali']='Balinese', + ['beng']='Bengali', + ['bopo']='Bopomofo', + ['brai']='Braille', + ['bugi']='Buginese', + ['buhd']='Buhid', + ['byzm']='Byzantine Music', + ['cans']='Canadian Syllabics', + ['cher']='Cherokee', + ['copt']='Coptic', + ['cprt']='Cypriot Syllabary', + ['cyrl']='Cyrillic', + ['deva']='Devanagari', + ['dsrt']='Deseret', + ['ethi']='Ethiopic', + ['geor']='Georgian', + ['glag']='Glagolitic', + ['goth']='Gothic', + ['grek']='Greek', + ['gujr']='Gujarati', + ['guru']='Gurmukhi', + ['hang']='Hangul', + ['hani']='CJK Ideographic', + ['hano']='Hanunoo', + ['hebr']='Hebrew', + ['ital']='Old Italic', + ['jamo']='Hangul Jamo', + ['java']='Javanese', + ['kana']='Hiragana and Katakana', + ['khar']='Kharosthi', + ['khmr']='Khmer', + ['knda']='Kannada', + ['lao' ]='Lao', + ['latn']='Latin', + ['limb']='Limbu', + ['linb']='Linear B', + ['math']='Mathematical Alphanumeric Symbols', + ['mlym']='Malayalam', + ['mong']='Mongolian', + ['musc']='Musical Symbols', + ['mymr']='Myanmar', + ['nko' ]="N'ko", + ['ogam']='Ogham', + ['orya']='Oriya', + ['osma']='Osmanya', + ['phag']='Phags-pa', + ['phnx']='Phoenician', + ['runr']='Runic', + ['shaw']='Shavian', + ['sinh']='Sinhala', + ['sylo']='Syloti Nagri', + ['syrc']='Syriac', + ['tagb']='Tagbanwa', + ['tale']='Tai Le', + ['talu']='Tai Lu', + ['taml']='Tamil', + ['telu']='Telugu', + ['tfng']='Tifinagh', + ['tglg']='Tagalog', + ['thaa']='Thaana', + ['thai']='Thai', + ['tibt']='Tibetan', + ['ugar']='Ugaritic Cuneiform', + ['xpeo']='Old Persian Cuneiform', + ['xsux']='Sumero-Akkadian Cuneiform', + ['yi' ]='Yi', +} +otf.tables.languages={ + ['dflt']='Default', + ['aba']='Abaza', + ['abk']='Abkhazian', + ['ady']='Adyghe', + ['afk']='Afrikaans', + ['afr']='Afar', + ['agw']='Agaw', + ['als']='Alsatian', + ['alt']='Altai', + ['amh']='Amharic', + ['ara']='Arabic', + ['ari']='Aari', + ['ark']='Arakanese', + ['asm']='Assamese', + ['ath']='Athapaskan', + ['avr']='Avar', + ['awa']='Awadhi', + ['aym']='Aymara', + ['aze']='Azeri', + ['bad']='Badaga', + ['bag']='Baghelkhandi', + ['bal']='Balkar', + ['bau']='Baule', + ['bbr']='Berber', + ['bch']='Bench', + ['bcr']='Bible Cree', + ['bel']='Belarussian', + ['bem']='Bemba', + ['ben']='Bengali', + ['bgr']='Bulgarian', + ['bhi']='Bhili', + ['bho']='Bhojpuri', + ['bik']='Bikol', + ['bil']='Bilen', + ['bkf']='Blackfoot', + ['bli']='Balochi', + ['bln']='Balante', + ['blt']='Balti', + ['bmb']='Bambara', + ['bml']='Bamileke', + ['bos']='Bosnian', + ['bre']='Breton', + ['brh']='Brahui', + ['bri']='Braj Bhasha', + ['brm']='Burmese', + ['bsh']='Bashkir', + ['bti']='Beti', + ['cat']='Catalan', + ['ceb']='Cebuano', + ['che']='Chechen', + ['chg']='Chaha Gurage', + ['chh']='Chattisgarhi', + ['chi']='Chichewa', + ['chk']='Chukchi', + ['chp']='Chipewyan', + ['chr']='Cherokee', + ['chu']='Chuvash', + ['cmr']='Comorian', + ['cop']='Coptic', + ['cos']='Corsican', + ['cre']='Cree', + ['crr']='Carrier', + ['crt']='Crimean Tatar', + ['csl']='Church Slavonic', + ['csy']='Czech', + ['dan']='Danish', + ['dar']='Dargwa', + ['dcr']='Woods Cree', + ['deu']='German', + ['dgr']='Dogri', + ['div']='Divehi', + ['djr']='Djerma', + ['dng']='Dangme', + ['dnk']='Dinka', + ['dri']='Dari', + ['dun']='Dungan', + ['dzn']='Dzongkha', + ['ebi']='Ebira', + ['ecr']='Eastern Cree', + ['edo']='Edo', + ['efi']='Efik', + ['ell']='Greek', + ['eng']='English', + ['erz']='Erzya', + ['esp']='Spanish', + ['eti']='Estonian', + ['euq']='Basque', + ['evk']='Evenki', + ['evn']='Even', + ['ewe']='Ewe', + ['fan']='French Antillean', + ['far']='Farsi', + ['fin']='Finnish', + ['fji']='Fijian', + ['fle']='Flemish', + ['fne']='Forest Nenets', + ['fon']='Fon', + ['fos']='Faroese', + ['fra']='French', + ['fri']='Frisian', + ['frl']='Friulian', + ['fta']='Futa', + ['ful']='Fulani', + ['gad']='Ga', + ['gae']='Gaelic', + ['gag']='Gagauz', + ['gal']='Galician', + ['gar']='Garshuni', + ['gaw']='Garhwali', + ['gez']="Ge'ez", + ['gil']='Gilyak', + ['gmz']='Gumuz', + ['gon']='Gondi', + ['grn']='Greenlandic', + ['gro']='Garo', + ['gua']='Guarani', + ['guj']='Gujarati', + ['hai']='Haitian', + ['hal']='Halam', + ['har']='Harauti', + ['hau']='Hausa', + ['haw']='Hawaiin', + ['hbn']='Hammer-Banna', + ['hil']='Hiligaynon', + ['hin']='Hindi', + ['hma']='High Mari', + ['hnd']='Hindko', + ['ho']='Ho', + ['hri']='Harari', + ['hrv']='Croatian', + ['hun']='Hungarian', + ['hye']='Armenian', + ['ibo']='Igbo', + ['ijo']='Ijo', + ['ilo']='Ilokano', + ['ind']='Indonesian', + ['ing']='Ingush', + ['inu']='Inuktitut', + ['iri']='Irish', + ['irt']='Irish Traditional', + ['isl']='Icelandic', + ['ism']='Inari Sami', + ['ita']='Italian', + ['iwr']='Hebrew', + ['jan']='Japanese', + ['jav']='Javanese', + ['jii']='Yiddish', + ['jud']='Judezmo', + ['jul']='Jula', + ['kab']='Kabardian', + ['kac']='Kachchi', + ['kal']='Kalenjin', + ['kan']='Kannada', + ['kar']='Karachay', + ['kat']='Georgian', + ['kaz']='Kazakh', + ['keb']='Kebena', + ['kge']='Khutsuri Georgian', + ['kha']='Khakass', + ['khk']='Khanty-Kazim', + ['khm']='Khmer', + ['khs']='Khanty-Shurishkar', + ['khv']='Khanty-Vakhi', + ['khw']='Khowar', + ['kik']='Kikuyu', + ['kir']='Kirghiz', + ['kis']='Kisii', + ['kkn']='Kokni', + ['klm']='Kalmyk', + ['kmb']='Kamba', + ['kmn']='Kumaoni', + ['kmo']='Komo', + ['kms']='Komso', + ['knr']='Kanuri', + ['kod']='Kodagu', + ['koh']='Korean Old Hangul', + ['kok']='Konkani', + ['kon']='Kikongo', + ['kop']='Komi-Permyak', + ['kor']='Korean', + ['koz']='Komi-Zyrian', + ['kpl']='Kpelle', + ['kri']='Krio', + ['krk']='Karakalpak', + ['krl']='Karelian', + ['krm']='Karaim', + ['krn']='Karen', + ['krt']='Koorete', + ['ksh']='Kashmiri', + ['ksi']='Khasi', + ['ksm']='Kildin Sami', + ['kui']='Kui', + ['kul']='Kulvi', + ['kum']='Kumyk', + ['kur']='Kurdish', + ['kuu']='Kurukh', + ['kuy']='Kuy', + ['kyk']='Koryak', + ['lad']='Ladin', + ['lah']='Lahuli', + ['lak']='Lak', + ['lam']='Lambani', + ['lao']='Lao', + ['lat']='Latin', + ['laz']='Laz', + ['lcr']='L-Cree', + ['ldk']='Ladakhi', + ['lez']='Lezgi', + ['lin']='Lingala', + ['lma']='Low Mari', + ['lmb']='Limbu', + ['lmw']='Lomwe', + ['lsb']='Lower Sorbian', + ['lsm']='Lule Sami', + ['lth']='Lithuanian', + ['ltz']='Luxembourgish', + ['lub']='Luba', + ['lug']='Luganda', + ['luh']='Luhya', + ['luo']='Luo', + ['lvi']='Latvian', + ['maj']='Majang', + ['mak']='Makua', + ['mal']='Malayalam Traditional', + ['man']='Mansi', + ['map']='Mapudungun', + ['mar']='Marathi', + ['maw']='Marwari', + ['mbn']='Mbundu', + ['mch']='Manchu', + ['mcr']='Moose Cree', + ['mde']='Mende', + ['men']="Me'en", + ['miz']='Mizo', + ['mkd']='Macedonian', + ['mle']='Male', + ['mlg']='Malagasy', + ['mln']='Malinke', + ['mlr']='Malayalam Reformed', + ['mly']='Malay', + ['mnd']='Mandinka', + ['mng']='Mongolian', + ['mni']='Manipuri', + ['mnk']='Maninka', + ['mnx']='Manx Gaelic', + ['moh']='Mohawk', + ['mok']='Moksha', + ['mol']='Moldavian', + ['mon']='Mon', + ['mor']='Moroccan', + ['mri']='Maori', + ['mth']='Maithili', + ['mts']='Maltese', + ['mun']='Mundari', + ['nag']='Naga-Assamese', + ['nan']='Nanai', + ['nas']='Naskapi', + ['ncr']='N-Cree', + ['ndb']='Ndebele', + ['ndg']='Ndonga', + ['nep']='Nepali', + ['new']='Newari', + ['ngr']='Nagari', + ['nhc']='Norway House Cree', + ['nis']='Nisi', + ['niu']='Niuean', + ['nkl']='Nkole', + ['nko']="N'ko", + ['nld']='Dutch', + ['nog']='Nogai', + ['nor']='Norwegian', + ['nsm']='Northern Sami', + ['nta']='Northern Tai', + ['nto']='Esperanto', + ['nyn']='Nynorsk', + ['oci']='Occitan', + ['ocr']='Oji-Cree', + ['ojb']='Ojibway', + ['ori']='Oriya', + ['oro']='Oromo', + ['oss']='Ossetian', + ['paa']='Palestinian Aramaic', + ['pal']='Pali', + ['pan']='Punjabi', + ['pap']='Palpa', + ['pas']='Pashto', + ['pgr']='Polytonic Greek', + ['pil']='Pilipino', + ['plg']='Palaung', + ['plk']='Polish', + ['pro']='Provencal', + ['ptg']='Portuguese', + ['qin']='Chin', + ['raj']='Rajasthani', + ['rbu']='Russian Buriat', + ['rcr']='R-Cree', + ['ria']='Riang', + ['rms']='Rhaeto-Romanic', + ['rom']='Romanian', + ['roy']='Romany', + ['rsy']='Rusyn', + ['rua']='Ruanda', + ['rus']='Russian', + ['sad']='Sadri', + ['san']='Sanskrit', + ['sat']='Santali', + ['say']='Sayisi', + ['sek']='Sekota', + ['sel']='Selkup', + ['sgo']='Sango', + ['shn']='Shan', + ['sib']='Sibe', + ['sid']='Sidamo', + ['sig']='Silte Gurage', + ['sks']='Skolt Sami', + ['sky']='Slovak', + ['sla']='Slavey', + ['slv']='Slovenian', + ['sml']='Somali', + ['smo']='Samoan', + ['sna']='Sena', + ['snd']='Sindhi', + ['snh']='Sinhalese', + ['snk']='Soninke', + ['sog']='Sodo Gurage', + ['sot']='Sotho', + ['sqi']='Albanian', + ['srb']='Serbian', + ['srk']='Saraiki', + ['srr']='Serer', + ['ssl']='South Slavey', + ['ssm']='Southern Sami', + ['sur']='Suri', + ['sva']='Svan', + ['sve']='Swedish', + ['swa']='Swadaya Aramaic', + ['swk']='Swahili', + ['swz']='Swazi', + ['sxt']='Sutu', + ['syr']='Syriac', + ['tab']='Tabasaran', + ['taj']='Tajiki', + ['tam']='Tamil', + ['tat']='Tatar', + ['tcr']='TH-Cree', + ['tel']='Telugu', + ['tgn']='Tongan', + ['tgr']='Tigre', + ['tgy']='Tigrinya', + ['tha']='Thai', + ['tht']='Tahitian', + ['tib']='Tibetan', + ['tkm']='Turkmen', + ['tmn']='Temne', + ['tna']='Tswana', + ['tne']='Tundra Nenets', + ['tng']='Tonga', + ['tod']='Todo', + ['trk']='Turkish', + ['tsg']='Tsonga', + ['tua']='Turoyo Aramaic', + ['tul']='Tulu', + ['tuv']='Tuvin', + ['twi']='Twi', + ['udm']='Udmurt', + ['ukr']='Ukrainian', + ['urd']='Urdu', + ['usb']='Upper Sorbian', + ['uyg']='Uyghur', + ['uzb']='Uzbek', + ['ven']='Venda', + ['vit']='Vietnamese', + ['wa' ]='Wa', + ['wag']='Wagdi', + ['wcr']='West-Cree', + ['wel']='Welsh', + ['wlf']='Wolof', + ['xbd']='Tai Lue', + ['xhs']='Xhosa', + ['yak']='Yakut', + ['yba']='Yoruba', + ['ycr']='Y-Cree', + ['yic']='Yi Classic', + ['yim']='Yi Modern', + ['zhh']='Chinese Hong Kong', + ['zhp']='Chinese Phonetic', + ['zhs']='Chinese Simplified', + ['zht']='Chinese Traditional', + ['znd']='Zande', + ['zul']='Zulu' +} +otf.tables.features={ + ['aalt']='Access All Alternates', + ['abvf']='Above-Base Forms', + ['abvm']='Above-Base Mark Positioning', + ['abvs']='Above-Base Substitutions', + ['afrc']='Alternative Fractions', + ['akhn']='Akhands', + ['blwf']='Below-Base Forms', + ['blwm']='Below-Base Mark Positioning', + ['blws']='Below-Base Substitutions', + ['c2pc']='Petite Capitals From Capitals', + ['c2sc']='Small Capitals From Capitals', + ['calt']='Contextual Alternates', + ['case']='Case-Sensitive Forms', + ['ccmp']='Glyph Composition/Decomposition', + ['cjct']='Conjunct Forms', + ['clig']='Contextual Ligatures', + ['cpsp']='Capital Spacing', + ['cswh']='Contextual Swash', + ['curs']='Cursive Positioning', + ['dflt']='Default Processing', + ['dist']='Distances', + ['dlig']='Discretionary Ligatures', + ['dnom']='Denominators', + ['dtls']='Dotless Forms', + ['expt']='Expert Forms', + ['falt']='Final glyph Alternates', + ['fin2']='Terminal Forms #2', + ['fin3']='Terminal Forms #3', + ['fina']='Terminal Forms', + ['flac']='Flattened Accents Over Capitals', + ['frac']='Fractions', + ['fwid']='Full Width', + ['half']='Half Forms', + ['haln']='Halant Forms', + ['halt']='Alternate Half Width', + ['hist']='Historical Forms', + ['hkna']='Horizontal Kana Alternates', + ['hlig']='Historical Ligatures', + ['hngl']='Hangul', + ['hojo']='Hojo Kanji Forms', + ['hwid']='Half Width', + ['init']='Initial Forms', + ['isol']='Isolated Forms', + ['ital']='Italics', + ['jalt']='Justification Alternatives', + ['jp04']='JIS2004 Forms', + ['jp78']='JIS78 Forms', + ['jp83']='JIS83 Forms', + ['jp90']='JIS90 Forms', + ['kern']='Kerning', + ['lfbd']='Left Bounds', + ['liga']='Standard Ligatures', + ['ljmo']='Leading Jamo Forms', + ['lnum']='Lining Figures', + ['locl']='Localized Forms', + ['mark']='Mark Positioning', + ['med2']='Medial Forms #2', + ['medi']='Medial Forms', + ['mgrk']='Mathematical Greek', + ['mkmk']='Mark to Mark Positioning', + ['mset']='Mark Positioning via Substitution', + ['nalt']='Alternate Annotation Forms', + ['nlck']='NLC Kanji Forms', + ['nukt']='Nukta Forms', + ['numr']='Numerators', + ['onum']='Old Style Figures', + ['opbd']='Optical Bounds', + ['ordn']='Ordinals', + ['ornm']='Ornaments', + ['palt']='Proportional Alternate Width', + ['pcap']='Petite Capitals', + ['pnum']='Proportional Figures', + ['pref']='Pre-base Forms', + ['pres']='Pre-base Substitutions', + ['pstf']='Post-base Forms', + ['psts']='Post-base Substitutions', + ['pwid']='Proportional Widths', + ['qwid']='Quarter Widths', + ['rand']='Randomize', + ['rkrf']='Rakar Forms', + ['rlig']='Required Ligatures', + ['rphf']='Reph Form', + ['rtbd']='Right Bounds', + ['rtla']='Right-To-Left Alternates', + ['rtlm']='Right To Left Math', + ['ruby']='Ruby Notation Forms', + ['salt']='Stylistic Alternates', + ['sinf']='Scientific Inferiors', + ['size']='Optical Size', + ['smcp']='Small Capitals', + ['smpl']='Simplified Forms', + ['ss01']='Stylistic Set 1', + ['ss02']='Stylistic Set 2', + ['ss03']='Stylistic Set 3', + ['ss04']='Stylistic Set 4', + ['ss05']='Stylistic Set 5', + ['ss06']='Stylistic Set 6', + ['ss07']='Stylistic Set 7', + ['ss08']='Stylistic Set 8', + ['ss09']='Stylistic Set 9', + ['ss10']='Stylistic Set 10', + ['ss11']='Stylistic Set 11', + ['ss12']='Stylistic Set 12', + ['ss13']='Stylistic Set 13', + ['ss14']='Stylistic Set 14', + ['ss15']='Stylistic Set 15', + ['ss16']='Stylistic Set 16', + ['ss17']='Stylistic Set 17', + ['ss18']='Stylistic Set 18', + ['ss19']='Stylistic Set 19', + ['ss20']='Stylistic Set 20', + ['ssty']='Script Style', + ['subs']='Subscript', + ['sups']='Superscript', + ['swsh']='Swash', + ['titl']='Titling', + ['tjmo']='Trailing Jamo Forms', + ['tnam']='Traditional Name Forms', + ['tnum']='Tabular Figures', + ['trad']='Traditional Forms', + ['twid']='Third Widths', + ['unic']='Unicase', + ['valt']='Alternate Vertical Metrics', + ['vatu']='Vattu Variants', + ['vert']='Vertical Writing', + ['vhal']='Alternate Vertical Half Metrics', + ['vjmo']='Vowel Jamo Forms', + ['vkna']='Vertical Kana Alternates', + ['vkrn']='Vertical Kerning', + ['vpal']='Proportional Alternate Vertical Metrics', + ['vrt2']='Vertical Rotation', + ['zero']='Slashed Zero', + ['trep']='Traditional TeX Replacements', + ['tlig']='Traditional TeX Ligatures', +} +otf.tables.baselines={ + ['hang']='Hanging baseline', + ['icfb']='Ideographic character face bottom edge baseline', + ['icft']='Ideographic character face tope edige baseline', + ['ideo']='Ideographic em-box bottom edge baseline', + ['idtp']='Ideographic em-box top edge baseline', + ['math']='Mathmatical centered baseline', + ['romn']='Roman baseline' +} +function otf.tables.to_tag(id) + return stringformat("%4s",lower(id)) +end +local function resolve(tab,id) + if tab and id then + id=lower(id) + return tab[id] or tab[gsub(id," ","")] or tab['dflt'] or '' + else + return "unknown" + end +end +function otf.meanings.script(id) + return resolve(otf.tables.scripts,id) +end +function otf.meanings.language(id) + return resolve(otf.tables.languages,id) +end +function otf.meanings.feature(id) + return resolve(otf.tables.features,id) +end +function otf.meanings.baseline(id) + return resolve(otf.tables.baselines,id) +end +otf.tables.to_scripts=table.reverse_hash(otf.tables.scripts ) +otf.tables.to_languages=table.reverse_hash(otf.tables.languages) +otf.tables.to_features=table.reverse_hash(otf.tables.features ) +local scripts=otf.tables.scripts +local languages=otf.tables.languages +local features=otf.tables.features +local to_scripts=otf.tables.to_scripts +local to_languages=otf.tables.to_languages +local to_features=otf.tables.to_features +for k,v in next,to_features do + local stripped=gsub(k,"%-"," ") + to_features[stripped]=v + local stripped=gsub(k,"[^a-zA-Z0-9]","") + to_features[stripped]=v +end +for k,v in next,to_features do + to_features[lower(k)]=v +end +otf.meanings.checkers={ + rand=function(v) + return v and "random" + end +} +local checkers=otf.meanings.checkers +function otf.meanings.normalize(features) + local h={} + for k,v in next,features do + k=lower(k) + if k=="language" or k=="lang" then + v=gsub(lower(v),"[^a-z0-9%-]","") + if not languages[v] then + h.language=to_languages[v] or "dflt" + else + h.language=v + end + elseif k=="script" then + v=gsub(lower(v),"[^a-z0-9%-]","") + if not scripts[v] then + h.script=to_scripts[v] or "dflt" + else + h.script=v + end + else + if type(v)=="string" then + local b=v:is_boolean() + if type(b)=="nil" then + v=tonumber(v) or lower(v) + else + v=b + end + end + k=to_features[k] or k + local c=checkers[k] + h[k]=c and c(v) or v + end + end + return h +end + +end -- closure + +do -- begin closure to overcome local limits and interference + +if not modules then modules={} end modules ['font-map']={ + version=1.001, + comment="companion to font-ini.mkiv", + author="Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright="PRAGMA ADE / ConTeXt Development Team", + license="see context related readme files" +} +local utf=unicode.utf8 +local match,format,find,concat,gsub,lower=string.match,string.format,string.find,table.concat,string.gsub,string.lower +local lpegmatch=lpeg.match +local utfbyte=utf.byte +local trace_loading=false trackers.register("otf.loading",function(v) trace_loading=v end) +local trace_unimapping=false trackers.register("otf.unimapping",function(v) trace_unimapping=v end) +local ctxcatcodes=tex and tex.ctxcatcodes +fonts=fonts or {} +fonts.map=fonts.map or {} +local function load_lum_table(filename) + local lumname=file.replacesuffix(file.basename(filename),"lum") + local lumfile=resolvers.find_file(lumname,"map") or "" + if lumfile~="" and lfs.isfile(lumfile) then + if trace_loading or trace_unimapping then + logs.report("load otf","enhance: loading %s ",lumfile) + end + lumunic=dofile(lumfile) + return lumunic,lumfile + end +end +local hex=lpeg.R("AF","09") +local hexfour=(hex*hex*hex*hex)/function(s) return tonumber(s,16) end +local hexsix=(hex^1)/function(s) return tonumber(s,16) end +local dec=(lpeg.R("09")^1)/tonumber +local period=lpeg.P(".") +local unicode=lpeg.P("uni")*(hexfour*(period+lpeg.P(-1))*lpeg.Cc(false)+lpeg.Ct(hexfour^1)*lpeg.Cc(true)) +local ucode=lpeg.P("u")*(hexsix*(period+lpeg.P(-1))*lpeg.Cc(false)+lpeg.Ct(hexsix^1)*lpeg.Cc(true)) +local index=lpeg.P("index")*dec*lpeg.Cc(false) +local parser=unicode+ucode+index +local parsers={} +local function make_name_parser(str) + if not str or str=="" then + return parser + else + local p=parsers[str] + if not p then + p=lpeg.P(str)*period*dec*lpeg.Cc(false) + parsers[str]=p + end + return p + end +end +local function tounicode16(unicode) + if unicode<0x10000 then + return format("%04X",unicode) + else + return format("%04X%04X",unicode/1024+0xD800,unicode%1024+0xDC00) + end +end +local function tounicode16sequence(unicodes) + local t={} + for l=1,#unicodes do + local unicode=unicodes[l] + if unicode<0x10000 then + t[l]=format("%04X",unicode) + else + t[l]=format("%04X%04X",unicode/1024+0xD800,unicode%1024+0xDC00) + end + end + return concat(t) +end +fonts.map.load_lum_table=load_lum_table +fonts.map.make_name_parser=make_name_parser +fonts.map.tounicode16=tounicode16 +fonts.map.tounicode16sequence=tounicode16sequence +local separator=lpeg.S("_.") +local other=lpeg.C((1-separator)^1) +local ligsplitter=lpeg.Ct(other*(separator*other)^0) +fonts.map.add_to_unicode=function(data,filename) + local unicodes=data.luatex and data.luatex.unicodes + if not unicodes then + return + end + unicodes['space']=unicodes['space'] or 32 + unicodes['hyphen']=unicodes['hyphen'] or 45 + unicodes['zwj']=unicodes['zwj'] or 0x200D + unicodes['zwnj']=unicodes['zwnj'] or 0x200C + local tounicode,originals,ns,nl,private,unknown={},{},0,0,fonts.private,format("%04X",utfbyte("?")) + data.luatex.tounicode,data.luatex.originals=tounicode,originals + local lumunic,uparser,oparser + if false then + lumunic=load_lum_table(filename) + lumunic=lumunic and lumunic.tounicode + end + local cidinfo,cidnames,cidcodes=data.cidinfo + local usedmap=cidinfo and cidinfo.usedname + usedmap=usedmap and lower(usedmap) + usedmap=usedmap and fonts.cid.map[usedmap] + if usedmap then + oparser=usedmap and make_name_parser(cidinfo.ordering) + cidnames=usedmap.names + cidcodes=usedmap.unicodes + end + uparser=make_name_parser() + local aglmap=fonts.map and fonts.map.agl_to_unicode + for index,glyph in next,data.glyphs do + local name,unic=glyph.name,glyph.unicode or -1 + if unic==-1 or unic>=private or (unic>=0xE000 and unic<=0xF8FF) or unic==0xFFFE or unic==0xFFFF then + local unicode=(lumunic and lumunic[name]) or (aglmap and aglmap[name]) + if unicode then + originals[index],tounicode[index],ns=unicode,tounicode16(unicode),ns+1 + end + if (not unicode) and usedmap then + local foundindex=lpegmatch(oparser,name) + if foundindex then + unicode=cidcodes[foundindex] + if unicode then + originals[index],tounicode[index],ns=unicode,tounicode16(unicode),ns+1 + else + local reference=cidnames[foundindex] + if reference then + local foundindex=lpegmatch(oparser,reference) + if foundindex then + unicode=cidcodes[foundindex] + if unicode then + originals[index],tounicode[index],ns=unicode,tounicode16(unicode),ns+1 + end + end + if not unicode then + local foundcodes,multiple=lpegmatch(uparser,reference) + if foundcodes then + if multiple then + originals[index],tounicode[index],nl,unicode=foundcodes,tounicode16sequence(foundcodes),nl+1,true + else + originals[index],tounicode[index],ns,unicode=foundcodes,tounicode16(foundcodes),ns+1,foundcodes + end + end + end + end + end + end + end + if not unicode then + local split=lpegmatch(ligsplitter,name) + local nplit=(split and #split) or 0 + if nplit==0 then + elseif nplit==1 then + local base=split[1] + unicode=unicodes[base] or (aglmap and aglmap[base]) + if unicode then + if type(unicode)=="table" then + unicode=unicode[1] + end + originals[index],tounicode[index],ns=unicode,tounicode16(unicode),ns+1 + end + else + local t={} + for l=1,nplit do + local base=split[l] + local u=unicodes[base] or (aglmap and aglmap[base]) + if not u then + break + elseif type(u)=="table" then + t[#t+1]=u[1] + else + t[#t+1]=u + end + end + if #t>0 then + originals[index],tounicode[index],nl,unicode=t,tounicode16sequence(t),nl+1,true + end + end + end + if not unicode then + local foundcodes,multiple=lpegmatch(uparser,name) + if foundcodes then + if multiple then + originals[index],tounicode[index],nl,unicode=foundcodes,tounicode16sequence(foundcodes),nl+1,true + else + originals[index],tounicode[index],ns,unicode=foundcodes,tounicode16(foundcodes),ns+1,foundcodes + end + end + end + if not unicode then + originals[index],tounicode[index]=0xFFFD,"FFFD" + end + end + end + if trace_unimapping then + for index,glyph in table.sortedhash(data.glyphs) do + local toun,name,unic=tounicode[index],glyph.name,glyph.unicode or -1 + if toun then + logs.report("load otf","internal: 0x%05X, name: %s, unicode: 0x%05X, tounicode: %s",index,name,unic,toun) + else + logs.report("load otf","internal: 0x%05X, name: %s, unicode: 0x%05X",index,name,unic) + end + end + end + if trace_loading and (ns>0 or nl>0) then + logs.report("load otf","enhance: %s tounicode entries added (%s ligatures)",nl+ns,ns) + end +end + +end -- closure + +do -- begin closure to overcome local limits and interference + +if not modules then modules={} end modules ['font-otf']={ + version=1.001, + comment="companion to font-ini.mkiv", + author="Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright="PRAGMA ADE / ConTeXt Development Team", + license="see context related readme files" +} +local utf=unicode.utf8 +local concat,utfbyte=table.concat,utf.byte +local format,gmatch,gsub,find,match,lower,strip=string.format,string.gmatch,string.gsub,string.find,string.match,string.lower,string.strip +local type,next,tonumber,tostring=type,next,tonumber,tostring +local abs=math.abs +local getn=table.getn +local lpegmatch=lpeg.match +local trace_private=false trackers.register("otf.private",function(v) trace_private=v end) +local trace_loading=false trackers.register("otf.loading",function(v) trace_loading=v end) +local trace_features=false trackers.register("otf.features",function(v) trace_features=v end) +local trace_dynamics=false trackers.register("otf.dynamics",function(v) trace_dynamics=v end) +local trace_sequences=false trackers.register("otf.sequences",function(v) trace_sequences=v end) +local trace_math=false trackers.register("otf.math",function(v) trace_math=v end) +local trace_defining=false trackers.register("fonts.defining",function(v) trace_defining=v end) +fonts=fonts or {} +fonts.otf=fonts.otf or {} +fonts.tfm=fonts.tfm or {} +local otf=fonts.otf +local tfm=fonts.tfm +local fontdata=fonts.ids +otf.tables=otf.tables or {} +otf.meanings=otf.meanings or {} +otf.tables.features=otf.tables.features or {} +otf.tables.languages=otf.tables.languages or {} +otf.tables.scripts=otf.tables.scripts or {} +otf.features=otf.features or {} +otf.features.list=otf.features.list or {} +otf.features.default=otf.features.default or {} +otf.enhancers=otf.enhancers or {} +otf.glists={ "gsub","gpos" } +otf.version=2.653 +otf.pack=true +otf.syncspace=true +otf.notdef=false +otf.cache=containers.define("fonts","otf",otf.version,true) +otf.cleanup_aat=false +local wildcard="*" +local default="dflt" +otf.tables.global_fields=table.tohash { + "lookups", + "glyphs", + "subfonts", + "luatex", + "pfminfo", + "cidinfo", + "tables", + "names", + "unicodes", + "names", + "anchor_classes", + "kern_classes", + "gpos", + "gsub" +} +otf.tables.valid_fields={ + "anchor_classes", + "ascent", + "cache_version", + "cidinfo", + "copyright", + "creationtime", + "descent", + "design_range_bottom", + "design_range_top", + "design_size", + "encodingchanged", + "extrema_bound", + "familyname", + "fontname", + "fontstyle_id", + "fontstyle_name", + "fullname", + "glyphs", + "hasvmetrics", + "head_optimized_for_cleartype", + "horiz_base", + "issans", + "isserif", + "italicangle", + "kerns", + "lookups", + "macstyle", + "modificationtime", + "onlybitmaps", + "origname", + "os2_version", + "pfminfo", + "private", + "serifcheck", + "sfd_version", + "strokedfont", + "strokewidth", + "subfonts", + "table_version", + "ttf_tables", + "uni_interp", + "uniqueid", + "units_per_em", + "upos", + "use_typo_metrics", + "uwidth", + "validation_state", + "verbose", + "version", + "vert_base", + "weight", + "weight_width_slope_only", + "xuid", +} +local function load_featurefile(ff,featurefile) + if featurefile then + featurefile=resolvers.find_file(file.addsuffix(featurefile,'fea'),'fea') + if featurefile and featurefile~="" then + if trace_loading then + logs.report("load otf","featurefile: %s",featurefile) + end + fontloader.apply_featurefile(ff,featurefile) + end + end +end +function otf.enhance(name,data,filename,verbose) + local enhancer=otf.enhancers[name] + if enhancer then + if (verbose~=nil and verbose) or trace_loading then + logs.report("load otf","enhance: %s (%s)",name,filename) + end + enhancer(data,filename) + end +end +local enhancers={ + "patch bugs", + "merge cid fonts","prepare unicode","cleanup ttf tables","compact glyphs","reverse coverage", + "cleanup aat","enrich with features","add some missing characters", + "reorganize mark classes", + "reorganize kerns", + "flatten glyph lookups","flatten anchor tables","flatten feature tables", + "simplify glyph lookups", + "prepare luatex tables", + "analyse features","rehash features", + "analyse anchors","analyse marks","analyse unicodes","analyse subtables", + "check italic correction","check math", + "share widths", + "strip not needed data", + "migrate metadata", + "check math parameters", +} +function otf.load(filename,format,sub,featurefile) + local name=file.basename(file.removesuffix(filename)) + local attr=lfs.attributes(filename) + local size,time=attr.size or 0,attr.modification or 0 + if featurefile then + local fattr=lfs.attributes(featurefile) + local fsize,ftime=fattr and fattr.size or 0,fattr and fattr.modification or 0 + name=name.."@"..file.removesuffix(file.basename(featurefile))..ftime..fsize + end + if sub=="" then sub=false end + local hash=name + if sub then + hash=hash.."-"..sub + end + hash=containers.cleanname(hash) + local data=containers.read(otf.cache,hash) + if not data or data.verbose~=fonts.verbose or data.size~=size or data.time~=time then + logs.report("load otf","loading: %s (hash: %s)",filename,hash) + local ff,messages + if sub then + ff,messages=fontloader.open(filename,sub) + else + ff,messages=fontloader.open(filename) + end + if trace_loading and messages and #messages>0 then + if type(messages)=="string" then + logs.report("load otf","warning: %s",messages) + else + for m=1,#messages do + logs.report("load otf","warning: %s",tostring(messages[m])) + end + end + else + logs.report("load otf","font loaded okay") + end + if ff then + load_featurefile(ff,featurefile) + data=fontloader.to_table(ff) + fontloader.close(ff) + if data then + logs.report("load otf","file size: %s",size) + logs.report("load otf","enhancing ...") + for e=1,#enhancers do + otf.enhance(enhancers[e],data,filename) + io.flush() + end + if otf.pack and not fonts.verbose then + otf.enhance("pack",data,filename) + end + data.size=size + data.time=time + data.verbose=fonts.verbose + logs.report("load otf","saving in cache: %s",filename) + data=containers.write(otf.cache,hash,data) + collectgarbage("collect") + data=containers.read(otf.cache,hash) + collectgarbage("collect") + else + logs.report("load otf","loading failed (table conversion error)") + end + else + logs.report("load otf","loading failed (file read error)") + end + end + if data then + if trace_defining then + logs.report("define font","loading from cache: %s",hash) + end + otf.enhance("unpack",data,filename,false) + otf.add_dimensions(data) + if trace_sequences then + otf.show_feature_order(data,filename) + end + end + return data +end +function otf.add_dimensions(data) + if data then + local force=otf.notdef + local luatex=data.luatex + local defaultwidth=luatex.defaultwidth or 0 + local defaultheight=luatex.defaultheight or 0 + local defaultdepth=luatex.defaultdepth or 0 + for _,d in next,data.glyphs do + local bb,wd=d.boundingbox,d.width + if not wd then + d.width=defaultwidth + elseif wd~=0 and d.class=="mark" then + d.width=-wd + end + if force and not d.name then + d.name=".notdef" + end + if bb then + local ht,dp=bb[4],-bb[2] + if ht==0 or ht<0 then + else + d.height=ht + end + if dp==0 or dp<0 then + else + d.depth=dp + end + end + end + end +end +function otf.show_feature_order(otfdata,filename) + local sequences=otfdata.luatex.sequences + if sequences and #sequences>0 then + if trace_loading then + logs.report("otf check","font %s has %s sequences",filename,#sequences) + logs.report("otf check"," ") + end + for nos=1,#sequences do + local sequence=sequences[nos] + local typ=sequence.type or "no-type" + local name=sequence.name or "no-name" + local subtables=sequence.subtables or { "no-subtables" } + local features=sequence.features + if trace_loading then + logs.report("otf check","%3i %-15s %-20s [%s]",nos,name,typ,concat(subtables,",")) + end + if features then + for feature,scripts in next,features do + local tt={} + for script,languages in next,scripts do + local ttt={} + for language,_ in next,languages do + ttt[#ttt+1]=language + end + tt[#tt+1]=format("[%s: %s]",script,concat(ttt," ")) + end + if trace_loading then + logs.report("otf check"," %s: %s",feature,concat(tt," ")) + end + end + end + end + if trace_loading then + logs.report("otf check","\n") + end + elseif trace_loading then + logs.report("otf check","font %s has no sequences",filename) + end +end +otf.enhancers["reorganize mark classes"]=function(data,filename) + if data.mark_classes then + local unicodes=data.luatex.unicodes + local reverse={} + for name,class in next,data.mark_classes do + local t={} + for s in gmatch(class,"[^ ]+") do + local us=unicodes[s] + if type(us)=="table" then + for u=1,#us do + t[us[u]]=true + end + else + t[us]=true + end + end + reverse[name]=t + end + data.luatex.markclasses=reverse + data.mark_classes=nil + end +end +otf.enhancers["prepare luatex tables"]=function(data,filename) + data.luatex=data.luatex or {} + local luatex=data.luatex + luatex.filename=filename + luatex.version=otf.version + luatex.creator="context mkiv" +end +otf.enhancers["cleanup aat"]=function(data,filename) + if otf.cleanup_aat then + end +end +local function analyze_features(g,features) + if g then + local t,done={},{} + for k=1,#g do + local f=features or g[k].features + if f then + for k=1,#f do + local tag=f[k].tag + if not done[tag] then + t[#t+1]=tag + done[tag]=true + end + end + end + end + if #t>0 then + return t + end + end + return nil +end +otf.enhancers["analyse features"]=function(data,filename) +end +otf.enhancers["rehash features"]=function(data,filename) + local features={} + data.luatex.features=features + for k,what in next,otf.glists do + local dw=data[what] + if dw then + local f={} + features[what]=f + for i=1,#dw do + local d=dw[i] + local dfeatures=d.features + if dfeatures then + for i=1,#dfeatures do + local df=dfeatures[i] + local tag=strip(lower(df.tag)) + local ft=f[tag] if not ft then ft={} f[tag]=ft end + local dscripts=df.scripts + for script,languages in next,dscripts do + script=strip(lower(script)) + local fts=ft[script] if not fts then fts={} ft[script]=fts end + for i=1,#languages do + fts[strip(lower(languages[i]))]=true + end + end + end + end + end + end + end +end +otf.enhancers["analyse anchors"]=function(data,filename) + local classes=data.anchor_classes + local luatex=data.luatex + local anchor_to_lookup,lookup_to_anchor={},{} + luatex.anchor_to_lookup,luatex.lookup_to_anchor=anchor_to_lookup,lookup_to_anchor + if classes then + for c=1,#classes do + local class=classes[c] + local anchor=class.name + local lookups=class.lookup + if type(lookups)~="table" then + lookups={ lookups } + end + local a=anchor_to_lookup[anchor] + if not a then a={} anchor_to_lookup[anchor]=a end + for l=1,#lookups do + local lookup=lookups[l] + local l=lookup_to_anchor[lookup] + if not l then l={} lookup_to_anchor[lookup]=l end + l[anchor]=true + a[lookup]=true + end + end + end +end +otf.enhancers["analyse marks"]=function(data,filename) + local glyphs=data.glyphs + local marks={} + data.luatex.marks=marks + for unicode,index in next,data.luatex.indices do + local glyph=glyphs[index] + if glyph.class=="mark" then + marks[unicode]=true + end + end +end +otf.enhancers["analyse unicodes"]=fonts.map.add_to_unicode +otf.enhancers["analyse subtables"]=function(data,filename) + data.luatex=data.luatex or {} + local luatex=data.luatex + local sequences={} + local lookups={} + luatex.sequences=sequences + luatex.lookups=lookups + for _,g in next,{ data.gsub,data.gpos } do + for k=1,#g do + local gk=g[k] + local typ=gk.type + if typ=="gsub_contextchain" or typ=="gpos_contextchain" then + gk.chain=1 + elseif typ=="gsub_reversecontextchain" or typ=="gpos_reversecontextchain" then + gk.chain=-1 + else + gk.chain=0 + end + local features=gk.features + if features then + sequences[#sequences+1]=gk + local t={} + for f=1,#features do + local feature=features[f] + local hash={} + for s,languages in next,feature.scripts do + s=lower(s) + local h=hash[s] + if not h then h={} hash[s]=h end + for l=1,#languages do + h[strip(lower(languages[l]))]=true + end + end + t[feature.tag]=hash + end + gk.features=t + else + lookups[gk.name]=gk + gk.name=nil + end + local subtables=gk.subtables + if subtables then + local t={} + for s=1,#subtables do + local subtable=subtables[s] + local name=subtable.name + t[#t+1]=name + end + gk.subtables=t + end + local flags=gk.flags + if flags then + gk.flags={ + (flags.ignorecombiningmarks and "mark") or false, + (flags.ignoreligatures and "ligature") or false, + (flags.ignorebaseglyphs and "base") or false, + flags.r2l or false, + } + if flags.mark_class then + gk.markclass=luatex.markclasses[flags.mark_class] + end + end + end + end +end +otf.enhancers["merge cid fonts"]=function(data,filename) + if data.subfonts then + if data.glyphs and next(data.glyphs) then + logs.report("load otf","replacing existing glyph table due to subfonts") + end + local cidinfo=data.cidinfo + local verbose=fonts.verbose + if cidinfo.registry then + local cidmap,cidname=fonts.cid.getmap(cidinfo.registry,cidinfo.ordering,cidinfo.supplement) + if cidmap then + cidinfo.usedname=cidmap.usedname + local glyphs,uni_to_int,int_to_uni,nofnames,nofunicodes={},{},{},0,0 + local unicodes,names=cidmap.unicodes,cidmap.names + for n,subfont in next,data.subfonts do + for index,g in next,subfont.glyphs do + if not next(g) then + else + local unicode,name=unicodes[index],names[index] + g.cidindex=n + g.boundingbox=g.boundingbox + g.name=g.name or name or "unknown" + if unicode then + uni_to_int[unicode]=index + int_to_uni[index]=unicode + nofunicodes=nofunicodes+1 + g.unicode=unicode + elseif name then + nofnames=nofnames+1 + g.unicode=-1 + end + glyphs[index]=g + end + end + subfont.glyphs=nil + end + if trace_loading then + logs.report("load otf","cid font remapped, %s unicode points, %s symbolic names, %s glyphs",nofunicodes,nofnames,nofunicodes+nofnames) + end + data.glyphs=glyphs + data.map=data.map or {} + data.map.map=uni_to_int + data.map.backmap=int_to_uni + elseif trace_loading then + logs.report("load otf","unable to remap cid font, missing cid file for %s",filename) + end + elseif trace_loading then + logs.report("load otf","font %s has no glyphs",filename) + end + end +end +otf.enhancers["prepare unicode"]=function(data,filename) + local luatex=data.luatex + if not luatex then luatex={} data.luatex=luatex end + local indices,unicodes,multiples,internals={},{},{},{} + local glyphs=data.glyphs + local mapmap=data.map + if not mapmap then + logs.report("load otf","no map in %s",filename) + mapmap={} + data.map={ map=mapmap } + elseif not mapmap.map then + logs.report("load otf","no unicode map in %s",filename) + mapmap={} + data.map.map=mapmap + else + mapmap=mapmap.map + end + local criterium=fonts.private + local private=fonts.private + for index,glyph in next,glyphs do + if index>0 then + local name=glyph.name + if name then + local unicode=glyph.unicode + if unicode==-1 or unicode>=criterium then + glyph.unicode=private + indices[private]=index + unicodes[name]=private + internals[index]=true + if trace_private then + logs.report("load otf","enhance: glyph %s at index U+%04X is moved to private unicode slot U+%04X",name,index,private) + end + private=private+1 + else + indices[unicode]=index + unicodes[name]=unicode + end + end + end + end + for unicode,index in next,mapmap do + if not internals[index] then + local name=glyphs[index].name + if name then + local un=unicodes[name] + if not un then + unicodes[name]=unicode + elseif type(un)=="number" then + if un~=unicode then + multiples[#multiples+1]=name + unicodes[name]={ un,unicode } + indices[unicode]=index + end + else + local ok=false + for u=1,#un do + if un[u]==unicode then + ok=true + break + end + end + if not ok then + multiples[#multiples+1]=name + un[#un+1]=unicode + indices[unicode]=index + end + end + end + end + end + if trace_loading then + if #multiples>0 then + logs.report("load otf","%s glyph are reused: %s",#multiples,concat(multiples," ")) + else + logs.report("load otf","no glyph are reused") + end + end + luatex.indices=indices + luatex.unicodes=unicodes + luatex.private=private +end +otf.enhancers["cleanup ttf tables"]=function(data,filename) + local ttf_tables=data.ttf_tables + if ttf_tables then + for k=1,#ttf_tables do + if ttf_tables[k].data then ttf_tables[k].data="deleted" end + end + end + data.ttf_tab_saved=nil +end +otf.enhancers["compact glyphs"]=function(data,filename) + table.compact(data.glyphs) + if data.subfonts then + for _,subfont in next,data.subfonts do + table.compact(subfont.glyphs) + end + end +end +otf.enhancers["reverse coverage"]=function(data,filename) + if data.lookups then + for _,v in next,data.lookups do + if v.rules then + for _,vv in next,v.rules do + local c=vv.coverage + if c and c.before then + c.before=table.reverse(c.before) + end + end + end + end + end +end +otf.enhancers["check italic correction"]=function(data,filename) + local glyphs=data.glyphs + local ok=false + for index,glyph in next,glyphs do + local ic=glyph.italic_correction + if ic then + if ic~=0 then + glyph.italic=ic + end + glyph.italic_correction=nil + ok=true + end + end + otf.tables.valid_fields[#otf.tables.valid_fields+1]="has_italic" + data.has_italic=true +end +otf.enhancers["check math"]=function(data,filename) + if data.math then + local glyphs=data.glyphs + local unicodes=data.luatex.unicodes + for index,glyph in next,glyphs do + local mk=glyph.mathkern + local hv=glyph.horiz_variants + local vv=glyph.vert_variants + if mk or hv or vv then + local math={} + glyph.math=math + if mk then + for k,v in next,mk do + if not next(v) then + mk[k]=nil + end + end + math.kerns=mk + glyph.mathkern=nil + end + if hv then + math.horiz_variants=hv.variants + local p=hv.parts + if p and #p>0 then + for i=1,#p do + local pi=p[i] + pi.glyph=unicodes[pi.component] or 0 + end + math.horiz_parts=p + end + local ic=hv.italic_correction + if ic and ic~=0 then + math.horiz_italic_correction=ic + end + glyph.horiz_variants=nil + end + if vv then + local uc=unicodes[index] + math.vert_variants=vv.variants + local p=vv.parts + if p and #p>0 then + for i=1,#p do + local pi=p[i] + pi.glyph=unicodes[pi.component] or 0 + end + math.vert_parts=p + end + local ic=vv.italic_correction + if ic and ic~=0 then + math.vert_italic_correction=ic + end + glyph.vert_variants=nil + end + local ic=glyph.italic_correction + if ic then + if ic~=0 then + math.italic_correction=ic + end + glyph.italic_correction=nil + end + end + end + end +end +otf.enhancers["share widths"]=function(data,filename) + local glyphs=data.glyphs + local widths={} + for index,glyph in next,glyphs do + local width=glyph.width + widths[width]=(widths[width] or 0)+1 + end + local wd,most=0,1 + for k,v in next,widths do + if v>most then + wd,most=k,v + end + end + if most>1000 then + if trace_loading then + logs.report("load otf","most common width: %s (%s times), sharing (cjk font)",wd,most) + end + for k,v in next,glyphs do + if v.width==wd then + v.width=nil + end + end + data.luatex.defaultwidth=wd + end +end +otf.enhancers["reorganize kerns"]=function(data,filename) + local glyphs,mapmap,unicodes=data.glyphs,data.luatex.indices,data.luatex.unicodes + local mkdone=false + local function do_it(lookup,first_unicode,kerns) + local glyph=glyphs[mapmap[first_unicode]] + if glyph then + local mykerns=glyph.mykerns + if not mykerns then + mykerns={} + glyph.mykerns=mykerns + end + local lookupkerns=mykerns[lookup] + if not lookupkerns then + lookupkerns={} + mykerns[lookup]=lookupkerns + end + for second_unicode,kern in next,kerns do + lookupkerns[second_unicode]=kern + end + elseif trace_loading then + logs.report("load otf","no glyph data for U+%04X",first_unicode) + end + end + for index,glyph in next,glyphs do + if glyph.kerns then + local mykerns={} + for k,v in next,glyph.kerns do + local vc,vo,vl=v.char,v.off,v.lookup + if vc and vo and vl then + local uvc=unicodes[vc] + if not uvc then + if trace_loading then + logs.report("load otf","problems with unicode %s of kern %s at glyph %s",vc,k,index) + end + else + if type(vl)~="table" then + vl={ vl } + end + for l=1,#vl do + local vll=vl[l] + local mkl=mykerns[vll] + if not mkl then + mkl={} + mykerns[vll]=mkl + end + if type(uvc)=="table" then + for u=1,#uvc do + mkl[uvc[u]]=vo + end + else + mkl[uvc]=vo + end + end + end + end + end + glyph.mykerns=mykerns + glyph.kerns=nil + mkdone=true + end + end + if trace_loading and mkdone then + logs.report("load otf","replacing 'kerns' tables by 'mykerns' tables") + end + if data.kerns then + if trace_loading then + logs.report("load otf","removing global 'kern' table") + end + data.kerns=nil + end + local dgpos=data.gpos + if dgpos then + local separator=lpeg.P(" ") + local other=((1-separator)^0)/unicodes + local splitter=lpeg.Ct(other*(separator*other)^0) + for gp=1,#dgpos do + local gpos=dgpos[gp] + local subtables=gpos.subtables + if subtables then + for s=1,#subtables do + local subtable=subtables[s] + local kernclass=subtable.kernclass + if kernclass then + local split={} + for k=1,#kernclass do + local kcl=kernclass[k] + local firsts,seconds,offsets,lookups=kcl.firsts,kcl.seconds,kcl.offsets,kcl.lookup + if type(lookups)~="table" then + lookups={ lookups } + end + local maxfirsts,maxseconds=#firsts,#seconds + for _,s in next,firsts do + split[s]=split[s] or lpegmatch(splitter,s) + end + for _,s in next,seconds do + split[s]=split[s] or lpegmatch(splitter,s) + end + for l=1,#lookups do + local lookup=lookups[l] + for fk=1,#firsts do + local fv=firsts[fk] + local splt=split[fv] + if splt then + local kerns,baseoffset={},(fk-1)*maxseconds + for sk=2,maxseconds do + local sv=seconds[sk] + local splt=split[sv] + if splt then + local offset=offsets[baseoffset+sk] + if offset then + for i=1,#splt do + local second_unicode=splt[i] + if tonumber(second_unicode) then + kerns[second_unicode]=offset + else for s=1,#second_unicode do + kerns[second_unicode[s]]=offset + end end + end + end + end + end + for i=1,#splt do + local first_unicode=splt[i] + if tonumber(first_unicode) then + do_it(lookup,first_unicode,kerns) + else for f=1,#first_unicode do + do_it(lookup,first_unicode[f],kerns) + end end + end + end + end + end + end + subtable.comment="The kernclass table is merged into mykerns in the indexed glyph tables." + subtable.kernclass={} + end + end + end + end + end +end +otf.enhancers["strip not needed data"]=function(data,filename) + local verbose=fonts.verbose + local int_to_uni=data.luatex.unicodes + for k,v in next,data.glyphs do + local d=v.dependents + if d then v.dependents=nil end + local a=v.altuni + if a then v.altuni=nil end + if verbose then + local code=int_to_uni[k] + if code then + local vu=v.unicode + if not vu then + v.unicode=code + elseif type(vu)=="table" then + if vu[#vu]==code then + else + vu[#vu+1]=code + end + elseif vu~=code then + v.unicode={ vu,code } + end + end + else + v.unicode=nil + v.index=nil + end + end + data.luatex.comment="Glyph tables have their original index. When present, mykern tables are indexed by unicode." + data.map=nil + data.names=nil + data.glyphcnt=nil + data.glyphmax=nil + if true then + data.gpos=nil + data.gsub=nil + data.anchor_classes=nil + end +end +otf.enhancers["migrate metadata"]=function(data,filename) + local global_fields=otf.tables.global_fields + local metadata={} + for k,v in next,data do + if not global_fields[k] then + metadata[k]=v + data[k]=nil + end + end + data.metadata=metadata + local pfminfo=data.pfminfo + metadata.isfixedpitch=metadata.isfixedpitch or (pfminfo.panose and pfminfo.panose["proportion"]=="Monospaced") + metadata.charwidth=pfminfo and pfminfo.avgwidth +end +local private_math_parameters={ + "FractionDelimiterSize", + "FractionDelimiterDisplayStyleSize", +} +otf.enhancers["check math parameters"]=function(data,filename) + local mathdata=data.metadata.math + if mathdata then + for m=1,#private_math_parameters do + local pmp=private_math_parameters[m] + if not mathdata[pmp] then + if trace_loading then + logs.report("load otf","setting math parameter '%s' to 0",pmp) + end + mathdata[pmp]=0 + end + end + end +end +otf.enhancers["flatten glyph lookups"]=function(data,filename) + for k,v in next,data.glyphs do + local lookups=v.lookups + if lookups then + for kk,vv in next,lookups do + for kkk=1,#vv do + local vvv=vv[kkk] + local s=vvv.specification + if s then + local t=vvv.type + if t=="ligature" then + vv[kkk]={ "ligature",s.components,s.char } + elseif t=="alternate" then + vv[kkk]={ "alternate",s.components } + elseif t=="substitution" then + vv[kkk]={ "substitution",s.variant } + elseif t=="multiple" then + vv[kkk]={ "multiple",s.components } + elseif t=="position" then + vv[kkk]={ "position",{ s.x or 0,s.y or 0,s.h or 0,s.v or 0 } } + elseif t=="pair" then + local one,two,paired=s.offsets[1],s.offsets[2],s.paired or "" + if one then + if two then + vv[kkk]={ "pair",paired,{ one.x or 0,one.y or 0,one.h or 0,one.v or 0 },{ two.x or 0,two.y or 0,two.h or 0,two.v or 0 } } + else + vv[kkk]={ "pair",paired,{ one.x or 0,one.y or 0,one.h or 0,one.v or 0 } } + end + else + if two then + vv[kkk]={ "pair",paired,{},{ two.x or 0,two.y or 0,two.h or 0,two.v or 0} } + else + vv[kkk]={ "pair",paired } + end + end + else + if trace_loading then + logs.report("load otf","flattening needed, report to context list") + end + for a,b in next,s do + if trace_loading and vvv[a] then + logs.report("load otf","flattening conflict, report to context list") + end + vvv[a]=b + end + vvv.specification=nil + end + end + end + end + end + end +end +otf.enhancers["simplify glyph lookups"]=function(data,filename) + for k,v in next,data.glyphs do + local lookups=v.lookups + if lookups then + local slookups,mlookups + for kk,vv in next,lookups do + if #vv==1 then + if not slookups then + slookups={} + v.slookups=slookups + end + slookups[kk]=vv[1] + else + if not mlookups then + mlookups={} + v.mlookups=mlookups + end + mlookups[kk]=vv + end + end + v.lookups=nil + end + end +end +otf.enhancers["flatten anchor tables"]=function(data,filename) + for k,v in next,data.glyphs do + if v.anchors then + for kk,vv in next,v.anchors do + for kkk,vvv in next,vv do + if vvv.x or vvv.y then + vv[kkk]={ vvv.x or 0,vvv.y or 0 } + else + for kkkk=1,#vvv do + local vvvv=vvv[kkkk] + vvv[kkkk]={ vvvv.x or 0,vvvv.y or 0 } + end + end + end + end + end + end +end +otf.enhancers["flatten feature tables"]=function(data,filename) + for _,tag in next,otf.glists do + if data[tag] then + if trace_loading then + logs.report("load otf","flattening %s table",tag) + end + for k,v in next,data[tag] do + local features=v.features + if features then + for kk=1,#features do + local vv=features[kk] + local t={} + local scripts=vv.scripts + for kkk=1,#scripts do + local vvv=scripts[kkk] + t[vvv.script]=vvv.langs + end + vv.scripts=t + end + end + end + end + end +end +otf.enhancers.patches=otf.enhancers.patches or {} +otf.enhancers["patch bugs"]=function(data,filename) + local basename=file.basename(lower(filename)) + for pattern,action in next,otf.enhancers.patches do + if find(basename,pattern) then + action(data,filename) + end + end +end +fonts.otf.enhancers["enrich with features"]=function(data,filename) +end +function otf.features.register(name,default) + otf.features.list[#otf.features.list+1]=name + otf.features.default[name]=default +end +function otf.set_features(tfmdata,features) + local processes={} + if features and next(features) then + local lists={ + fonts.triggers, + fonts.processors, + fonts.manipulators, + } + local mode=tfmdata.mode or fonts.mode + local initializers=fonts.initializers + local fi=initializers[mode] + if fi then + local fiotf=fi.otf + if fiotf then + local done={} + for l=1,4 do + local list=lists[l] + if list then + for i=1,#list do + local f=list[i] + local value=features[f] + if value and fiotf[f] then + if not done[f] then + if trace_features then + logs.report("define otf","initializing feature %s to %s for mode %s for font %s",f,tostring(value),mode or 'unknown',tfmdata.fullname or 'unknown') + end + fiotf[f](tfmdata,value) + mode=tfmdata.mode or fonts.mode + local im=initializers[mode] + if im then + fiotf=initializers[mode].otf + end + done[f]=true + end + end + end + end + end + end + end + local fm=fonts.methods[mode] + if fm then + local fmotf=fm.otf + if fmotf then + for l=1,4 do + local list=lists[l] + if list then + for i=1,#list do + local f=list[i] + if fmotf[f] then + if trace_features then + logs.report("define otf","installing feature handler %s for mode %s for font %s",f,mode or 'unknown',tfmdata.fullname or 'unknown') + end + processes[#processes+1]=fmotf[f] + end + end + end + end + end + else + end + end + return processes,features +end +function otf.otf_to_tfm(specification) + local name=specification.name + local sub=specification.sub + local filename=specification.filename + local format=specification.format + local features=specification.features.normal + local cache_id=specification.hash + local tfmdata=containers.read(tfm.cache,cache_id) + if not tfmdata then + local otfdata=otf.load(filename,format,sub,features and features.featurefile) + if otfdata and next(otfdata) then + otfdata.shared=otfdata.shared or { + featuredata={}, + anchorhash={}, + initialized=false, + } + tfmdata=otf.copy_to_tfm(otfdata,cache_id) + if tfmdata and next(tfmdata) then + tfmdata.unique=tfmdata.unique or {} + tfmdata.shared=tfmdata.shared or {} + local shared=tfmdata.shared + shared.otfdata=otfdata + shared.features=features + shared.dynamics={} + shared.processes={} + shared.set_dynamics=otf.set_dynamics + tfmdata.luatex=otfdata.luatex + tfmdata.indices=otfdata.luatex.indices + tfmdata.unicodes=otfdata.luatex.unicodes + tfmdata.marks=otfdata.luatex.marks + tfmdata.originals=otfdata.luatex.originals + tfmdata.changed={} + tfmdata.has_italic=otfdata.metadata.has_italic + if not tfmdata.language then tfmdata.language='dflt' end + if not tfmdata.script then tfmdata.script='dflt' end + shared.processes,shared.features=otf.set_features(tfmdata,fonts.define.check(features,otf.features.default)) + end + end + containers.write(tfm.cache,cache_id,tfmdata) + end + return tfmdata +end +fonts.formats.dfont="truetype" +fonts.formats.ttc="truetype" +fonts.formats.ttf="truetype" +fonts.formats.otf="opentype" +function otf.copy_to_tfm(data,cache_id) + if data then + local glyphs,pfminfo,metadata=data.glyphs or {},data.pfminfo or {},data.metadata or {} + local luatex=data.luatex + local unicodes=luatex.unicodes + local indices=luatex.indices + local characters,parameters,math_parameters,descriptions={},{},{},{} + local designsize=metadata.designsize or metadata.design_size or 100 + if designsize==0 then + designsize=100 + end + local spaceunits,spacer=500,"space" + for u,i in next,indices do + characters[u]={} + descriptions[u]=glyphs[i] + end + if metadata.math then + for name,value in next,metadata.math do + math_parameters[name]=value + end + for u,char in next,characters do + local d=descriptions[u] + local m=d.math + if m then + local variants,parts,c,uc=m.horiz_variants,m.horiz_parts,char,u + if variants then + for n in gmatch(variants,"[^ ]+") do + local un=unicodes[n] + if un and uc~=un then + c.next=un + c=characters[un] + uc=un + end + end + c.horiz_variants=parts + elseif parts then + c.horiz_variants=parts + end + local variants,parts,c,uc=m.vert_variants,m.vert_parts,char,u + if variants then + for n in gmatch(variants,"[^ ]+") do + local un=unicodes[n] + if un and uc~=un then + c.next=un + c=characters[un] + uc=un + end + end + c.vert_variants=parts + elseif parts then + c.vert_variants=parts + end + local italic_correction=m.vert_italic_correction + if italic_correction then + c.vert_italic_correction=italic_correction + end + local kerns=m.kerns + if kerns then + char.mathkerns=kerns + end + end + end + end + local endash,emdash,space=0x20,0x2014,"space" + if metadata.isfixedpitch then + if descriptions[endash] then + spaceunits,spacer=descriptions[endash].width,"space" + end + if not spaceunits and descriptions[emdash] then + spaceunits,spacer=descriptions[emdash].width,"emdash" + end + if not spaceunits and metadata.charwidth then + spaceunits,spacer=metadata.charwidth,"charwidth" + end + else + if descriptions[endash] then + spaceunits,spacer=descriptions[endash].width,"space" + end + if not spaceunits and descriptions[emdash] then + spaceunits,spacer=descriptions[emdash].width/2,"emdash/2" + end + if not spaceunits and metadata.charwidth then + spaceunits,spacer=metadata.charwidth,"charwidth" + end + end + spaceunits=tonumber(spaceunits) or tfm.units/2 + local filename=fonts.tfm.checked_filename(luatex) + local fontname=metadata.fontname + local fullname=metadata.fullname or fontname + local cidinfo=data.cidinfo + local units=metadata.units_per_em or 1000 + cidinfo.registry=cidinfo and cidinfo.registry or "" + parameters.slant=0 + parameters.space=spaceunits + parameters.space_stretch=units/2 + parameters.space_shrink=1*units/3 + parameters.x_height=2*units/5 + parameters.quad=units + if spaceunits<2*units/5 then + end + local italicangle=metadata.italicangle + if italicangle then + parameters.slant=parameters.slant-math.round(math.tan(italicangle*math.pi/180)) + end + if metadata.isfixedpitch then + parameters.space_stretch=0 + parameters.space_shrink=0 + elseif otf.syncspace then + parameters.space_stretch=spaceunits/2 + parameters.space_shrink=spaceunits/3 + end + parameters.extra_space=parameters.space_shrink + if pfminfo.os2_xheight and pfminfo.os2_xheight>0 then + parameters.x_height=pfminfo.os2_xheight + else + local x=0x78 + if x then + local x=descriptions[x] + if x then + parameters.x_height=x.height + end + end + end + return { + characters=characters, + parameters=parameters, + math_parameters=math_parameters, + descriptions=descriptions, + indices=indices, + unicodes=unicodes, + type="real", + direction=0, + boundarychar_label=0, + boundarychar=65536, + designsize=(designsize/10)*65536, + spacer="500 units", + encodingbytes=2, + filename=filename, + fontname=fontname, + fullname=fullname, + psname=fontname or fullname, + name=filename or fullname, + units=units, + format=fonts.fontformat(filename,"opentype"), + cidinfo=cidinfo, + ascender=abs(metadata.ascent or 0), + descender=abs(metadata.descent or 0), + spacer=spacer, + italicangle=italicangle, + } + else + return nil + end +end +otf.features.register('mathsize') +function tfm.read_from_open_type(specification) + local tfmtable=otf.otf_to_tfm(specification) + if tfmtable then + local otfdata=tfmtable.shared.otfdata + tfmtable.name=specification.name + tfmtable.sub=specification.sub + local s=specification.size + local m=otfdata.metadata.math + if m then + local f=specification.features + if f then + local f=f.normal + if f and f.mathsize then + local mathsize=specification.mathsize or 0 + if mathsize==2 then + local p=m.ScriptPercentScaleDown + if p then + local ps=p*specification.textsize/100 + if trace_math then + logs.report("define font","asked script size: %s, used: %s (%2.2f %%)",s,ps,(ps/s)*100) + end + s=ps + end + elseif mathsize==3 then + local p=m.ScriptScriptPercentScaleDown + if p then + local ps=p*specification.textsize/100 + if trace_math then + logs.report("define font","asked scriptscript size: %s, used: %s (%2.2f %%)",s,ps,(ps/s)*100) + end + s=ps + end + end + end + end + end + tfmtable=tfm.scale(tfmtable,s,specification.relativeid) + if tfm.fontname_mode=="specification" then + local specname=specification.specification + if specname then + tfmtable.name=specname + if trace_defining then + logs.report("define font","overloaded fontname: '%s'",specname) + end + end + end + fonts.logger.save(tfmtable,file.extname(specification.filename),specification) + end + return tfmtable +end +function otf.collect_lookups(otfdata,kind,script,language) + local sequences=otfdata.luatex.sequences + if sequences then + local featuremap,featurelist={},{} + for s=1,#sequences do + local sequence=sequences[s] + local features=sequence.features + features=features and features[kind] + features=features and (features[script] or features[default] or features[wildcard]) + features=features and (features[language] or features[default] or features[wildcard]) + if features then + local subtables=sequence.subtables + if subtables then + for s=1,#subtables do + local ss=subtables[s] + if not featuremap[s] then + featuremap[ss]=true + featurelist[#featurelist+1]=ss + end + end + end + end + end + if #featurelist>0 then + return featuremap,featurelist + end + end + return nil,nil +end + +end -- closure + +do -- begin closure to overcome local limits and interference + +if not modules then modules={} end modules ['font-otd']={ + version=1.001, + comment="companion to font-ini.mkiv", + author="Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright="PRAGMA ADE / ConTeXt Development Team", + license="see context related readme files" +} +local trace_dynamics=false trackers.register("otf.dynamics",function(v) trace_dynamics=v end) +fonts=fonts or {} +fonts.otf=fonts.otf or {} +local otf=fonts.otf +local fontdata=fonts.ids +otf.features=otf.features or {} +otf.features.default=otf.features.default or {} +local context_setups=fonts.define.specify.context_setups +local context_numbers=fonts.define.specify.context_numbers +local a_to_script={} otf.a_to_script=a_to_script +local a_to_language={} otf.a_to_language=a_to_language +function otf.set_dynamics(font,dynamics,attribute) + local features=context_setups[context_numbers[attribute]] + if features then + local script=features.script or 'dflt' + local language=features.language or 'dflt' + local ds=dynamics[script] + if not ds then + ds={} + dynamics[script]=ds + end + local dsl=ds[language] + if not dsl then + dsl={} + ds[language]=dsl + end + local dsla=dsl[attribute] + if dsla then + return dsla + else + local tfmdata=fontdata[font] + a_to_script [attribute]=script + a_to_language[attribute]=language + local saved={ + script=tfmdata.script, + language=tfmdata.language, + mode=tfmdata.mode, + features=tfmdata.shared.features + } + tfmdata.mode="node" + tfmdata.language=language + tfmdata.script=script + tfmdata.shared.features={} + local set=fonts.define.check(features,otf.features.default) + dsla=otf.set_features(tfmdata,set) + if trace_dynamics then + logs.report("otf define","setting dynamics %s: attribute %s, script %s, language %s, set: %s",context_numbers[attribute],attribute,script,language,table.sequenced(set)) + end + tfmdata.script=saved.script + tfmdata.language=saved.language + tfmdata.mode=saved.mode + tfmdata.shared.features=saved.features + dynamics[script][language][attribute]=dsla + return dsla + end + end + return nil +end + +end -- closure + +do -- begin closure to overcome local limits and interference + +if not modules then modules={} end modules ['font-oti']={ + version=1.001, + comment="companion to font-ini.mkiv", + author="Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright="PRAGMA ADE / ConTeXt Development Team", + license="see context related readme files" +} +local lower=string.lower +local otf=fonts.otf +otf.default_language='latn' +otf.default_script='dflt' +local languages=otf.tables.languages +local scripts=otf.tables.scripts +function otf.features.language(tfmdata,value) + if value then + value=lower(value) + if languages[value] then + tfmdata.language=value + end + end +end +function otf.features.script(tfmdata,value) + if value then + value=lower(value) + if scripts[value] then + tfmdata.script=value + end + end +end +function otf.features.mode(tfmdata,value) + if value then + tfmdata.mode=lower(value) + end +end +fonts.initializers.base.otf.language=otf.features.language +fonts.initializers.base.otf.script=otf.features.script +fonts.initializers.base.otf.mode=otf.features.mode +fonts.initializers.base.otf.method=otf.features.mode +fonts.initializers.node.otf.language=otf.features.language +fonts.initializers.node.otf.script=otf.features.script +fonts.initializers.node.otf.mode=otf.features.mode +fonts.initializers.node.otf.method=otf.features.mode +otf.features.register("features",true) +table.insert(fonts.processors,"features") + +end -- closure + +do -- begin closure to overcome local limits and interference + +if not modules then modules={} end modules ['font-otb']={ + version=1.001, + comment="companion to font-ini.mkiv", + author="Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright="PRAGMA ADE / ConTeXt Development Team", + license="see context related readme files" +} +local concat=table.concat +local format,gmatch,gsub,find,match,lower,strip=string.format,string.gmatch,string.gsub,string.find,string.match,string.lower,string.strip +local type,next,tonumber,tostring=type,next,tonumber,tostring +local lpegmatch=lpeg.match +local otf=fonts.otf +local tfm=fonts.tfm +local trace_baseinit=false trackers.register("otf.baseinit",function(v) trace_baseinit=v end) +local trace_singles=false trackers.register("otf.singles",function(v) trace_singles=v end) +local trace_multiples=false trackers.register("otf.multiples",function(v) trace_multiples=v end) +local trace_alternatives=false trackers.register("otf.alternatives",function(v) trace_alternatives=v end) +local trace_ligatures=false trackers.register("otf.ligatures",function(v) trace_ligatures=v end) +local trace_kerns=false trackers.register("otf.kerns",function(v) trace_kerns=v end) +local trace_preparing=false trackers.register("otf.preparing",function(v) trace_preparing=v end) +local wildcard="*" +local default="dflt" +local split_at_space=lpeg.Ct(lpeg.splitat(" ")) +local pcache,fcache={},{} +local function gref(descriptions,n) + if type(n)=="number" then + local name=descriptions[n].name + if name then + return format("U+%04X (%s)",n,name) + else + return format("U+%04X") + end + elseif n then + local num,nam={},{} + for i=1,#n do + local ni=n[i] + num[i]=format("U+%04X",ni) + nam[i]=descriptions[ni].name or "?" + end + return format("%s (%s)",concat(num," "),concat(nam," ")) + else + return "?" + end +end +local function cref(kind,lookupname) + if lookupname then + return format("feature %s, lookup %s",kind,lookupname) + else + return format("feature %s",kind) + end +end +local function resolve_ligatures(tfmdata,ligatures,kind) + kind=kind or "unknown" + local unicodes=tfmdata.unicodes + local characters=tfmdata.characters + local descriptions=tfmdata.descriptions + local changed=tfmdata.changed + local done={} + while true do + local ok=false + for k,v in next,ligatures do + local lig=v[1] + if not done[lig] then + local ligs=lpegmatch(split_at_space,lig) + if #ligs==2 then + local uc=v[2] + local c,f,s=characters[uc],ligs[1],ligs[2] + local uft,ust=unicodes[f] or 0,unicodes[s] or 0 + if not uft or not ust then + logs.report("define otf","%s: unicode problem with base ligature %s = %s + %s",cref(kind),gref(descriptions,uc),gref(descriptions,uft),gref(descriptions,ust)) + else + if type(uft)=="number" then uft={ uft } end + if type(ust)=="number" then ust={ ust } end + for ufi=1,#uft do + local uf=uft[ufi] + for usi=1,#ust do + local us=ust[usi] + if changed[uf] or changed[us] then + if trace_baseinit and trace_ligatures then + logs.report("define otf","%s: base ligature %s + %s ignored",cref(kind),gref(descriptions,uf),gref(descriptions,us)) + end + else + local first,second=characters[uf],us + if first and second then + local t=first.ligatures + if not t then + t={} + first.ligatures=t + end + if type(uc)=="number" then + t[second]={ type=0,char=uc } + else + t[second]={ type=0,char=uc[1] } + end + if trace_baseinit and trace_ligatures then + logs.report("define otf","%s: base ligature %s + %s => %s",cref(kind),gref(descriptions,uf),gref(descriptions,us),gref(descriptions,uc)) + end + end + end + end + end + end + ok,done[lig]=true,descriptions[uc].name + end + end + end + if ok then + for d,n in next,done do + local pattern=pcache[d] if not pattern then pattern="^("..d..") " pcache[d]=pattern end + local fnc=fcache[n] if not fnc then fnc=function() return n.." " end fcache[n]=fnc end + for k,v in next,ligatures do + v[1]=gsub(v[1],pattern,fnc) + end + end + else + break + end + end +end +local splitter=lpeg.splitat(" ") +local function prepare_base_substitutions(tfmdata,kind,value) + if value then + local otfdata=tfmdata.shared.otfdata + local validlookups,lookuplist=otf.collect_lookups(otfdata,kind,tfmdata.script,tfmdata.language) + if validlookups then + local ligatures={} + local unicodes=tfmdata.unicodes + local indices=tfmdata.indices + local characters=tfmdata.characters + local descriptions=tfmdata.descriptions + local changed=tfmdata.changed + local actions={ + substitution=function(p,lookup,k,glyph,unicode) + local pv=p[2] + if pv then + local upv=unicodes[pv] + if upv then + if type(upv)=="table" then + upv=upv[1] + end + if characters[upv] then + if trace_baseinit and trace_singles then + logs.report("define otf","%s: base substitution %s => %s",cref(kind,lookup),gref(descriptions,k),gref(descriptions,upv)) + end + changed[k]=upv + end + end + end + end, + alternate=function(p,lookup,k,glyph,unicode) + local pc=p[2] + if pc then + if value==1 then + pc=lpegmatch(splitter,pc) + elseif value==2 then + local a,b=lpegmatch(splitter,pc) + pc=b or a + else + pc={ lpegmatch(splitter,pc) } + pc=pc[value] or pc[#pc] + end + if pc then + local upc=unicodes[pc] + if upc then + if type(upc)=="table" then + upc=upc[1] + end + if characters[upc] then + if trace_baseinit and trace_alternatives then + logs.report("define otf","%s: base alternate %s %s => %s",cref(kind,lookup),tostring(value),gref(descriptions,k),gref(descriptions,upc)) + end + changed[k]=upc + end + end + end + end + end, + ligature=function(p,lookup,k,glyph,unicode) + local pc=p[2] + if pc then + if trace_baseinit and trace_ligatures then + local upc={ lpegmatch(splitter,pc) } + for i=1,#upc do upc[i]=unicodes[upc[i]] end + logs.report("define otf","%s: base ligature %s => %s",cref(kind,lookup),gref(descriptions,upc),gref(descriptions,k)) + end + ligatures[#ligatures+1]={ pc,k } + end + end, + } + for k,c in next,characters do + local glyph=descriptions[k] + local lookups=glyph.slookups + if lookups then + for l=1,#lookuplist do + local lookup=lookuplist[l] + local p=lookups[lookup] + if p then + local a=actions[p[1]] + if a then + a(p,lookup,k,glyph,unicode) + end + end + end + end + local lookups=glyph.mlookups + if lookups then + for l=1,#lookuplist do + local lookup=lookuplist[l] + local ps=lookups[lookup] + if ps then + for i=1,#ps do + local p=ps[i] + local a=actions[p[1]] + if a then + a(p,lookup,k,glyph,unicode) + end + end + end + end + end + end + resolve_ligatures(tfmdata,ligatures,kind) + end + else + tfmdata.ligatures=tfmdata.ligatures or {} + end +end +local function prepare_base_kerns(tfmdata,kind,value) + if value then + local otfdata=tfmdata.shared.otfdata + local validlookups,lookuplist=otf.collect_lookups(otfdata,kind,tfmdata.script,tfmdata.language) + if validlookups then + local unicodes=tfmdata.unicodes + local indices=tfmdata.indices + local characters=tfmdata.characters + local descriptions=tfmdata.descriptions + local sharedkerns={} + for u,chr in next,characters do + local d=descriptions[u] + if d then + local dk=d.mykerns + if dk then + local s=sharedkerns[dk] + if s==false then + elseif s then + chr.kerns=s + else + local t,done=chr.kerns or {},false + for l=1,#lookuplist do + local lookup=lookuplist[l] + local kerns=dk[lookup] + if kerns then + for k,v in next,kerns do + if v~=0 and not t[k] then + t[k],done=v,true + if trace_baseinit and trace_kerns then + logs.report("define otf","%s: base kern %s + %s => %s",cref(kind,lookup),gref(descriptions,u),gref(descriptions,k),v) + end + end + end + end + end + if done then + sharedkerns[dk]=t + chr.kerns=t + else + sharedkerns[dk]=false + end + end + end + end + end + end + end +end +local supported_gsub={ + 'liga','dlig','rlig','hlig', + 'pnum','onum','tnum','lnum', + 'zero', + 'smcp','cpsp','c2sc','ornm','aalt', + 'hwid','fwid', + 'ssty','rtlm', +} +local supported_gpos={ + 'kern' +} +function otf.features.register_base_substitution(tag) + supported_gsub[#supported_gsub+1]=tag +end +function otf.features.register_base_kern(tag) + supported_gsub[#supported_gpos+1]=tag +end +local basehash,basehashes={},1 +function fonts.initializers.base.otf.features(tfmdata,value) + if true then + local t=trace_preparing and os.clock() + local features=tfmdata.shared.features + if features then + local h={} + for f=1,#supported_gsub do + local feature=supported_gsub[f] + local value=features[feature] + prepare_base_substitutions(tfmdata,feature,value) + if value then + h[#h+1]=feature.."="..tostring(value) + end + end + for f=1,#supported_gpos do + local feature=supported_gpos[f] + local value=features[feature] + prepare_base_kerns(tfmdata,feature,features[feature]) + if value then + h[#h+1]=feature.."="..tostring(value) + end + end + local hash=concat(h," ") + local base=basehash[hash] + if not base then + basehashes=basehashes+1 + base=basehashes + basehash[hash]=base + end + tfmdata.fullname=tfmdata.fullname.."-"..base + end + if trace_preparing then + logs.report("otf define","preparation time is %0.3f seconds for %s",os.clock()-t,tfmdata.fullname or "?") + end + end +end + +end -- closure + +do -- begin closure to overcome local limits and interference + +if not modules then modules={} end modules ['font-otn']={ + version=1.001, + comment="companion to font-ini.mkiv", + author="Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright="PRAGMA ADE / ConTeXt Development Team", + license="see context related readme files" +} +local concat,insert,remove=table.concat,table.insert,table.remove +local format,gmatch,gsub,find,match,lower,strip=string.format,string.gmatch,string.gsub,string.find,string.match,string.lower,string.strip +local type,next,tonumber,tostring=type,next,tonumber,tostring +local lpegmatch=lpeg.match +local otf=fonts.otf +local tfm=fonts.tfm +local trace_lookups=false trackers.register("otf.lookups",function(v) trace_lookups=v end) +local trace_singles=false trackers.register("otf.singles",function(v) trace_singles=v end) +local trace_multiples=false trackers.register("otf.multiples",function(v) trace_multiples=v end) +local trace_alternatives=false trackers.register("otf.alternatives",function(v) trace_alternatives=v end) +local trace_ligatures=false trackers.register("otf.ligatures",function(v) trace_ligatures=v end) +local trace_contexts=false trackers.register("otf.contexts",function(v) trace_contexts=v end) +local trace_marks=false trackers.register("otf.marks",function(v) trace_marks=v end) +local trace_kerns=false trackers.register("otf.kerns",function(v) trace_kerns=v end) +local trace_cursive=false trackers.register("otf.cursive",function(v) trace_cursive=v end) +local trace_preparing=false trackers.register("otf.preparing",function(v) trace_preparing=v end) +local trace_bugs=false trackers.register("otf.bugs",function(v) trace_bugs=v end) +local trace_details=false trackers.register("otf.details",function(v) trace_details=v end) +local trace_applied=false trackers.register("otf.applied",function(v) trace_applied=v end) +local trace_steps=false trackers.register("otf.steps",function(v) trace_steps=v end) +local trace_skips=false trackers.register("otf.skips",function(v) trace_skips=v end) +local trace_directions=false trackers.register("otf.directions",function(v) trace_directions=v end) +trackers.register("otf.verbose_chain",function(v) otf.setcontextchain(v and "verbose") end) +trackers.register("otf.normal_chain",function(v) otf.setcontextchain(v and "normal") end) +trackers.register("otf.replacements","otf.singles,otf.multiples,otf.alternatives,otf.ligatures") +trackers.register("otf.positions","otf.marks,otf.kerns,otf.cursive") +trackers.register("otf.actions","otf.replacements,otf.positions") +trackers.register("otf.injections","nodes.injections") +trackers.register("*otf.sample","otf.steps,otf.actions,otf.analyzing") +local insert_node_after=node.insert_after +local delete_node=nodes.delete +local copy_node=node.copy +local find_node_tail=node.tail or node.slide +local set_attribute=node.set_attribute +local has_attribute=node.has_attribute +local zwnj=0x200C +local zwj=0x200D +local wildcard="*" +local default="dflt" +local split_at_space=lpeg.splitters[" "] or lpeg.Ct(lpeg.splitat(" ")) +local glyph=node.id('glyph') +local glue=node.id('glue') +local kern=node.id('kern') +local disc=node.id('disc') +local whatsit=node.id('whatsit') +local state=attributes.private('state') +local markbase=attributes.private('markbase') +local markmark=attributes.private('markmark') +local markdone=attributes.private('markdone') +local cursbase=attributes.private('cursbase') +local curscurs=attributes.private('curscurs') +local cursdone=attributes.private('cursdone') +local kernpair=attributes.private('kernpair') +local set_mark=nodes.set_mark +local set_cursive=nodes.set_cursive +local set_kern=nodes.set_kern +local set_pair=nodes.set_pair +local markonce=true +local cursonce=true +local kernonce=true +local fontdata=fonts.ids +otf.features.process={} +local tfmdata=false +local otfdata=false +local characters=false +local descriptions=false +local marks=false +local indices=false +local unicodes=false +local currentfont=false +local lookuptable=false +local anchorlookups=false +local handlers={} +local rlmode=0 +local featurevalue=false +local context_setups=fonts.define.specify.context_setups +local context_numbers=fonts.define.specify.context_numbers +local context_merged=fonts.define.specify.context_merged +local special_attributes={ + init=1, + medi=2, + fina=3, + isol=4 +} +local checkstep=(nodes and nodes.tracers and nodes.tracers.steppers.check) or function() end +local registerstep=(nodes and nodes.tracers and nodes.tracers.steppers.register) or function() end +local registermessage=(nodes and nodes.tracers and nodes.tracers.steppers.message) or function() end +local function logprocess(...) + if trace_steps then + registermessage(...) + end + logs.report("otf direct",...) +end +local function logwarning(...) + logs.report("otf direct",...) +end +local function gref(n) + if type(n)=="number" then + local description=descriptions[n] + local name=description and description.name + if name then + return format("U+%04X (%s)",n,name) + else + return format("U+%04X",n) + end + elseif not n then + return "<error in tracing>" + else + local num,nam={},{} + for i=1,#n do + local ni=n[i] + num[#num+1]=format("U+%04X",ni) + local dni=descriptions[ni] + nam[#num]=(dni and dni.name) or "?" + end + return format("%s (%s)",concat(num," "),concat(nam," ")) + end +end +local function cref(kind,chainname,chainlookupname,lookupname,index) + if index then + return format("feature %s, chain %s, sub %s, lookup %s, index %s",kind,chainname,chainlookupname,lookupname,index) + elseif lookupname then + return format("feature %s, chain %s, sub %s, lookup %s",kind,chainname or "?",chainlookupname or "?",lookupname) + elseif chainlookupname then + return format("feature %s, chain %s, sub %s",kind,chainname or "?",chainlookupname) + elseif chainname then + return format("feature %s, chain %s",kind,chainname) + else + return format("feature %s",kind) + end +end +local function pref(kind,lookupname) + return format("feature %s, lookup %s",kind,lookupname) +end +local function markstoligature(kind,lookupname,start,stop,char) + local n=copy_node(start) + local keep=start + local current + current,start=insert_node_after(start,start,n) + local snext=stop.next + current.next=snext + if snext then + snext.prev=current + end + start.prev,stop.next=nil,nil + current.char,current.subtype,current.components=char,2,start + return keep +end +local function toligature(kind,lookupname,start,stop,char,markflag,discfound) + if start~=stop then + if discfound then + local lignode=copy_node(start) + lignode.font,lignode.char,lignode.subtype=start.font,char,2 + local next,prev=stop.next,start.prev + stop.next=nil + lignode=node.do_ligature_n(start,stop,lignode) + prev.next=lignode + if next then + next.prev=lignode + end + lignode.next,lignode.prev=next,prev + start=lignode + else + local deletemarks=markflag~="mark" + local n=copy_node(start) + local current + current,start=insert_node_after(start,start,n) + local snext=stop.next + current.next=snext + if snext then + snext.prev=current + end + start.prev,stop.next=nil,nil + current.char,current.subtype,current.components=char,2,start + local head=current + if deletemarks then + if trace_marks then + while start do + if marks[start.char] then + logwarning("%s: remove mark %s",pref(kind,lookupname),gref(start.char)) + end + start=start.next + end + end + else + local i=0 + while start do + if marks[start.char] then + set_attribute(start,markdone,i) + if trace_marks then + logwarning("%s: keep mark %s, gets index %s",pref(kind,lookupname),gref(start.char),i) + end + head,current=insert_node_after(head,current,copy_node(start)) + else + i=i+1 + end + start=start.next + end + start=current.next + while start and start.id==glyph do + if marks[start.char] then + set_attribute(start,markdone,i) + if trace_marks then + logwarning("%s: keep mark %s, gets index %s",pref(kind,lookupname),gref(start.char),i) + end + else + break + end + start=start.next + end + end + return head + end + else + start.char=char + end + return start +end +function handlers.gsub_single(start,kind,lookupname,replacement) + if trace_singles then + logprocess("%s: replacing %s by single %s",pref(kind,lookupname),gref(start.char),gref(replacement)) + end + start.char=replacement + return start,true +end +local function alternative_glyph(start,alternatives,kind,chainname,chainlookupname,lookupname) + local value,choice,n=featurevalue or tfmdata.shared.features[kind],nil,#alternatives + if value=="random" then + local r=math.random(1,n) + value,choice=format("random, choice %s",r),alternatives[r] + elseif value=="first" then + value,choice=format("first, choice %s",1),alternatives[1] + elseif value=="last" then + value,choice=format("last, choice %s",n),alternatives[n] + else + value=tonumber(value) + if type(value)~="number" then + value,choice="default, choice 1",alternatives[1] + elseif value>n then + value,choice=format("no %s variants, taking %s",value,n),alternatives[n] + elseif value==0 then + value,choice=format("choice %s (no change)",value),start.char + elseif value<1 then + value,choice=format("no %s variants, taking %s",value,1),alternatives[1] + else + value,choice=format("choice %s",value),alternatives[value] + end + end + if not choice then + logwarning("%s: no variant %s for %s",cref(kind,chainname,chainlookupname,lookupname),value,gref(start.char)) + choice,value=start.char,format("no replacement instead of %s",value) + end + return choice,value +end +function handlers.gsub_alternate(start,kind,lookupname,alternative,sequence) + local choice,index=alternative_glyph(start,alternative,kind,lookupname) + if trace_alternatives then + logprocess("%s: replacing %s by alternative %s (%s)",pref(kind,lookupname),gref(start.char),gref(choice),index) + end + start.char=choice + return start,true +end +function handlers.gsub_multiple(start,kind,lookupname,multiple) + if trace_multiples then + logprocess("%s: replacing %s by multiple %s",pref(kind,lookupname),gref(start.char),gref(multiple)) + end + start.char=multiple[1] + if #multiple>1 then + for k=2,#multiple do + local n=copy_node(start) + n.char=multiple[k] + local sn=start.next + n.next=sn + n.prev=start + if sn then + sn.prev=n + end + start.next=n + start=n + end + end + return start,true +end +function handlers.gsub_ligature(start,kind,lookupname,ligature,sequence) + local s,stop,discfound=start.next,nil,false + local startchar=start.char + if marks[startchar] then + while s do + local id=s.id + if id==glyph and s.subtype<256 then + if s.font==currentfont then + local char=s.char + local lg=ligature[1][char] + if not lg then + break + else + stop=s + ligature=lg + s=s.next + end + else + break + end + else + break + end + end + if stop and ligature[2] then + if trace_ligatures then + local stopchar=stop.char + start=markstoligature(kind,lookupname,start,stop,ligature[2]) + logprocess("%s: replacing %s upto %s by ligature %s",pref(kind,lookupname),gref(startchar),gref(stopchar),gref(start.char)) + else + start=markstoligature(kind,lookupname,start,stop,ligature[2]) + end + return start,true + end + else + local skipmark=sequence.flags[1] + while s do + local id=s.id + if id==glyph and s.subtype<256 then + if s.font==currentfont then + local char=s.char + if skipmark and marks[char] then + s=s.next + else + local lg=ligature[1][char] + if not lg then + break + else + stop=s + ligature=lg + s=s.next + end + end + else + break + end + elseif id==disc then + discfound=true + s=s.next + else + break + end + end + if stop and ligature[2] then + if trace_ligatures then + local stopchar=stop.char + start=toligature(kind,lookupname,start,stop,ligature[2],skipmark,discfound) + logprocess("%s: replacing %s upto %s by ligature %s",pref(kind,lookupname),gref(startchar),gref(stopchar),gref(start.char)) + else + start=toligature(kind,lookupname,start,stop,ligature[2],skipmark,discfound) + end + return start,true + end + end + return start,false +end +function handlers.gpos_mark2base(start,kind,lookupname,markanchors,sequence) + local markchar=start.char + if marks[markchar] then + local base=start.prev + if base and base.id==glyph and base.subtype<256 and base.font==currentfont then + local basechar=base.char + if marks[basechar] then + while true do + base=base.prev + if base and base.id==glyph and base.subtype<256 and base.font==currentfont then + basechar=base.char + if not marks[basechar] then + break + end + else + if trace_bugs then + logwarning("%s: no base for mark %s",pref(kind,lookupname),gref(markchar)) + end + return start,false + end + end + end + local baseanchors=descriptions[basechar] + if baseanchors then + baseanchors=baseanchors.anchors + end + if baseanchors then + local baseanchors=baseanchors['basechar'] + if baseanchors then + local al=anchorlookups[lookupname] + for anchor,ba in next,baseanchors do + if al[anchor] then + local ma=markanchors[anchor] + if ma then + local dx,dy,bound=set_mark(start,base,tfmdata.factor,rlmode,ba,ma) + if trace_marks then + logprocess("%s, anchor %s, bound %s: anchoring mark %s to basechar %s => (%s,%s)", + pref(kind,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy) + end + return start,true + end + end + end + if trace_bugs then + logwarning("%s, no matching anchors for mark %s and base %s",pref(kind,lookupname),gref(markchar),gref(basechar)) + end + end + else + fonts.register_message(currentfont,basechar,"no base anchors") + end + elseif trace_bugs then + logwarning("%s: prev node is no char",pref(kind,lookupname)) + end + elseif trace_bugs then + logwarning("%s: mark %s is no mark",pref(kind,lookupname),gref(markchar)) + end + return start,false +end +function handlers.gpos_mark2ligature(start,kind,lookupname,markanchors,sequence) + local markchar=start.char + if marks[markchar] then + local base=start.prev + local index=1 + if base and base.id==glyph and base.subtype<256 and base.font==currentfont then + local basechar=base.char + if marks[basechar] then + index=index+1 + while true do + base=base.prev + if base and base.id==glyph and base.subtype<256 and base.font==currentfont then + basechar=base.char + if marks[basechar] then + index=index+1 + else + break + end + else + if trace_bugs then + logwarning("%s: no base for mark %s",pref(kind,lookupname),gref(markchar)) + end + return start,false + end + end + end + local i=has_attribute(start,markdone) + if i then index=i end + local baseanchors=descriptions[basechar] + if baseanchors then + baseanchors=baseanchors.anchors + if baseanchors then + local baseanchors=baseanchors['baselig'] + if baseanchors then + local al=anchorlookups[lookupname] + for anchor,ba in next,baseanchors do + if al[anchor] then + local ma=markanchors[anchor] + if ma then + ba=ba[index] + if ba then + local dx,dy,bound=set_mark(start,base,tfmdata.factor,rlmode,ba,ma,index) + if trace_marks then + logprocess("%s, anchor %s, index %s, bound %s: anchoring mark %s to baselig %s at index %s => (%s,%s)", + pref(kind,lookupname),anchor,index,bound,gref(markchar),gref(basechar),index,dx,dy) + end + return start,true + end + end + end + end + if trace_bugs then + logwarning("%s: no matching anchors for mark %s and baselig %s",pref(kind,lookupname),gref(markchar),gref(basechar)) + end + end + end + else + fonts.register_message(currentfont,basechar,"no base anchors") + end + elseif trace_bugs then + logwarning("%s: prev node is no char",pref(kind,lookupname)) + end + elseif trace_bugs then + logwarning("%s: mark %s is no mark",pref(kind,lookupname),gref(markchar)) + end + return start,false +end +function handlers.gpos_mark2mark(start,kind,lookupname,markanchors,sequence) + local markchar=start.char + if marks[markchar] then + local base=start.prev + if base and base.id==glyph and base.subtype<256 and base.font==currentfont then + local basechar=base.char + local baseanchors=descriptions[basechar] + if baseanchors then + baseanchors=baseanchors.anchors + if baseanchors then + baseanchors=baseanchors['basemark'] + if baseanchors then + local al=anchorlookups[lookupname] + for anchor,ba in next,baseanchors do + if al[anchor] then + local ma=markanchors[anchor] + if ma then + local dx,dy,bound=set_mark(start,base,tfmdata.factor,rlmode,ba,ma) + if trace_marks then + logprocess("%s, anchor %s, bound %s: anchoring mark %s to basemark %s => (%s,%s)", + pref(kind,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy) + end + return start,true + end + end + end + if trace_bugs then + logwarning("%s: no matching anchors for mark %s and basemark %s",pref(kind,lookupname),gref(markchar),gref(basechar)) + end + end + end + else + fonts.register_message(currentfont,basechar,"no base anchors") + end + elseif trace_bugs then + logwarning("%s: prev node is no mark",pref(kind,lookupname)) + end + elseif trace_bugs then + logwarning("%s: mark %s is no mark",pref(kind,lookupname),gref(markchar)) + end + return start,false +end +function handlers.gpos_cursive(start,kind,lookupname,exitanchors,sequence) + local alreadydone=cursonce and has_attribute(start,cursbase) + if not alreadydone then + local done=false + local startchar=start.char + if marks[startchar] then + if trace_cursive then + logprocess("%s: ignoring cursive for mark %s",pref(kind,lookupname),gref(startchar)) + end + else + local nxt=start.next + while not done and nxt and nxt.id==glyph and nxt.subtype<256 and nxt.font==currentfont do + local nextchar=nxt.char + if marks[nextchar] then + nxt=nxt.next + else + local entryanchors=descriptions[nextchar] + if entryanchors then + entryanchors=entryanchors.anchors + if entryanchors then + entryanchors=entryanchors['centry'] + if entryanchors then + local al=anchorlookups[lookupname] + for anchor,entry in next,entryanchors do + if al[anchor] then + local exit=exitanchors[anchor] + if exit then + local dx,dy,bound=set_cursive(start,nxt,tfmdata.factor,rlmode,exit,entry,characters[startchar],characters[nextchar]) + if trace_cursive then + logprocess("%s: moving %s to %s cursive (%s,%s) using anchor %s and bound %s in rlmode %s",pref(kind,lookupname),gref(startchar),gref(nextchar),dx,dy,anchor,bound,rlmode) + end + done=true + break + end + end + end + end + end + else + fonts.register_message(currentfont,startchar,"no entry anchors") + end + break + end + end + end + return start,done + else + if trace_cursive and trace_details then + logprocess("%s, cursive %s is already done",pref(kind,lookupname),gref(start.char),alreadydone) + end + return start,false + end +end +function handlers.gpos_single(start,kind,lookupname,kerns,sequence) + local startchar=start.char + local dx,dy,w,h=set_pair(start,tfmdata.factor,rlmode,sequence.flags[4],kerns,characters[startchar]) + if trace_kerns then + logprocess("%s: shifting single %s by (%s,%s) and correction (%s,%s)",pref(kind,lookupname),gref(startchar),dx,dy,w,h) + end + return start,false +end +function handlers.gpos_pair(start,kind,lookupname,kerns,sequence) + local snext=start.next + if not snext then + return start,false + else + local prev,done=start,false + local factor=tfmdata.factor + while snext and snext.id==glyph and snext.subtype<256 and snext.font==currentfont do + local nextchar=snext.char +local krn=kerns[nextchar] + if not krn and marks[nextchar] then + prev=snext + snext=snext.next + else + local krn=kerns[nextchar] + if not krn then + elseif type(krn)=="table" then + if krn[1]=="pair" then + local a,b=krn[3],krn[4] + if a and #a>0 then + local startchar=start.char + local x,y,w,h=set_pair(start,factor,rlmode,sequence.flags[4],a,characters[startchar]) + if trace_kerns then + logprocess("%s: shifting first of pair %s and %s by (%s,%s) and correction (%s,%s)",pref(kind,lookupname),gref(startchar),gref(nextchar),x,y,w,h) + end + end + if b and #b>0 then + local startchar=start.char + local x,y,w,h=set_pair(snext,factor,rlmode,sequence.flags[4],b,characters[nextchar]) + if trace_kerns then + logprocess("%s: shifting second of pair %s and %s by (%s,%s) and correction (%s,%s)",pref(kind,lookupname),gref(startchar),gref(nextchar),x,y,w,h) + end + end + else + logs.report("%s: check this out (old kern stuff)",pref(kind,lookupname)) + local a,b=krn[3],krn[7] + if a and a~=0 then + local k=set_kern(snext,factor,rlmode,a) + if trace_kerns then + logprocess("%s: inserting first kern %s between %s and %s",pref(kind,lookupname),k,gref(prev.char),gref(nextchar)) + end + end + if b and b~=0 then + logwarning("%s: ignoring second kern xoff %s",pref(kind,lookupname),b*factor) + end + end + done=true + elseif krn~=0 then + local k=set_kern(snext,factor,rlmode,krn) + if trace_kerns then + logprocess("%s: inserting kern %s between %s and %s",pref(kind,lookupname),k,gref(prev.char),gref(nextchar)) + end + done=true + end + break + end + end + return start,done + end +end +local chainmores={} +local chainprocs={} +local function logprocess(...) + if trace_steps then + registermessage(...) + end + logs.report("otf subchain",...) +end +local function logwarning(...) + logs.report("otf subchain",...) +end +function chainmores.chainsub(start,stop,kind,chainname,currentcontext,cache,lookuplist,chainlookupname,n) + logprocess("%s: a direct call to chainsub cannot happen",cref(kind,chainname,chainlookupname)) + return start,false +end +function chainmores.gsub_multiple(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname,n) + logprocess("%s: gsub_multiple not yet supported",cref(kind,chainname,chainlookupname)) + return start,false +end +function chainmores.gsub_alternate(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname,n) + logprocess("%s: gsub_alternate not yet supported",cref(kind,chainname,chainlookupname)) + return start,false +end +local function logprocess(...) + if trace_steps then + registermessage(...) + end + logs.report("otf chain",...) +end +local function logwarning(...) + logs.report("otf chain",...) +end +function chainprocs.chainsub(start,stop,kind,chainname,currentcontext,cache,lookuplist,chainlookupname) + logwarning("%s: a direct call to chainsub cannot happen",cref(kind,chainname,chainlookupname)) + return start,false +end +function chainprocs.reversesub(start,stop,kind,chainname,currentcontext,cache,replacements) + local char=start.char + local replacement=replacements[char] + if replacement then + if trace_singles then + logprocess("%s: single reverse replacement of %s by %s",cref(kind,chainname),gref(char),gref(replacement)) + end + start.char=replacement + return start,true + else + return start,false + end +end +local function delete_till_stop(start,stop,ignoremarks) + if start~=stop then + local done=false + while not done do + done=start==stop + delete_node(start,start.next) + end + end +end +function chainprocs.gsub_single(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname,chainindex) + if not chainindex then + delete_till_stop(start,stop) + end + local current=start + local subtables=currentlookup.subtables + while current do + if current.id==glyph then + local currentchar=current.char + local lookupname=subtables[1] + local replacement=cache.gsub_single[lookupname] + if not replacement then + if trace_bugs then + logwarning("%s: no single hits",cref(kind,chainname,chainlookupname,lookupname,chainindex)) + end + else + replacement=replacement[currentchar] + if not replacement then + if trace_bugs then + logwarning("%s: no single for %s",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(currentchar)) + end + else + if trace_singles then + logprocess("%s: replacing single %s by %s",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(currentchar),gref(replacement)) + end + current.char=replacement + end + end + return start,true + elseif current==stop then + break + else + current=current.next + end + end + return start,false +end +chainmores.gsub_single=chainprocs.gsub_single +function chainprocs.gsub_multiple(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname) + delete_till_stop(start,stop) + local startchar=start.char + local subtables=currentlookup.subtables + local lookupname=subtables[1] + local replacements=cache.gsub_multiple[lookupname] + if not replacements then + if trace_bugs then + logwarning("%s: no multiple hits",cref(kind,chainname,chainlookupname,lookupname)) + end + else + replacements=replacements[startchar] + if not replacements then + if trace_bugs then + logwarning("%s: no multiple for %s",cref(kind,chainname,chainlookupname,lookupname),gref(startchar)) + end + else + if trace_multiples then + logprocess("%s: replacing %s by multiple characters %s",cref(kind,chainname,chainlookupname,lookupname),gref(startchar),gref(replacements)) + end + local sn=start.next + for k=1,#replacements do + if k==1 then + start.char=replacements[k] + else + local n=copy_node(start) + n.char=replacements[k] + n.next,n.prev=sn,start + if sn then + sn.prev=n + end + start.next,start=n,n + end + end + return start,true + end + end + return start,false +end +function chainprocs.gsub_alternate(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname) + delete_till_stop(start,stop) + local current=start + local subtables=currentlookup.subtables + while current do + if current.id==glyph then + local currentchar=current.char + local lookupname=subtables[1] + local alternatives=cache.gsub_alternate[lookupname] + if not alternatives then + if trace_bugs then + logwarning("%s: no alternative hits",cref(kind,chainname,chainlookupname,lookupname)) + end + else + alternatives=alternatives[currentchar] + if not alternatives then + if trace_bugs then + logwarning("%s: no alternative for %s",cref(kind,chainname,chainlookupname,lookupname),gref(currentchar)) + end + else + local choice,index=alternative_glyph(current,alternatives,kind,chainname,chainlookupname,lookupname) + current.char=choice + if trace_alternatives then + logprocess("%s: replacing single %s by alternative %s (%s)",cref(kind,chainname,chainlookupname,lookupname),index,gref(currentchar),gref(choice),index) + end + end + end + return start,true + elseif current==stop then + break + else + current=current.next + end + end + return start,false +end +function chainprocs.gsub_ligature(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname,chainindex) + local startchar=start.char + local subtables=currentlookup.subtables + local lookupname=subtables[1] + local ligatures=cache.gsub_ligature[lookupname] + if not ligatures then + if trace_bugs then + logwarning("%s: no ligature hits",cref(kind,chainname,chainlookupname,lookupname,chainindex)) + end + else + ligatures=ligatures[startchar] + if not ligatures then + if trace_bugs then + logwarning("%s: no ligatures starting with %s",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar)) + end + else + local s,discfound,last,nofreplacements=start.next,false,stop,0 + while s do + local id=s.id + if id==disc then + s=s.next + discfound=true + else + local schar=s.char + if marks[schar] then + s=s.next + else + local lg=ligatures[1][schar] + if not lg then + break + else + ligatures,last,nofreplacements=lg,s,nofreplacements+1 + if s==stop then + break + else + s=s.next + end + end + end + end + end + local l2=ligatures[2] + if l2 then + if chainindex then + stop=last + end + if trace_ligatures then + if start==stop then + logprocess("%s: replacing character %s by ligature %s",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar),gref(l2)) + else + logprocess("%s: replacing character %s upto %s by ligature %s",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar),gref(stop.char),gref(l2)) + end + end + start=toligature(kind,lookupname,start,stop,l2,currentlookup.flags[1],discfound) + return start,true,nofreplacements + elseif trace_bugs then + if start==stop then + logwarning("%s: replacing character %s by ligature fails",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar)) + else + logwarning("%s: replacing character %s upto %s by ligature fails",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar),gref(stop.char)) + end + end + end + end + return start,false,0 +end +chainmores.gsub_ligature=chainprocs.gsub_ligature +function chainprocs.gpos_mark2base(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname) + local markchar=start.char + if marks[markchar] then + local subtables=currentlookup.subtables + local lookupname=subtables[1] + local markanchors=cache.gpos_mark2base[lookupname] + if markanchors then + markanchors=markanchors[markchar] + end + if markanchors then + local base=start.prev + if base and base.id==glyph and base.subtype<256 and base.font==currentfont then + local basechar=base.char + if marks[basechar] then + while true do + base=base.prev + if base and base.id==glyph and base.subtype<256 and base.font==currentfont then + basechar=base.char + if not marks[basechar] then + break + end + else + if trace_bugs then + logwarning("%s: no base for mark %s",pref(kind,lookupname),gref(markchar)) + end + return start,false + end + end + end + local baseanchors=descriptions[basechar].anchors + if baseanchors then + local baseanchors=baseanchors['basechar'] + if baseanchors then + local al=anchorlookups[lookupname] + for anchor,ba in next,baseanchors do + if al[anchor] then + local ma=markanchors[anchor] + if ma then + local dx,dy,bound=set_mark(start,base,tfmdata.factor,rlmode,ba,ma) + if trace_marks then + logprocess("%s, anchor %s, bound %s: anchoring mark %s to basechar %s => (%s,%s)", + cref(kind,chainname,chainlookupname,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy) + end + return start,true + end + end + end + if trace_bugs then + logwarning("%s, no matching anchors for mark %s and base %s",cref(kind,chainname,chainlookupname,lookupname),gref(markchar),gref(basechar)) + end + end + end + elseif trace_bugs then + logwarning("%s: prev node is no char",cref(kind,chainname,chainlookupname,lookupname)) + end + elseif trace_bugs then + logwarning("%s: mark %s has no anchors",cref(kind,chainname,chainlookupname,lookupname),gref(markchar)) + end + elseif trace_bugs then + logwarning("%s: mark %s is no mark",cref(kind,chainname,chainlookupname),gref(markchar)) + end + return start,false +end +function chainprocs.gpos_mark2ligature(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname) + local markchar=start.char + if marks[markchar] then + local subtables=currentlookup.subtables + local lookupname=subtables[1] + local markanchors=cache.gpos_mark2ligature[lookupname] + if markanchors then + markanchors=markanchors[markchar] + end + if markanchors then + local base=start.prev + local index=1 + if base and base.id==glyph and base.subtype<256 and base.font==currentfont then + local basechar=base.char + if marks[basechar] then + index=index+1 + while true do + base=base.prev + if base and base.id==glyph and base.subtype<256 and base.font==currentfont then + basechar=base.char + if marks[basechar] then + index=index+1 + else + break + end + else + if trace_bugs then + logwarning("%s: no base for mark %s",cref(kind,chainname,chainlookupname,lookupname),markchar) + end + return start,false + end + end + end + local i=has_attribute(start,markdone) + if i then index=i end + local baseanchors=descriptions[basechar].anchors + if baseanchors then + local baseanchors=baseanchors['baselig'] + if baseanchors then + local al=anchorlookups[lookupname] + for anchor,ba in next,baseanchors do + if al[anchor] then + local ma=markanchors[anchor] + if ma then + ba=ba[index] + if ba then + local dx,dy,bound=set_mark(start,base,tfmdata.factor,rlmode,ba,ma,index) + if trace_marks then + logprocess("%s, anchor %s, bound %s: anchoring mark %s to baselig %s at index %s => (%s,%s)", + cref(kind,chainname,chainlookupname,lookupname),anchor,a or bound,gref(markchar),gref(basechar),index,dx,dy) + end + return start,true + end + end + end + end + if trace_bugs then + logwarning("%s: no matching anchors for mark %s and baselig %s",cref(kind,chainname,chainlookupname,lookupname),gref(markchar),gref(basechar)) + end + end + end + elseif trace_bugs then + logwarning("feature %s, lookup %s: prev node is no char",kind,lookupname) + end + elseif trace_bugs then + logwarning("%s: mark %s has no anchors",cref(kind,chainname,chainlookupname,lookupname),gref(markchar)) + end + elseif trace_bugs then + logwarning("%s: mark %s is no mark",cref(kind,chainname,chainlookupname),gref(markchar)) + end + return start,false +end +function chainprocs.gpos_mark2mark(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname) + local markchar=start.char + if marks[markchar] then + local subtables=currentlookup.subtables + local lookupname=subtables[1] + local markanchors=cache.gpos_mark2mark[lookupname] + if markanchors then + markanchors=markanchors[markchar] + end + if markanchors then + local base=start.prev + if base and base.id==glyph and base.subtype<256 and base.font==currentfont then + local basechar=base.char + local baseanchors=descriptions[basechar].anchors + if baseanchors then + baseanchors=baseanchors['basemark'] + if baseanchors then + local al=anchorlookups[lookupname] + for anchor,ba in next,baseanchors do + if al[anchor] then + local ma=markanchors[anchor] + if ma then + local dx,dy,bound=set_mark(start,base,tfmdata.factor,rlmode,ba,ma) + if trace_marks then + logprocess("%s, anchor %s, bound %s: anchoring mark %s to basemark %s => (%s,%s)", + cref(kind,chainname,chainlookupname,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy) + end + return start,true + end + end + end + if trace_bugs then + logwarning("%s: no matching anchors for mark %s and basemark %s",gref(kind,chainname,chainlookupname,lookupname),gref(markchar),gref(basechar)) + end + end + end + elseif trace_bugs then + logwarning("%s: prev node is no mark",cref(kind,chainname,chainlookupname,lookupname)) + end + elseif trace_bugs then + logwarning("%s: mark %s has no anchors",cref(kind,chainname,chainlookupname,lookupname),gref(markchar)) + end + elseif trace_bugs then + logwarning("%s: mark %s is no mark",cref(kind,chainname,chainlookupname),gref(markchar)) + end + return start,false +end +function chainprocs.gpos_cursive(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname) + local alreadydone=cursonce and has_attribute(start,cursbase) + if not alreadydone then + local startchar=start.char + local subtables=currentlookup.subtables + local lookupname=subtables[1] + local exitanchors=cache.gpos_cursive[lookupname] + if exitanchors then + exitanchors=exitanchors[startchar] + end + if exitanchors then + local done=false + if marks[startchar] then + if trace_cursive then + logprocess("%s: ignoring cursive for mark %s",pref(kind,lookupname),gref(startchar)) + end + else + local nxt=start.next + while not done and nxt and nxt.id==glyph and nxt.subtype<256 and nxt.font==currentfont do + local nextchar=nxt.char + if marks[nextchar] then + nxt=nxt.next + else + local entryanchors=descriptions[nextchar] + if entryanchors then + entryanchors=entryanchors.anchors + if entryanchors then + entryanchors=entryanchors['centry'] + if entryanchors then + local al=anchorlookups[lookupname] + for anchor,entry in next,entryanchors do + if al[anchor] then + local exit=exitanchors[anchor] + if exit then + local dx,dy,bound=set_cursive(start,nxt,tfmdata.factor,rlmode,exit,entry,characters[startchar],characters[nextchar]) + if trace_cursive then + logprocess("%s: moving %s to %s cursive (%s,%s) using anchor %s and bound %s in rlmode %s",pref(kind,lookupname),gref(startchar),gref(nextchar),dx,dy,anchor,bound,rlmode) + end + done=true + break + end + end + end + end + end + else + fonts.register_message(currentfont,startchar,"no entry anchors") + end + break + end + end + end + return start,done + else + if trace_cursive and trace_details then + logprocess("%s, cursive %s is already done",pref(kind,lookupname),gref(start.char),alreadydone) + end + return start,false + end + end + return start,false +end +function chainprocs.gpos_single(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname,chainindex,sequence) + local startchar=start.char + local subtables=currentlookup.subtables + local lookupname=subtables[1] + local kerns=cache.gpos_single[lookupname] + if kerns then + kerns=kerns[startchar] + if kerns then + local dx,dy,w,h=set_pair(start,tfmdata.factor,rlmode,sequence.flags[4],kerns,characters[startchar]) + if trace_kerns then + logprocess("%s: shifting single %s by (%s,%s) and correction (%s,%s)",cref(kind,chainname,chainlookupname),gref(startchar),dx,dy,w,h) + end + end + end + return start,false +end +function chainprocs.gpos_pair(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname,chainindex,sequence) + local snext=start.next + if snext then + local startchar=start.char + local subtables=currentlookup.subtables + local lookupname=subtables[1] + local kerns=cache.gpos_pair[lookupname] + if kerns then + kerns=kerns[startchar] + if kerns then + local prev,done=start,false + local factor=tfmdata.factor + while snext and snext.id==glyph and snext.subtype<256 and snext.font==currentfont do + local nextchar=snext.char + local krn=kerns[nextchar] + if not krn and marks[nextchar] then + prev=snext + snext=snext.next + else + if not krn then + elseif type(krn)=="table" then + if krn[1]=="pair" then + local a,b=krn[3],krn[4] + if a and #a>0 then + local startchar=start.char + local x,y,w,h=set_pair(start,factor,rlmode,sequence.flags[4],a,characters[startchar]) + if trace_kerns then + logprocess("%s: shifting first of pair %s and %s by (%s,%s) and correction (%s,%s)",cref(kind,chainname,chainlookupname),gref(startchar),gref(nextchar),x,y,w,h) + end + end + if b and #b>0 then + local startchar=start.char + local x,y,w,h=set_pair(snext,factor,rlmode,sequence.flags[4],b,characters[nextchar]) + if trace_kerns then + logprocess("%s: shifting second of pair %s and %s by (%s,%s) and correction (%s,%s)",cref(kind,chainname,chainlookupname),gref(startchar),gref(nextchar),x,y,w,h) + end + end + else + logs.report("%s: check this out (old kern stuff)",cref(kind,chainname,chainlookupname)) + local a,b=krn[3],krn[7] + if a and a~=0 then + local k=set_kern(snext,factor,rlmode,a) + if trace_kerns then + logprocess("%s: inserting first kern %s between %s and %s",cref(kind,chainname,chainlookupname),k,gref(prev.char),gref(nextchar)) + end + end + if b and b~=0 then + logwarning("%s: ignoring second kern xoff %s",cref(kind,chainname,chainlookupname),b*factor) + end + end + done=true + elseif krn~=0 then + local k=set_kern(snext,factor,rlmode,krn) + if trace_kerns then + logprocess("%s: inserting kern %s between %s and %s",cref(kind,chainname,chainlookupname),k,gref(prev.char),gref(nextchar)) + end + done=true + end + break + end + end + return start,done + end + end + end + return start,false +end +local function show_skip(kind,chainname,char,ck,class) + if ck[9] then + logwarning("%s: skipping char %s (%s) in rule %s, lookuptype %s (%s=>%s)",cref(kind,chainname),gref(char),class,ck[1],ck[2],ck[9],ck[10]) + else + logwarning("%s: skipping char %s (%s) in rule %s, lookuptype %s",cref(kind,chainname),gref(char),class,ck[1],ck[2]) + end +end +local function normal_handle_contextchain(start,kind,chainname,contexts,sequence,cache) + local flags,done=sequence.flags,false + local skipmark,skipligature,skipbase=flags[1],flags[2],flags[3] + local someskip=skipmark or skipligature or skipbase + local markclass=sequence.markclass + local skipped=false + for k=1,#contexts do + local match,current,last=true,start,start + local ck=contexts[k] + local seq=ck[3] + local s=#seq + if s==1 then + match=current.id==glyph and current.subtype<256 and current.font==currentfont and seq[1][current.char] + else + local f,l=ck[4],ck[5] + if f==l then + match=true + else + local n=f+1 + last=last.next + while n<=l do + if last then + local id=last.id + if id==glyph then + if last.subtype<256 and last.font==currentfont then + local char=last.char + local ccd=descriptions[char] + if ccd then + local class=ccd.class + if class==skipmark or class==skipligature or class==skipbase or (markclass and class=="mark" and not markclass[char]) then + skipped=true + if trace_skips then + show_skip(kind,chainname,char,ck,class) + end + last=last.next + elseif seq[n][char] then + if n<l then + last=last.next + end + n=n+1 + else + match=false break + end + else + match=false break + end + else + match=false break + end + elseif id==disc then + last=last.next + else + match=false break + end + else + match=false break + end + end + end + if match and f>1 then + local prev=start.prev + if prev then + local n=f-1 + while n>=1 do + if prev then + local id=prev.id + if id==glyph then + if prev.subtype<256 and prev.font==currentfont then + local char=prev.char + local ccd=descriptions[char] + if ccd then + local class=ccd.class + if class==skipmark or class==skipligature or class==skipbase or (markclass and class=="mark" and not markclass[char]) then + skipped=true + if trace_skips then + show_skip(kind,chainname,char,ck,class) + end + elseif seq[n][char] then + n=n -1 + else + match=false break + end + else + match=false break + end + else + match=false break + end + elseif id==disc then + elseif seq[n][32] then + n=n -1 + else + match=false break + end + prev=prev.prev + elseif seq[n][32] then + n=n -1 + else + match=false break + end + end + elseif f==2 then + match=seq[1][32] + else + for n=f-1,1 do + if not seq[n][32] then + match=false break + end + end + end + end + if match and s>l then + local current=last.next + if current then + local n=l+1 + while n<=s do + if current then + local id=current.id + if id==glyph then + if current.subtype<256 and current.font==currentfont then + local char=current.char + local ccd=descriptions[char] + if ccd then + local class=ccd.class + if class==skipmark or class==skipligature or class==skipbase or (markclass and class=="mark" and not markclass[char]) then + skipped=true + if trace_skips then + show_skip(kind,chainname,char,ck,class) + end + elseif seq[n][char] then + n=n+1 + else + match=false break + end + else + match=false break + end + else + match=false break + end + elseif id==disc then + elseif seq[n][32] then + n=n+1 + else + match=false break + end + current=current.next + elseif seq[n][32] then + n=n+1 + else + match=false break + end + end + elseif s-l==1 then + match=seq[s][32] + else + for n=l+1,s do + if not seq[n][32] then + match=false break + end + end + end + end + end + if match then + if trace_contexts then + local rule,lookuptype,f,l=ck[1],ck[2],ck[4],ck[5] + local char=start.char + if ck[9] then + logwarning("%s: rule %s matches at char %s for (%s,%s,%s) chars, lookuptype %s (%s=>%s)",cref(kind,chainname),rule,gref(char),f-1,l-f+1,s-l,lookuptype,ck[9],ck[10]) + else + logwarning("%s: rule %s matches at char %s for (%s,%s,%s) chars, lookuptype %s",cref(kind,chainname),rule,gref(char),f-1,l-f+1,s-l,lookuptype) + end + end + local chainlookups=ck[6] + if chainlookups then + local nofchainlookups=#chainlookups + if nofchainlookups==1 then + local chainlookupname=chainlookups[1] + local chainlookup=lookuptable[chainlookupname] + local cp=chainprocs[chainlookup.type] + if cp then + start,done=cp(start,last,kind,chainname,ck,cache,chainlookup,chainlookupname,nil,sequence) + else + logprocess("%s: %s is not yet supported",cref(kind,chainname,chainlookupname),chainlookup.type) + end + else + local i=1 + repeat +if skipped then + while true do + local char=start.char + local ccd=descriptions[char] + if ccd then + local class=ccd.class + if class==skipmark or class==skipligature or class==skipbase or (markclass and class=="mark" and not markclass[char]) then + start=start.next + else + break + end + else + break + end + end +end + local chainlookupname=chainlookups[i] + local chainlookup=lookuptable[chainlookupname] + local cp=chainmores[chainlookup.type] + if cp then + local ok,n + start,ok,n=cp(start,last,kind,chainname,ck,cache,chainlookup,chainlookupname,i,sequence) + if ok then + done=true + i=i+(n or 1) + else + i=i+1 + end + else + logprocess("%s: multiple subchains for %s are not yet supported",cref(kind,chainname,chainlookupname),chainlookup.type) + i=i+1 + end + start=start.next + until i>nofchainlookups + end + else + local replacements=ck[7] + if replacements then + start,done=chainprocs.reversesub(start,last,kind,chainname,ck,cache,replacements) + else + done=true + if trace_contexts then + logprocess("%s: skipping match",cref(kind,chainname)) + end + end + end + end + end + return start,done +end +local verbose_handle_contextchain=function(font,...) + logwarning("no verbose handler installed, reverting to 'normal'") + otf.setcontextchain() + return normal_handle_contextchain(...) +end +otf.chainhandlers={ + normal=normal_handle_contextchain, + verbose=verbose_handle_contextchain, +} +function otf.setcontextchain(method) + if not method or method=="normal" or not otf.chainhandlers[method] then + if handlers.contextchain then + logwarning("installing normal contextchain handler") + end + handlers.contextchain=normal_handle_contextchain + else + logwarning("installing contextchain handler '%s'",method) + local handler=otf.chainhandlers[method] + handlers.contextchain=function(...) + return handler(currentfont,...) + end + end + handlers.gsub_context=handlers.contextchain + handlers.gsub_contextchain=handlers.contextchain + handlers.gsub_reversecontextchain=handlers.contextchain + handlers.gpos_contextchain=handlers.contextchain + handlers.gpos_context=handlers.contextchain +end +otf.setcontextchain() +local missing={} +local function logprocess(...) + if trace_steps then + registermessage(...) + end + logs.report("otf process",...) +end +local function logwarning(...) + logs.report("otf process",...) +end +local function report_missing_cache(typ,lookup) + local f=missing[currentfont] if not f then f={} missing[currentfont]=f end + local t=f[typ] if not t then t={} f[typ]=t end + if not t[lookup] then + t[lookup]=true + logwarning("missing cache for lookup %s of type %s in font %s (%s)",lookup,typ,currentfont,tfmdata.fullname) + end +end +local resolved={} +function fonts.methods.node.otf.features(head,font,attr) + if trace_steps then + checkstep(head) + end + tfmdata=fontdata[font] + local shared=tfmdata.shared + otfdata=shared.otfdata + local luatex=otfdata.luatex + descriptions=tfmdata.descriptions + characters=tfmdata.characters + indices=tfmdata.indices + unicodes=tfmdata.unicodes + marks=tfmdata.marks + anchorlookups=luatex.lookup_to_anchor + currentfont=font + rlmode=0 + local featuredata=otfdata.shared.featuredata + local sequences=luatex.sequences + lookuptable=luatex.lookups + local done=false + local script,language,s_enabled,a_enabled,dyn + local attribute_driven=attr and attr~=0 + if attribute_driven then + local features=context_setups[context_numbers[attr]] + dyn=context_merged[attr] or 0 + language,script=features.language or "dflt",features.script or "dflt" + a_enabled=features + if dyn==2 or dyn==-2 then + s_enabled=shared.features + end + else + language,script=tfmdata.language or "dflt",tfmdata.script or "dflt" + s_enabled=shared.features + dyn=0 + end + local res=resolved[font] if not res then res={} resolved[font]=res end + local rs=res [script] if not rs then rs={} res [script]=rs end + local rl=rs [language] if not rl then rl={} rs [language]=rl end + local ra=rl [attr] if ra==nil then ra={} rl [attr]=ra end + for s=1,#sequences do + local pardir,txtdir,success=0,{},false + local sequence=sequences[s] + local r=ra[s] + if r==nil then + local chain=sequence.chain or 0 + local features=sequence.features + if not features then + r=false + else + local valid,attribute,kind,what=false,false + for k,v in next,features do + local s_e=s_enabled and s_enabled[k] + local a_e=a_enabled and a_enabled[k] + if s_e or a_e then + local l=v[script] or v[wildcard] + if l then + if l[language] then + valid,what=s_e or a_e,language + elseif l[wildcard] then + valid,what=s_e or a_e,wildcard + end + if valid then + kind,attribute=k,special_attributes[k] or false + if a_e and dyn<0 then + valid=false + end + if trace_applied then + local typ,action=match(sequence.type,"(.*)_(.*)") + logs.report("otf node mode", + "%s font: %03i, dynamic: %03i, kind: %s, lookup: %3i, script: %-4s, language: %-4s (%-4s), type: %s, action: %s, name: %s", + (valid and "+") or "-",font,attr or 0,kind,s,script,language,what,typ,action,sequence.name) + end + break + end + end + end + end + if valid then + r={ valid,attribute,chain,kind } + else + r=false + end + end + ra[s]=r + end + featurevalue=r and r[1] + if featurevalue then + local attribute,chain,typ,subtables=r[2],r[3],sequence.type,sequence.subtables + if chain<0 then + local handler=handlers[typ] + local thecache=featuredata[typ] or {} + local start=find_node_tail(head) + while start do + local id=start.id + if id==glyph then + if start.subtype<256 and start.font==font then + local a=has_attribute(start,0) + if a then + a=a==attr + else + a=true + end + if a then + for i=1,#subtables do + local lookupname=subtables[i] + local lookupcache=thecache[lookupname] + if lookupcache then + local lookupmatch=lookupcache[start.char] + if lookupmatch then + start,success=handler(start,r[4],lookupname,lookupmatch,sequence,featuredata,i) + if success then + break + end + end + else + report_missing_cache(typ,lookupname) + end + end + if start then start=start.prev end + else + start=start.prev + end + else + start=start.prev + end + else + start=start.prev + end + end + else + local handler=handlers[typ] + local ns=#subtables + local thecache=featuredata[typ] or {} + local start=head + rlmode=0 + if ns==1 then + local lookupname=subtables[1] + local lookupcache=thecache[lookupname] + if not lookupcache then + report_missing_cache(typ,lookupname) + else + while start do + local id=start.id + if id==glyph then + if start.subtype<256 and start.font==font then + local a=has_attribute(start,0) + if a then + a=(a==attr) and (not attribute or has_attribute(start,state,attribute)) + else + a=not attribute or has_attribute(start,state,attribute) + end + if a then + local lookupmatch=lookupcache[start.char] + if lookupmatch then + local ok + start,ok=handler(start,r[4],lookupname,lookupmatch,sequence,featuredata,1) + if ok then + success=true + end + end + if start then start=start.next end + else + start=start.next + end + else + start=start.next + end + elseif id==whatsit then + local subtype=start.subtype + if subtype==7 then + local dir=start.dir + if dir=="+TRT" or dir=="+TLT" then + insert(txtdir,dir) + elseif dir=="-TRT" or dir=="-TLT" then + remove(txtdir) + end + local d=txtdir[#txtdir] + if d=="+TRT" then + rlmode=-1 + elseif d=="+TLT" then + rlmode=1 + else + rlmode=pardir + end + if trace_directions then + logs.report("fonts","directions after textdir %s: pardir=%s, txtdir=%s:%s, rlmode=%s",dir,pardir,#txtdir,txtdir[#txtdir] or "unset",rlmode) + end + elseif subtype==6 then + local dir=start.dir + if dir=="TRT" then + pardir=-1 + elseif dir=="TLT" then + pardir=1 + else + pardir=0 + end + rlmode=pardir + if trace_directions then + logs.report("fonts","directions after pardir %s: pardir=%s, txtdir=%s:%s, rlmode=%s",dir,pardir,#txtdir,txtdir[#txtdir] or "unset",rlmode) + end + end + start=start.next + else + start=start.next + end + end + end + else + while start do + local id=start.id + if id==glyph then + if start.subtype<256 and start.font==font then + local a=has_attribute(start,0) + if a then + a=(a==attr) and (not attribute or has_attribute(start,state,attribute)) + else + a=not attribute or has_attribute(start,state,attribute) + end + if a then + for i=1,ns do + local lookupname=subtables[i] + local lookupcache=thecache[lookupname] + if lookupcache then + local lookupmatch=lookupcache[start.char] + if lookupmatch then + local ok + start,ok=handler(start,r[4],lookupname,lookupmatch,sequence,featuredata,i) + if ok then + success=true + break + end + end + else + report_missing_cache(typ,lookupname) + end + end + if start then start=start.next end + else + start=start.next + end + else + start=start.next + end + elseif id==whatsit then + local subtype=start.subtype + if subtype==7 then + local dir=start.dir + if dir=="+TRT" or dir=="+TLT" then + insert(txtdir,dir) + elseif dir=="-TRT" or dir=="-TLT" then + remove(txtdir) + end + local d=txtdir[#txtdir] + if d=="+TRT" then + rlmode=-1 + elseif d=="+TLT" then + rlmode=1 + else + rlmode=pardir + end + if trace_directions then + logs.report("fonts","directions after textdir %s: pardir=%s, txtdir=%s:%s, rlmode=%s",dir,pardir,#txtdir,txtdir[#txtdir] or "unset",rlmode) + end + elseif subtype==6 then + local dir=start.dir + if dir=="TRT" then + pardir=-1 + elseif dir=="TLT" then + pardir=1 + else + pardir=0 + end + rlmode=pardir + if trace_directions then + logs.report("fonts","directions after pardir %s: pardir=%s, txtdir=%s:%s, rlmode=%s",dir,pardir,#txtdir,txtdir[#txtdir] or "unset",rlmode) + end + end + start=start.next + else + start=start.next + end + end + end + end + if success then + done=true + end + if trace_steps then + registerstep(head) + end + end + end + return head,done +end +otf.features.prepare={} +local function split(replacement,original,cache,unicodes) + local o,t,n={},{},0 + for s in gmatch(original,"[^ ]+") do + local us=unicodes[s] + if type(us)=="number" then + o[#o+1]=us + else + o[#o+1]=us[1] + end + end + for s in gmatch(replacement,"[^ ]+") do + n=n+1 + local us=unicodes[s] + if type(us)=="number" then + t[o[n]]=us + else + t[o[n]]=us[1] + end + end + return t +end +local function uncover(covers,result,cache,unicodes) + for n=1,#covers do + local c=covers[n] + local cc=cache[c] + if not cc then + local t={} + for s in gmatch(c,"[^ ]+") do + local us=unicodes[s] + if type(us)=="number" then + t[us]=true + else + for i=1,#us do + t[us[i]]=true + end + end + end + cache[c]=t + result[#result+1]=t + else + result[#result+1]=cc + end + end +end +local function prepare_lookups(tfmdata) + local otfdata=tfmdata.shared.otfdata + local featuredata=otfdata.shared.featuredata + local anchor_to_lookup=otfdata.luatex.anchor_to_lookup + local lookup_to_anchor=otfdata.luatex.lookup_to_anchor + local multiple=featuredata.gsub_multiple + local alternate=featuredata.gsub_alternate + local single=featuredata.gsub_single + local ligature=featuredata.gsub_ligature + local pair=featuredata.gpos_pair + local position=featuredata.gpos_single + local kerns=featuredata.gpos_pair + local mark=featuredata.gpos_mark2mark + local cursive=featuredata.gpos_cursive + local unicodes=tfmdata.unicodes + local indices=tfmdata.indices + local descriptions=tfmdata.descriptions + local action={ + substitution=function(p,lookup,glyph,unicode) + local old,new=unicode,unicodes[p[2]] + if type(new)=="table" then + new=new[1] + end + local s=single[lookup] + if not s then s={} single[lookup]=s end + s[old]=new + end, + multiple=function (p,lookup,glyph,unicode) + local old,new=unicode,{} + local m=multiple[lookup] + if not m then m={} multiple[lookup]=m end + m[old]=new + for pc in gmatch(p[2],"[^ ]+") do + local upc=unicodes[pc] + if type(upc)=="number" then + new[#new+1]=upc + else + new[#new+1]=upc[1] + end + end + end, + alternate=function(p,lookup,glyph,unicode) + local old,new=unicode,{} + local a=alternate[lookup] + if not a then a={} alternate[lookup]=a end + a[old]=new + for pc in gmatch(p[2],"[^ ]+") do + local upc=unicodes[pc] + if type(upc)=="number" then + new[#new+1]=upc + else + new[#new+1]=upc[1] + end + end + end, + ligature=function (p,lookup,glyph,unicode) + local first=true + local t=ligature[lookup] + if not t then t={} ligature[lookup]=t end + for s in gmatch(p[2],"[^ ]+") do + if first then + local u=unicodes[s] + if not u then + logs.report("define otf","lookup %s: ligature %s => %s ignored due to invalid unicode",lookup,p[2],glyph.name) + break + elseif type(u)=="number" then + if not t[u] then + t[u]={ {} } + end + t=t[u] + else + local tt=t + local tu + for i=1,#u do + local u=u[i] + if i==1 then + if not t[u] then + t[u]={ {} } + end + tu=t[u] + t=tu + else + if not t[u] then + tt[u]=tu + end + end + end + end + first=false + else + s=unicodes[s] + local t1=t[1] + if not t1[s] then + t1[s]={ {} } + end + t=t1[s] + end + end + t[2]=unicode + end, + position=function(p,lookup,glyph,unicode) + local s=position[lookup] + if not s then s={} position[lookup]=s end + s[unicode]=p[2] + end, + pair=function(p,lookup,glyph,unicode) + local s=pair[lookup] + if not s then s={} pair[lookup]=s end + local others=s[unicode] + if not others then others={} s[unicode]=others end + local two=p[2] + local upc=unicodes[two] + if not upc then + for pc in gmatch(two,"[^ ]+") do + local upc=unicodes[pc] + if type(upc)=="number" then + others[upc]=p + else + for i=1,#upc do + others[upc[i]]=p + end + end + end + elseif type(upc)=="number" then + others[upc]=p + else + for i=1,#upc do + others[upc[i]]=p + end + end + end, + } + for unicode,glyph in next,descriptions do + local lookups=glyph.slookups + if lookups then + for lookup,p in next,lookups do + action[p[1]](p,lookup,glyph,unicode) + end + end + local lookups=glyph.mlookups + if lookups then + for lookup,whatever in next,lookups do + for i=1,#whatever do + local p=whatever[i] + action[p[1]](p,lookup,glyph,unicode) + end + end + end + local list=glyph.mykerns + if list then + for lookup,krn in next,list do + local k=kerns[lookup] + if not k then k={} kerns[lookup]=k end + k[unicode]=krn + end + end + local oanchor=glyph.anchors + if oanchor then + for typ,anchors in next,oanchor do + if typ=="mark" then + for name,anchor in next,anchors do + local lookups=anchor_to_lookup[name] + if lookups then + for lookup,_ in next,lookups do + local f=mark[lookup] + if not f then f={} mark[lookup]=f end + f[unicode]=anchors + end + end + end + elseif typ=="cexit" then + for name,anchor in next,anchors do + local lookups=anchor_to_lookup[name] + if lookups then + for lookup,_ in next,lookups do + local f=cursive[lookup] + if not f then f={} cursive[lookup]=f end + f[unicode]=anchors + end + end + end + end + end + end + end +end +luatex=luatex or {} +local function prepare_contextchains(tfmdata) + local otfdata=tfmdata.shared.otfdata + local lookups=otfdata.lookups + if lookups then + local featuredata=otfdata.shared.featuredata + local contextchain=featuredata.gsub_contextchain + local reversecontextchain=featuredata.gsub_reversecontextchain + local characters=tfmdata.characters + local unicodes=tfmdata.unicodes + local indices=tfmdata.indices + local cache=luatex.covers + if not cache then + cache={} + luatex.covers=cache + end + for lookupname,lookupdata in next,otfdata.lookups do + local lookuptype=lookupdata.type + if not lookuptype then + logs.report("otf process","missing lookuptype for %s",lookupname) + else + local rules=lookupdata.rules + if rules then + local fmt=lookupdata.format + if fmt=="coverage" then + if lookuptype~="chainsub" and lookuptype~="chainpos" then + logs.report("otf process","unsupported coverage %s for %s",lookuptype,lookupname) + else + local contexts=contextchain[lookupname] + if not contexts then + contexts={} + contextchain[lookupname]=contexts + end + local t={} + for nofrules=1,#rules do + local rule=rules[nofrules] + local coverage=rule.coverage + if coverage and coverage.current then + local current,before,after,sequence=coverage.current,coverage.before,coverage.after,{} + if before then + uncover(before,sequence,cache,unicodes) + end + local start=#sequence+1 + uncover(current,sequence,cache,unicodes) + local stop=#sequence + if after then + uncover(after,sequence,cache,unicodes) + end + if sequence[1] then + t[#t+1]={ nofrules,lookuptype,sequence,start,stop,rule.lookups } + for unic,_ in next,sequence[start] do + local cu=contexts[unic] + if not cu then + contexts[unic]=t + end + end + end + end + end + end + elseif fmt=="reversecoverage" then + if lookuptype~="reversesub" then + logs.report("otf process","unsupported reverse coverage %s for %s",lookuptype,lookupname) + else + local contexts=reversecontextchain[lookupname] + if not contexts then + contexts={} + reversecontextchain[lookupname]=contexts + end + local t={} + for nofrules=1,#rules do + local rule=rules[nofrules] + local reversecoverage=rule.reversecoverage + if reversecoverage and reversecoverage.current then + local current,before,after,replacements,sequence=reversecoverage.current,reversecoverage.before,reversecoverage.after,reversecoverage.replacements,{} + if before then + uncover(before,sequence,cache,unicodes) + end + local start=#sequence+1 + uncover(current,sequence,cache,unicodes) + local stop=#sequence + if after then + uncover(after,sequence,cache,unicodes) + end + if replacements then + replacements=split(replacements,current[1],cache,unicodes) + end + if sequence[1] then + t[#t+1]={ nofrules,lookuptype,sequence,start,stop,rule.lookups,replacements } + for unic,_ in next,sequence[start] do + local cu=contexts[unic] + if not cu then + contexts[unic]=t + end + end + end + end + end + end + elseif fmt=="glyphs" then + if lookuptype~="chainsub" and lookuptype~="chainpos" then + logs.report("otf process","unsupported coverage %s for %s",lookuptype,lookupname) + else + local contexts=contextchain[lookupname] + if not contexts then + contexts={} + contextchain[lookupname]=contexts + end + local t={} + for nofrules=1,#rules do + local rule=rules[nofrules] + local glyphs=rule.glyphs + if glyphs and glyphs.names then + local fore,back,names,sequence=glyphs.fore,glyphs.back,glyphs.names,{} + if fore and fore~="" then + fore=lpegmatch(split_at_space,fore) + uncover(fore,sequence,cache,unicodes) + end + local start=#sequence+1 + names=lpegmatch(split_at_space,names) + uncover(names,sequence,cache,unicodes) + local stop=#sequence + if back and back~="" then + back=lpegmatch(split_at_space,back) + uncover(back,sequence,cache,unicodes) + end + if sequence[1] then + t[#t+1]={ nofrules,lookuptype,sequence,start,stop,rule.lookups } + for unic,_ in next,sequence[start] do + local cu=contexts[unic] + if not cu then + contexts[unic]=t + end + end + end + end + end + end + end + end + end + end + end +end +function fonts.initializers.node.otf.features(tfmdata,value) + if true then + if not tfmdata.shared.otfdata.shared.initialized then + local t=trace_preparing and os.clock() + local otfdata=tfmdata.shared.otfdata + local featuredata=otfdata.shared.featuredata + featuredata.gsub_multiple={} + featuredata.gsub_alternate={} + featuredata.gsub_single={} + featuredata.gsub_ligature={} + featuredata.gsub_contextchain={} + featuredata.gsub_reversecontextchain={} + featuredata.gpos_pair={} + featuredata.gpos_single={} + featuredata.gpos_mark2base={} + featuredata.gpos_mark2ligature=featuredata.gpos_mark2base + featuredata.gpos_mark2mark=featuredata.gpos_mark2base + featuredata.gpos_cursive={} + featuredata.gpos_contextchain=featuredata.gsub_contextchain + featuredata.gpos_reversecontextchain=featuredata.gsub_reversecontextchain + prepare_contextchains(tfmdata) + prepare_lookups(tfmdata) + otfdata.shared.initialized=true + if trace_preparing then + logs.report("otf process","preparation time is %0.3f seconds for %s",os.clock()-t,tfmdata.fullname or "?") + end + end + end +end + +end -- closure + +do -- begin closure to overcome local limits and interference + +if not modules then modules={} end modules ['font-ota']={ + version=1.001, + comment="companion to font-otf.lua (analysing)", + author="Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright="PRAGMA ADE / ConTeXt Development Team", + license="see context related readme files" +} +local type,tostring,match,format,concat=type,tostring,string.match,string.format,table.concat +if not trackers then trackers={ register=function() end } end +local trace_analyzing=false trackers.register("otf.analyzing",function(v) trace_analyzing=v end) +local trace_cjk=false trackers.register("cjk.injections",function(v) trace_cjk=v end) +trackers.register("cjk.analyzing","otf.analyzing") +fonts=fonts or {} +fonts.analyzers=fonts.analyzers or {} +fonts.analyzers.initializers=fonts.analyzers.initializers or { node={ otf={} } } +fonts.analyzers.methods=fonts.analyzers.methods or { node={ otf={} } } +local otf=fonts.otf +local tfm=fonts.tfm +local initializers=fonts.analyzers.initializers +local methods=fonts.analyzers.methods +local glyph=node.id('glyph') +local glue=node.id('glue') +local penalty=node.id('penalty') +local set_attribute=node.set_attribute +local has_attribute=node.has_attribute +local traverse_id=node.traverse_id +local traverse_node_list=node.traverse +local fontdata=fonts.ids +local state=attributes.private('state') +local fcs=(fonts.color and fonts.color.set) or function() end +local fcr=(fonts.color and fonts.color.reset) or function() end +local a_to_script=otf.a_to_script +local a_to_language=otf.a_to_language +function fonts.initializers.node.otf.analyze(tfmdata,value,attr) + local script,language + if attr and attr>0 then + script,language=a_to_script[attr],a_to_language[attr] + else + script,language=tfmdata.script,tfmdata.language + end + local action=initializers[script] + if action then + if type(action)=="function" then + return action(tfmdata,value) + else + local action=action[language] + if action then + return action(tfmdata,value) + end + end + end + return nil +end +function fonts.methods.node.otf.analyze(head,font,attr) + local tfmdata=fontdata[font] + local script,language + if attr and attr>0 then + script,language=a_to_script[attr],a_to_language[attr] + else + script,language=tfmdata.script,tfmdata.language + end + local action=methods[script] + if action then + if type(action)=="function" then + return action(head,font,attr) + else + action=action[language] + if action then + return action(head,font,attr) + end + end + end + return head,false +end +otf.features.register("analyze",true) +table.insert(fonts.triggers,"analyze") +fonts.analyzers.methods.latn=fonts.analyzers.aux.setstate +local zwnj=0x200C +local zwj=0x200D +local isol={ + [0x0600]=true,[0x0601]=true,[0x0602]=true,[0x0603]=true, + [0x0608]=true,[0x060B]=true,[0x0621]=true,[0x0674]=true, + [0x06DD]=true,[zwnj]=true, +} +local isol_fina={ + [0x0622]=true,[0x0623]=true,[0x0624]=true,[0x0625]=true, + [0x0627]=true,[0x0629]=true,[0x062F]=true,[0x0630]=true, + [0x0631]=true,[0x0632]=true,[0x0648]=true,[0x0671]=true, + [0x0672]=true,[0x0673]=true,[0x0675]=true,[0x0676]=true, + [0x0677]=true,[0x0688]=true,[0x0689]=true,[0x068A]=true, + [0x068B]=true,[0x068C]=true,[0x068D]=true,[0x068E]=true, + [0x068F]=true,[0x0690]=true,[0x0691]=true,[0x0692]=true, + [0x0693]=true,[0x0694]=true,[0x0695]=true,[0x0696]=true, + [0x0697]=true,[0x0698]=true,[0x0699]=true,[0x06C0]=true, + [0x06C3]=true,[0x06C4]=true,[0x06C5]=true,[0x06C6]=true, + [0x06C7]=true,[0x06C8]=true,[0x06C9]=true,[0x06CA]=true, + [0x06CB]=true,[0x06CD]=true,[0x06CF]=true,[0x06D2]=true, + [0x06D3]=true,[0x06D5]=true,[0x06EE]=true,[0x06EF]=true, + [0x0759]=true,[0x075A]=true,[0x075B]=true,[0x076B]=true, + [0x076C]=true,[0x0771]=true,[0x0773]=true,[0x0774]=true, + [0x0778]=true,[0x0779]=true,[0xFEF5]=true,[0xFEF7]=true, + [0xFEF9]=true,[0xFEFB]=true, +} +local isol_fina_medi_init={ + [0x0626]=true,[0x0628]=true,[0x062A]=true,[0x062B]=true, + [0x062C]=true,[0x062D]=true,[0x062E]=true,[0x0633]=true, + [0x0634]=true,[0x0635]=true,[0x0636]=true,[0x0637]=true, + [0x0638]=true,[0x0639]=true,[0x063A]=true,[0x063B]=true, + [0x063C]=true,[0x063D]=true,[0x063E]=true,[0x063F]=true, + [0x0640]=true,[0x0641]=true,[0x0642]=true,[0x0643]=true, + [0x0644]=true,[0x0645]=true,[0x0646]=true,[0x0647]=true, + [0x0649]=true,[0x064A]=true,[0x066E]=true,[0x066F]=true, + [0x0678]=true,[0x0679]=true,[0x067A]=true,[0x067B]=true, + [0x067C]=true,[0x067D]=true,[0x067E]=true,[0x067F]=true, + [0x0680]=true,[0x0681]=true,[0x0682]=true,[0x0683]=true, + [0x0684]=true,[0x0685]=true,[0x0686]=true,[0x0687]=true, + [0x069A]=true,[0x069B]=true,[0x069C]=true,[0x069D]=true, + [0x069E]=true,[0x069F]=true,[0x06A0]=true,[0x06A1]=true, + [0x06A2]=true,[0x06A3]=true,[0x06A4]=true,[0x06A5]=true, + [0x06A6]=true,[0x06A7]=true,[0x06A8]=true,[0x06A9]=true, + [0x06AA]=true,[0x06AB]=true,[0x06AC]=true,[0x06AD]=true, + [0x06AE]=true,[0x06AF]=true,[0x06B0]=true,[0x06B1]=true, + [0x06B2]=true,[0x06B3]=true,[0x06B4]=true,[0x06B5]=true, + [0x06B6]=true,[0x06B7]=true,[0x06B8]=true,[0x06B9]=true, + [0x06BA]=true,[0x06BB]=true,[0x06BC]=true,[0x06BD]=true, + [0x06BE]=true,[0x06BF]=true,[0x06C1]=true,[0x06C2]=true, + [0x06CC]=true,[0x06CE]=true,[0x06D0]=true,[0x06D1]=true, + [0x06FA]=true,[0x06FB]=true,[0x06FC]=true,[0x06FF]=true, + [0x0750]=true,[0x0751]=true,[0x0752]=true,[0x0753]=true, + [0x0754]=true,[0x0755]=true,[0x0756]=true,[0x0757]=true, + [0x0758]=true,[0x075C]=true,[0x075D]=true,[0x075E]=true, + [0x075F]=true,[0x0760]=true,[0x0761]=true,[0x0762]=true, + [0x0763]=true,[0x0764]=true,[0x0765]=true,[0x0766]=true, + [0x0767]=true,[0x0768]=true,[0x0769]=true,[0x076A]=true, + [0x076D]=true,[0x076E]=true,[0x076F]=true,[0x0770]=true, + [0x0772]=true,[0x0775]=true,[0x0776]=true,[0x0777]=true, + [0x077A]=true,[0x077B]=true,[0x077C]=true,[0x077D]=true, + [0x077E]=true,[0x077F]=true,[zwj]=true, +} +local arab_warned={} +local function warning(current,what) + local char=current.char + if not arab_warned[char] then + log.report("analyze","arab: character %s (U+%04X) has no %s class",char,char,what) + arab_warned[char]=true + end +end +function fonts.analyzers.methods.nocolor(head,font,attr) + for n in traverse_node_list(head,glyph) do + if not font or n.font==font then + fcr(n) + end + end + return head,true +end +local function finish(first,last) + if last then + if first==last then + local fc=first.char + if isol_fina_medi_init[fc] or isol_fina[fc] then + set_attribute(first,state,4) + if trace_analyzing then fcs(first,"font:isol") end + else + warning(first,"isol") + set_attribute(first,state,0) + if trace_analyzing then fcr(first) end + end + else + local lc=last.char + if isol_fina_medi_init[lc] or isol_fina[lc] then + set_attribute(last,state,3) + if trace_analyzing then fcs(last,"font:fina") end + else + warning(last,"fina") + set_attribute(last,state,0) + if trace_analyzing then fcr(last) end + end + end + first,last=nil,nil + elseif first then + local fc=first.char + if isol_fina_medi_init[fc] or isol_fina[fc] then + set_attribute(first,state,4) + if trace_analyzing then fcs(first,"font:isol") end + else + warning(first,"isol") + set_attribute(first,state,0) + if trace_analyzing then fcr(first) end + end + first=nil + end + return first,last +end +function fonts.analyzers.methods.arab(head,font,attr) + local tfmdata=fontdata[font] + local marks=tfmdata.marks + local first,last,current,done=nil,nil,head,false + while current do + if current.id==glyph and current.subtype<256 and current.font==font and not has_attribute(current,state) then + done=true + local char=current.char + if marks[char] then + set_attribute(current,state,5) + if trace_analyzing then fcs(current,"font:mark") end + elseif isol[char] then + first,last=finish(first,last) + set_attribute(current,state,4) + if trace_analyzing then fcs(current,"font:isol") end + first,last=nil,nil + elseif not first then + if isol_fina_medi_init[char] then + set_attribute(current,state,1) + if trace_analyzing then fcs(current,"font:init") end + first,last=first or current,current + elseif isol_fina[char] then + set_attribute(current,state,4) + if trace_analyzing then fcs(current,"font:isol") end + first,last=nil,nil + else + first,last=finish(first,last) + end + elseif isol_fina_medi_init[char] then + first,last=first or current,current + set_attribute(current,state,2) + if trace_analyzing then fcs(current,"font:medi") end + elseif isol_fina[char] then + if not has_attribute(last,state,1) then + set_attribute(last,state,2) + if trace_analyzing then fcs(last,"font:medi") end + end + set_attribute(current,state,3) + if trace_analyzing then fcs(current,"font:fina") end + first,last=nil,nil + elseif char>=0x0600 and char<=0x06FF then + if trace_analyzing then fcs(current,"font:rest") end + first,last=finish(first,last) + else + first,last=finish(first,last) + end + else + first,last=finish(first,last) + end + current=current.next + end + first,last=finish(first,last) + return head,done +end + +end -- closure + +do -- begin closure to overcome local limits and interference + +if not modules then modules={} end modules ['font-otc']={ + version=1.001, + comment="companion to font-otf.lua (context)", + author="Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright="PRAGMA ADE / ConTeXt Development Team", + license="see context related readme files" +} +local format,insert=string.format,table.insert +local type,next=type,next +local trace_loading=false trackers.register("otf.loading",function(v) trace_loading=v end) +local otf=fonts.otf +local tfm=fonts.tfm +local extra_lists={ + tlig={ + { + endash="hyphen hyphen", + emdash="hyphen hyphen hyphen", + quotedblleft="quoteleft quoteleft", + quotedblright="quoteright quoteright", + quotedblleft="grave grave", + quotedblright="quotesingle quotesingle", + quotedblbase="comma comma", + exclamdown="exclam grave", + questiondown="question grave", + guillemotleft="less less", + guillemotright="greater greater", + }, + }, + trep={ + { + [0x0022]=0x201D, + [0x0027]=0x2019, + [0x0060]=0x2018, + }, + }, + anum={ + { + [0x0030]=0x0660, + [0x0031]=0x0661, + [0x0032]=0x0662, + [0x0033]=0x0663, + [0x0034]=0x0664, + [0x0035]=0x0665, + [0x0036]=0x0666, + [0x0037]=0x0667, + [0x0038]=0x0668, + [0x0039]=0x0669, + }, + { + [0x0030]=0x06F0, + [0x0031]=0x06F1, + [0x0032]=0x06F2, + [0x0033]=0x06F3, + [0x0034]=0x06F4, + [0x0035]=0x06F5, + [0x0036]=0x06F6, + [0x0037]=0x06F7, + [0x0038]=0x06F8, + [0x0039]=0x06F9, + }, + }, +} +local extra_features={ + tlig={ + { + features={ { scripts={ { script="*",langs={ "*" },} },tag="tlig",comment="added bij mkiv" },}, + name="ctx_tlig_1", + subtables={ { name="ctx_tlig_1_s" } }, + type="gsub_ligature", + flags={}, + }, + }, + trep={ + { + features={ { scripts={ { script="*",langs={ "*" },} },tag="trep",comment="added bij mkiv" },}, + name="ctx_trep_1", + subtables={ { name="ctx_trep_1_s" } }, + type="gsub_single", + flags={}, + }, + }, + anum={ + { + features={ { scripts={ { script="arab",langs={ "dflt","ARA" },} },tag="anum",comment="added bij mkiv" },}, + name="ctx_anum_1", + subtables={ { name="ctx_anum_1_s" } }, + type="gsub_single", + flags={}, + }, + { + features={ { scripts={ { script="arab",langs={ "FAR" },} },tag="anum",comment="added bij mkiv" },}, + name="ctx_anum_2", + subtables={ { name="ctx_anum_2_s" } }, + type="gsub_single", + flags={}, + }, + }, +} +fonts.otf.enhancers["add some missing characters"]=function(data,filename) +end +fonts.otf.enhancers["enrich with features"]=function(data,filename) + local used={} + for i=1,#otf.glists do + local g=data[otf.glists[i]] + if g then + for i=1,#g do + local f=g[i].features + if f then + for i=1,#f do + local t=f[i].tag + if t then used[t]=true end + end + end + end + end + end + local glyphs=data.glyphs + local indices=data.map.map + data.gsub=data.gsub or {} + for kind,specifications in next,extra_features do + if not used[kind] then + local done=0 + for s=1,#specifications do + local added=false + local specification=specifications[s] + local list=extra_lists[kind][s] + local name=specification.name.."_s" + if specification.type=="gsub_ligature" then + for unicode,index in next,indices do + local glyph=glyphs[index] + local ligature=list[glyph.name] + if ligature then + local o=glyph.lookups or {} + o[name]={ + { + ["type"]="ligature", + ["specification"]={ + char=glyph.name, + components=ligature, + } + } + } + glyph.lookups,done,added=o,done+1,true + end + end + elseif specification.type=="gsub_single" then + for unicode,index in next,indices do + local glyph=glyphs[index] + local r=list[unicode] + if r then + local replacement=indices[r] + if replacement and glyphs[replacement] then + local o=glyph.lookups or {} + o[name]={ + { + ["type"]="substitution", + ["specification"]={ + variant=glyphs[replacement].name, + } + } + } + glyph.lookups,done,added=o,done+1,true + end + end + end + end + if added then + insert(data.gsub,s,table.fastcopy(specification)) + end + end + if done>0 then + if trace_loading then + logs.report("load otf","enhance: registering %s feature (%s glyphs affected)",kind,done) + end + end + end + end +end +otf.tables.features['tlig']='TeX Ligatures' +otf.tables.features['trep']='TeX Replacements' +otf.tables.features['anum']='Arabic Digits' +otf.features.register_base_substitution('tlig') +otf.features.register_base_substitution('trep') +otf.features.register_base_substitution('anum') +fonts.initializers.base.otf.equaldigits=fonts.initializers.common.equaldigits +fonts.initializers.node.otf.equaldigits=fonts.initializers.common.equaldigits +fonts.initializers.base.otf.lineheight=fonts.initializers.common.lineheight +fonts.initializers.node.otf.lineheight=fonts.initializers.common.lineheight +fonts.initializers.base.otf.compose=fonts.initializers.common.compose +fonts.initializers.node.otf.compose=fonts.initializers.common.compose + +end -- closure + +do -- begin closure to overcome local limits and interference + +if not modules then modules={} end modules ['font-def']={ + version=1.001, + comment="companion to font-ini.mkiv", + author="Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright="PRAGMA ADE / ConTeXt Development Team", + license="see context related readme files" +} +local format,concat,gmatch,match,find,lower=string.format,table.concat,string.gmatch,string.match,string.find,string.lower +local tostring,next=tostring,next +local lpegmatch=lpeg.match +local trace_defining=false trackers .register("fonts.defining",function(v) trace_defining=v end) +local directive_embedall=false directives.register("fonts.embedall",function(v) directive_embedall=v end) +trackers.register("fonts.loading","fonts.defining","otf.loading","afm.loading","tfm.loading") +trackers.register("fonts.all","fonts.*","otf.*","afm.*","tfm.*") +fonts=fonts or {} +fonts.define=fonts.define or {} +fonts.tfm=fonts.tfm or {} +fonts.ids=fonts.ids or {} +fonts.vf=fonts.vf or {} +fonts.used=fonts.used or {} +local tfm=fonts.tfm +local vf=fonts.vf +local define=fonts.define +tfm.version=1.01 +tfm.cache=containers.define("fonts","tfm",tfm.version,false) +define.method="afm or tfm" +define.specify=fonts.define.specify or {} +define.methods=fonts.define.methods or {} +tfm.fonts=tfm.fonts or {} +tfm.readers=tfm.readers or {} +tfm.internalized=tfm.internalized or {} +tfm.readers.sequence={ 'otf','ttf','afm','tfm' } +tfm.auto_afm=true +local readers=tfm.readers +local sequence=readers.sequence +fonts.version=1.05 +fonts.cache=containers.define("fonts","def",fonts.version,false) +local splitter,specifiers=nil,"" +local P,C,S,Cc=lpeg.P,lpeg.C,lpeg.S,lpeg.Cc +local left=P("(") +local right=P(")") +local colon=P(":") +local space=P(" ") +define.defaultlookup="file" +local prefixpattern=P(false) +function define.add_specifier(symbol) + specifiers=specifiers..symbol + local method=S(specifiers) + local lookup=C(prefixpattern)*colon + local sub=left*C(P(1-left-right-method)^1)*right + local specification=C(method)*C(P(1)^1) + local name=C((1-sub-specification)^1) + splitter=P((lookup+Cc(""))*name*(sub+Cc(""))*(specification+Cc(""))) +end +function define.add_lookup(str,default) + prefixpattern=prefixpattern+P(str) +end +define.add_lookup("file") +define.add_lookup("name") +define.add_lookup("spec") +function define.get_specification(str) + return lpegmatch(splitter,str) +end +function define.register_split(symbol,action) + define.add_specifier(symbol) + define.specify[symbol]=action +end +function define.makespecification(specification,lookup,name,sub,method,detail,size) + size=size or 655360 + if trace_defining then + logs.report("define font","%s -> lookup: %s, name: %s, sub: %s, method: %s, detail: %s", + specification,(lookup~="" and lookup) or "[file]",(name~="" and name) or "-", + (sub~="" and sub) or "-",(method~="" and method) or "-",(detail~="" and detail) or "-") + end + if not lookup or lookup=="" then + lookup=define.defaultlookup + end + local t={ + lookup=lookup, + specification=specification, + size=size, + name=name, + sub=sub, + method=method, + detail=detail, + resolved="", + forced="", + features={}, + } + return t +end +function define.analyze(specification,size) + local lookup,name,sub,method,detail=define.get_specification(specification or "") + return define.makespecification(specification,lookup,name,sub,method,detail,size) +end +local sortedhashkeys=table.sortedhashkeys +function tfm.hash_features(specification) + local features=specification.features + if features then + local t={} + local normal=features.normal + if normal and next(normal) then + local f=sortedhashkeys(normal) + for i=1,#f do + local v=f[i] + if v~="number" and v~="features" then + t[#t+1]=v..'='..tostring(normal[v]) + end + end + end + local vtf=features.vtf + if vtf and next(vtf) then + local f=sortedhashkeys(vtf) + for i=1,#f do + local v=f[i] + t[#t+1]=v..'='..tostring(vtf[v]) + end + end + if #t>0 then + return concat(t,"+") + end + end + return "unknown" +end +fonts.designsizes={} +function tfm.hash_instance(specification,force) + local hash,size,fallbacks=specification.hash,specification.size,specification.fallbacks + if force or not hash then + hash=tfm.hash_features(specification) + specification.hash=hash + end + if size<1000 and fonts.designsizes[hash] then + size=math.round(tfm.scaled(size,fonts.designsizes[hash])) + specification.size=size + end + if fallbacks then + return hash..' @ '..tostring(size)..' @ '..fallbacks + else + return hash..' @ '..tostring(size) + end +end +define.resolvers=resolvers +function define.resolvers.file(specification) + local suffix=file.suffix(specification.name) + if fonts.formats[suffix] then + specification.forced=suffix + specification.name=file.removesuffix(specification.name) + end +end +function define.resolvers.name(specification) + local resolve=fonts.names.resolve + if resolve then + local resolved,sub=fonts.names.resolve(specification) + specification.resolved,specification.sub=resolved,sub + if resolved then + local suffix=file.suffix(resolved) + if fonts.formats[suffix] then + specification.forced=suffix + specification.name=file.removesuffix(resolved) + else + specification.name=resolved + end + end + else + define.resolvers.file(specification) + end +end +function define.resolvers.spec(specification) + local resolvespec=fonts.names.resolvespec + if resolvespec then + specification.resolved,specification.sub=fonts.names.resolvespec(specification) + if specification.resolved then + specification.forced=file.extname(specification.resolved) + specification.name=file.removesuffix(specification.resolved) + end + else + define.resolvers.name(specification) + end +end +function define.resolve(specification) + if not specification.resolved or specification.resolved=="" then + local r=define.resolvers[specification.lookup] + if r then + r(specification) + end + end + if specification.forced=="" then + specification.forced=nil + else + specification.forced=specification.forced + end + specification.hash=lower(specification.name..' @ '..tfm.hash_features(specification)) + if specification.sub and specification.sub~="" then + specification.hash=specification.sub..' @ '..specification.hash + end + return specification +end +function tfm.read(specification) + local hash=tfm.hash_instance(specification) + local tfmtable=tfm.fonts[hash] + if not tfmtable then + local forced=specification.forced or "" + if forced~="" then + tfmtable=readers[lower(forced)](specification) + if not tfmtable then + logs.report("define font","forced type %s of %s not found",forced,specification.name) + end + else + for s=1,#sequence do + local reader=sequence[s] + if readers[reader] then + if trace_defining then + logs.report("define font","trying (reader sequence driven) type %s for %s with file %s",reader,specification.name,specification.filename or "unknown") + end + tfmtable=readers[reader](specification) + if tfmtable then + break + else + specification.filename=nil + end + end + end + end + if tfmtable then + if directive_embedall then + tfmtable.embedding="full" + elseif tfmtable.filename and fonts.dontembed[tfmtable.filename] then + tfmtable.embedding="no" + else + tfmtable.embedding="subset" + end + tfm.fonts[hash]=tfmtable + fonts.designsizes[specification.hash]=tfmtable.designsize + end + end + if not tfmtable then + logs.report("define font","font with name %s is not found",specification.name) + end + return tfmtable +end +function tfm.read_and_define(name,size) + local specification=define.analyze(name,size) + local method=specification.method + if method and define.specify[method] then + specification=define.specify[method](specification) + end + specification=define.resolve(specification) + local hash=tfm.hash_instance(specification) + local id=define.registered(hash) + if not id then + local fontdata=tfm.read(specification) + if fontdata then + fontdata.hash=hash + id=font.define(fontdata) + define.register(fontdata,id) + tfm.cleanup_table(fontdata) + else + id=0 + end + end + return fonts.ids[id],id +end +local function check_tfm(specification,fullname) + local foundname=resolvers.findbinfile(fullname,'tfm') or "" + if foundname=="" then + foundname=resolvers.findbinfile(fullname,'ofm') or "" + end + if foundname~="" then + specification.filename,specification.format=foundname,"ofm" + return tfm.read_from_tfm(specification) + end +end +local function check_afm(specification,fullname) + local foundname=resolvers.findbinfile(fullname,'afm') or "" + if foundname=="" and tfm.auto_afm then + local encoding,shortname=match(fullname,"^(.-)%-(.*)$") + if encoding and shortname and fonts.enc.known[encoding] then + shortname=resolvers.findbinfile(shortname,'afm') or "" + if shortname~="" then + foundname=shortname + if trace_loading then + logs.report("load afm","stripping encoding prefix from filename %s",afmname) + end + end + end + end + if foundname~="" then + specification.filename,specification.format=foundname,"afm" + return tfm.read_from_afm(specification) + end +end +function readers.tfm(specification) + local fullname,tfmtable=specification.filename or "",nil + if fullname=="" then + local forced=specification.forced or "" + if forced~="" then + tfmtable=check_tfm(specification,specification.name.."."..forced) + end + if not tfmtable then + tfmtable=check_tfm(specification,specification.name) + end + else + tfmtable=check_tfm(specification,fullname) + end + return tfmtable +end +function readers.afm(specification,method) + local fullname,tfmtable=specification.filename or "",nil + if fullname=="" then + local forced=specification.forced or "" + if forced~="" then + tfmtable=check_afm(specification,specification.name.."."..forced) + end + if not tfmtable then + method=method or define.method or "afm or tfm" + if method=="tfm" then + tfmtable=check_tfm(specification,specification.name) + elseif method=="afm" then + tfmtable=check_afm(specification,specification.name) + elseif method=="tfm or afm" then + tfmtable=check_tfm(specification,specification.name) or check_afm(specification,specification.name) + else + tfmtable=check_afm(specification,specification.name) or check_tfm(specification,specification.name) + end + end + else + tfmtable=check_afm(specification,fullname) + end + return tfmtable +end +local function check_otf(forced,specification,suffix,what) + local name=specification.name + if forced then + name=file.addsuffix(name,suffix,true) + end + local fullname,tfmtable=resolvers.findbinfile(name,suffix) or "",nil + if fullname=="" then + local fb=fonts.names.old_to_new[name] + if fb then + fullname=resolvers.findbinfile(fb,suffix) or "" + end + end + if fullname=="" then + local fb=fonts.names.new_to_old[name] + if fb then + fullname=resolvers.findbinfile(fb,suffix) or "" + end + end + if fullname~="" then + specification.filename,specification.format=fullname,what + tfmtable=tfm.read_from_open_type(specification) + end + return tfmtable +end +function readers.opentype(specification,suffix,what) + local forced=specification.forced or "" + if forced=="otf" then + return check_otf(true,specification,forced,"opentype") + elseif forced=="ttf" or forced=="ttc" or forced=="dfont" then + return check_otf(true,specification,forced,"truetype") + else + return check_otf(false,specification,suffix,what) + end +end +function readers.otf (specification) return readers.opentype(specification,"otf","opentype") end +function readers.ttf (specification) return readers.opentype(specification,"ttf","truetype") end +function readers.ttc (specification) return readers.opentype(specification,"ttf","truetype") end +function readers.dfont(specification) return readers.opentype(specification,"ttf","truetype") end +function define.check(features,defaults) + local done=false + if features and next(features) then + for k,v in next,defaults do + if features[k]==nil then + features[k],done=v,true + end + end + else + features,done=table.fastcopy(defaults),true + end + return features,done +end +define.last=nil +function define.register(fontdata,id) + if fontdata and id then + local hash=fontdata.hash + if not tfm.internalized[hash] then + if trace_defining then + logs.report("define font","loading at 2 id %s, hash: %s",id or "?",hash or "?") + end + fonts.identifiers[id]=fontdata + fonts.characters [id]=fontdata.characters + fonts.quads [id]=fontdata.parameters.quad + tfm.internalized[hash]=id + end + end +end +function define.registered(hash) + local id=tfm.internalized[hash] + return id,id and fonts.ids[id] +end +local cache_them=false +function tfm.make(specification) + local fvm=define.methods[specification.features.vtf.preset] + if fvm then + return fvm(specification) + else + return nil + end +end +function define.read(specification,size,id) + statistics.starttiming(fonts) + if type(specification)=="string" then + specification=define.analyze(specification,size) + end + local method=specification.method + if method and define.specify[method] then + specification=define.specify[method](specification) + end + specification=define.resolve(specification) + local hash=tfm.hash_instance(specification) + if cache_them then + local fontdata=containers.read(fonts.cache,hash) + end + local fontdata=define.registered(hash) + if not fontdata then + if specification.features.vtf and specification.features.vtf.preset then + fontdata=tfm.make(specification) + else + fontdata=tfm.read(specification) + if fontdata then + tfm.check_virtual_id(fontdata) + end + end + if cache_them then + fontdata=containers.write(fonts.cache,hash,fontdata) + end + if fontdata then + fontdata.hash=hash + fontdata.cache="no" + if id then + define.register(fontdata,id) + end + end + end + define.last=fontdata or id + if not fontdata then + logs.report("define font","unknown font %s, loading aborted",specification.name) + elseif trace_defining and type(fontdata)=="table" then + logs.report("define font","using %s font with id %s, name:%s size:%s bytes:%s encoding:%s fullname:%s filename:%s", + fontdata.type or "unknown", + id or "?", + fontdata.name or "?", + fontdata.size or "default", + fontdata.encodingbytes or "?", + fontdata.encodingname or "unicode", + fontdata.fullname or "?", + file.basename(fontdata.filename or "?")) + end + statistics.stoptiming(fonts) + return fontdata +end +function vf.find(name) + name=file.removesuffix(file.basename(name)) + if tfm.resolve_vf then + local format=fonts.logger.format(name) + if format=='tfm' or format=='ofm' then + if trace_defining then + logs.report("define font","locating vf for %s",name) + end + return resolvers.findbinfile(name,"ovf") + else + if trace_defining then + logs.report("define font","vf for %s is already taken care of",name) + end + return nil + end + else + if trace_defining then + logs.report("define font","locating vf for %s",name) + end + return resolvers.findbinfile(name,"ovf") + end +end +callbacks.register('define_font',define.read,"definition of fonts (tfmtable preparation)") +callbacks.register('find_vf_file',vf.find,"locating virtual fonts, insofar needed") + +end -- closure + +do -- begin closure to overcome local limits and interference + +if not modules then modules={} end modules ['font-xtx']={ + version=1.001, + comment="companion to font-ini.mkiv", + author="Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright="PRAGMA ADE / ConTeXt Development Team", + license="see context related readme files" +} +local texsprint,count=tex.sprint,tex.count +local format,concat,gmatch,match,find,lower=string.format,table.concat,string.gmatch,string.match,string.find,string.lower +local tostring,next=tostring,next +local lpegmatch=lpeg.match +local trace_defining=false trackers.register("fonts.defining",function(v) trace_defining=v end) +local list={} +fonts.define.specify.colonized_default_lookup="file" +local function isstyle(s) + local style=string.lower(s):split("/") + for _,v in ipairs(style) do + if v=="b" then + list.style="bold" + elseif v=="i" then + list.style="italic" + elseif v=="bi" or v=="ib" then + list.style="bolditalic" + elseif v:find("^s=") then + list.optsize=v:split("=")[2] + elseif v=="aat" or v=="icu" or v=="gr" then + logs.report("load font","unsupported font option: %s",v) + elseif not v:is_empty() then + list.style=v:gsub("[^%a%d]","") + end + end +end +fonts=fonts or {} +fonts.otf=fonts.otf or {} +local otf=fonts.otf +otf.tables=otf.tables or {} +otf.tables.defaults={ + dflt={ + "ccmp","locl","rlig","liga","clig", + "kern","mark","mkmk","itlc", + }, + arab={ + "ccmp","locl","isol","fina","fin2", + "fin3","medi","med2","init","rlig", + "calt","liga","cswh","mset","curs", + "kern","mark","mkmk", + }, + deva={ + "ccmp","locl","init","nukt","akhn", + "rphf","blwf","half","pstf","vatu", + "pres","blws","abvs","psts","haln", + "calt","blwm","abvm","dist","kern", + "mark","mkmk", + }, + khmr={ + "ccmp","locl","pref","blwf","abvf", + "pstf","pres","blws","abvs","psts", + "clig","calt","blwm","abvm","dist", + "kern","mark","mkmk", + }, + thai={ + "ccmp","locl","liga","kern","mark", + "mkmk", + }, + hang={ + "ccmp","ljmo","vjmo","tjmo", + }, +} +otf.tables.defaults.beng=otf.tables.defaults.deva +otf.tables.defaults.guru=otf.tables.defaults.deva +otf.tables.defaults.gujr=otf.tables.defaults.deva +otf.tables.defaults.orya=otf.tables.defaults.deva +otf.tables.defaults.taml=otf.tables.defaults.deva +otf.tables.defaults.telu=otf.tables.defaults.deva +otf.tables.defaults.knda=otf.tables.defaults.deva +otf.tables.defaults.mlym=otf.tables.defaults.deva +otf.tables.defaults.sinh=otf.tables.defaults.deva +otf.tables.defaults.syrc=otf.tables.defaults.arab +otf.tables.defaults.mong=otf.tables.defaults.arab +otf.tables.defaults.nko=otf.tables.defaults.arab +otf.tables.defaults.tibt=otf.tables.defaults.khmr +otf.tables.defaults.lao=otf.tables.defaults.thai +local function parse_script(script) + if otf.tables.scripts[script] then + local dflt + if otf.tables.defaults[script] then + logs.report("load font","auto-selecting default features for script: %s",script) + dflt=otf.tables.defaults[script] + else + logs.report("load font","auto-selecting default features for script: dflt (was %s)",script) + dflt=otf.tables.defaults["dflt"] + end + for _,v in next,dflt do + list[v]="yes" + end + else + logs.report("load font","unknown script: %s",script) + end +end +local function issome () list.lookup=fonts.define.specify.colonized_default_lookup end +local function isfile () list.lookup='file' end +local function isname () list.lookup='name' end +local function thename(s) list.name=s end +local function issub (v) list.sub=v end +local function iskey (k,v) + if k=="script" then + parse_script(v) + end + list[k]=v +end +local function istrue (s) list[s]=true end +local function isfalse(s) list[s]=false end +local spaces=lpeg.P(" ")^0 +local namespec=(1-lpeg.S("/:("))^0 +local filespec=(lpeg.R("az","AZ")*lpeg.P(":"))^-1*(1-lpeg.S(":("))^1 +local crapspec=spaces*lpeg.P("/")*(((1-lpeg.P(":"))^0)/isstyle)*spaces +local filename=(lpeg.P("file:")/isfile*(filespec/thename))+(lpeg.P("[")*lpeg.P(true)/isfile*(((1-lpeg.P("]"))^0)/thename)*lpeg.P("]")) +local fontname=(lpeg.P("name:")/isname*(namespec/thename))+lpeg.P(true)/issome*(namespec/thename) +local sometext=(lpeg.R("az","AZ","09")+lpeg.S("+-."))^1 +local truevalue=lpeg.P("+")*spaces*(sometext/istrue) +local falsevalue=lpeg.P("-")*spaces*(sometext/isfalse) +local keyvalue=lpeg.P("+")+(lpeg.C(sometext)*spaces*lpeg.P("=")*spaces*lpeg.C(sometext))/iskey +local somevalue=sometext/istrue +local subvalue=lpeg.P("(")*(lpeg.C(lpeg.P(1-lpeg.S("()"))^1)/issub)*lpeg.P(")") +local option=spaces*(keyvalue+falsevalue+truevalue+somevalue)*spaces +local options=lpeg.P(":")*spaces*(lpeg.P(";")^0*option)^0 +local pattern=(filename+fontname)*subvalue^0*crapspec^0*options^0 +local normalize_meanings=fonts.otf.meanings.normalize +function fonts.define.specify.colonized(specification) + list={} + lpegmatch(pattern,specification.specification) + if list.style then + specification.style=list.style + list.style=nil + end + if list.optsize then + specification.optsize=list.optsize + list.optsize=nil + end + if list.name then + if resolvers.find_file(list.name,"tfm") then + list.lookup="file" + list.name=file.addsuffix(list.name,"tfm") + elseif resolvers.find_file(list.name,"ofm") then + list.lookup="file" + list.name=file.addsuffix(list.name,"ofm") + end + specification.name=list.name + list.name=nil + end + if list.lookup then + specification.lookup=list.lookup + list.lookup=nil + end + if list.sub then + specification.sub=list.sub + list.sub=nil + end + specification.features.normal=normalize_meanings(list) + return specification +end +fonts.define.register_split(":",fonts.define.specify.colonized) + +end -- closure + +do -- begin closure to overcome local limits and interference + +if not modules then modules={} end modules ['font-dum']={ + version=1.001, + comment="companion to luatex-*.tex", + author="Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright="PRAGMA ADE / ConTeXt Development Team", + license="see context related readme files" +} +fonts=fonts or {} +fonts.otf.pack=false +fonts.tfm.resolve_vf=false +fonts.tfm.fontname_mode="specification" +fonts.tfm.readers=fonts.tfm.readers or {} +fonts.tfm.readers.sequence={ 'otf','ttf','tfm' } +fonts.tfm.readers.afm=nil +fonts.define=fonts.define or {} +fonts.define.specify.colonized_default_lookup="name" +function fonts.define.get_specification(str) + return "",str,"",":",str +end +fonts.logger=fonts.logger or {} +function fonts.logger.save() +end +fonts.names=fonts.names or {} +fonts.names.version=1.001 +fonts.names.basename="luatex-fonts-names.lua" +fonts.names.new_to_old={} +fonts.names.old_to_new={} +local data,loaded=nil,false +local fileformats={ "lua","tex","other text files" } +function fonts.names.resolve(name,sub) + if not loaded then + local basename=fonts.names.basename + if basename and basename~="" then + for i=1,#fileformats do + local format=fileformats[i] + local foundname=resolvers.find_file(basename,format) or "" + if foundname~="" then + data=dofile(foundname) + break + end + end + end + loaded=true + end + if type(data)=="table" and data.version==fonts.names.version then + local condensed=string.gsub(string.lower(name),"[^%a%d]","") + local found=data.mappings and data.mappings[condensed] + if found then + local fontname,filename,subfont=found[1],found[2],found[3] + if subfont then + return filename,fontname + else + return filename,false + end + else + return name,false + end + end +end +fonts.names.resolvespec=fonts.names.resolve +table.insert(fonts.triggers,"itlc") +local function itlc(tfmdata,value) + if value then + local metadata=tfmdata.shared.otfdata.metadata + if metadata then + local italicangle=metadata.italicangle + if italicangle and italicangle~=0 then + local uwidth=(metadata.uwidth or 40)/2 + for unicode,d in next,tfmdata.descriptions do + local it=d.boundingbox[3]-d.width+uwidth + if it~=0 then + d.italic=it + end + end + tfmdata.has_italic=true + end + end + end +end +fonts.initializers.base.otf.itlc=itlc +fonts.initializers.node.otf.itlc=itlc +function fonts.initializers.common.slant(tfmdata,value) + value=tonumber(value) + if not value then + value=0 + elseif value>1 then + value=1 + elseif value<-1 then + value=-1 + end + tfmdata.slant_factor=value +end +function fonts.initializers.common.extend(tfmdata,value) + value=tonumber(value) + if not value then + value=0 + elseif value>10 then + value=10 + elseif value<-10 then + value=-10 + end + tfmdata.extend_factor=value +end +table.insert(fonts.triggers,"slant") +table.insert(fonts.triggers,"extend") +fonts.initializers.base.otf.slant=fonts.initializers.common.slant +fonts.initializers.node.otf.slant=fonts.initializers.common.slant +fonts.initializers.base.otf.extend=fonts.initializers.common.extend +fonts.initializers.node.otf.extend=fonts.initializers.common.extend +fonts.protrusions=fonts.protrusions or {} +fonts.protrusions.setups=fonts.protrusions.setups or {} +local setups=fonts.protrusions.setups +local function map_opbd_onto_protrusion(tfmdata,value,opbd) + local characters,descriptions=tfmdata.characters,tfmdata.descriptions + local otfdata=tfmdata.shared.otfdata + local singles=otfdata.shared.featuredata.gpos_single + local script,language=tfmdata.script,tfmdata.language + local done,factor,left,right=false,1,1,1 + local setup=setups[value] + if setup then + factor=setup.factor or 1 + left=setup.left or 1 + right=setup.right or 1 + else + factor=tonumber(value) or 1 + end + if opbd~="right" then + local validlookups,lookuplist=fonts.otf.collect_lookups(otfdata,"lfbd",script,language) + if validlookups then + for i=1,#lookuplist do + local lookup=lookuplist[i] + local data=singles[lookup] + if data then + if trace_protrusion then + logs.report("fonts","set left protrusion using lfbd lookup '%s'",lookup) + end + for k,v in next,data do + local p=- (v[1]/1000)*factor*left + characters[k].left_protruding=p + if trace_protrusion then + logs.report("opbd","lfbd -> %s -> 0x%05X (%s) -> %0.03f (%s)",lookup,k,utfchar(k),p,concat(v," ")) + end + end + done=true + end + end + end + end + if opbd~="left" then + local validlookups,lookuplist=fonts.otf.collect_lookups(otfdata,"rtbd",script,language) + if validlookups then + for i=1,#lookuplist do + local lookup=lookuplist[i] + local data=singles[lookup] + if data then + if trace_protrusion then + logs.report("fonts","set right protrusion using rtbd lookup '%s'",lookup) + end + for k,v in next,data do + local p=(v[1]/1000)*factor*right + characters[k].right_protruding=p + if trace_protrusion then + logs.report("opbd","rtbd -> %s -> 0x%05X (%s) -> %0.03f (%s)",lookup,k,utfchar(k),p,concat(v," ")) + end + end + end + done=true + end + end + end + tfmdata.auto_protrude=done +end +function fonts.initializers.common.protrusion(tfmdata,value) + if value then + local opbd=tfmdata.shared.features.opbd + if opbd then + map_opbd_onto_protrusion(tfmdata,value,opbd) + elseif value then + local setup=setups[value] + if setup then + local factor,left,right=setup.factor or 1,setup.left or 1,setup.right or 1 + local emwidth=tfmdata.parameters.quad + tfmdata.auto_protrude=true + for i,chr in next,tfmdata.characters do + local v,pl,pr=setup[i],nil,nil + if v then + pl,pr=v[1],v[2] + end + if pl and pl~=0 then chr.left_protruding=left*pl*factor end + if pr and pr~=0 then chr.right_protruding=right*pr*factor end + end + end + end + end +end +fonts.expansions=fonts.expansions or {} +fonts.expansions.setups=fonts.expansions.setups or {} +local setups=fonts.expansions.setups +function fonts.initializers.common.expansion(tfmdata,value) + if value then + local setup=setups[value] + if setup then + local stretch,shrink,step,factor=setup.stretch or 0,setup.shrink or 0,setup.step or 0,setup.factor or 1 + tfmdata.stretch,tfmdata.shrink,tfmdata.step,tfmdata.auto_expand=stretch*10,shrink*10,step*10,true + for i,chr in next,tfmdata.characters do + local v=setup[i] + if v and v~=0 then + chr.expansion_factor=v*factor + else + chr.expansion_factor=factor + end + end + end + end +end +table.insert(fonts.manipulators,"protrusion") +table.insert(fonts.manipulators,"expansion") +fonts.initializers.base.otf.protrusion=fonts.initializers.common.protrusion +fonts.initializers.node.otf.protrusion=fonts.initializers.common.protrusion +fonts.initializers.base.otf.expansion=fonts.initializers.common.expansion +fonts.initializers.node.otf.expansion=fonts.initializers.common.expansion +function fonts.register_message() +end +local byte=string.byte +fonts.expansions.setups['default']={ + stretch=2,shrink=2,step=.5,factor=1, + [byte('A')]=0.5,[byte('B')]=0.7,[byte('C')]=0.7,[byte('D')]=0.5,[byte('E')]=0.7, + [byte('F')]=0.7,[byte('G')]=0.5,[byte('H')]=0.7,[byte('K')]=0.7,[byte('M')]=0.7, + [byte('N')]=0.7,[byte('O')]=0.5,[byte('P')]=0.7,[byte('Q')]=0.5,[byte('R')]=0.7, + [byte('S')]=0.7,[byte('U')]=0.7,[byte('W')]=0.7,[byte('Z')]=0.7, + [byte('a')]=0.7,[byte('b')]=0.7,[byte('c')]=0.7,[byte('d')]=0.7,[byte('e')]=0.7, + [byte('g')]=0.7,[byte('h')]=0.7,[byte('k')]=0.7,[byte('m')]=0.7,[byte('n')]=0.7, + [byte('o')]=0.7,[byte('p')]=0.7,[byte('q')]=0.7,[byte('s')]=0.7,[byte('u')]=0.7, + [byte('w')]=0.7,[byte('z')]=0.7, + [byte('2')]=0.7,[byte('3')]=0.7,[byte('6')]=0.7,[byte('8')]=0.7,[byte('9')]=0.7, +} +fonts.protrusions.setups['default']={ + factor=1,left=1,right=1, + [0x002C]={ 0,1 }, + [0x002E]={ 0,1 }, + [0x003A]={ 0,1 }, + [0x003B]={ 0,1 }, + [0x002D]={ 0,1 }, + [0x2013]={ 0,0.50 }, + [0x2014]={ 0,0.33 }, + [0x3001]={ 0,1 }, + [0x3002]={ 0,1 }, + [0x060C]={ 0,1 }, + [0x061B]={ 0,1 }, + [0x06D4]={ 0,1 }, +} +fonts.otf.meanings=fonts.otf.meanings or {} +fonts.otf.meanings.normalize=fonts.otf.meanings.normalize or function(t) + if t.rand then + t.rand="random" + end +end +function fonts.otf.name_to_slot(name) + local tfmdata=fonts.ids[font.current()] + if tfmdata and tfmdata.shared then + local otfdata=tfmdata.shared.otfdata + local unicode=otfdata.luatex.unicodes[name] + return unicode and (type(unicode)=="number" and unicode or unicode[1]) + end +end +function fonts.otf.char(n) + if type(n)=="string" then + n=fonts.otf.name_to_slot(n) + end + if type(n)=="number" then + tex.sprint("\\char"..n) + end +end +fonts.strippables=table.tohash { + 0x000AD,0x017B4,0x017B5,0x0200B,0x0200C,0x0200D,0x0200E,0x0200F,0x0202A,0x0202B, + 0x0202C,0x0202D,0x0202E,0x02060,0x02061,0x02062,0x02063,0x0206A,0x0206B,0x0206C, + 0x0206D,0x0206E,0x0206F,0x0FEFF,0x1D173,0x1D174,0x1D175,0x1D176,0x1D177,0x1D178, + 0x1D179,0x1D17A,0xE0001,0xE0020,0xE0021,0xE0022,0xE0023,0xE0024,0xE0025,0xE0026, + 0xE0027,0xE0028,0xE0029,0xE002A,0xE002B,0xE002C,0xE002D,0xE002E,0xE002F,0xE0030, + 0xE0031,0xE0032,0xE0033,0xE0034,0xE0035,0xE0036,0xE0037,0xE0038,0xE0039,0xE003A, + 0xE003B,0xE003C,0xE003D,0xE003E,0xE003F,0xE0040,0xE0041,0xE0042,0xE0043,0xE0044, + 0xE0045,0xE0046,0xE0047,0xE0048,0xE0049,0xE004A,0xE004B,0xE004C,0xE004D,0xE004E, + 0xE004F,0xE0050,0xE0051,0xE0052,0xE0053,0xE0054,0xE0055,0xE0056,0xE0057,0xE0058, + 0xE0059,0xE005A,0xE005B,0xE005C,0xE005D,0xE005E,0xE005F,0xE0060,0xE0061,0xE0062, + 0xE0063,0xE0064,0xE0065,0xE0066,0xE0067,0xE0068,0xE0069,0xE006A,0xE006B,0xE006C, + 0xE006D,0xE006E,0xE006F,0xE0070,0xE0071,0xE0072,0xE0073,0xE0074,0xE0075,0xE0076, + 0xE0077,0xE0078,0xE0079,0xE007A,0xE007B,0xE007C,0xE007D,0xE007E,0xE007F, +} + +end -- closure + +do -- begin closure to overcome local limits and interference + +if not modules then modules={} end modules ['font-clr']={ + version=1.001, + comment="companion to font-otf.lua (font color)", + author="Khaled Hosny and Elie Roux", + copyright="Luaotfload Development Team", + license="GPL" +} +fonts.triggers=fonts.triggers or {} +fonts.initializers=fonts.initializers or {} +fonts.initializers.common=fonts.initializers.common or {} +local initializers,format=fonts.initializers,string.format +table.insert(fonts.triggers,"color") +function initializers.common.color(tfmdata,value) + local sanitized + if value then + value=tostring(value) + if #value==6 or #value==8 then + sanitized=value + elseif #value==7 then + _,_,sanitized=value:find("(......)") + elseif #value>8 then + _,_,sanitized=value:find("(........)") + else + end + end + if sanitized then + tfmdata.color=sanitized + add_color_callback() + end +end +initializers.base.otf.color=initializers.common.color +initializers.node.otf.color=initializers.common.color +local function hex2dec(hex,one) + if one then + return format("%.1g",tonumber(hex,16)/255) + else + return format("%.3g",tonumber(hex,16)/255) + end +end +local res +local function pageresources(a) + local res2 + if not res then + res="/TransGs1<</ca 1/CA 1>>" + end + res2=format("/TransGs%s<</ca %s/CA %s>>",a,a,a) + res=format("%s%s",res,res:find(res2) and "" or res2) +end +local function hex_to_rgba(hex) + local r,g,b,a,push,pop,res3 + if hex then + if #hex==6 then + _,_,r,g,b=hex:find('(..)(..)(..)') + elseif #hex==8 then + _,_,r,g,b,a=hex:find('(..)(..)(..)(..)') + a=hex2dec(a,true) + pageresources(a) + end + else + return nil + end + r=hex2dec(r) + g=hex2dec(g) + b=hex2dec(b) + if a then + push=format('/TransGs%g gs %s %s %s rg',a,r,g,b) + pop='0 g /TransGs1 gs' + else + push=format('%s %s %s rg',r,g,b) + pop='0 g' + end + return push,pop +end +local glyph=node.id('glyph') +local hlist=node.id('hlist') +local vlist=node.id('vlist') +local whatsit=node.id('whatsit') +local pgi=node.id('page_insert') +local sbox=node.id('sub_box') +local function lookup_next_color(head) + for n in node.traverse(head) do + if n.id==glyph then + if fonts.ids[n.font] and fonts.ids[n.font].color then + return fonts.ids[n.font].color + else + return -1 + end + elseif n.id==vlist or n.id==hlist or n.id==sbox then + local r=lookup_next_color(n.list) + if r==-1 then + return -1 + elseif r then + return r + end + elseif n.id==whatsit or n.id==pgi then + return -1 + end + end + return nil +end +local function node_colorize(head,current_color,next_color) + for n in node.traverse(head) do + if n.id==hlist or n.id==vlist or n.id==sbox then + local next_color_in=lookup_next_color(n.next) or next_color + n.list,current_color=node_colorize(n.list,current_color,next_color_in) + elseif n.id==glyph then + local tfmdata=fonts.ids[n.font] + if tfmdata and tfmdata.color then + if tfmdata.color~=current_color then + local pushcolor=hex_to_rgba(tfmdata.color) + local push=node.new(whatsit,8) + push.mode=1 + push.data=pushcolor + head=node.insert_before(head,n,push) + current_color=tfmdata.color + end + local next_color_in=lookup_next_color (n.next) or next_color + if next_color_in~=tfmdata.color then + local _,popcolor=hex_to_rgba(tfmdata.color) + local pop=node.new(whatsit,8) + pop.mode=1 + pop.data=popcolor + head=node.insert_after(head,n,pop) + current_color=nil + end + end + end + end + return head,current_color +end +local function font_colorize(head) + if res then + local r="/ExtGState<<"..res..">>" + tex.pdfpageresources=tex.pdfpageresources:gsub(r,"") + end + local h=node_colorize(head,nil,nil) + if res and res:find("%S") then + local r="/ExtGState<<"..res..">>" + tex.pdfpageresources=tex.pdfpageresources..r + end + return h +end +local color_callback_activated=0 +function add_color_callback() + if color_callback_activated==0 then + luatexbase.add_to_callback("pre_output_filter",font_colorize,"loaotfload.colorize") + color_callback_activated=1 + end +end + +end -- closure diff --git a/luaotfload-legacy-tool.lua b/luaotfload-legacy-tool.lua new file mode 100755 index 0000000..a7a7714 --- /dev/null +++ b/luaotfload-legacy-tool.lua @@ -0,0 +1,105 @@ +#!/usr/bin/env texlua +--[[ +This file is copyright 2010 Elie Roux and Khaled Hosny and is under CC0 +license (see http://creativecommons.org/publicdomain/zero/1.0/legalcode). + +This file is a wrapper for the luaotfload's font names module. It is part of the +luaotfload bundle, please see the luaotfload documentation for more info. +--]] + +kpse.set_program_name("luatex") + +require("lualibs") +require("otfl-font-nms") +require("alt_getopt") + +local name = 'mkluatexfontdb' +local version = '1.07' -- same version number as luaotfload + +local names = fonts.names + +local function help_msg() + texio.write(string.format([[ +Usage: %s [OPTION]... + +================================================================================ + please update your luatex binary + this version is unsupported and likely to break things +================================================================================ + +Rebuild the LuaTeX font database. + +Valid options: + -f --force force re-indexing all fonts + -q --quiet don't output anything + -v --verbose=LEVEL be more verbose (print the searched directories) + -vv print the loaded fonts + -vvv print all steps of directory searching + -V --version print version and exit + -h --help print this message + +The output database file is named otfl-names.lua and is placed under: + + %s + +contact: https://github.com/lualatex/luaotfload + +]], name, names.path.localdir)) +end + +local function version_msg() + texio.write(string.format( + "%s version %s, database version %s.\n", name, version, names.version)) +end + +--[[ +Command-line processing. +Here we fill cmdargs with the good values, and then analyze it. +--]] + +local long_opts = { + force = "f", + quiet = "q", + help = "h", + verbose = 1 , + version = "V", +} + +local short_opts = "fqpvVh" + +local force_reload = nil + +local function process_cmdline() + local opts, optind, optarg = alt_getopt.get_ordered_opts (arg, short_opts, long_opts) + local log_level = 1 + for i,v in ipairs(opts) do + if v == "q" then + log_level = 0 + elseif v == "v" then + if log_level > 0 then + log_level = log_level + 1 + else + log_level = 2 + end + elseif v == "V" then + version_msg() + os.exit(0) + elseif v == "h" then + help_msg() + os.exit(0) + elseif v == "f" then + force_reload = 1 + end + end + names.set_log_level(log_level) +end + +local function generate(force) + local fontnames, saved + fontnames = names.update(fontnames, force) + logs.report("%s fonts in the database", #fontnames.mappings) + saved = names.save(fontnames) +end + +process_cmdline() +generate(force_reload) diff --git a/luaotfload-legacy.lua b/luaotfload-legacy.lua new file mode 100644 index 0000000..261c0ff --- /dev/null +++ b/luaotfload-legacy.lua @@ -0,0 +1,154 @@ +-- +-- This is file `luaotfload.lua', +-- generated with the docstrip utility. +-- +-- The original source files were: +-- +-- luaotfload.dtx (with options: `lua') +-- This is a generated file. +-- +-- Copyright (C) 2009-2013 by by Elie Roux <elie.roux@telecom-bretagne.eu> +-- and Khaled Hosny <khaledhosny@eglug.org> +-- (Support: <lualatex-dev@tug.org>.) +-- +-- This work is under the CC0 license. +-- +-- This work consists of the main source file luaotfload.dtx +-- and the derived files +-- luaotfload.sty, luaotfload.lua +-- +module("luaotfload", package.seeall) + +luaotfload.module = { + name = "luaotfload", + version = 1.29, + date = "2013/04/25", + description = "OpenType layout system.", + author = "Elie Roux & Hans Hagen", + copyright = "Elie Roux", + license = "CC0" +} + +local error, warning, info, log = luatexbase.provides_module(luaotfload.module) +kpse.init_prog("", 600, "/") +local luatex_version = 60 + +if tex.luatexversion < luatex_version then + warning("LuaTeX v%.2f is old, v%.2f is recommended.", + tex.luatexversion/100, + luatex_version /100) +end + + +function luaotfload.loadmodule(tofind) + local found = kpse.find_file(tofind,"tex") + if found then + log("loading file %s.", found) + dofile(found) + else + error("file %s not found.", tofind) + end +end +local loadmodule = luaotfload.loadmodule + +loadmodule"luaotfload-legacy-merged.lua" + +if not fonts then + loadmodule("otfl-luat-dum.lua") -- not used in context at all + loadmodule("otfl-luat-ovr.lua") -- override some luat-dum functions + loadmodule("otfl-data-con.lua") -- maybe some day we don't need this one + loadmodule("otfl-font-ini.lua") + loadmodule("otfl-node-dum.lua") + loadmodule("otfl-node-inj.lua") + loadmodule("luaotfload-legacy-attributes.lua") -- patch attributes + loadmodule("otfl-font-tfm.lua") + loadmodule("otfl-font-cid.lua") + loadmodule("otfl-font-ott.lua") + loadmodule("otfl-font-map.lua") + loadmodule("otfl-font-otf.lua") + loadmodule("otfl-font-otd.lua") + loadmodule("otfl-font-oti.lua") + loadmodule("otfl-font-otb.lua") + loadmodule("otfl-font-otn.lua") + loadmodule("otfl-font-ota.lua") + loadmodule("otfl-font-otc.lua") + loadmodule("otfl-font-def.lua") + loadmodule("otfl-font-xtx.lua") + loadmodule("otfl-font-dum.lua") + loadmodule("otfl-font-clr.lua") +end +loadmodule"luaotfload-legacy-database.lua" --- unmerged coz needed in db script + +if fonts and fonts.tfm and fonts.tfm.readers then + fonts.tfm.readers.ofm = fonts.tfm.readers.tfm +end +luatexbase.create_callback("luaotfload.patch_font", "simple", function() end) +local function def_font(...) + local fontdata = fonts.define.read(...) + if type(fontdata) == "table" and fontdata.shared then + local otfdata = fontdata.shared.otfdata + if otfdata.metadata.math then + local mc = { } + for k,v in next, otfdata.metadata.math do + if k:find("Percent") then + -- keep percent values as is + mc[k] = v + else + mc[k] = v / fontdata.units * fontdata.size + end + end + -- for \overwithdelims + mc.FractionDelimiterSize = 1.01 * fontdata.size + mc.FractionDelimiterDisplayStyleSize = 2.39 * fontdata.size + + fontdata.MathConstants = mc + end + luatexbase.call_callback("luaotfload.patch_font", fontdata) + end + return fontdata +end +--fonts.define.resolvers.file = fonts.define.resolvers.name +fonts.mode = "node" +local register_base_sub = fonts.otf.features.register_base_substitution +local gsubs = { + "ss01", "ss02", "ss03", "ss04", "ss05", + "ss06", "ss07", "ss08", "ss09", "ss10", + "ss11", "ss12", "ss13", "ss14", "ss15", + "ss16", "ss17", "ss18", "ss19", "ss20", +} + +for _,v in next, gsubs do + register_base_sub(v) +end +luatexbase.add_to_callback("pre_linebreak_filter", + nodes.simple_font_handler, + "luaotfload.pre_linebreak_filter") +luatexbase.add_to_callback("hpack_filter", + nodes.simple_font_handler, + "luaotfload.hpack_filter") +luatexbase.reset_callback("define_font") +luatexbase.add_to_callback("define_font", + def_font, + "luaotfload.define_font", 1) +luatexbase.add_to_callback("find_vf_file", + fonts.vf.find, + "luaotfload.find_vf_file") +local function set_sscale_diments(fontdata) + local mc = fontdata.MathConstants + if mc then + if mc["ScriptPercentScaleDown"] then + fontdata.parameters[10] = mc.ScriptPercentScaleDown + else -- resort to plain TeX default + fontdata.parameters[10] = 70 + end + if mc["ScriptScriptPercentScaleDown"] then + fontdata.parameters[11] = mc.ScriptScriptPercentScaleDown + else -- resort to plain TeX default + fontdata.parameters[11] = 50 + end + end +end + +luatexbase.add_to_callback("luaotfload.patch_font", set_sscale_diments, "unicodemath.set_sscale_diments") +-- +-- End of File `luaotfload.lua'. |