summaryrefslogtreecommitdiff
path: root/src/fontloader/misc/fontloader-font-otf.lua
diff options
context:
space:
mode:
Diffstat (limited to 'src/fontloader/misc/fontloader-font-otf.lua')
-rw-r--r--src/fontloader/misc/fontloader-font-otf.lua3051
1 files changed, 0 insertions, 3051 deletions
diff --git a/src/fontloader/misc/fontloader-font-otf.lua b/src/fontloader/misc/fontloader-font-otf.lua
deleted file mode 100644
index e90ec73..0000000
--- a/src/fontloader/misc/fontloader-font-otf.lua
+++ /dev/null
@@ -1,3051 +0,0 @@
-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"
-}
-
--- langs -> languages enz
--- anchor_classes vs kernclasses
--- modification/creationtime in subfont is runtime dus zinloos
--- to_table -> totable
--- ascent descent
-
--- to be checked: combinations like:
---
--- current="ABCD" with [A]=nothing, [BC]=ligature, [D]=single (applied to result of BC so funny index)
---
--- unlikely but possible
-
--- more checking against low level calls of functions
-
-local utfbyte = utf.byte
-local gmatch, gsub, find, match, lower, strip = 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 reversed, concat, insert, remove, sortedkeys = table.reversed, table.concat, table.insert, table.remove, table.sortedkeys
-local ioflush = io.flush
-local fastcopy, tohash, derivetable = table.fastcopy, table.tohash, table.derive
-local formatters = string.formatters
-local P, R, S, C, Ct, lpegmatch = lpeg.P, lpeg.R, lpeg.S, lpeg.C, lpeg.Ct, lpeg.match
-
-local setmetatableindex = table.setmetatableindex
-local allocate = utilities.storage.allocate
-local registertracker = trackers.register
-local registerdirective = directives.register
-local starttiming = statistics.starttiming
-local stoptiming = statistics.stoptiming
-local elapsedtime = statistics.elapsedtime
-local findbinfile = resolvers.findbinfile
-
-local trace_private = false registertracker("otf.private", function(v) trace_private = v end)
-local trace_subfonts = false registertracker("otf.subfonts", function(v) trace_subfonts = v end)
-local trace_loading = false registertracker("otf.loading", function(v) trace_loading = v end)
-local trace_features = false registertracker("otf.features", function(v) trace_features = v end)
-local trace_dynamics = false registertracker("otf.dynamics", function(v) trace_dynamics = v end)
-local trace_sequences = false registertracker("otf.sequences", function(v) trace_sequences = v end)
-local trace_markwidth = false registertracker("otf.markwidth", function(v) trace_markwidth = v end)
-local trace_defining = false registertracker("fonts.defining", function(v) trace_defining = v end)
-
-local compact_lookups = true registertracker("otf.compactlookups", function(v) compact_lookups = v end)
-local purge_names = true registertracker("otf.purgenames", function(v) purge_names = v end)
-
-local report_otf = logs.reporter("fonts","otf loading")
-
-local fonts = fonts
-local otf = fonts.handlers.otf
-
-otf.glists = { "gsub", "gpos" }
-
-otf.version = 2.820 -- beware: also sync font-mis.lua and in mtx-fonts
-otf.cache = containers.define("fonts", "otf", otf.version, true)
-
-local hashes = fonts.hashes
-local definers = fonts.definers
-local readers = fonts.readers
-local constructors = fonts.constructors
-
-local fontdata = hashes and hashes.identifiers
-local chardata = characters and characters.data -- not used
-
-local otffeatures = constructors.newfeatures("otf")
-local registerotffeature = otffeatures.register
-
-local enhancers = allocate()
-otf.enhancers = enhancers
-local patches = { }
-enhancers.patches = patches
-
-local forceload = false
-local cleanup = 0 -- mk: 0=885M 1=765M 2=735M (regular run 730M)
-local packdata = true
-local syncspace = true
-local forcenotdef = false
-local includesubfonts = false
-local overloadkerns = false -- experiment
-
-local applyruntimefixes = fonts.treatments and fonts.treatments.applyfixes
-
-local wildcard = "*"
-local default = "dflt"
-
-local fontloader = fontloader
-local open_font = fontloader.open
-local close_font = fontloader.close
-local font_fields = fontloader.fields
-local apply_featurefile = fontloader.apply_featurefile
-
-local mainfields = nil
-local glyphfields = nil -- not used yet
-
-local formats = fonts.formats
-
-formats.otf = "opentype"
-formats.ttf = "truetype"
-formats.ttc = "truetype"
-formats.dfont = "truetype"
-
-registerdirective("fonts.otf.loader.cleanup", function(v) cleanup = tonumber(v) or (v and 1) or 0 end)
-registerdirective("fonts.otf.loader.force", function(v) forceload = v end)
-registerdirective("fonts.otf.loader.pack", function(v) packdata = v end)
-registerdirective("fonts.otf.loader.syncspace", function(v) syncspace = v end)
-registerdirective("fonts.otf.loader.forcenotdef", function(v) forcenotdef = v end)
-registerdirective("fonts.otf.loader.overloadkerns", function(v) overloadkerns = v end)
------------------("fonts.otf.loader.alldimensions", function(v) alldimensions = v end)
-
-function otf.fileformat(filename)
- local leader = lower(io.loadchunk(filename,4))
- local suffix = lower(file.suffix(filename))
- if leader == "otto" then
- return formats.otf, suffix == "otf"
- elseif leader == "ttcf" then
- return formats.ttc, suffix == "ttc"
- -- elseif leader == "true" then
- -- return formats.ttf, suffix == "ttf"
- elseif suffix == "ttc" then
- return formats.ttc, true
- elseif suffix == "dfont" then
- return formats.dfont, true
- else
- return formats.ttf, suffix == "ttf"
- end
-end
-
--- local function otf_format(filename)
--- -- return formats[lower(file.suffix(filename))]
--- end
-
-local function otf_format(filename)
- local format, okay = otf.fileformat(filename)
- if not okay then
- report_otf("font %a is actually an %a file",filename,format)
- end
- return format
-end
-
-local function load_featurefile(raw,featurefile)
- if featurefile and featurefile ~= "" then
- if trace_loading then
- report_otf("using featurefile %a", featurefile)
- end
- apply_featurefile(raw, featurefile)
- end
-end
-
-local function showfeatureorder(rawdata,filename)
- local sequences = rawdata.resources.sequences
- if sequences and #sequences > 0 then
- if trace_loading then
- report_otf("font %a has %s sequences",filename,#sequences)
- report_otf(" ")
- 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
- report_otf("%3i %-15s %-20s [% t]",nos,name,typ,subtables)
- end
- if features then
- for feature, scripts in next, features do
- local tt = { }
- if type(scripts) == "table" then
- for script, languages in next, scripts do
- local ttt = { }
- for language, _ in next, languages do
- ttt[#ttt+1] = language
- end
- tt[#tt+1] = formatters["[%s: % t]"](script,ttt)
- end
- if trace_loading then
- report_otf(" %s: % t",feature,tt)
- end
- else
- if trace_loading then
- report_otf(" %s: %S",feature,scripts)
- end
- end
- end
- end
- end
- if trace_loading then
- report_otf("\n")
- end
- elseif trace_loading then
- report_otf("font %a has no sequences",filename)
- end
-end
-
---[[ldx--
-<p>We start with a lot of tables and related functions.</p>
---ldx]]--
-
-local valid_fields = table.tohash {
- -- "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",
- -- "size",
- "strokedfont",
- "strokewidth",
- -- "subfonts",
- "table_version",
- -- "tables",
- -- "ttf_tab_saved",
- "ttf_tables",
- "uni_interp",
- "uniqueid",
- "units_per_em",
- "upos",
- "use_typo_metrics",
- "uwidth",
- "validation_state",
- "version",
- "vert_base",
- "weight",
- "weight_width_slope_only",
- -- "xuid",
-}
-
-local ordered_enhancers = {
- "prepare tables",
-
- "prepare glyphs",
- "prepare lookups",
-
- "analyze glyphs",
- "analyze math",
-
- -- "prepare tounicode",
-
- "reorganize lookups",
- "reorganize mark classes",
- "reorganize anchor classes",
-
- "reorganize glyph kerns",
- "reorganize glyph lookups",
- "reorganize glyph anchors",
-
- "merge kern classes",
-
- "reorganize features",
- "reorganize subtables",
-
- "check glyphs",
- "check metadata",
-
- "prepare tounicode",
-
- "check encoding", -- moved
- "add duplicates",
-
- "expand lookups", -- a temp hack awaiting the lua loader
-
- "check extra features", -- after metadata and duplicates
-
- "cleanup tables",
-
- "compact lookups",
- "purge names",
-}
-
---[[ldx--
-<p>Here we go.</p>
---ldx]]--
-
-local actions = allocate()
-local before = allocate()
-local after = allocate()
-
-patches.before = before
-patches.after = after
-
-local function enhance(name,data,filename,raw)
- local enhancer = actions[name]
- if enhancer then
- if trace_loading then
- report_otf("apply enhancement %a to file %a",name,filename)
- ioflush()
- end
- enhancer(data,filename,raw)
- else
- -- no message as we can have private ones
- end
-end
-
-function enhancers.apply(data,filename,raw)
- local basename = file.basename(lower(filename))
- if trace_loading then
- report_otf("%s enhancing file %a","start",filename)
- end
- ioflush() -- we want instant messages
- for e=1,#ordered_enhancers do
- local enhancer = ordered_enhancers[e]
- local b = before[enhancer]
- if b then
- for pattern, action in next, b do
- if find(basename,pattern) then
- action(data,filename,raw)
- end
- end
- end
- enhance(enhancer,data,filename,raw)
- local a = after[enhancer]
- if a then
- for pattern, action in next, a do
- if find(basename,pattern) then
- action(data,filename,raw)
- end
- end
- end
- ioflush() -- we want instant messages
- end
- if trace_loading then
- report_otf("%s enhancing file %a","stop",filename)
- end
- ioflush() -- we want instant messages
-end
-
--- patches.register("before","migrate metadata","cambria",function() end)
-
-function patches.register(what,where,pattern,action)
- local pw = patches[what]
- if pw then
- local ww = pw[where]
- if ww then
- ww[pattern] = action
- else
- pw[where] = { [pattern] = action}
- end
- end
-end
-
-function patches.report(fmt,...)
- if trace_loading then
- report_otf("patching: %s",formatters[fmt](...))
- end
-end
-
-function enhancers.register(what,action) -- only already registered can be overloaded
- actions[what] = action
-end
-
-function otf.load(filename,sub,featurefile) -- second argument (format) is gone !
- local base = file.basename(file.removesuffix(filename))
- local name = file.removesuffix(base)
- local attr = lfs.attributes(filename)
- local size = attr and attr.size or 0
- local time = attr and attr.modification or 0
- if featurefile then
- name = name .. "@" .. file.removesuffix(file.basename(featurefile))
- end
- -- or: sub = tonumber(sub)
- if sub == "" then
- sub = false
- end
- local hash = name
- if sub then
- hash = hash .. "-" .. sub
- end
- hash = containers.cleanname(hash)
- local featurefiles
- if featurefile then
- featurefiles = { }
- for s in gmatch(featurefile,"[^,]+") do
- local name = resolvers.findfile(file.addsuffix(s,'fea'),'fea') or ""
- if name == "" then
- report_otf("loading error, no featurefile %a",s)
- else
- local attr = lfs.attributes(name)
- featurefiles[#featurefiles+1] = {
- name = name,
- size = attr and attr.size or 0,
- time = attr and attr.modification or 0,
- }
- end
- end
- if #featurefiles == 0 then
- featurefiles = nil
- end
- end
- local data = containers.read(otf.cache,hash)
- local reload = not data or data.size ~= size or data.time ~= time
- if forceload then
- report_otf("forced reload of %a due to hard coded flag",filename)
- reload = true
- end
- if not reload then
- local featuredata = data.featuredata
- if featurefiles then
- if not featuredata or #featuredata ~= #featurefiles then
- reload = true
- else
- for i=1,#featurefiles do
- local fi, fd = featurefiles[i], featuredata[i]
- if fi.name ~= fd.name or fi.size ~= fd.size or fi.time ~= fd.time then
- reload = true
- break
- end
- end
- end
- elseif featuredata then
- reload = true
- end
- if reload then
- report_otf("loading: forced reload due to changed featurefile specification %a",featurefile)
- end
- end
- if reload then
- starttiming("fontloader")
- report_otf("loading %a, hash %a",filename,hash)
- local fontdata, messages
- if sub then
- fontdata, messages = open_font(filename,sub)
- else
- fontdata, messages = open_font(filename)
- end
- if fontdata then
- mainfields = mainfields or (font_fields and font_fields(fontdata))
- end
- if trace_loading and messages and #messages > 0 then
- if type(messages) == "string" then
- report_otf("warning: %s",messages)
- else
- for m=1,#messages do
- report_otf("warning: %S",messages[m])
- end
- end
- else
- report_otf("loading done")
- end
- if fontdata then
- if featurefiles then
- for i=1,#featurefiles do
- load_featurefile(fontdata,featurefiles[i].name)
- end
- end
- local unicodes = {
- -- names to unicodes
- }
- local splitter = lpeg.splitter(" ",unicodes)
- data = {
- size = size,
- time = time,
- subfont = sub,
- format = otf_format(filename),
- featuredata = featurefiles,
- resources = {
- filename = resolvers.unresolve(filename), -- no shortcut
- version = otf.version,
- creator = "context mkiv",
- unicodes = unicodes,
- indices = {
- -- index to unicodes
- },
- duplicates = {
- -- alternative unicodes
- },
- variants = {
- -- alternative unicodes (variants)
- },
- lookuptypes = {
- },
- },
- warnings = {
- },
- metadata = {
- -- raw metadata, not to be used
- },
- properties = {
- -- normalized metadata
- },
- descriptions = {
- },
- goodies = {
- },
- helpers = { -- might go away
- tounicodelist = splitter,
- tounicodetable = Ct(splitter),
- },
- }
- report_otf("file size: %s", size)
- enhancers.apply(data,filename,fontdata)
- local packtime = { }
- if packdata then
- if cleanup > 0 then
- collectgarbage("collect")
- end
- starttiming(packtime)
- enhance("pack",data,filename,nil)
- stoptiming(packtime)
- end
- report_otf("saving %a in cache",filename)
- data = containers.write(otf.cache, hash, data)
- if cleanup > 1 then
- collectgarbage("collect")
- end
- stoptiming("fontloader")
- if elapsedtime then -- not in generic
- report_otf("loading, optimizing, packing and caching time %s, pack time %s",
- elapsedtime("fontloader"),packdata and elapsedtime(packtime) or 0)
- end
- close_font(fontdata) -- free memory
- if cleanup > 3 then
- collectgarbage("collect")
- end
- data = containers.read(otf.cache, hash) -- this frees the old table and load the sparse one
- if cleanup > 2 then
- collectgarbage("collect")
- end
- else
- stoptiming("fontloader")
- data = nil
- report_otf("loading failed due to read error")
- end
- end
- if data then
- if trace_defining then
- report_otf("loading from cache using hash %a",hash)
- end
- enhance("unpack",data,filename,nil,false)
- --
- local resources = data.resources
- local lookuptags = resources.lookuptags
- local unicodes = resources.unicodes
- if not lookuptags then
- lookuptags = { }
- resources.lookuptags = lookuptags
- end
- setmetatableindex(lookuptags,function(t,k)
- local v = type(k) == "number" and ("lookup " .. k) or k
- t[k] = v
- return v
- end)
- if not unicodes then
- unicodes = { }
- resources.unicodes = unicodes
- setmetatableindex(unicodes,function(t,k)
- -- use rawget when no table has to be built
- setmetatableindex(unicodes,nil)
- for u, d in next, data.descriptions do
- local n = d.name
- if n then
- t[n] = u
- -- report_otf("accessing known name %a",k)
- else
- -- report_otf("accessing unknown name %a",k)
- end
- end
- return rawget(t,k)
- end)
- end
- constructors.addcoreunicodes(unicodes) -- do we really need this?
- --
- if applyruntimefixes then
- applyruntimefixes(filename,data)
- end
- enhance("add dimensions",data,filename,nil,false)
- if trace_sequences then
- showfeatureorder(data,filename)
- end
- end
- return data
-end
-
-local mt = {
- __index = function(t,k) -- maybe set it
- if k == "height" then
- local ht = t.boundingbox[4]
- return ht < 0 and 0 or ht
- elseif k == "depth" then
- local dp = -t.boundingbox[2]
- return dp < 0 and 0 or dp
- elseif k == "width" then
- return 0
- elseif k == "name" then -- or maybe uni*
- return forcenotdef and ".notdef"
- end
- end
-}
-
-actions["prepare tables"] = function(data,filename,raw)
- data.properties.hasitalics = false
-end
-
-actions["add dimensions"] = function(data,filename)
- -- todo: forget about the width if it's the defaultwidth (saves mem)
- -- we could also build the marks hash here (instead of storing it)
- if data then
- local descriptions = data.descriptions
- local resources = data.resources
- local defaultwidth = resources.defaultwidth or 0
- local defaultheight = resources.defaultheight or 0
- local defaultdepth = resources.defaultdepth or 0
- local basename = trace_markwidth and file.basename(filename)
- for _, d in next, descriptions do
- local bb, wd = d.boundingbox, d.width
- if not wd then
- -- or bb?
- d.width = defaultwidth
- elseif trace_markwidth and wd ~= 0 and d.class == "mark" then
- report_otf("mark %a with width %b found in %a",d.name or "<noname>",wd,basename)
- -- d.width = -wd
- end
- -- if forcenotdef and not d.name then
- -- d.name = ".notdef"
- -- end
- if bb then
- local ht = bb[4]
- local dp = -bb[2]
- -- if alldimensions then
- -- if ht ~= 0 then
- -- d.height = ht
- -- end
- -- if dp ~= 0 then
- -- d.depth = dp
- -- end
- -- else
- if ht == 0 or ht < 0 then
- -- not set
- else
- d.height = ht
- end
- if dp == 0 or dp < 0 then
- -- not set
- else
- d.depth = dp
- end
- -- end
- end
- end
- end
-end
-
-local function somecopy(old) -- fast one
- if old then
- local new = { }
- if type(old) == "table" then
- for k, v in next, old do
- if k == "glyphs" then
- -- skip
- elseif type(v) == "table" then
- new[k] = somecopy(v)
- else
- new[k] = v
- end
- end
- else
- for i=1,#mainfields do
- local k = mainfields[i]
- local v = old[k]
- if k == "glyphs" then
- -- skip
- elseif type(v) == "table" then
- new[k] = somecopy(v)
- else
- new[k] = v
- end
- end
- end
- return new
- else
- return { }
- end
-end
-
--- not setting hasitalics and class (when nil) during table construction can save some mem
-
-actions["prepare glyphs"] = function(data,filename,raw)
- local tableversion = tonumber(raw.table_version) or 0
- local rawglyphs = raw.glyphs
- local rawsubfonts = raw.subfonts
- local rawcidinfo = raw.cidinfo
- local criterium = constructors.privateoffset
- local private = criterium
- local resources = data.resources
- local metadata = data.metadata
- local properties = data.properties
- local descriptions = data.descriptions
- local unicodes = resources.unicodes -- name to unicode
- local indices = resources.indices -- index to unicode
- local duplicates = resources.duplicates
- local variants = resources.variants
-
- if rawsubfonts then
-
- metadata.subfonts = includesubfonts and { }
- properties.cidinfo = rawcidinfo
-
- if rawcidinfo.registry then
- local cidmap = fonts.cid.getmap(rawcidinfo)
- if cidmap then
- rawcidinfo.usedname = cidmap.usedname
- local nofnames = 0
- local nofunicodes = 0
- local cidunicodes = cidmap.unicodes
- local cidnames = cidmap.names
- local cidtotal = 0
- local unique = trace_subfonts and { }
- for cidindex=1,#rawsubfonts do
- local subfont = rawsubfonts[cidindex]
- local cidglyphs = subfont.glyphs
- if includesubfonts then
- metadata.subfonts[cidindex] = somecopy(subfont)
- end
- local cidcnt, cidmin, cidmax
- if tableversion > 0.3 then
- -- we have delayed loading so we cannot use next
- cidcnt = subfont.glyphcnt
- cidmin = subfont.glyphmin
- cidmax = subfont.glyphmax
- else
- cidcnt = subfont.glyphcnt
- cidmin = 0
- cidmax = cidcnt - 1
- end
- if trace_subfonts then
- local cidtot = cidmax - cidmin + 1
- cidtotal = cidtotal + cidtot
- report_otf("subfont: %i, min: %i, max: %i, cnt: %i, n: %i",cidindex,cidmin,cidmax,cidtot,cidcnt)
- end
- if cidcnt > 0 then
- for cidslot=cidmin,cidmax do
- local glyph = cidglyphs[cidslot]
- if glyph then
- local index = tableversion > 0.3 and glyph.orig_pos or cidslot
- if trace_subfonts then
- unique[index] = true
- end
- local unicode = glyph.unicode
- if unicode >= 0x00E000 and unicode <= 0x00F8FF then
- unicode = -1
- elseif unicode >= 0x0F0000 and unicode <= 0x0FFFFD then
- unicode = -1
- elseif unicode >= 0x100000 and unicode <= 0x10FFFD then
- unicode = -1
- end
- local name = glyph.name or cidnames[index]
- if not unicode or unicode == -1 then -- or unicode >= criterium then
- unicode = cidunicodes[index]
- end
- if unicode and descriptions[unicode] then
- if trace_private then
- report_otf("preventing glyph %a at index %H to overload unicode %U",name or "noname",index,unicode)
- end
- unicode = -1
- end
- if not unicode or unicode == -1 then -- or unicode >= criterium then
- if not name then
- name = formatters["u%06X.ctx"](private)
- end
- unicode = private
- unicodes[name] = private
- if trace_private then
- report_otf("glyph %a at index %H is moved to private unicode slot %U",name,index,private)
- end
- private = private + 1
- nofnames = nofnames + 1
- else
- -- if unicode > criterium then
- -- local taken = descriptions[unicode]
- -- if taken then
- -- private = private + 1
- -- descriptions[private] = taken
- -- unicodes[taken.name] = private
- -- indices[taken.index] = private
- -- if trace_private then
- -- report_otf("slot %U is moved to %U due to private in font",unicode)
- -- end
- -- end
- -- end
- if not name then
- name = formatters["u%06X.ctx"](unicode)
- end
- unicodes[name] = unicode
- nofunicodes = nofunicodes + 1
- end
- indices[index] = unicode -- each index is unique (at least now)
- local description = {
- -- width = glyph.width,
- boundingbox = glyph.boundingbox,
- -- name = glyph.name or name or "unknown", -- uniXXXX
- name = name or "unknown", -- uniXXXX
- cidindex = cidindex,
- index = cidslot,
- glyph = glyph,
- }
- descriptions[unicode] = description
- local altuni = glyph.altuni
- if altuni then
- -- local d
- for i=1,#altuni do
- local a = altuni[i]
- local u = a.unicode
- if u ~= unicode then
- local v = a.variant
- if v then
- -- tricky: no addition to d? needs checking but in practice such dups are either very simple
- -- shapes or e.g cjk with not that many features
- local vv = variants[v]
- if vv then
- vv[u] = unicode
- else -- xits-math has some:
- vv = { [u] = unicode }
- variants[v] = vv
- end
- -- elseif d then
- -- d[#d+1] = u
- -- else
- -- d = { u }
- end
- end
- end
- -- if d then
- -- duplicates[unicode] = d -- is this needed ?
- -- end
- end
- end
- end
- else
- report_otf("potential problem: no glyphs found in subfont %i",cidindex)
- end
- end
- if trace_subfonts then
- report_otf("nofglyphs: %i, unique: %i",cidtotal,table.count(unique))
- end
- if trace_loading then
- report_otf("cid font remapped, %s unicode points, %s symbolic names, %s glyphs",nofunicodes, nofnames, nofunicodes+nofnames)
- end
- elseif trace_loading then
- report_otf("unable to remap cid font, missing cid file for %a",filename)
- end
- elseif trace_loading then
- report_otf("font %a has no glyphs",filename)
- end
-
- else
-
- local cnt = raw.glyphcnt or 0
- local min = tableversion > 0.3 and raw.glyphmin or 0
- local max = tableversion > 0.3 and raw.glyphmax or (raw.glyphcnt - 1)
- if cnt > 0 then
--- for index=0,cnt-1 do
- for index=min,max do
- local glyph = rawglyphs[index]
- if glyph then
- local unicode = glyph.unicode
- local name = glyph.name
- if not unicode or unicode == -1 then -- or unicode >= criterium then
- unicode = private
- unicodes[name] = private
- if trace_private then
- report_otf("glyph %a at index %H is moved to private unicode slot %U",name,index,private)
- end
- private = private + 1
- else
- -- We have a font that uses and exposes the private area. As this is rather unreliable it's
- -- advised no to trust slots here (better use glyphnames). Anyway, we need a double check:
- -- we need to move already moved entries and we also need to bump the next private to after
- -- the (currently) last slot. This could leave us with a hole but we have holes anyway.
- if unicode > criterium then
- -- \definedfont[file:HANBatang-LVT.ttf] \fontchar{uF0135} \char"F0135
- local taken = descriptions[unicode]
- if taken then
- if unicode >= private then
- private = unicode + 1 -- restart private (so we can have mixed now)
- else
- private = private + 1 -- move on
- end
- descriptions[private] = taken
- unicodes[taken.name] = private
- indices[taken.index] = private
- if trace_private then
- report_otf("slot %U is moved to %U due to private in font",unicode)
- end
- else
- if unicode >= private then
- private = unicode + 1 -- restart (so we can have mixed now)
- end
- end
- end
- unicodes[name] = unicode
- end
- indices[index] = unicode
- -- if not name then
- -- name = formatters["u%06X"](unicode) -- u%06X.ctx
- -- end
- descriptions[unicode] = {
- -- width = glyph.width,
- boundingbox = glyph.boundingbox,
- name = name,
- index = index,
- glyph = glyph,
- }
- local altuni = glyph.altuni
- if altuni then
- -- local d
- for i=1,#altuni do
- local a = altuni[i]
- local u = a.unicode
- if u ~= unicode then
- local v = a.variant
- if v then
- -- tricky: no addition to d? needs checking but in practice such dups are either very simple
- -- shapes or e.g cjk with not that many features
- local vv = variants[v]
- if vv then
- vv[u] = unicode
- else -- xits-math has some:
- vv = { [u] = unicode }
- variants[v] = vv
- end
- -- elseif d then
- -- d[#d+1] = u
- -- else
- -- d = { u }
- end
- end
- end
- -- if d then
- -- duplicates[unicode] = d -- is this needed ?
- -- end
- end
- else
- report_otf("potential problem: glyph %U is used but empty",index)
- end
- end
- else
- report_otf("potential problem: no glyphs found")
- end
-
- end
-
- resources.private = private
-
-end
-
--- the next one is still messy but will get better when we have
--- flattened map/enc tables in the font loader
-
--- the next one is not using a valid base for unicode privates
---
--- PsuedoEncodeUnencoded(EncMap *map,struct ttfinfo *info)
-
-actions["check encoding"] = function(data,filename,raw)
- local descriptions = data.descriptions
- local resources = data.resources
- local properties = data.properties
- local unicodes = resources.unicodes -- name to unicode
- local indices = resources.indices -- index to unicodes
- local duplicates = resources.duplicates
-
- -- begin of messy (not needed when cidmap)
-
- local mapdata = raw.map or { }
- local unicodetoindex = mapdata and mapdata.map or { }
- local indextounicode = mapdata and mapdata.backmap or { }
- -- local encname = lower(data.enc_name or raw.enc_name or mapdata.enc_name or "")
- local encname = lower(data.enc_name or mapdata.enc_name or "")
- local criterium = 0xFFFF -- for instance cambria has a lot of mess up there
- local privateoffset = constructors.privateoffset
-
- -- end of messy
-
- if find(encname,"unicode") then -- unicodebmp, unicodefull, ...
- if trace_loading then
- report_otf("checking embedded unicode map %a",encname)
- end
- local reported = { }
- -- we loop over the original unicode->index mapping but we
- -- need to keep in mind that that one can have weird entries
- -- so we need some extra checking
- for maybeunicode, index in next, unicodetoindex do
- if descriptions[maybeunicode] then
- -- we ignore invalid unicodes (unicode = -1) (ff can map wrong to non private)
- else
- local unicode = indices[index]
- if not unicode then
- -- weird (cjk or so?)
- elseif maybeunicode == unicode then
- -- no need to add
- elseif unicode > privateoffset then
- -- we have a non-unicode
- else
- local d = descriptions[unicode]
- if d then
- local c = d.copies
- if c then
- c[maybeunicode] = true
- else
- d.copies = { [maybeunicode] = true }
- end
- elseif index and not reported[index] then
- report_otf("missing index %i",index)
- reported[index] = true
- end
- end
- end
- end
- for unicode, data in next, descriptions do
- local d = data.copies
- if d then
- duplicates[unicode] = sortedkeys(d)
- data.copies = nil
- end
- end
- elseif properties.cidinfo then
- report_otf("warning: no unicode map, used cidmap %a",properties.cidinfo.usedname)
- else
- report_otf("warning: non unicode map %a, only using glyph unicode data",encname or "whatever")
- end
-
- if mapdata then
- mapdata.map = { } -- clear some memory (virtual and created each time anyway)
- mapdata.backmap = { } -- clear some memory (virtual and created each time anyway)
- end
-end
-
--- for the moment we assume that a font with lookups will not use
--- altuni so we stick to kerns only .. alternatively we can always
--- do an indirect lookup uni_to_uni . but then we need that in
--- all lookups
-
-actions["add duplicates"] = function(data,filename,raw)
- local descriptions = data.descriptions
- local resources = data.resources
- local properties = data.properties
- local unicodes = resources.unicodes -- name to unicode
- local indices = resources.indices -- index to unicodes
- local duplicates = resources.duplicates
- for unicode, d in next, duplicates do
- local nofduplicates = #d
- if nofduplicates > 4 then
- if trace_loading then
- report_otf("ignoring excessive duplicates of %U (n=%s)",unicode,nofduplicates)
- end
- else
- -- local validduplicates = { }
- for i=1,nofduplicates do
- local u = d[i]
- if not descriptions[u] then
- local description = descriptions[unicode]
- local n = 0
- for _, description in next, descriptions do
- local kerns = description.kerns
- if kerns then
- for _, k in next, kerns do
- local ku = k[unicode]
- if ku then
- k[u] = ku
- n = n + 1
- end
- end
- end
- -- todo: lookups etc
- end
- if u > 0 then -- and
- local duplicate = table.copy(description) -- else packing problem
- duplicate.comment = formatters["copy of %U"](unicode)
- descriptions[u] = duplicate
- -- validduplicates[#validduplicates+1] = u
- if trace_loading then
- report_otf("duplicating %U to %U with index %H (%s kerns)",unicode,u,description.index,n)
- end
- end
- end
- end
- -- duplicates[unicode] = #validduplicates > 0 and validduplicates or nil
- end
- end
-end
-
--- class : nil base mark ligature component (maybe we don't need it in description)
--- boundingbox: split into ht/dp takes more memory (larger tables and less sharing)
-
-actions["analyze glyphs"] = function(data,filename,raw) -- maybe integrate this in the previous
- local descriptions = data.descriptions
- local resources = data.resources
- local metadata = data.metadata
- local properties = data.properties
- local hasitalics = false
- local widths = { }
- local marks = { } -- always present (saves checking)
- for unicode, description in next, descriptions do
- local glyph = description.glyph
- local italic = glyph.italic_correction -- only in a math font (we also have vert/horiz)
- if not italic then
- -- skip
- elseif italic == 0 then
- -- skip
- else
- description.italic = italic
- hasitalics = true
- end
- local width = glyph.width
- widths[width] = (widths[width] or 0) + 1
- local class = glyph.class
- if class then
- if class == "mark" then
- marks[unicode] = true
- end
- description.class = class
- end
- end
- -- flag italic
- properties.hasitalics = hasitalics
- -- flag marks
- resources.marks = marks
- -- share most common width for cjk fonts
- 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 -- maybe 500
- if trace_loading then
- report_otf("most common width: %s (%s times), sharing (cjk font)",wd,most)
- end
- for unicode, description in next, descriptions do
- if description.width == wd then
- -- description.width = nil
- else
- description.width = description.glyph.width
- end
- end
- resources.defaultwidth = wd
- else
- for unicode, description in next, descriptions do
- description.width = description.glyph.width
- end
- end
-end
-
-actions["reorganize mark classes"] = function(data,filename,raw)
- local mark_classes = raw.mark_classes
- if mark_classes then
- local resources = data.resources
- local unicodes = resources.unicodes
- local markclasses = { }
- resources.markclasses = markclasses -- reversed
- for name, class in next, mark_classes do
- local t = { }
- for s in gmatch(class,"[^ ]+") do
- t[unicodes[s]] = true
- end
- markclasses[name] = t
- end
- end
-end
-
-actions["reorganize features"] = function(data,filename,raw) -- combine with other
- local features = { }
- data.resources.features = features
- for k=1,#otf.glists do
- local what = otf.glists[k]
- local dw = raw[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 i=1,#dscripts do
- local d = dscripts[i]
- local languages = d.langs
- local script = strip(lower(d.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
-
-actions["reorganize anchor classes"] = function(data,filename,raw)
- local resources = data.resources
- local anchor_to_lookup = { }
- local lookup_to_anchor = { }
- resources.anchor_to_lookup = anchor_to_lookup
- resources.lookup_to_anchor = lookup_to_anchor
- local classes = raw.anchor_classes -- anchor classes not in final table
- 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 l then
- l[anchor] = true
- else
- l = { [anchor] = true }
- lookup_to_anchor[lookup] = l
- end
- a[lookup] = true
- end
- end
- end
-end
-
--- local function checklookups(data,missing,nofmissing)
--- local resources = data.resources
--- local unicodes = resources.unicodes
--- local lookuptypes = resources.lookuptypes
--- if not unicodes or not lookuptypes then
--- return
--- elseif nofmissing <= 0 then
--- return
--- end
--- local descriptions = data.descriptions
--- local private = fonts.constructors and fonts.constructors.privateoffset or 0xF0000 -- 0x10FFFF
--- --
--- local ns, nl = 0, 0
-
--- local guess = { }
--- -- helper
--- local function check(gname,code,unicode)
--- local description = descriptions[code]
--- -- no need to add a self reference
--- local variant = description.name
--- if variant == gname then
--- return
--- end
--- -- the variant already has a unicode (normally that results in a default tounicode to self)
--- local unic = unicodes[variant]
--- if unic == -1 or unic >= private or (unic >= 0xE000 and unic <= 0xF8FF) or unic == 0xFFFE or unic == 0xFFFF then
--- -- no default mapping and therefore maybe no tounicode yet
--- else
--- return
--- end
--- -- the variant already has a tounicode
--- if descriptions[code].unicode then
--- return
--- end
--- -- add to the list
--- local g = guess[variant]
--- -- local r = overloads[unicode]
--- -- if r then
--- -- unicode = r.unicode
--- -- end
--- if g then
--- g[gname] = unicode
--- else
--- guess[variant] = { [gname] = unicode }
--- end
--- end
--- --
--- for unicode, description in next, descriptions do
--- local slookups = description.slookups
--- if slookups then
--- local gname = description.name
--- for tag, data in next, slookups do
--- local lookuptype = lookuptypes[tag]
--- if lookuptype == "alternate" then
--- for i=1,#data do
--- check(gname,data[i],unicode)
--- end
--- elseif lookuptype == "substitution" then
--- check(gname,data,unicode)
--- end
--- end
--- end
--- local mlookups = description.mlookups
--- if mlookups then
--- local gname = description.name
--- for tag, list in next, mlookups do
--- local lookuptype = lookuptypes[tag]
--- if lookuptype == "alternate" then
--- for i=1,#list do
--- local data = list[i]
--- for i=1,#data do
--- check(gname,data[i],unicode)
--- end
--- end
--- elseif lookuptype == "substitution" then
--- for i=1,#list do
--- check(gname,list[i],unicode)
--- end
--- end
--- end
--- end
--- end
--- -- resolve references
--- local done = true
--- while done do
--- done = false
--- for k, v in next, guess do
--- if type(v) ~= "number" then
--- for kk, vv in next, v do
--- if vv == -1 or vv >= private or (vv >= 0xE000 and vv <= 0xF8FF) or vv == 0xFFFE or vv == 0xFFFF then
--- local uu = guess[kk]
--- if type(uu) == "number" then
--- guess[k] = uu
--- done = true
--- end
--- else
--- guess[k] = vv
--- done = true
--- end
--- end
--- end
--- end
--- end
--- -- wrap up
--- local orphans = 0
--- local guessed = 0
--- for k, v in next, guess do
--- if type(v) == "number" then
--- descriptions[unicodes[k]].unicode = descriptions[v].unicode or v -- can also be a table
--- guessed = guessed + 1
--- else
--- local t = nil
--- local l = lower(k)
--- local u = unicodes[l]
--- if not u then
--- orphans = orphans + 1
--- elseif u == -1 or u >= private or (u >= 0xE000 and u <= 0xF8FF) or u == 0xFFFE or u == 0xFFFF then
--- local unicode = descriptions[u].unicode
--- if unicode then
--- descriptions[unicodes[k]].unicode = unicode
--- guessed = guessed + 1
--- else
--- orphans = orphans + 1
--- end
--- else
--- orphans = orphans + 1
--- end
--- end
--- end
--- if trace_loading and orphans > 0 or guessed > 0 then
--- report_otf("%s glyphs with no related unicode, %s guessed, %s orphans",guessed+orphans,guessed,orphans)
--- end
--- end
-
-actions["prepare tounicode"] = function(data,filename,raw)
- fonts.mappings.addtounicode(data,filename)
-end
-
-local g_directions = {
- gsub_contextchain = 1,
- gpos_contextchain = 1,
- -- gsub_context = 1,
- -- gpos_context = 1,
- gsub_reversecontextchain = -1,
- gpos_reversecontextchain = -1,
-}
--- The following is no longer needed as AAT is ignored per end October 2013.
---
--- -- Research by Khaled Hosny has demonstrated that the font loader merges
--- -- regular and AAT features and that these can interfere (especially because
--- -- we dropped checking for valid features elsewhere. So, we just check for
--- -- the special flag and drop the feature if such a tag is found.
---
--- local function supported(features)
--- for i=1,#features do
--- if features[i].ismac then
--- return false
--- end
--- end
--- return true
--- end
-
-actions["reorganize subtables"] = function(data,filename,raw)
- local resources = data.resources
- local sequences = { }
- local lookups = { }
- local chainedfeatures = { }
- resources.sequences = sequences
- resources.lookups = lookups -- we also have lookups in data itself
- for k=1,#otf.glists do
- local what = otf.glists[k]
- local dw = raw[what]
- if dw then
- for k=1,#dw do
- local gk = dw[k]
- local features = gk.features
- -- if not features or supported(features) then -- not always features !
- local typ = gk.type
- local chain = g_directions[typ] or 0
- local subtables = gk.subtables
- if subtables then
- local t = { }
- for s=1,#subtables do
- t[s] = subtables[s].name
- end
- subtables = t
- end
- local flags, markclass = gk.flags, nil
- if flags then
- local t = { -- forcing false packs nicer
- (flags.ignorecombiningmarks and "mark") or false,
- (flags.ignoreligatures and "ligature") or false,
- (flags.ignorebaseglyphs and "base") or false,
- flags.r2l or false,
- }
- markclass = flags.mark_class
- if markclass then
- markclass = resources.markclasses[markclass]
- end
- flags = t
- end
- --
- local name = gk.name
- --
- if not name then
- -- in fact an error
- report_otf("skipping weird lookup number %s",k)
- elseif features then
- -- scripts, tag, ismac
- local f = { }
- local o = { }
- for i=1,#features do
- local df = features[i]
- local tag = strip(lower(df.tag))
- local ft = f[tag]
- if not ft then
- ft = { }
- f[tag] = ft
- o[#o+1] = tag
- end
- local dscripts = df.scripts
- for i=1,#dscripts do
- local d = dscripts[i]
- local languages = d.langs
- local script = strip(lower(d.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
- sequences[#sequences+1] = {
- type = typ,
- chain = chain,
- flags = flags,
- name = name,
- subtables = subtables,
- markclass = markclass,
- features = f,
- order = o,
- }
- else
- lookups[name] = {
- type = typ,
- chain = chain,
- flags = flags,
- subtables = subtables,
- markclass = markclass,
- }
- end
- -- end
- end
- end
- end
-end
-
-actions["prepare lookups"] = function(data,filename,raw)
- local lookups = raw.lookups
- if lookups then
- data.lookups = lookups
- end
-end
-
--- The reverse handler does a bit redundant splitting but it's seldom
--- seen so we don't bother too much. We could store the replacement
--- in the current list (value instead of true) but it makes other code
--- uglier. Maybe some day.
-
-local function t_uncover(splitter,cache,covers)
- local result = { }
- for n=1,#covers do
- local cover = covers[n]
- local uncovered = cache[cover]
- if not uncovered then
- uncovered = lpegmatch(splitter,cover)
- cache[cover] = uncovered
- end
- result[n] = uncovered
- end
- return result
-end
-
-local function s_uncover(splitter,cache,cover)
- if cover == "" then
- return nil
- else
- local uncovered = cache[cover]
- if not uncovered then
- uncovered = lpegmatch(splitter,cover)
- -- for i=1,#uncovered do
- -- uncovered[i] = { [uncovered[i]] = true }
- -- end
- cache[cover] = uncovered
- end
- return { uncovered }
- end
-end
-
-local function t_hashed(t,cache)
- if t then
- local ht = { }
- for i=1,#t do
- local ti = t[i]
- local tih = cache[ti]
- if not tih then
- local tn = #ti
- if tn == 1 then
- tih = { [ti[1]] = true }
- else
- tih = { }
- for i=1,tn do
- tih[ti[i]] = true
- end
- end
- cache[ti] = tih
- end
- ht[i] = tih
- end
- return ht
- else
- return nil
- end
-end
-
--- local s_hashed = t_hashed
-
-local function s_hashed(t,cache)
- if t then
- local tf = t[1]
- local nf = #tf
- if nf == 1 then
- return { [tf[1]] = true }
- else
- local ht = { }
- for i=1,nf do
- ht[i] = { [tf[i]] = true }
- end
- return ht
- end
- else
- return nil
- end
-end
-
-local function r_uncover(splitter,cache,cover,replacements)
- if cover == "" then
- return nil
- else
- -- we always have current as { } even in the case of one
- local uncovered = cover[1]
- local replaced = cache[replacements]
- if not replaced then
- replaced = lpegmatch(splitter,replacements)
- cache[replacements] = replaced
- end
- local nu, nr = #uncovered, #replaced
- local r = { }
- if nu == nr then
- for i=1,nu do
- r[uncovered[i]] = replaced[i]
- end
- end
- return r
- end
-end
-
-actions["reorganize lookups"] = function(data,filename,raw) -- we could check for "" and n == 0
- -- we prefer the before lookups in a normal order
- if data.lookups then
- local helpers = data.helpers
- local duplicates = data.resources.duplicates
- local splitter = helpers.tounicodetable
- local t_u_cache = { }
- local s_u_cache = t_u_cache -- string keys
- local t_h_cache = { }
- local s_h_cache = t_h_cache -- table keys (so we could use one cache)
- local r_u_cache = { } -- maybe shared
- helpers.matchcache = t_h_cache -- so that we can add duplicates
- --
- for _, lookup in next, data.lookups do
- local rules = lookup.rules
- if rules then
- local format = lookup.format
- if format == "class" then
- local before_class = lookup.before_class
- if before_class then
- before_class = t_uncover(splitter,t_u_cache,reversed(before_class))
- end
- local current_class = lookup.current_class
- if current_class then
- current_class = t_uncover(splitter,t_u_cache,current_class)
- end
- local after_class = lookup.after_class
- if after_class then
- after_class = t_uncover(splitter,t_u_cache,after_class)
- end
- for i=1,#rules do
- local rule = rules[i]
- local class = rule.class
- local before = class.before
- if before then
- for i=1,#before do
- before[i] = before_class[before[i]] or { }
- end
- rule.before = t_hashed(before,t_h_cache)
- end
- local current = class.current
- local lookups = rule.lookups
- if current then
- for i=1,#current do
- current[i] = current_class[current[i]] or { }
- -- let's not be sparse
- if lookups and not lookups[i] then
- lookups[i] = "" -- (was: false) e.g. we can have two lookups and one replacement
- end
- -- end of fix
- end
- rule.current = t_hashed(current,t_h_cache)
- end
- local after = class.after
- if after then
- for i=1,#after do
- after[i] = after_class[after[i]] or { }
- end
- rule.after = t_hashed(after,t_h_cache)
- end
- rule.class = nil
- end
- lookup.before_class = nil
- lookup.current_class = nil
- lookup.after_class = nil
- lookup.format = "coverage"
- elseif format == "coverage" then
- for i=1,#rules do
- local rule = rules[i]
- local coverage = rule.coverage
- if coverage then
- local before = coverage.before
- if before then
- before = t_uncover(splitter,t_u_cache,reversed(before))
- rule.before = t_hashed(before,t_h_cache)
- end
- local current = coverage.current
- if current then
- current = t_uncover(splitter,t_u_cache,current)
- -- let's not be sparse
- local lookups = rule.lookups
- if lookups then
- for i=1,#current do
- if not lookups[i] then
- lookups[i] = "" -- fix sparse array
- end
- end
- end
- --
- rule.current = t_hashed(current,t_h_cache)
- end
- local after = coverage.after
- if after then
- after = t_uncover(splitter,t_u_cache,after)
- rule.after = t_hashed(after,t_h_cache)
- end
- rule.coverage = nil
- end
- end
- elseif format == "reversecoverage" then -- special case, single substitution only
- for i=1,#rules do
- local rule = rules[i]
- local reversecoverage = rule.reversecoverage
- if reversecoverage then
- local before = reversecoverage.before
- if before then
- before = t_uncover(splitter,t_u_cache,reversed(before))
- rule.before = t_hashed(before,t_h_cache)
- end
- local current = reversecoverage.current
- if current then
- current = t_uncover(splitter,t_u_cache,current)
- rule.current = t_hashed(current,t_h_cache)
- end
- local after = reversecoverage.after
- if after then
- after = t_uncover(splitter,t_u_cache,after)
- rule.after = t_hashed(after,t_h_cache)
- end
- local replacements = reversecoverage.replacements
- if replacements then
- rule.replacements = r_uncover(splitter,r_u_cache,current,replacements)
- end
- rule.reversecoverage = nil
- end
- end
- elseif format == "glyphs" then
- -- I could store these more efficient (as not we use a nested tables for before,
- -- after and current but this features happens so seldom that I don't bother
- -- about it right now.
- for i=1,#rules do
- local rule = rules[i]
- local glyphs = rule.glyphs
- if glyphs then
- local fore = glyphs.fore
- if fore and fore ~= "" then
- fore = s_uncover(splitter,s_u_cache,fore)
- rule.after = s_hashed(fore,s_h_cache)
- end
- local back = glyphs.back
- if back then
- back = s_uncover(splitter,s_u_cache,back)
- rule.before = s_hashed(back,s_h_cache)
- end
- local names = glyphs.names
- if names then
- names = s_uncover(splitter,s_u_cache,names)
- rule.current = s_hashed(names,s_h_cache)
- end
- rule.glyphs = nil
- local lookups = rule.lookups
- if lookups then
- for i=1,#names do
- if not lookups[i] then
- lookups[i] = "" -- fix sparse array
- end
- end
- end
- end
- end
- end
- end
- end
- end
-end
-
-actions["expand lookups"] = function(data,filename,raw) -- we could check for "" and n == 0
- if data.lookups then
- local cache = data.helpers.matchcache
- if cache then
- local duplicates = data.resources.duplicates
- for key, hash in next, cache do
- local done = nil
- for key in next, hash do
- local unicode = duplicates[key]
- if not unicode then
- -- no duplicate
- elseif type(unicode) == "table" then
- -- multiple duplicates
- for i=1,#unicode do
- local u = unicode[i]
- if hash[u] then
- -- already in set
- elseif done then
- done[u] = key
- else
- done = { [u] = key }
- end
- end
- else
- -- one duplicate
- if hash[unicode] then
- -- already in set
- elseif done then
- done[unicode] = key
- else
- done = { [unicode] = key }
- end
- end
- end
- if done then
- for u in next, done do
- hash[u] = true
- end
- end
- end
- end
- end
-end
-
-local function check_variants(unicode,the_variants,splitter,unicodes)
- local variants = the_variants.variants
- if variants then -- use splitter
- local glyphs = lpegmatch(splitter,variants)
- local done = { [unicode] = true }
- local n = 0
- for i=1,#glyphs do
- local g = glyphs[i]
- if done[g] then
- if i > 1 then
- report_otf("skipping cyclic reference %U in math variant %U",g,unicode)
- end
- else
- if n == 0 then
- n = 1
- variants = { g }
- else
- n = n + 1
- variants[n] = g
- end
- done[g] = true
- end
- end
- if n == 0 then
- variants = nil
- end
- end
- local parts = the_variants.parts
- if parts then
- local p = #parts
- if p > 0 then
- for i=1,p do
- local pi = parts[i]
- pi.glyph = unicodes[pi.component] or 0
- pi.component = nil
- end
- else
- parts = nil
- end
- end
- local italic = the_variants.italic
- if italic and italic == 0 then
- italic = nil
- end
- return variants, parts, italic
-end
-
-actions["analyze math"] = function(data,filename,raw)
- if raw.math then
- data.metadata.math = raw.math
- local unicodes = data.resources.unicodes
- local splitter = data.helpers.tounicodetable
- for unicode, description in next, data.descriptions do
- local glyph = description.glyph
- local mathkerns = glyph.mathkern -- singular
- local hvariants = glyph.horiz_variants
- local vvariants = glyph.vert_variants
- local accent = glyph.top_accent
- local italic = glyph.italic_correction
- if mathkerns or hvariants or vvariants or accent or italic then
- local math = { }
- if accent then
- math.accent = accent
- end
- if mathkerns then
- for k, v in next, mathkerns do
- if not next(v) then
- mathkerns[k] = nil
- else
- for k, v in next, v do
- if v == 0 then
- k[v] = nil -- height / kern can be zero
- end
- end
- end
- end
- math.kerns = mathkerns
- end
- if hvariants then
- math.hvariants, math.hparts, math.hitalic = check_variants(unicode,hvariants,splitter,unicodes)
- end
- if vvariants then
- math.vvariants, math.vparts, math.vitalic = check_variants(unicode,vvariants,splitter,unicodes)
- end
- if italic and italic ~= 0 then
- math.italic = italic
- end
- description.math = math
- end
- end
- end
-end
-
-actions["reorganize glyph kerns"] = function(data,filename,raw)
- local descriptions = data.descriptions
- local resources = data.resources
- local unicodes = resources.unicodes
- for unicode, description in next, descriptions do
- local kerns = description.glyph.kerns
- if kerns then
- local newkerns = { }
- for k, kern in next, kerns do
- local name = kern.char
- local offset = kern.off
- local lookup = kern.lookup
- if name and offset and lookup then
- local unicode = unicodes[name]
- if unicode then
- if type(lookup) == "table" then
- for l=1,#lookup do
- local lookup = lookup[l]
- local lookupkerns = newkerns[lookup]
- if lookupkerns then
- lookupkerns[unicode] = offset
- else
- newkerns[lookup] = { [unicode] = offset }
- end
- end
- else
- local lookupkerns = newkerns[lookup]
- if lookupkerns then
- lookupkerns[unicode] = offset
- else
- newkerns[lookup] = { [unicode] = offset }
- end
- end
- elseif trace_loading then
- report_otf("problems with unicode %a of kern %a of glyph %U",name,k,unicode)
- end
- end
- end
- description.kerns = newkerns
- end
- end
-end
-
-actions["merge kern classes"] = function(data,filename,raw)
- local gposlist = raw.gpos
- if gposlist then
- local descriptions = data.descriptions
- local resources = data.resources
- local unicodes = resources.unicodes
- local splitter = data.helpers.tounicodetable
- local ignored = 0
- local blocked = 0
- for gp=1,#gposlist do
- local gpos = gposlist[gp]
- local subtables = gpos.subtables
- if subtables then
- local first_done = { } -- could become an option so that we can deal with buggy fonts that don't get fixed
- local split = { } -- saves time .. although probably not that much any more in the fixed luatex kernclass table
- for s=1,#subtables do
- local subtable = subtables[s]
- local kernclass = subtable.kernclass -- name is inconsistent with anchor_classes
- local lookup = subtable.lookup or subtable.name
- if kernclass then -- the next one is quite slow
- -- as fas as i can see the kernclass is a table with one entry and offsets
- -- have no [1] so we could remov eon elevel (kernclass) and start offsets
- -- at 1 but we're too far down the road now to fix that
- if #kernclass > 0 then
- kernclass = kernclass[1]
- lookup = type(kernclass.lookup) == "string" and kernclass.lookup or lookup
- report_otf("fixing kernclass table of lookup %a",lookup)
- end
- local firsts = kernclass.firsts
- local seconds = kernclass.seconds
- local offsets = kernclass.offsets
- -- if offsets[1] == nil then
- -- offsets[1] = "" -- defaults ?
- -- end
- for n, s in next, firsts do
- split[s] = split[s] or lpegmatch(splitter,s)
- end
- local maxseconds = 0
- for n, s in next, seconds do
- if n > maxseconds then
- maxseconds = n
- end
- split[s] = split[s] or lpegmatch(splitter,s)
- end
- for fk=1,#firsts do -- maxfirsts ?
- local fv = firsts[fk]
- local splt = split[fv]
- if splt then
- local extrakerns = { }
- local baseoffset = (fk-1) * maxseconds
- -- for sk, sv in next, seconds do
- for sk=2,maxseconds do
- local sv = seconds[sk]
- if sv then
- local splt = split[sv]
- if splt then -- redundant test
- local offset = offsets[baseoffset + sk]
- if offset then
- for i=1,#splt do
- extrakerns[splt[i]] = offset
- end
- end
- end
- end
- end
- for i=1,#splt do
- local first_unicode = splt[i]
- if first_done[first_unicode] then
- report_otf("lookup %a: ignoring further kerns of %C",lookup,first_unicode)
- blocked = blocked + 1
- else
- first_done[first_unicode] = true
- local description = descriptions[first_unicode]
- if description then
- local kerns = description.kerns
- if not kerns then
- kerns = { } -- unicode indexed !
- description.kerns = kerns
- end
- local lookupkerns = kerns[lookup]
- if not lookupkerns then
- lookupkerns = { }
- kerns[lookup] = lookupkerns
- end
- if overloadkerns then
- for second_unicode, kern in next, extrakerns do
- lookupkerns[second_unicode] = kern
- end
- else
- for second_unicode, kern in next, extrakerns do
- local k = lookupkerns[second_unicode]
- if not k then
- lookupkerns[second_unicode] = kern
- elseif k ~= kern then
- if trace_loading then
- report_otf("lookup %a: ignoring overload of kern between %C and %C, rejecting %a, keeping %a",lookup,first_unicode,second_unicode,k,kern)
- end
- ignored = ignored + 1
- end
- end
- end
- elseif trace_loading then
- report_otf("no glyph data for %U", first_unicode)
- end
- end
- end
- end
- end
- subtable.kernclass = { }
- end
- end
- end
- end
- if ignored > 0 then
- report_otf("%s kern overloads ignored",ignored)
- end
- if blocked > 0 then
- report_otf("%s successive kerns blocked",blocked)
- end
- end
-end
-
-actions["check glyphs"] = function(data,filename,raw)
- for unicode, description in next, data.descriptions do
- description.glyph = nil
- end
-end
-
--- future versions will remove _
-
-local valid = (R("\x00\x7E") - S("(){}[]<>%/ \n\r\f\v"))^0 * P(-1)
-
-local function valid_ps_name(str)
- return str and str ~= "" and #str < 64 and lpegmatch(valid,str) and true or false
-end
-
-actions["check metadata"] = function(data,filename,raw)
- local metadata = data.metadata
- for _, k in next, mainfields do
- if valid_fields[k] then
- local v = raw[k]
- if not metadata[k] then
- metadata[k] = v
- end
- end
- end
- -- metadata.pfminfo = raw.pfminfo -- not already done?
- local ttftables = metadata.ttf_tables
- if ttftables then
- for i=1,#ttftables do
- ttftables[i].data = "deleted"
- end
- end
- --
- local names = raw.names
- --
- if metadata.validation_state and table.contains(metadata.validation_state,"bad_ps_fontname") then
- -- the ff library does a bit too much (and wrong) checking ... so we need to catch this
- -- at least for now
- local function valid(what)
- if names then
- for i=1,#names do
- local list = names[i]
- local names = list.names
- if names then
- local name = names[what]
- if name and valid_ps_name(name) then
- return name
- end
- end
- end
- end
- end
- local function check(what)
- local oldname = metadata[what]
- if valid_ps_name(oldname) then
- report_otf("ignoring warning %a because %s %a is proper ASCII","bad_ps_fontname",what,oldname)
- else
- local newname = valid(what)
- if not newname then
- newname = formatters["bad-%s-%s"](what,file.nameonly(filename))
- end
- local warning = formatters["overloading %s from invalid ASCII name %a to %a"](what,oldname,newname)
- data.warnings[#data.warnings+1] = warning
- report_otf(warning)
- metadata[what] = newname
- end
- end
- check("fontname")
- check("fullname")
- end
- --
- if names then
- local psname = metadata.psname
- if not psname or psname == "" then
- for i=1,#names do
- local name = names[i]
- -- Currently we use the same restricted search as in the new context (specific) font loader
- -- but we might add more lang checks (it worked ok in the new loaded so now we're in sync)
- -- This check here is also because there are (esp) cjk fonts out there with psnames different
- -- from fontnames (gives a bad lookup in backend).
- if lower(name.lang) == "english (us)" then
- local specification = name.names
- if specification then
- local postscriptname = specification.postscriptname
- if postscriptname then
- psname = postscriptname
- end
- end
- end
- break
- end
- end
- if psname ~= metadata.fontname then
- report_otf("fontname %a, fullname %a, psname %a",metadata.fontname,metadata.fullname,psname)
- end
- metadata.psname = psname
- end
- --
-end
-
-actions["cleanup tables"] = function(data,filename,raw)
- local duplicates = data.resources.duplicates
- if duplicates then
- for k, v in next, duplicates do
- if #v == 1 then
- duplicates[k] = v[1]
- end
- end
- end
- data.resources.indices = nil -- not needed
- data.resources.unicodes = nil -- delayed
- data.helpers = nil -- tricky as we have no unicodes any more
-end
-
--- kern: ttf has a table with kerns
---
--- Weird, as maxfirst and maxseconds can have holes, first seems to be indexed, but
--- seconds can start at 2 .. this need to be fixed as getn as well as # are sort of
--- unpredictable alternatively we could force an [1] if not set (maybe I will do that
--- anyway).
-
--- we can share { } as it is never set
-
--- ligatures have an extra specification.char entry that we don't use
-
--- mlookups only with pairs and ligatures
-
-actions["reorganize glyph lookups"] = function(data,filename,raw)
- local resources = data.resources
- local unicodes = resources.unicodes
- local descriptions = data.descriptions
- local splitter = data.helpers.tounicodelist
-
- local lookuptypes = resources.lookuptypes
-
- for unicode, description in next, descriptions do
- local lookups = description.glyph.lookups
- if lookups then
- for tag, lookuplist in next, lookups do
- for l=1,#lookuplist do
- local lookup = lookuplist[l]
- local specification = lookup.specification
- local lookuptype = lookup.type
- local lt = lookuptypes[tag]
- if not lt then
- lookuptypes[tag] = lookuptype
- elseif lt ~= lookuptype then
- report_otf("conflicting lookuptypes, %a points to %a and %a",tag,lt,lookuptype)
- end
- if lookuptype == "ligature" then
- lookuplist[l] = { lpegmatch(splitter,specification.components) }
- elseif lookuptype == "alternate" then
- lookuplist[l] = { lpegmatch(splitter,specification.components) }
- elseif lookuptype == "substitution" then
- lookuplist[l] = unicodes[specification.variant]
- elseif lookuptype == "multiple" then
- lookuplist[l] = { lpegmatch(splitter,specification.components) }
- elseif lookuptype == "position" then
- lookuplist[l] = {
- specification.x or 0,
- specification.y or 0,
- specification.h or 0,
- specification.v or 0
- }
- elseif lookuptype == "pair" then
- local one = specification.offsets[1]
- local two = specification.offsets[2]
- local paired = unicodes[specification.paired]
- if one then
- if two then
- lookuplist[l] = { 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
- lookuplist[l] = { paired, { one.x or 0, one.y or 0, one.h or 0, one.v or 0 } }
- end
- else
- if two then
- lookuplist[l] = { paired, { }, { two.x or 0, two.y or 0, two.h or 0, two.v or 0} } -- maybe nil instead of { }
- else
- lookuplist[l] = { paired }
- end
- end
- end
- end
- end
- local slookups, mlookups
- for tag, lookuplist in next, lookups do
- if #lookuplist == 1 then
- if slookups then
- slookups[tag] = lookuplist[1]
- else
- slookups = { [tag] = lookuplist[1] }
- end
- else
- if mlookups then
- mlookups[tag] = lookuplist
- else
- mlookups = { [tag] = lookuplist }
- end
- end
- end
- if slookups then
- description.slookups = slookups
- end
- if mlookups then
- description.mlookups = mlookups
- end
- -- description.lookups = nil
- end
- end
-end
-
-local zero = { 0, 0 }
-
-actions["reorganize glyph anchors"] = function(data,filename,raw)
- local descriptions = data.descriptions
- for unicode, description in next, descriptions do
- local anchors = description.glyph.anchors
- if anchors then
- for class, data in next, anchors do
- if class == "baselig" then
- for tag, specification in next, data do
- -- for i=1,#specification do
- -- local si = specification[i]
- -- specification[i] = { si.x or 0, si.y or 0 }
- -- end
- -- can be sparse so we need to fill the holes
- local n = 0
- for k, v in next, specification do
- if k > n then
- n = k
- end
- local x, y = v.x, v.y
- if x or y then
- specification[k] = { x or 0, y or 0 }
- else
- specification[k] = zero
- end
- end
- local t = { }
- for i=1,n do
- t[i] = specification[i] or zero
- end
- data[tag] = t -- so # is okay (nicer for packer)
- end
- else
- for tag, specification in next, data do
- local x, y = specification.x, specification.y
- if x or y then
- data[tag] = { x or 0, y or 0 }
- else
- data[tag] = zero
- end
- end
- end
- end
- description.anchors = anchors
- end
- end
-end
-
-local bogusname = (P("uni") + P("u")) * R("AF","09")^4
- + (P("index") + P("glyph") + S("Ii") * P("dentity") * P(".")^0) * R("09")^1
-local uselessname = (1-bogusname)^0 * bogusname
-
-actions["purge names"] = function(data,filename,raw) -- not used yet
- if purge_names then
- local n = 0
- for u, d in next, data.descriptions do
- if lpegmatch(uselessname,d.name) then
- n = n + 1
- d.name = nil
- end
- -- d.comment = nil
- end
- if n > 0 then
- report_otf("%s bogus names removed",n)
- end
- end
-end
-
-actions["compact lookups"] = function(data,filename,raw)
- if not compact_lookups then
- report_otf("not compacting")
- return
- end
- -- create keyhash
- local last = 0
- local tags = table.setmetatableindex({ },
- function(t,k)
- last = last + 1
- t[k] = last
- return last
- end
- )
- --
- local descriptions = data.descriptions
- local resources = data.resources
- --
- for u, d in next, descriptions do
- --
- -- -- we can also compact anchors and cursives (basechar basemark baselig mark)
- --
- local slookups = d.slookups
- if type(slookups) == "table" then
- local s = { }
- for k, v in next, slookups do
- s[tags[k]] = v
- end
- d.slookups = s
- end
- --
- local mlookups = d.mlookups
- if type(mlookups) == "table" then
- local m = { }
- for k, v in next, mlookups do
- m[tags[k]] = v
- end
- d.mlookups = m
- end
- --
- local kerns = d.kerns
- if type(kerns) == "table" then
- local t = { }
- for k, v in next, kerns do
- t[tags[k]] = v
- end
- d.kerns = t
- end
- end
- --
- local lookups = data.lookups
- if lookups then
- local l = { }
- for k, v in next, lookups do
- local rules = v.rules
- if rules then
- for i=1,#rules do
- local l = rules[i].lookups
- if type(l) == "table" then
- for i=1,#l do
- l[i] = tags[l[i]]
- end
- end
- end
- end
- l[tags[k]] = v
- end
- data.lookups = l
- end
- --
- local lookups = resources.lookups
- if lookups then
- local l = { }
- for k, v in next, lookups do
- local s = v.subtables
- if type(s) == "table" then
- for i=1,#s do
- s[i] = tags[s[i]]
- end
- end
- l[tags[k]] = v
- end
- resources.lookups = l
- end
- --
- local sequences = resources.sequences
- if sequences then
- for i=1,#sequences do
- local s = sequences[i]
- local n = s.name
- if n then
- s.name = tags[n]
- end
- local t = s.subtables
- if type(t) == "table" then
- for i=1,#t do
- t[i] = tags[t[i]]
- end
- end
- end
- end
- --
- local lookuptypes = resources.lookuptypes
- if lookuptypes then
- local l = { }
- for k, v in next, lookuptypes do
- l[tags[k]] = v
- end
- resources.lookuptypes = l
- end
- --
- local anchor_to_lookup = resources.anchor_to_lookup
- if anchor_to_lookup then
- for anchor, lookups in next, anchor_to_lookup do
- local l = { }
- for lookup, value in next, lookups do
- l[tags[lookup]] = value
- end
- anchor_to_lookup[anchor] = l
- end
- end
- --
- local lookup_to_anchor = resources.lookup_to_anchor
- if lookup_to_anchor then
- local l = { }
- for lookup, value in next, lookup_to_anchor do
- l[tags[lookup]] = value
- end
- resources.lookup_to_anchor = l
- end
- --
- tags = table.swapped(tags)
- --
- report_otf("%s lookup tags compacted",#tags)
- --
- resources.lookuptags = tags
-end
-
--- modes: node, base, none
-
-function otf.setfeatures(tfmdata,features)
- local okay = constructors.initializefeatures("otf",tfmdata,features,trace_features,report_otf)
- if okay then
- return constructors.collectprocessors("otf",tfmdata,features,trace_features,report_otf)
- else
- return { } -- will become false
- end
-end
-
--- the first version made a top/mid/not extensible table, now we just
--- pass on the variants data and deal with it in the tfm scaler (there
--- is no longer an extensible table anyway)
---
--- we cannot share descriptions as virtual fonts might extend them (ok,
--- we could use a cache with a hash
---
--- we already assign an empty tabel to characters as we can add for
--- instance protruding info and loop over characters; one is not supposed
--- to change descriptions and if one does so one should make a copy!
-
-local function copytotfm(data,cache_id)
- if data then
- local metadata = data.metadata
- local warnings = data.warnings
- local resources = data.resources
- local properties = derivetable(data.properties)
- local descriptions = derivetable(data.descriptions)
- local goodies = derivetable(data.goodies)
- local characters = { }
- local parameters = { }
- local mathparameters = { }
- --
- local pfminfo = metadata.pfminfo or { }
- local resources = data.resources
- local unicodes = resources.unicodes
- -- local mode = data.mode or "base"
- local spaceunits = 500
- local spacer = "space"
- local designsize = metadata.designsize or metadata.design_size or 100
- local minsize = metadata.minsize or metadata.design_range_bottom or designsize
- local maxsize = metadata.maxsize or metadata.design_range_top or designsize
- local mathspecs = metadata.math
- --
- if designsize == 0 then
- designsize = 100
- minsize = 100
- maxsize = 100
- end
- if mathspecs then
- for name, value in next, mathspecs do
- mathparameters[name] = value
- end
- end
- for unicode, _ in next, data.descriptions do -- use parent table
- characters[unicode] = { }
- end
- if mathspecs then
- -- we could move this to the scaler but not that much is saved
- -- and this is cleaner
- for unicode, character in next, characters do
- local d = descriptions[unicode]
- local m = d.math
- if m then
- -- watch out: luatex uses horiz_variants for the parts
- --
- local italic = m.italic
- local vitalic = m.vitalic
- --
- local variants = m.hvariants
- local parts = m.hparts
- -- local done = { [unicode] = true }
- if variants then
- local c = character
- for i=1,#variants do
- local un = variants[i]
- -- if done[un] then
- -- -- report_otf("skipping cyclic reference %U in math variant %U",un,unicode)
- -- else
- c.next = un
- c = characters[un]
- -- done[un] = true
- -- end
- end -- c is now last in chain
- c.horiz_variants = parts
- elseif parts then
- character.horiz_variants = parts
- italic = m.hitalic
- end
- --
- local variants = m.vvariants
- local parts = m.vparts
- -- local done = { [unicode] = true }
- if variants then
- local c = character
- for i=1,#variants do
- local un = variants[i]
- -- if done[un] then
- -- -- report_otf("skipping cyclic reference %U in math variant %U",un,unicode)
- -- else
- c.next = un
- c = characters[un]
- -- done[un] = true
- -- end
- end -- c is now last in chain
- c.vert_variants = parts
- elseif parts then
- character.vert_variants = parts
- end
- --
- if italic and italic ~= 0 then
- character.italic = italic -- overload
- end
- if vitalic and vitalic ~= 0 then
- character.vert_italic = vitalic
- end
- --
- local accent = m.accent
- if accent then
- character.accent = accent
- end
- --
- local kerns = m.kerns
- if kerns then
- character.mathkerns = kerns
- end
- end
- end
- end
- -- end math
- -- we need a runtime lookup because of running from cdrom or zip, brrr (shouldn't we use the basename then?)
- local filename = constructors.checkedfilename(resources)
- local fontname = metadata.fontname
- local fullname = metadata.fullname or fontname
- local psname = metadata.psname or fontname or fullname
- local units = metadata.units or metadata.units_per_em or 1000
- --
- if units == 0 then -- catch bugs in fonts
- units = 1000 -- maybe 2000 when ttf
- metadata.units = 1000
- report_otf("changing %a units to %a",0,units)
- end
- --
- local monospaced = metadata.monospaced or metadata.isfixedpitch or (pfminfo.panose and pfminfo.panose.proportion == "Monospaced")
- local charwidth = pfminfo.avgwidth -- or unset
- local charxheight = pfminfo.os2_xheight and pfminfo.os2_xheight > 0 and pfminfo.os2_xheight
--- charwidth = charwidth * units/1000
--- charxheight = charxheight * units/1000
- local italicangle = metadata.italicangle
- properties.monospaced = monospaced
- parameters.italicangle = italicangle
- parameters.charwidth = charwidth
- parameters.charxheight = charxheight
- --
- local space = 0x0020
- local emdash = 0x2014
- if monospaced then
- if descriptions[space] then
- spaceunits, spacer = descriptions[space].width, "space"
- end
- if not spaceunits and descriptions[emdash] then
- spaceunits, spacer = descriptions[emdash].width, "emdash"
- end
- if not spaceunits and charwidth then
- spaceunits, spacer = charwidth, "charwidth"
- end
- else
- if descriptions[space] then
- spaceunits, spacer = descriptions[space].width, "space"
- end
- if not spaceunits and descriptions[emdash] then
- spaceunits, spacer = descriptions[emdash].width/2, "emdash/2"
- end
- if not spaceunits and charwidth then
- spaceunits, spacer = charwidth, "charwidth"
- end
- end
- spaceunits = tonumber(spaceunits) or 500 -- brrr
- --
- parameters.slant = 0
- parameters.space = spaceunits -- 3.333 (cmr10)
- parameters.space_stretch = units/2 -- 500 -- 1.666 (cmr10)
- parameters.space_shrink = 1*units/3 -- 333 -- 1.111 (cmr10)
- parameters.x_height = 2*units/5 -- 400
- parameters.quad = units -- 1000
- if spaceunits < 2*units/5 then
- -- todo: warning
- end
- if italicangle and italicangle ~= 0 then
- parameters.italicangle = italicangle
- parameters.italicfactor = math.cos(math.rad(90+italicangle))
- parameters.slant = - math.tan(italicangle*math.pi/180)
- end
- if monospaced then
- parameters.space_stretch = 0
- parameters.space_shrink = 0
- elseif syncspace then --
- parameters.space_stretch = spaceunits/2
- parameters.space_shrink = spaceunits/3
- end
- parameters.extra_space = parameters.space_shrink -- 1.111 (cmr10)
- if charxheight then
- parameters.x_height = charxheight
- else
- local x = 0x0078
- if x then
- local x = descriptions[x]
- if x then
- parameters.x_height = x.height
- end
- end
- end
- --
- parameters.designsize = (designsize/10)*65536
- parameters.minsize = (minsize /10)*65536
- parameters.maxsize = (maxsize /10)*65536
- parameters.ascender = abs(metadata.ascender or metadata.ascent or 0)
- parameters.descender = abs(metadata.descender or metadata.descent or 0)
- parameters.units = units
- --
- properties.space = spacer
- properties.encodingbytes = 2
- properties.format = data.format or otf_format(filename) or formats.otf
- properties.noglyphnames = true
- properties.filename = filename
- properties.fontname = fontname
- properties.fullname = fullname
- properties.psname = psname
- properties.name = filename or fullname
- --
- -- properties.name = specification.name
- -- properties.sub = specification.sub
- --
- if warnings and #warnings > 0 then
- report_otf("warnings for font: %s",filename)
- report_otf()
- for i=1,#warnings do
- report_otf(" %s",warnings[i])
- end
- report_otf()
- end
- return {
- characters = characters,
- descriptions = descriptions,
- parameters = parameters,
- mathparameters = mathparameters,
- resources = resources,
- properties = properties,
- goodies = goodies,
- warnings = warnings,
- }
- end
-end
-
-local function otftotfm(specification)
- local cache_id = specification.hash
- local tfmdata = containers.read(constructors.cache,cache_id)
- if not tfmdata then
- local name = specification.name
- local sub = specification.sub
- local filename = specification.filename
- -- local format = specification.format
- local features = specification.features.normal
- local rawdata = otf.load(filename,sub,features and features.featurefile)
- if rawdata and next(rawdata) then
- local descriptions = rawdata.descriptions
- local duplicates = rawdata.resources.duplicates
- if duplicates then
- local nofduplicates, nofduplicated = 0, 0
- for parent, list in next, duplicates do
- if type(list) == "table" then
- local n = #list
- for i=1,n do
- local unicode = list[i]
- if not descriptions[unicode] then
- descriptions[unicode] = descriptions[parent] -- or copy
- nofduplicated = nofduplicated + 1
- end
- end
- nofduplicates = nofduplicates + n
- else
- if not descriptions[list] then
- descriptions[list] = descriptions[parent] -- or copy
- nofduplicated = nofduplicated + 1
- end
- nofduplicates = nofduplicates + 1
- end
- end
- if trace_otf and nofduplicated ~= nofduplicates then
- report_otf("%i extra duplicates copied out of %i",nofduplicated,nofduplicates)
- end
- end
- rawdata.lookuphash = { }
- tfmdata = copytotfm(rawdata,cache_id)
- if tfmdata and next(tfmdata) then
- -- at this moment no characters are assigned yet, only empty slots
- local features = constructors.checkedfeatures("otf",features)
- local shared = tfmdata.shared
- if not shared then
- shared = { }
- tfmdata.shared = shared
- end
- shared.rawdata = rawdata
- -- shared.features = features -- default
- shared.dynamics = { }
- -- shared.processes = { }
- tfmdata.changed = { }
- shared.features = features
- shared.processes = otf.setfeatures(tfmdata,features)
- end
- end
- containers.write(constructors.cache,cache_id,tfmdata)
- end
- return tfmdata
-end
-
-local function read_from_otf(specification)
- local tfmdata = otftotfm(specification)
- if tfmdata then
- -- this late ? .. needs checking
- tfmdata.properties.name = specification.name
- tfmdata.properties.sub = specification.sub
- --
- tfmdata = constructors.scale(tfmdata,specification)
- local allfeatures = tfmdata.shared.features or specification.features.normal
- constructors.applymanipulators("otf",tfmdata,allfeatures,trace_features,report_otf)
- constructors.setname(tfmdata,specification) -- only otf?
- fonts.loggers.register(tfmdata,file.suffix(specification.filename),specification)
- end
- return tfmdata
-end
-
-local function checkmathsize(tfmdata,mathsize)
- local mathdata = tfmdata.shared.rawdata.metadata.math
- local mathsize = tonumber(mathsize)
- if mathdata then -- we cannot use mathparameters as luatex will complain
- local parameters = tfmdata.parameters
- parameters.scriptpercentage = mathdata.ScriptPercentScaleDown
- parameters.scriptscriptpercentage = mathdata.ScriptScriptPercentScaleDown
- parameters.mathsize = mathsize
- end
-end
-
-registerotffeature {
- name = "mathsize",
- description = "apply mathsize specified in the font",
- initializers = {
- base = checkmathsize,
- node = checkmathsize,
- }
-}
-
--- helpers
-
-function otf.collectlookups(rawdata,kind,script,language)
- local sequences = rawdata.resources.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
-
--- readers (a bit messy, this forced so I might redo that bit: foo.ttf FOO.ttf foo.TTF FOO.TTF)
-
-local function check_otf(forced,specification,suffix)
- local name = specification.name
- if forced then
- name = specification.forcedname -- messy
- end
- local fullname = findbinfile(name,suffix) or ""
- if fullname == "" then
- fullname = fonts.names.getfilename(name,suffix) or ""
- end
- if fullname ~= "" and not fonts.names.ignoredfile(fullname) then
- specification.filename = fullname
- return read_from_otf(specification)
- end
-end
-
-local function opentypereader(specification,suffix)
- local forced = specification.forced or ""
- if formats[forced] then
- return check_otf(true,specification,forced)
- else
- return check_otf(false,specification,suffix)
- end
-end
-
-readers.opentype = opentypereader -- kind of useless and obsolete
-
-function readers.otf (specification) return opentypereader(specification,"otf") end
-function readers.ttf (specification) return opentypereader(specification,"ttf") end
-function readers.ttc (specification) return opentypereader(specification,"ttf") end
-function readers.dfont(specification) return opentypereader(specification,"ttf") end
-
--- this will be overloaded
-
-function otf.scriptandlanguage(tfmdata,attr)
- local properties = tfmdata.properties
- return properties.script or "dflt", properties.language or "dflt"
-end
-
--- a little bit of abstraction
-
-local function justset(coverage,unicode,replacement)
- coverage[unicode] = replacement
-end
-
-otf.coverup = {
- stepkey = "subtables",
- actions = {
- substitution = justset,
- alternate = justset,
- multiple = justset,
- ligature = justset,
- kern = justset,
- chainsubstitution = justset,
- chainposition = justset,
- },
- register = function(coverage,lookuptype,format,feature,n,descriptions,resources)
- local name = formatters["ctx_%s_%s_%s"](feature,lookuptype,n) -- we can have a mix of types
- if lookuptype == "kern" then
- resources.lookuptypes[name] = "position"
- else
- resources.lookuptypes[name] = lookuptype
- end
- for u, c in next, coverage do
- local description = descriptions[u]
- local slookups = description.slookups
- if slookups then
- slookups[name] = c
- else
- description.slookups = { [name] = c }
- end
- end
- return name
- end
-}
-
--- moved from font-oth.lua
-
-local function getgsub(tfmdata,k,kind)
- local description = tfmdata.descriptions[k]
- if description then
- local slookups = description.slookups -- we assume only slookups (we can always extend)
- if slookups then
- local shared = tfmdata.shared
- local rawdata = shared and shared.rawdata
- if rawdata then
- local lookuptypes = rawdata.resources.lookuptypes
- if lookuptypes then
- local properties = tfmdata.properties
- -- we could cache these
- local validlookups, lookuplist = otf.collectlookups(rawdata,kind,properties.script,properties.language)
- if validlookups then
- for l=1,#lookuplist do
- local lookup = lookuplist[l]
- local found = slookups[lookup]
- if found then
- return found, lookuptypes[lookup]
- end
- end
- end
- end
- end
- end
- end
-end
-
-otf.getgsub = getgsub -- returns value, gsub_kind
-
-function otf.getsubstitution(tfmdata,k,kind,value)
- local found, kind = getgsub(tfmdata,k,kind)
- if not found then
- --
- elseif kind == "substitution" then
- return found
- elseif kind == "alternate" then
- local choice = tonumber(value) or 1 -- no random here (yet)
- return found[choice] or found[1] or k
- end
- return k
-end
-
-otf.getalternate = otf.getsubstitution
-
-function otf.getmultiple(tfmdata,k,kind)
- local found, kind = getgsub(tfmdata,k,kind)
- if found and kind == "multiple" then
- return found
- end
- return { k }
-end
-
-function otf.getkern(tfmdata,left,right,kind)
- local kerns = getgsub(tfmdata,left,kind or "kern",true) -- for now we use getsub
- if kerns then
- local found = kerns[right]
- local kind = type(found)
- if kind == "table" then
- found = found[1][3] -- can be more clever
- elseif kind ~= "number" then
- found = false
- end
- if found then
- return found * tfmdata.parameters.factor
- end
- end
- return 0
-end