summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/tex/generic/context/luatex-fonts-merged.lua
diff options
context:
space:
mode:
Diffstat (limited to 'Master/texmf-dist/tex/generic/context/luatex-fonts-merged.lua')
-rw-r--r--Master/texmf-dist/tex/generic/context/luatex-fonts-merged.lua14505
1 files changed, 7246 insertions, 7259 deletions
diff --git a/Master/texmf-dist/tex/generic/context/luatex-fonts-merged.lua b/Master/texmf-dist/tex/generic/context/luatex-fonts-merged.lua
index da81735ff23..7e954182ad7 100644
--- a/Master/texmf-dist/tex/generic/context/luatex-fonts-merged.lua
+++ b/Master/texmf-dist/tex/generic/context/luatex-fonts-merged.lua
@@ -1,6 +1,6 @@
-- merged file : luatex-fonts-merged.lua
-- parent file : luatex-fonts.lua
--- merge date : 05/24/10 13:05:12
+-- merge date : 05/18/11 18:04:18
do -- begin closure to overcome local limits and interference
@@ -12,8 +12,9 @@ if not modules then modules = { } end modules ['l-string'] = {
license = "see context related readme files"
}
+local string = string
local sub, gsub, find, match, gmatch, format, char, byte, rep, lower = string.sub, string.gsub, string.find, string.match, string.gmatch, string.format, string.char, string.byte, string.rep, string.lower
-local lpegmatch = lpeg.match
+local lpegmatch, S, C, Ct = lpeg.match, lpeg.S, lpeg.C, lpeg.Ct
-- some functions may disappear as they are not used anywhere
@@ -21,196 +22,75 @@ if not string.split then
-- this will be overloaded by a faster lpeg variant
- function string:split(pattern)
- if #self > 0 then
- local t = { }
- for s in gmatch(self..pattern,"(.-)"..pattern) do
- t[#t+1] = s
+ function string.split(str,pattern)
+ local t = { }
+ if #str > 0 then
+ local n = 1
+ for s in gmatch(str..pattern,"(.-)"..pattern) do
+ t[n] = s
+ n = n + 1
end
- return t
- else
- return { }
end
+ return t
end
end
-local chr_to_esc = {
- ["%"] = "%%",
- ["."] = "%.",
- ["+"] = "%+", ["-"] = "%-", ["*"] = "%*",
- ["^"] = "%^", ["$"] = "%$",
- ["["] = "%[", ["]"] = "%]",
- ["("] = "%(", [")"] = "%)",
- ["{"] = "%{", ["}"] = "%}"
-}
-
-string.chr_to_esc = chr_to_esc
-
-function string:esc() -- variant 2
- return (gsub(self,"(.)",chr_to_esc))
-end
-
-function string:unquote()
- return (gsub(self,"^([\"\'])(.*)%1$","%2"))
+function string.unquoted(str)
+ return (gsub(str,"^([\"\'])(.*)%1$","%2"))
end
---~ function string:unquote()
---~ if find(self,"^[\'\"]") then
---~ return sub(self,2,-2)
+--~ function stringunquoted(str)
+--~ if find(str,"^[\'\"]") then
+--~ return sub(str,2,-2)
--~ else
---~ return self
+--~ return str
--~ end
--~ end
-function string:quote() -- we could use format("%q")
- return format("%q",self)
+function string.quoted(str)
+ return format("%q",str) -- always "
end
-function string:count(pattern) -- variant 3
+function string.count(str,pattern) -- variant 3
local n = 0
- for _ in gmatch(self,pattern) do
+ for _ in gmatch(str,pattern) do -- not for utf
n = n + 1
end
return n
end
-function string:limit(n,sentinel)
- if #self > n then
- sentinel = sentinel or " ..."
- return sub(self,1,(n-#sentinel)) .. sentinel
+function string.limit(str,n,sentinel) -- not utf proof
+ if #str > n then
+ sentinel = sentinel or "..."
+ return sub(str,1,(n-#sentinel)) .. sentinel
else
- return self
- end
-end
-
---~ function string:strip() -- the .- is quite efficient
---~ -- return match(self,"^%s*(.-)%s*$") or ""
---~ -- return match(self,'^%s*(.*%S)') or '' -- posted on lua list
---~ return find(s,'^%s*$') and '' or match(s,'^%s*(.*%S)')
---~ end
-
-do -- roberto's variant:
- local space = lpeg.S(" \t\v\n")
- local nospace = 1 - space
- local stripper = space^0 * lpeg.C((space^0 * nospace^1)^0)
- function string.strip(str)
- return lpegmatch(stripper,str) or ""
- end
-end
-
-function string:is_empty()
- return not find(self,"%S")
-end
-
-function string:enhance(pattern,action)
- local ok, n = true, 0
- while ok do
- ok = false
- self = gsub(self,pattern, function(...)
- ok, n = true, n + 1
- return action(...)
- end)
- end
- return self, n
-end
-
-local chr_to_hex, hex_to_chr = { }, { }
-
-for i=0,255 do
- local c, h = char(i), format("%02X",i)
- chr_to_hex[c], hex_to_chr[h] = h, c
-end
-
-function string:to_hex()
- return (gsub(self or "","(.)",chr_to_hex))
-end
-
-function string:from_hex()
- return (gsub(self or "","(..)",hex_to_chr))
-end
-
-if not string.characters then
-
- local function nextchar(str, index)
- index = index + 1
- return (index <= #str) and index or nil, sub(str,index,index)
- end
- function string:characters()
- return nextchar, self, 0
- end
- local function nextbyte(str, index)
- index = index + 1
- return (index <= #str) and index or nil, byte(sub(str,index,index))
- end
- function string:bytes()
- return nextbyte, self, 0
- end
-
-end
-
--- we can use format for this (neg n)
-
-function string:rpadd(n,chr)
- local m = n-#self
- if m > 0 then
- return self .. rep(chr or " ",m)
- else
- return self
- end
-end
-
-function string:lpadd(n,chr)
- local m = n-#self
- if m > 0 then
- return rep(chr or " ",m) .. self
- else
- return self
+ return str
end
end
-string.padd = string.rpadd
+local space = S(" \t\v\n")
+local nospace = 1 - space
+local stripper = space^0 * C((space^0 * nospace^1)^0) -- roberto's code
-function is_number(str) -- tonumber
- return find(str,"^[%-%+]?[%d]-%.?[%d+]$") == 1
+function string.strip(str)
+ return lpegmatch(stripper,str) or ""
end
---~ print(is_number("1"))
---~ print(is_number("1.1"))
---~ print(is_number(".1"))
---~ print(is_number("-0.1"))
---~ print(is_number("+0.1"))
---~ print(is_number("-.1"))
---~ print(is_number("+.1"))
-
-function string:split_settings() -- no {} handling, see l-aux for lpeg variant
- if find(self,"=") then
- local t = { }
- for k,v in gmatch(self,"(%a+)=([^%,]*)") do
- t[k] = v
- end
- return t
- else
- return nil
- end
+function string.is_empty(str)
+ return not find(str,"%S")
end
local patterns_escapes = {
- ["-"] = "%-",
- ["."] = "%.",
- ["+"] = "%+",
- ["*"] = "%*",
["%"] = "%%",
- ["("] = "%)",
- [")"] = "%)",
- ["["] = "%[",
- ["]"] = "%]",
+ ["."] = "%.",
+ ["+"] = "%+", ["-"] = "%-", ["*"] = "%*",
+ ["["] = "%[", ["]"] = "%]",
+ ["("] = "%(", [")"] = "%)",
+ -- ["{"] = "%{", ["}"] = "%}"
+ -- ["^"] = "%^", ["$"] = "%$",
}
-function string:pattesc()
- return (gsub(self,".",patterns_escapes))
-end
-
local simple_escapes = {
["-"] = "%-",
["."] = "%.",
@@ -218,80 +98,30 @@ local simple_escapes = {
["*"] = ".*",
}
-function string:simpleesc()
- return (gsub(self,".",simple_escapes))
-end
-
-function string:tohash()
- local t = { }
- for s in gmatch(self,"([^, ]+)") do -- lpeg
- t[s] = true
- end
- return t
-end
-
-local pattern = lpeg.Ct(lpeg.C(1)^0)
-
-function string:totable()
- return lpegmatch(pattern,self)
+function string.escapedpattern(str,simple)
+ return (gsub(str,".",simple and simple_escapes or patterns_escapes))
end
---~ local t = {
---~ "1234567123456712345671234567",
---~ "a\tb\tc",
---~ "aa\tbb\tcc",
---~ "aaa\tbbb\tccc",
---~ "aaaa\tbbbb\tcccc",
---~ "aaaaa\tbbbbb\tccccc",
---~ "aaaaaa\tbbbbbb\tcccccc",
---~ }
---~ for k,v do
---~ print(string.tabtospace(t[k]))
---~ end
-
-function string.tabtospace(str,tab)
- -- we don't handle embedded newlines
- while true do
- local s = find(str,"\t")
- if s then
- if not tab then tab = 7 end -- only when found
- local d = tab-(s-1) % tab
- if d > 0 then
- str = gsub(str,"\t",rep(" ",d),1)
- else
- str = gsub(str,"\t","",1)
- end
+function string.topattern(str,lowercase,strict)
+ if str == "" then
+ return ".*"
+ else
+ str = gsub(str,".",simple_escapes)
+ if lowercase then
+ str = lower(str)
+ end
+ if strict then
+ return "^" .. str .. "$"
else
- break
+ return str
end
end
- return str
-end
-
-function string:compactlong() -- strips newlines and leading spaces
- self = gsub(self,"[\n\r]+ *","")
- self = gsub(self,"^ *","")
- return self
end
-function string:striplong() -- strips newlines and leading spaces
- self = gsub(self,"^%s*","")
- self = gsub(self,"[\n\r]+ *","\n")
- return self
-end
+-- obsolete names:
-function string:topattern(lowercase,strict)
- if lowercase then
- self = lower(self)
- end
- self = gsub(self,".",simple_escapes)
- if self == "" then
- self = ".*"
- elseif strict then
- self = "^" .. self .. "$"
- end
- return self
-end
+string.quote = string.quoted
+string.unquote = string.unquoted
end -- closure
@@ -307,28 +137,75 @@ if not modules then modules = { } end modules ['l-lpeg'] = {
local lpeg = require("lpeg")
+local type = type
+
+-- Beware, we predefine a bunch of patterns here and one reason for doing so
+-- is that we get consistent behaviour in some of the visualizers.
+
lpeg.patterns = lpeg.patterns or { } -- so that we can share
local patterns = lpeg.patterns
-local P, R, S, Ct, C, Cs, Cc, V = lpeg.P, lpeg.R, lpeg.S, lpeg.Ct, lpeg.C, lpeg.Cs, lpeg.Cc, lpeg.V
-local match = lpeg.match
+local P, R, S, V, match = lpeg.P, lpeg.R, lpeg.S, lpeg.V, lpeg.match
+local Ct, C, Cs, Cc = lpeg.Ct, lpeg.C, lpeg.Cs, lpeg.Cc
+local lpegtype = lpeg.type
+
+local utfcharacters = string.utfcharacters
+local utfgmatch = unicode and unicode.utf8.gmatch
+
+local anything = P(1)
+local endofstring = P(-1)
+local alwaysmatched = P(true)
+
+patterns.anything = anything
+patterns.endofstring = endofstring
+patterns.beginofstring = alwaysmatched
+patterns.alwaysmatched = alwaysmatched
local digit, sign = R('09'), S('+-')
local cr, lf, crlf = P("\r"), P("\n"), P("\r\n")
-local utf8byte = R("\128\191")
+local newline = crlf + cr + lf
+local escaped = P("\\") * anything
+local squote = P("'")
+local dquote = P('"')
+local space = P(" ")
+
+local utfbom_32_be = P('\000\000\254\255')
+local utfbom_32_le = P('\255\254\000\000')
+local utfbom_16_be = P('\255\254')
+local utfbom_16_le = P('\254\255')
+local utfbom_8 = P('\239\187\191')
+local utfbom = utfbom_32_be + utfbom_32_le
+ + utfbom_16_be + utfbom_16_le
+ + utfbom_8
+local utftype = utfbom_32_be / "utf-32-be" + utfbom_32_le / "utf-32-le"
+ + utfbom_16_be / "utf-16-be" + utfbom_16_le / "utf-16-le"
+ + utfbom_8 / "utf-8" + alwaysmatched / "unknown"
+
+local utf8next = R("\128\191")
-patterns.utf8byte = utf8byte
patterns.utf8one = R("\000\127")
-patterns.utf8two = R("\194\223") * utf8byte
-patterns.utf8three = R("\224\239") * utf8byte * utf8byte
-patterns.utf8four = R("\240\244") * utf8byte * utf8byte * utf8byte
+patterns.utf8two = R("\194\223") * utf8next
+patterns.utf8three = R("\224\239") * utf8next * utf8next
+patterns.utf8four = R("\240\244") * utf8next * utf8next * utf8next
+patterns.utfbom = utfbom
+patterns.utftype = utftype
+
+local utf8char = patterns.utf8one + patterns.utf8two + patterns.utf8three + patterns.utf8four
+local validutf8char = utf8char^0 * endofstring * Cc(true) + Cc(false)
+
+patterns.utf8 = utf8char
+patterns.utf8char = utf8char
+patterns.validutf8 = validutf8char
+patterns.validutf8char = validutf8char
patterns.digit = digit
patterns.sign = sign
patterns.cardinal = sign^0 * digit^1
patterns.integer = sign^0 * digit^1
patterns.float = sign^0 * digit^0 * P('.') * digit^1
+patterns.cfloat = sign^0 * digit^0 * P(',') * digit^1
patterns.number = patterns.float + patterns.integer
+patterns.cnumber = patterns.cfloat + patterns.integer
patterns.oct = P("0") * R("07")^1
patterns.octal = patterns.oct
patterns.HEX = P("0x") * R("09","AF")^1
@@ -337,16 +214,46 @@ patterns.hexadecimal = P("0x") * R("09","AF","af")^1
patterns.lowercase = R("az")
patterns.uppercase = R("AZ")
patterns.letter = patterns.lowercase + patterns.uppercase
-patterns.space = S(" ")
+patterns.space = space
+patterns.tab = P("\t")
+patterns.spaceortab = patterns.space + patterns.tab
patterns.eol = S("\n\r")
patterns.spacer = S(" \t\f\v") -- + string.char(0xc2, 0xa0) if we want utf (cf mail roberto)
-patterns.newline = crlf + cr + lf
-patterns.nonspace = 1 - patterns.space
+patterns.newline = newline
+patterns.emptyline = newline^1
patterns.nonspacer = 1 - patterns.spacer
patterns.whitespace = patterns.eol + patterns.spacer
patterns.nonwhitespace = 1 - patterns.whitespace
-patterns.utf8 = patterns.utf8one + patterns.utf8two + patterns.utf8three + patterns.utf8four
-patterns.utfbom = P('\000\000\254\255') + P('\255\254\000\000') + P('\255\254') + P('\254\255') + P('\239\187\191')
+patterns.equal = P("=")
+patterns.comma = P(",")
+patterns.commaspacer = P(",") * patterns.spacer^0
+patterns.period = P(".")
+patterns.colon = P(":")
+patterns.semicolon = P(";")
+patterns.underscore = P("_")
+patterns.escaped = escaped
+patterns.squote = squote
+patterns.dquote = dquote
+patterns.nosquote = (escaped + (1-squote))^0
+patterns.nodquote = (escaped + (1-dquote))^0
+patterns.unsingle = (squote/"") * patterns.nosquote * (squote/"")
+patterns.undouble = (dquote/"") * patterns.nodquote * (dquote/"")
+patterns.unquoted = patterns.undouble + patterns.unsingle -- more often undouble
+patterns.unspacer = ((patterns.spacer^1)/"")^0
+
+patterns.somecontent = (anything - newline - space)^1 -- (utf8char - newline - space)^1
+patterns.beginline = #(1-newline)
+
+local unquoted = Cs(patterns.unquoted * endofstring) -- not C
+
+function string.unquoted(str)
+ return match(unquoted,str) or str
+end
+
+--~ print(string.unquoted("test"))
+--~ print(string.unquoted([["t\"est"]]))
+--~ print(string.unquoted([["t\"est"x]]))
+--~ print(string.unquoted("\'test\'"))
function lpeg.anywhere(pattern) --slightly adapted from website
return P { P(pattern) + 1 * V(1) } -- why so complex?
@@ -356,36 +263,18 @@ function lpeg.splitter(pattern, action)
return (((1-P(pattern))^1)/action+1)^0
end
-local spacing = patterns.spacer^0 * patterns.newline -- sort of strip
-local empty = spacing * Cc("")
-local nonempty = Cs((1-spacing)^1) * spacing^-1
-local content = (empty + nonempty)^1
-
-local capture = Ct(content^0)
-
-function string:splitlines()
- return match(capture,self)
-end
-
-patterns.textline = content
-
---~ local p = lpeg.splitat("->",false) print(match(p,"oeps->what->more")) -- oeps what more
---~ local p = lpeg.splitat("->",true) print(match(p,"oeps->what->more")) -- oeps what->more
---~ local p = lpeg.splitat("->",false) print(match(p,"oeps")) -- oeps
---~ local p = lpeg.splitat("->",true) print(match(p,"oeps")) -- oeps
-
local splitters_s, splitters_m = { }, { }
local function splitat(separator,single)
local splitter = (single and splitters_s[separator]) or splitters_m[separator]
if not splitter then
separator = P(separator)
+ local other = C((1 - separator)^0)
if single then
- local other, any = C((1 - separator)^0), P(1)
+ local any = anything
splitter = other * (separator * C(any^0) + "") -- ?
splitters_s[separator] = splitter
else
- local other = C((1 - separator)^0)
splitter = other * (separator * other)^0
splitters_m[separator] = splitter
end
@@ -395,6 +284,11 @@ end
lpeg.splitat = splitat
+--~ local p = splitat("->",false) print(match(p,"oeps->what->more")) -- oeps what more
+--~ local p = splitat("->",true) print(match(p,"oeps->what->more")) -- oeps what->more
+--~ local p = splitat("->",false) print(match(p,"oeps")) -- oeps
+--~ local p = splitat("->",true) print(match(p,"oeps")) -- oeps
+
local cache = { }
function lpeg.split(separator,str)
@@ -406,16 +300,45 @@ function lpeg.split(separator,str)
return match(c,str)
end
-function string:split(separator)
+function string.split(str,separator)
local c = cache[separator]
if not c then
c = Ct(splitat(separator))
cache[separator] = c
end
- return match(c,self)
+ return match(c,str)
+end
+
+local spacing = patterns.spacer^0 * newline -- sort of strip
+local empty = spacing * Cc("")
+local nonempty = Cs((1-spacing)^1) * spacing^-1
+local content = (empty + nonempty)^1
+
+patterns.textline = content
+
+--~ local linesplitter = Ct(content^0)
+--~
+--~ function string.splitlines(str)
+--~ return match(linesplitter,str)
+--~ end
+
+local linesplitter = Ct(splitat(newline))
+
+patterns.linesplitter = linesplitter
+
+function string.splitlines(str)
+ return match(linesplitter,str)
+end
+
+local utflinesplitter = utfbom^-1 * Ct(splitat(newline))
+
+patterns.utflinesplitter = utflinesplitter
+
+function string.utfsplitlines(str)
+ return match(utflinesplitter,str)
end
-lpeg.splitters = cache
+--~ lpeg.splitters = cache -- no longer public
local cache = { }
@@ -423,36 +346,24 @@ function lpeg.checkedsplit(separator,str)
local c = cache[separator]
if not c then
separator = P(separator)
- local other = C((1 - separator)^0)
+ local other = C((1 - separator)^1)
c = Ct(separator^0 * other * (separator^1 * other)^0)
cache[separator] = c
end
return match(c,str)
end
-function string:checkedsplit(separator)
+function string.checkedsplit(str,separator)
local c = cache[separator]
if not c then
separator = P(separator)
- local other = C((1 - separator)^0)
+ local other = C((1 - separator)^1)
c = Ct(separator^0 * other * (separator^1 * other)^0)
cache[separator] = c
end
- return match(c,self)
+ return match(c,str)
end
---~ function lpeg.append(list,pp)
---~ local p = pp
---~ for l=1,#list do
---~ if p then
---~ p = p + P(list[l])
---~ else
---~ p = P(list[l])
---~ end
---~ end
---~ return p
---~ end
-
--~ from roberto's site:
local f1 = string.byte
@@ -461,7 +372,302 @@ local function f2(s) local c1, c2 = f1(s,1,2) return c1 * 64 + c2
local function f3(s) local c1, c2, c3 = f1(s,1,3) return (c1 * 64 + c2) * 64 + c3 - 925824 end
local function f4(s) local c1, c2, c3, c4 = f1(s,1,4) return ((c1 * 64 + c2) * 64 + c3) * 64 + c4 - 63447168 end
-patterns.utf8byte = patterns.utf8one/f1 + patterns.utf8two/f2 + patterns.utf8three/f3 + patterns.utf8four/f4
+local utf8byte = patterns.utf8one/f1 + patterns.utf8two/f2 + patterns.utf8three/f3 + patterns.utf8four/f4
+
+patterns.utf8byte = utf8byte
+
+--~ local str = " a b c d "
+
+--~ local s = lpeg.stripper(lpeg.R("az")) print("["..lpeg.match(s,str).."]")
+--~ local s = lpeg.keeper(lpeg.R("az")) print("["..lpeg.match(s,str).."]")
+--~ local s = lpeg.stripper("ab") print("["..lpeg.match(s,str).."]")
+--~ local s = lpeg.keeper("ab") print("["..lpeg.match(s,str).."]")
+
+local cache = { }
+
+function lpeg.stripper(str)
+ if type(str) == "string" then
+ local s = cache[str]
+ if not s then
+ s = Cs(((S(str)^1)/"" + 1)^0)
+ cache[str] = s
+ end
+ return s
+ else
+ return Cs(((str^1)/"" + 1)^0)
+ end
+end
+
+local cache = { }
+
+function lpeg.keeper(str)
+ if type(str) == "string" then
+ local s = cache[str]
+ if not s then
+ s = Cs((((1-S(str))^1)/"" + 1)^0)
+ cache[str] = s
+ end
+ return s
+ else
+ return Cs((((1-str)^1)/"" + 1)^0)
+ end
+end
+
+function lpeg.frontstripper(str) -- or pattern (yet undocumented)
+ return (P(str) + P(true)) * Cs(P(1)^0)
+end
+
+function lpeg.endstripper(str) -- or pattern (yet undocumented)
+ return Cs((1 - P(str) * P(-1))^0)
+end
+
+-- Just for fun I looked at the used bytecode and
+-- p = (p and p + pp) or pp gets one more (testset).
+
+function lpeg.replacer(one,two)
+ if type(one) == "table" then
+ local no = #one
+ if no > 0 then
+ local p
+ for i=1,no do
+ local o = one[i]
+ local pp = P(o[1]) / o[2]
+ if p then
+ p = p + pp
+ else
+ p = pp
+ end
+ end
+ return Cs((p + 1)^0)
+ end
+ else
+ two = two or ""
+ return Cs((P(one)/two + 1)^0)
+ end
+end
+
+local splitters_f, splitters_s = { }, { }
+
+function lpeg.firstofsplit(separator) -- always return value
+ local splitter = splitters_f[separator]
+ if not splitter then
+ separator = P(separator)
+ splitter = C((1 - separator)^0)
+ splitters_f[separator] = splitter
+ end
+ return splitter
+end
+
+function lpeg.secondofsplit(separator) -- nil if not split
+ local splitter = splitters_s[separator]
+ if not splitter then
+ separator = P(separator)
+ splitter = (1 - separator)^0 * separator * C(anything^0)
+ splitters_s[separator] = splitter
+ end
+ return splitter
+end
+
+function lpeg.balancer(left,right)
+ left, right = P(left), P(right)
+ return P { left * ((1 - left - right) + V(1))^0 * right }
+end
+
+--~ print(1,match(lpeg.firstofsplit(":"),"bc:de"))
+--~ print(2,match(lpeg.firstofsplit(":"),":de")) -- empty
+--~ print(3,match(lpeg.firstofsplit(":"),"bc"))
+--~ print(4,match(lpeg.secondofsplit(":"),"bc:de"))
+--~ print(5,match(lpeg.secondofsplit(":"),"bc:")) -- empty
+--~ print(6,match(lpeg.secondofsplit(":",""),"bc"))
+--~ print(7,match(lpeg.secondofsplit(":"),"bc"))
+--~ print(9,match(lpeg.secondofsplit(":","123"),"bc"))
+
+--~ -- slower:
+--~
+--~ function lpeg.counter(pattern)
+--~ local n, pattern = 0, (lpeg.P(pattern)/function() n = n + 1 end + lpeg.anything)^0
+--~ return function(str) n = 0 ; lpegmatch(pattern,str) ; return n end
+--~ end
+
+local nany = utf8char/""
+
+function lpeg.counter(pattern)
+ pattern = Cs((P(pattern)/" " + nany)^0)
+ return function(str)
+ return #match(pattern,str)
+ end
+end
+
+if utfgmatch then
+
+ function lpeg.count(str,what) -- replaces string.count
+ if type(what) == "string" then
+ local n = 0
+ for _ in utfgmatch(str,what) do
+ n = n + 1
+ end
+ return n
+ else -- 4 times slower but still faster than / function
+ return #match(Cs((P(what)/" " + nany)^0),str)
+ end
+ end
+
+else
+
+ local cache = { }
+
+ function lpeg.count(str,what) -- replaces string.count
+ if type(what) == "string" then
+ local p = cache[what]
+ if not p then
+ p = Cs((P(what)/" " + nany)^0)
+ cache[p] = p
+ end
+ return #match(p,str)
+ else -- 4 times slower but still faster than / function
+ return #match(Cs((P(what)/" " + nany)^0),str)
+ end
+ end
+
+end
+
+local patterns_escapes = { -- also defines in l-string
+ ["%"] = "%%",
+ ["."] = "%.",
+ ["+"] = "%+", ["-"] = "%-", ["*"] = "%*",
+ ["["] = "%[", ["]"] = "%]",
+ ["("] = "%)", [")"] = "%)",
+ -- ["{"] = "%{", ["}"] = "%}"
+ -- ["^"] = "%^", ["$"] = "%$",
+}
+
+local simple_escapes = { -- also defines in l-string
+ ["-"] = "%-",
+ ["."] = "%.",
+ ["?"] = ".",
+ ["*"] = ".*",
+}
+
+local p = Cs((S("-.+*%()[]") / patterns_escapes + anything)^0)
+local s = Cs((S("-.+*%()[]") / simple_escapes + anything)^0)
+
+function string.escapedpattern(str,simple)
+ return match(simple and s or p,str)
+end
+
+-- utf extensies
+
+lpeg.UP = lpeg.P
+
+if utfcharacters then
+
+ function lpeg.US(str)
+ local p
+ for uc in utfcharacters(str) do
+ if p then
+ p = p + P(uc)
+ else
+ p = P(uc)
+ end
+ end
+ return p
+ end
+
+
+elseif utfgmatch then
+
+ function lpeg.US(str)
+ local p
+ for uc in utfgmatch(str,".") do
+ if p then
+ p = p + P(uc)
+ else
+ p = P(uc)
+ end
+ end
+ return p
+ end
+
+else
+
+ function lpeg.US(str)
+ local p
+ local f = function(uc)
+ if p then
+ p = p + P(uc)
+ else
+ p = P(uc)
+ end
+ end
+ match((utf8char/f)^0,str)
+ return p
+ end
+
+end
+
+local range = Cs(utf8byte) * (Cs(utf8byte) + Cc(false))
+
+local utfchar = unicode and unicode.utf8 and unicode.utf8.char
+
+function lpeg.UR(str,more)
+ local first, last
+ if type(str) == "number" then
+ first = str
+ last = more or first
+ else
+ first, last = match(range,str)
+ if not last then
+ return P(str)
+ end
+ end
+ if first == last then
+ return P(str)
+ elseif utfchar and last - first < 8 then -- a somewhat arbitrary criterium
+ local p
+ for i=first,last do
+ if p then
+ p = p + P(utfchar(i))
+ else
+ p = P(utfchar(i))
+ end
+ end
+ return p -- nil when invalid range
+ else
+ local f = function(b)
+ return b >= first and b <= last
+ end
+ return utf8byte / f -- nil when invalid range
+ end
+end
+
+--~ lpeg.print(lpeg.R("ab","cd","gh"))
+--~ lpeg.print(lpeg.P("a","b","c"))
+--~ lpeg.print(lpeg.S("a","b","c"))
+
+--~ print(lpeg.count("äáàa",lpeg.P("á") + lpeg.P("à")))
+--~ print(lpeg.count("äáàa",lpeg.UP("áà")))
+--~ print(lpeg.count("äáàa",lpeg.US("àá")))
+--~ print(lpeg.count("äáàa",lpeg.UR("aá")))
+--~ print(lpeg.count("äáàa",lpeg.UR("àá")))
+--~ print(lpeg.count("äáàa",lpeg.UR(0x0000,0xFFFF)))
+
+function lpeg.oneof(list,...) -- lpeg.oneof("elseif","else","if","then")
+ if type(list) ~= "table" then
+ list = { list, ... }
+ end
+ -- sort(list) -- longest match first
+ local p = P(list[1])
+ for l=2,#list do
+ p = p + P(list[l])
+ end
+ return p
+end
+
+function lpeg.is_lpeg(p)
+ return p and lpegtype(p) == "pattern"
+end
+
+--~ Cf(Ct("") * (Cg(C(...) * "=" * Cs(...)))^0, rawset)
end -- closure
@@ -475,12 +681,17 @@ if not modules then modules = { } end modules ['l-boolean'] = {
license = "see context related readme files"
}
+local type, tonumber = type, tonumber
+
boolean = boolean or { }
+local boolean = boolean
-local type, tonumber = type, tonumber
+-- function boolean.tonumber(b)
+-- return b and 1 or 0 -- test and test and return or return
+-- end
function boolean.tonumber(b)
- if b then return 1 else return 0 end
+ if b then return 1 else return 0 end -- test and return or return
end
function toboolean(str,tolerant)
@@ -504,7 +715,9 @@ function toboolean(str,tolerant)
end
end
-function string.is_boolean(str)
+string.toboolean = toboolean
+
+function string.is_boolean(str,default)
if type(str) == "string" then
if str == "true" or str == "yes" or str == "on" or str == "t" then
return true
@@ -512,15 +725,7 @@ function string.is_boolean(str)
return false
end
end
- return nil
-end
-
-function boolean.alwaystrue()
- return true
-end
-
-function boolean.falsetrue()
- return false
+ return default
end
end -- closure
@@ -538,35 +743,28 @@ if not modules then modules = { } end modules ['l-math'] = {
local floor, sin, cos, tan = math.floor, math.sin, math.cos, math.tan
if not math.round then
- function math.round(x)
- return floor(x + 0.5)
- end
+ function math.round(x) return floor(x + 0.5) end
end
if not math.div then
- function math.div(n,m)
- return floor(n/m)
- end
+ function math.div(n,m) return floor(n/m) end
end
if not math.mod then
- function math.mod(n,m)
- return n % m
- end
+ function math.mod(n,m) return n % m end
end
local pipi = 2*math.pi/360
-function math.sind(d)
- return sin(d*pipi)
-end
-
-function math.cosd(d)
- return cos(d*pipi)
+if not math.sind then
+ function math.sind(d) return sin(d*pipi) end
+ function math.cosd(d) return cos(d*pipi) end
+ function math.tand(d) return tan(d*pipi) end
end
-function math.tand(d)
- return tan(d*pipi)
+if not math.odd then
+ function math.odd (n) return n % 2 == 0 end
+ function math.even(n) return n % 2 ~= 0 end
end
end -- closure
@@ -581,12 +779,10 @@ if not modules then modules = { } end modules ['l-table'] = {
license = "see context related readme files"
}
-table.join = table.concat
-
+local type, next, tostring, tonumber, ipairs, table, string = type, next, tostring, tonumber, ipairs, table, string
local concat, sort, insert, remove = table.concat, table.sort, table.insert, table.remove
local format, find, gsub, lower, dump, match = string.format, string.find, string.gsub, string.lower, string.dump, string.match
local getmetatable, setmetatable = getmetatable, setmetatable
-local type, next, tostring, tonumber, ipairs = type, next, tostring, tonumber, ipairs
-- Starting with version 5.2 Lua no longer provide ipairs, which makes
-- sense. As we already used the for loop and # in most places the
@@ -638,50 +834,56 @@ end
-- extra functions, some might go (when not used)
function table.strip(tab)
- local lst = { }
+ local lst, l = { }, 0
for i=1,#tab do
local s = gsub(tab[i],"^%s*(.-)%s*$","%1")
if s == "" then
-- skip this one
else
- lst[#lst+1] = s
+ l = l + 1
+ lst[l] = s
end
end
return lst
end
function table.keys(t)
- local k = { }
+ local keys, k = { }, 0
for key, _ in next, t do
- k[#k+1] = key
+ k = k + 1
+ keys[k] = key
end
- return k
+ return keys
end
local function compare(a,b)
- return (tostring(a) < tostring(b))
+ local ta, tb = type(a), type(b) -- needed, else 11 < 2
+ if ta == tb then
+ return a < b
+ else
+ return tostring(a) < tostring(b)
+ end
end
local function sortedkeys(tab)
- local srt, kind = { }, 0 -- 0=unknown 1=string, 2=number 3=mixed
+ local srt, category, s = { }, 0, 0 -- 0=unknown 1=string, 2=number 3=mixed
for key,_ in next, tab do
- srt[#srt+1] = key
- if kind == 3 then
+ s = s + 1
+ srt[s] = key
+ if category == 3 then
-- no further check
else
local tkey = type(key)
if tkey == "string" then
- -- if kind == 2 then kind = 3 else kind = 1 end
- kind = (kind == 2 and 3) or 1
+ category = (category == 2 and 3) or 1
elseif tkey == "number" then
- -- if kind == 1 then kind = 3 else kind = 2 end
- kind = (kind == 1 and 3) or 2
+ category = (category == 1 and 3) or 2
else
- kind = 3
+ category = 3
end
end
end
- if kind == 0 or kind == 3 then
+ if category == 0 or category == 3 then
sort(srt,compare)
else
sort(srt)
@@ -690,9 +892,12 @@ local function sortedkeys(tab)
end
local function sortedhashkeys(tab) -- fast one
- local srt = { }
+ local srt, s = { }, 0
for key,_ in next, tab do
- srt[#srt+1] = key
+ if key then
+ s= s + 1
+ srt[s] = key
+ end
end
sort(srt)
return srt
@@ -701,34 +906,50 @@ end
table.sortedkeys = sortedkeys
table.sortedhashkeys = sortedhashkeys
-function table.sortedhash(t)
- local s = sortedhashkeys(t) -- maybe just sortedkeys
- local n = 0
- local function kv(s)
- n = n + 1
- local k = s[n]
- return k, t[k]
+local function nothing() end
+
+local function sortedhash(t)
+ if t then
+ local n, s = 0, sortedkeys(t) -- the robust one
+ local function kv(s)
+ n = n + 1
+ local k = s[n]
+ return k, t[k]
+ end
+ return kv, s
+ else
+ return nothing
end
- return kv, s
end
-table.sortedpairs = table.sortedhash
+table.sortedhash = sortedhash
+table.sortedpairs = sortedhash
function table.append(t, list)
- for _,v in next, list do
- insert(t,v)
+ local n = #t
+ for i=1,#list do
+ n = n + 1
+ t[n] = list[i]
end
+ return t
end
function table.prepend(t, list)
- for k,v in next, list do
- insert(t,k,v)
+ local nl = #list
+ local nt = nl + #t
+ for i=#t,1,-1 do
+ t[nt] = t[i]
+ nt = nt - 1
+ end
+ for i=1,#list do
+ t[i] = list[i]
end
+ return t
end
function table.merge(t, ...) -- first one is target
- t = t or {}
- local lst = {...}
+ t = t or { }
+ local lst = { ... }
for i=1,#lst do
for k, v in next, lst[i] do
t[k] = v
@@ -738,7 +959,7 @@ function table.merge(t, ...) -- first one is target
end
function table.merged(...)
- local tmp, lst = { }, {...}
+ local tmp, lst = { }, { ... }
for i=1,#lst do
for k, v in next, lst[i] do
tmp[k] = v
@@ -748,41 +969,45 @@ function table.merged(...)
end
function table.imerge(t, ...)
- local lst = {...}
+ local lst, nt = { ... }, #t
for i=1,#lst do
local nst = lst[i]
for j=1,#nst do
- t[#t+1] = nst[j]
+ nt = nt + 1
+ t[nt] = nst[j]
end
end
return t
end
function table.imerged(...)
- local tmp, lst = { }, {...}
+ local tmp, ntmp, lst = { }, 0, {...}
for i=1,#lst do
local nst = lst[i]
for j=1,#nst do
- tmp[#tmp+1] = nst[j]
+ ntmp = ntmp + 1
+ tmp[ntmp] = nst[j]
end
end
return tmp
end
-local function fastcopy(old) -- fast one
+local function fastcopy(old,metatabletoo) -- fast one
if old then
local new = { }
for k,v in next, old do
if type(v) == "table" then
- new[k] = fastcopy(v) -- was just table.copy
+ new[k] = fastcopy(v,metatabletoo) -- was just table.copy
else
new[k] = v
end
end
- -- optional second arg
- local mt = getmetatable(old)
- if mt then
- setmetatable(new,mt)
+ if metatabletoo then
+ -- optional second arg
+ local mt = getmetatable(old)
+ if mt then
+ setmetatable(new,mt)
+ end
end
return new
else
@@ -790,6 +1015,8 @@ local function fastcopy(old) -- fast one
end
end
+-- todo : copy without metatable
+
local function copy(t, tables) -- taken from lua wiki, slightly adapted
tables = tables or { }
local tcopy = {}
@@ -822,33 +1049,14 @@ end
table.fastcopy = fastcopy
table.copy = copy
--- roughly: copy-loop : unpack : sub == 0.9 : 0.4 : 0.45 (so in critical apps, use unpack)
-
-function table.sub(t,i,j)
- return { unpack(t,i,j) }
-end
-
-function table.replace(a,b)
- for k,v in next, b do
- a[k] = v
+function table.derive(parent)
+ local child = { }
+ if parent then
+ setmetatable(child,{ __index = parent })
end
+ return child
end
--- slower than #t on indexed tables (#t only returns the size of the numerically indexed slice)
-
-function table.is_empty(t) -- obolete, use inline code instead
- return not t or not next(t)
-end
-
-function table.one_entry(t) -- obolete, use inline code instead
- local n = next(t)
- return n and not next(t,n)
-end
-
---~ function table.starts_at(t) -- obsolete, not nice anyway
---~ return ipairs(t,1)(t,0)
---~ end
-
function table.tohash(t,value)
local h = { }
if t then
@@ -861,27 +1069,19 @@ function table.tohash(t,value)
end
function table.fromhash(t)
- local h = { }
+ local hsh, h = { }, 0
for k, v in next, t do -- no ipairs here
- if v then h[#h+1] = k end
+ if v then
+ h = h + 1
+ hsh[h] = k
+ end
end
- return h
+ return hsh
end
---~ print(table.serialize(t), "\n")
---~ print(table.serialize(t,"name"), "\n")
---~ print(table.serialize(t,false), "\n")
---~ print(table.serialize(t,true), "\n")
---~ print(table.serialize(t,"name",true), "\n")
---~ print(table.serialize(t,"name",true,true), "\n")
-
-table.serialize_functions = true
-table.serialize_compact = true
-table.serialize_inline = true
-
local noquotes, hexify, handle, reduce, compact, inline, functions
-local reserved = table.tohash { -- intercept a language flaw, no reserved words as key
+local reserved = table.tohash { -- intercept a language inconvenience: no reserved words as key
'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for', 'function', 'if',
'in', 'local', 'nil', 'not', 'or', 'repeat', 'return', 'then', 'true', 'until', 'while',
}
@@ -893,20 +1093,23 @@ local function simple_table(t)
n = n + 1
end
if n == #t then
- local tt = { }
+ local tt, nt = { }, 0
for i=1,#t do
local v = t[i]
local tv = type(v)
if tv == "number" then
+ nt = nt + 1
if hexify then
- tt[#tt+1] = format("0x%04X",v)
+ tt[nt] = format("0x%04X",v)
else
- tt[#tt+1] = tostring(v) -- tostring not needed
+ tt[nt] = tostring(v) -- tostring not needed
end
elseif tv == "boolean" then
- tt[#tt+1] = tostring(v)
+ nt = nt + 1
+ tt[nt] = tostring(v)
elseif tv == "string" then
- tt[#tt+1] = format("%q",v)
+ nt = nt + 1
+ tt[nt] = format("%q",v)
else
tt = nil
break
@@ -932,21 +1135,25 @@ local function do_serialize(root,name,depth,level,indexed)
depth = depth .. " "
if indexed then
handle(format("%s{",depth))
- elseif name then
- --~ handle(format("%s%s={",depth,key(name)))
- if type(name) == "number" then -- or find(k,"^%d+$") then
+ else
+ local tn = type(name)
+ if tn == "number" then -- or find(k,"^%d+$") then
if hexify then
handle(format("%s[0x%04X]={",depth,name))
else
handle(format("%s[%s]={",depth,name))
end
- elseif noquotes and not reserved[name] and find(name,"^%a[%w%_]*$") then
- handle(format("%s%s={",depth,name))
+ elseif tn == "string" then
+ if noquotes and not reserved[name] and find(name,"^%a[%w%_]*$") then
+ handle(format("%s%s={",depth,name))
+ else
+ handle(format("%s[%q]={",depth,name))
+ end
+ elseif tn == "boolean" then
+ handle(format("%s[%s]={",depth,tostring(name)))
else
- handle(format("%s[%q]={",depth,name))
+ handle(format("%s{",depth))
end
- else
- handle(format("%s{",depth))
end
end
-- we could check for k (index) being number (cardinal)
@@ -966,8 +1173,8 @@ local function do_serialize(root,name,depth,level,indexed)
--~ if v == root then
-- circular
--~ else
- local t = type(v)
- if compact and first and type(k) == "number" and k >= first and k <= last then
+ local t, tk = type(v), type(k)
+ if compact and first and tk == "number" and k >= first and k <= last then
if t == "number" then
if hexify then
handle(format("%s 0x%04X,",depth,v))
@@ -1009,17 +1216,18 @@ local function do_serialize(root,name,depth,level,indexed)
handle(format("%s __p__=nil,",depth))
end
elseif t == "number" then
- --~ if hexify then
- --~ handle(format("%s %s=0x%04X,",depth,key(k),v))
- --~ else
- --~ handle(format("%s %s=%s,",depth,key(k),v)) -- %.99g
- --~ end
- if type(k) == "number" then -- or find(k,"^%d+$") then
+ if tk == "number" then -- or find(k,"^%d+$") then
if hexify then
handle(format("%s [0x%04X]=0x%04X,",depth,k,v))
else
handle(format("%s [%s]=%s,",depth,k,v)) -- %.99g
end
+ elseif tk == "boolean" then
+ if hexify then
+ handle(format("%s [%s]=0x%04X,",depth,tostring(k),v))
+ else
+ handle(format("%s [%s]=%s,",depth,tostring(k),v)) -- %.99g
+ end
elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
if hexify then
handle(format("%s %s=0x%04X,",depth,k,v))
@@ -1035,26 +1243,28 @@ local function do_serialize(root,name,depth,level,indexed)
end
elseif t == "string" then
if reduce and tonumber(v) then
- --~ handle(format("%s %s=%s,",depth,key(k),v))
- if type(k) == "number" then -- or find(k,"^%d+$") then
+ if tk == "number" then -- or find(k,"^%d+$") then
if hexify then
handle(format("%s [0x%04X]=%s,",depth,k,v))
else
handle(format("%s [%s]=%s,",depth,k,v))
end
+ elseif tk == "boolean" then
+ handle(format("%s [%s]=%s,",depth,tostring(k),v))
elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
handle(format("%s %s=%s,",depth,k,v))
else
handle(format("%s [%q]=%s,",depth,k,v))
end
else
- --~ handle(format("%s %s=%q,",depth,key(k),v))
- if type(k) == "number" then -- or find(k,"^%d+$") then
+ if tk == "number" then -- or find(k,"^%d+$") then
if hexify then
handle(format("%s [0x%04X]=%q,",depth,k,v))
else
handle(format("%s [%s]=%q,",depth,k,v))
end
+ elseif tk == "boolean" then
+ handle(format("%s [%s]=%q,",depth,tostring(k),v))
elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
handle(format("%s %s=%q,",depth,k,v))
else
@@ -1063,13 +1273,14 @@ local function do_serialize(root,name,depth,level,indexed)
end
elseif t == "table" then
if not next(v) then
- --~ handle(format("%s %s={},",depth,key(k)))
- if type(k) == "number" then -- or find(k,"^%d+$") then
+ if tk == "number" then -- or find(k,"^%d+$") then
if hexify then
handle(format("%s [0x%04X]={},",depth,k))
else
handle(format("%s [%s]={},",depth,k))
end
+ elseif tk == "boolean" then
+ handle(format("%s [%s]={},",depth,tostring(k)))
elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
handle(format("%s %s={},",depth,k))
else
@@ -1078,13 +1289,14 @@ local function do_serialize(root,name,depth,level,indexed)
elseif inline then
local st = simple_table(v)
if st then
- --~ handle(format("%s %s={ %s },",depth,key(k),concat(st,", ")))
- if type(k) == "number" then -- or find(k,"^%d+$") then
+ if tk == "number" then -- or find(k,"^%d+$") then
if hexify then
handle(format("%s [0x%04X]={ %s },",depth,k,concat(st,", ")))
else
handle(format("%s [%s]={ %s },",depth,k,concat(st,", ")))
end
+ elseif tk == "boolean" then -- or find(k,"^%d+$") then
+ handle(format("%s [%s]={ %s },",depth,tostring(k),concat(st,", ")))
elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
handle(format("%s %s={ %s },",depth,k,concat(st,", ")))
else
@@ -1097,13 +1309,14 @@ local function do_serialize(root,name,depth,level,indexed)
do_serialize(v,k,depth,level+1)
end
elseif t == "boolean" then
- --~ handle(format("%s %s=%s,",depth,key(k),tostring(v)))
- if type(k) == "number" then -- or find(k,"^%d+$") then
+ if tk == "number" then -- or find(k,"^%d+$") then
if hexify then
handle(format("%s [0x%04X]=%s,",depth,k,tostring(v)))
else
handle(format("%s [%s]=%s,",depth,k,tostring(v)))
end
+ elseif tk == "boolean" then -- or find(k,"^%d+$") then
+ handle(format("%s [%s]=%s,",depth,tostring(k),tostring(v)))
elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
handle(format("%s %s=%s,",depth,k,tostring(v)))
else
@@ -1111,13 +1324,14 @@ local function do_serialize(root,name,depth,level,indexed)
end
elseif t == "function" then
if functions then
- --~ handle(format('%s %s=loadstring(%q),',depth,key(k),dump(v)))
- if type(k) == "number" then -- or find(k,"^%d+$") then
+ if tk == "number" then -- or find(k,"^%d+$") then
if hexify then
handle(format("%s [0x%04X]=loadstring(%q),",depth,k,dump(v)))
else
handle(format("%s [%s]=loadstring(%q),",depth,k,dump(v)))
end
+ elseif tk == "boolean" then
+ handle(format("%s [%s]=loadstring(%q),",depth,tostring(k),dump(v)))
elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
handle(format("%s %s=loadstring(%q),",depth,k,dump(v)))
else
@@ -1125,13 +1339,14 @@ local function do_serialize(root,name,depth,level,indexed)
end
end
else
- --~ handle(format("%s %s=%q,",depth,key(k),tostring(v)))
- if type(k) == "number" then -- or find(k,"^%d+$") then
+ if tk == "number" then -- or find(k,"^%d+$") then
if hexify then
handle(format("%s [0x%04X]=%q,",depth,k,tostring(v)))
else
handle(format("%s [%s]=%q,",depth,k,tostring(v)))
end
+ elseif tk == "boolean" then -- or find(k,"^%d+$") then
+ handle(format("%s [%s]=%q,",depth,tostring(k),tostring(v)))
elseif noquotes and not reserved[k] and find(k,"^%a[%w%_]*$") then
handle(format("%s %s=%q,",depth,k,tostring(v)))
else
@@ -1149,15 +1364,34 @@ end
-- replacing handle by a direct t[#t+1] = ... (plus test) is not much
-- faster (0.03 on 1.00 for zapfino.tma)
-local function serialize(root,name,_handle,_reduce,_noquotes,_hexify)
- noquotes = _noquotes
- hexify = _hexify
- handle = _handle or print
- reduce = _reduce or false
- compact = table.serialize_compact
- inline = compact and table.serialize_inline
- functions = table.serialize_functions
+local function serialize(_handle,root,name,specification) -- handle wins
local tname = type(name)
+ if type(specification) == "table" then
+ noquotes = specification.noquotes
+ hexify = specification.hexify
+ handle = _handle or specification.handle or print
+ reduce = specification.reduce or false
+ functions = specification.functions
+ compact = specification.compact
+ inline = specification.inline and compact
+ if functions == nil then
+ functions = true
+ end
+ if compact == nil then
+ compact = true
+ end
+ if inline == nil then
+ inline = compact
+ end
+ else
+ noquotes = false
+ hexify = false
+ handle = _handle or print
+ reduce = false
+ compact = true
+ inline = true
+ functions = true
+ end
if tname == "string" then
if name == "return" then
handle("return {")
@@ -1179,8 +1413,17 @@ local function serialize(root,name,_handle,_reduce,_noquotes,_hexify)
else
handle("t={")
end
- if root and next(root) then
- do_serialize(root,name,"",0,indexed)
+ if root then
+ -- The dummy access will initialize a table that has a delayed initialization
+ -- using a metatable. (maybe explicitly test for metatable)
+ if getmetatable(root) then -- todo: make this an option, maybe even per subtable
+ local dummy = root._w_h_a_t_e_v_e_r_
+ root._w_h_a_t_e_v_e_r_ = nil
+ end
+ -- Let's forget about empty tables.
+ if next(root) then
+ do_serialize(root,name,"",0)
+ end
end
handle("}")
end
@@ -1194,18 +1437,17 @@ end
--~ 'return' : return { }
--~ number : [number] = { }
-function table.serialize(root,name,reduce,noquotes,hexify)
- local t = { }
+function table.serialize(root,name,specification)
+ local t, n = { }, 0
local function flush(s)
- t[#t+1] = s
+ n = n + 1
+ t[n] = s
end
- serialize(root,name,flush,reduce,noquotes,hexify)
+ serialize(flush,root,name,specification)
return concat(t,"\n")
end
-function table.tohandle(handle,root,name,reduce,noquotes,hexify)
- serialize(root,name,handle,reduce,noquotes,hexify)
-end
+table.tohandle = serialize
-- sometimes tables are real use (zapfino extra pro is some 85M) in which
-- case a stepwise serialization is nice; actually, we could consider:
@@ -1216,73 +1458,63 @@ end
--
-- so this is on the todo list
-table.tofile_maxtab = 2*1024
+local maxtab = 2*1024
-function table.tofile(filename,root,name,reduce,noquotes,hexify)
+function table.tofile(filename,root,name,specification)
local f = io.open(filename,'w')
if f then
- local maxtab = table.tofile_maxtab
if maxtab > 1 then
- local t = { }
+ local t, n = { }, 0
local function flush(s)
- t[#t+1] = s
- if #t > maxtab then
+ n = n + 1
+ t[n] = s
+ if n > maxtab then
f:write(concat(t,"\n"),"\n") -- hm, write(sometable) should be nice
- t = { }
+ t, n = { }, 0 -- we could recycle t if needed
end
end
- serialize(root,name,flush,reduce,noquotes,hexify)
+ serialize(flush,root,name,specification)
f:write(concat(t,"\n"),"\n")
else
local function flush(s)
f:write(s,"\n")
end
- serialize(root,name,flush,reduce,noquotes,hexify)
+ serialize(flush,root,name,specification)
end
f:close()
+ io.flush()
end
end
-local function flatten(t,f,complete) -- is this used? meybe a variant with next, ...
- for i=1,#t do
- local v = t[i]
- if type(v) == "table" then
- if complete or type(v[1]) == "table" then
- flatten(v,f,complete)
+local function flattened(t,f,depth)
+ if f == nil then
+ f = { }
+ depth = 0xFFFF
+ elseif tonumber(f) then
+ -- assume then only two arguments are given
+ depth = f
+ f = { }
+ elseif not depth then
+ depth = 0xFFFF
+ end
+ for k, v in next, t do
+ if type(k) ~= "number" then
+ if depth > 0 and type(v) == "table" then
+ flattened(v,f,depth-1)
else
- f[#f+1] = v
+ f[k] = v
end
- else
- f[#f+1] = v
end
end
-end
-
-function table.flatten(t)
- local f = { }
- flatten(t,f,true)
- return f
-end
-
-function table.unnest(t) -- bad name
- local f = { }
- flatten(t,f,false)
- return f
-end
-
-table.flatten_one_level = table.unnest
-
--- a better one:
-
-local function flattened(t,f)
- if not f then
- f = { }
- end
- for k, v in next, t do
- if type(v) == "table" then
- flattened(v,f)
+ local n = #f
+ for k=1,#t do
+ local v = t[k]
+ if depth > 0 and type(v) == "table" then
+ flattened(v,f,depth-1)
+ n = #f
else
- f[k] = v
+ n = n + 1
+ f[n] = v
end
end
return f
@@ -1290,49 +1522,27 @@ end
table.flattened = flattened
--- the next three may disappear
-
-function table.remove_value(t,value) -- todo: n
- if value then
- for i=1,#t do
- if t[i] == value then
- remove(t,i)
- -- remove all, so no: return
- end
- end
+local function unnest(t,f) -- only used in mk, for old times sake
+ if not f then -- and only relevant for token lists
+ f = { }
end
-end
-
-function table.insert_before_value(t,value,str)
- if str then
- if value then
- for i=1,#t do
- if t[i] == value then
- insert(t,i,str)
- return
- end
+ for i=1,#t do
+ local v = t[i]
+ if type(v) == "table" then
+ if type(v[1]) == "table" then
+ unnest(v,f)
+ else
+ f[#f+1] = v
end
+ else
+ f[#f+1] = v
end
- insert(t,1,str)
- elseif value then
- insert(t,1,value)
end
+ return f
end
-function table.insert_after_value(t,value,str)
- if str then
- if value then
- for i=1,#t do
- if t[i] == value then
- insert(t,i+1,str)
- return
- end
- end
- end
- t[#t+1] = str
- elseif value then
- t[#t+1] = value
- end
+function table.unnest(t) -- bad name
+ return unnest(t)
end
local function are_equal(a,b,n,m) -- indexed
@@ -1359,7 +1569,7 @@ end
local function identical(a,b) -- assumes same structure
for ka, va in next, a do
- local vb = b[k]
+ local vb = b[ka]
if va == vb then
-- same
elseif type(va) == "table" and type(vb) == "table" then
@@ -1373,8 +1583,8 @@ local function identical(a,b) -- assumes same structure
return true
end
-table.are_equal = are_equal
table.identical = identical
+table.are_equal = are_equal
-- maybe also make a combined one
@@ -1400,89 +1610,93 @@ function table.contains(t, v)
end
function table.count(t)
- local n, e = 0, next(t)
- while e do
- n, e = n + 1, next(t,e)
+ local n = 0
+ for k, v in next, t do
+ n = n + 1
end
return n
end
-function table.swapped(t)
- local s = { }
+function table.swapped(t,s) -- hash
+ local n = { }
+ if s then
+--~ for i=1,#s do
+--~ n[i] = s[i]
+--~ end
+ for k, v in next, s do
+ n[k] = v
+ end
+ end
+--~ for i=1,#t do
+--~ local ti = t[i] -- don't ask but t[i] can be nil
+--~ if ti then
+--~ n[ti] = i
+--~ end
+--~ end
for k, v in next, t do
- s[v] = k
+ n[v] = k
end
- return s
+ return n
end
---~ function table.are_equal(a,b)
---~ return table.serialize(a) == table.serialize(b)
---~ end
-
-function table.clone(t,p) -- t is optional or nil or table
- if not p then
- t, p = { }, t or { }
- elseif not t then
- t = { }
+function table.reversed(t)
+ if t then
+ local tt, tn = { }, #t
+ if tn > 0 then
+ local ttn = 0
+ for i=tn,1,-1 do
+ ttn = ttn + 1
+ tt[ttn] = t[i]
+ end
+ end
+ return tt
end
- setmetatable(t, { __index = function(_,key) return p[key] end }) -- why not __index = p ?
- return t
-end
-
-function table.hexed(t,seperator)
- local tt = { }
- for i=1,#t do tt[i] = format("0x%04X",t[i]) end
- return concat(tt,seperator or " ")
end
-function table.reverse_hash(h)
- local r = { }
- for k,v in next, h do
- r[v] = lower(gsub(k," ",""))
+function table.sequenced(t,sep,simple) -- hash only
+ local s, n = { }, 0
+ for k, v in sortedhash(t) do
+ if simple then
+ if v == true then
+ n = n + 1
+ s[n] = k
+ elseif v and v~= "" then
+ n = n + 1
+ s[n] = k .. "=" .. tostring(v)
+ end
+ else
+ n = n + 1
+ s[n] = k .. "=" .. tostring(v)
+ end
end
- return r
+ return concat(s, sep or " | ")
end
-function table.reverse(t)
- local tt = { }
- if #t > 0 then
- for i=#t,1,-1 do
- tt[#tt+1] = t[i]
- end
+function table.print(t,...)
+ if type(t) ~= "table" then
+ print(tostring(t))
+ else
+ table.tohandle(print,t,...)
end
- return tt
end
-function table.insert_before_value(t,value,extra)
- for i=1,#t do
- if t[i] == extra then
- remove(t,i)
- end
- end
- for i=1,#t do
- if t[i] == value then
- insert(t,i,extra)
- return
- end
- end
- insert(t,1,extra)
+-- -- -- obsolete but we keep them for a while and might comment them later -- -- --
+
+-- roughly: copy-loop : unpack : sub == 0.9 : 0.4 : 0.45 (so in critical apps, use unpack)
+
+function table.sub(t,i,j)
+ return { unpack(t,i,j) }
end
-function table.insert_after_value(t,value,extra)
- for i=1,#t do
- if t[i] == extra then
- remove(t,i)
- end
- end
- for i=1,#t do
- if t[i] == value then
- insert(t,i+1,extra)
- return
- end
- end
- insert(t,#t+1,extra)
+-- slower than #t on indexed tables (#t only returns the size of the numerically indexed slice)
+
+function table.is_empty(t)
+ return not t or not next(t)
end
+function table.has_one_entry(t)
+ return t and not next(t,next(t))
+end
end -- closure
@@ -1498,47 +1712,93 @@ if not modules then modules = { } end modules ['l-file'] = {
-- needs a cleanup
-file = file or { }
+file = file or { }
+local file = file
-local concat = table.concat
-local find, gmatch, match, gsub, sub, char = string.find, string.gmatch, string.match, string.gsub, string.sub, string.char
+local insert, concat = table.insert, table.concat
+local find, gmatch, match, gsub, sub, char, lower = string.find, string.gmatch, string.match, string.gsub, string.sub, string.char, string.lower
local lpegmatch = lpeg.match
+local getcurrentdir, attributes = lfs.currentdir, lfs.attributes
-function file.removesuffix(filename)
- return (gsub(filename,"%.[%a%d]+$",""))
+local P, R, S, C, Cs, Cp, Cc = lpeg.P, lpeg.R, lpeg.S, lpeg.C, lpeg.Cs, lpeg.Cp, lpeg.Cc
+
+local function dirname(name,default)
+ return match(name,"^(.+)[/\\].-$") or (default or "")
end
-function file.addsuffix(filename, suffix)
- if not suffix or suffix == "" then
- return filename
- elseif not find(filename,"%.[%a%d]+$") then
- return filename .. "." .. suffix
- else
- return filename
- end
+local function basename(name)
+ return match(name,"^.+[/\\](.-)$") or name
end
-function file.replacesuffix(filename, suffix)
- return (gsub(filename,"%.[%a%d]+$","")) .. "." .. suffix
+local function nameonly(name)
+ return (gsub(match(name,"^.+[/\\](.-)$") or name,"%..*$",""))
end
-function file.dirname(name,default)
- return match(name,"^(.+)[/\\].-$") or (default or "")
+local function extname(name,default)
+ return match(name,"^.+%.([^/\\]-)$") or default or ""
end
-function file.basename(name)
- return match(name,"^.+[/\\](.-)$") or name
+local function splitname(name)
+ local n, s = match(name,"^(.+)%.([^/\\]-)$")
+ return n or name, s or ""
end
-function file.nameonly(name)
- return (gsub(match(name,"^.+[/\\](.-)$") or name,"%..*$",""))
+file.basename = basename
+file.dirname = dirname
+file.nameonly = nameonly
+file.extname = extname
+file.suffix = extname
+
+function file.removesuffix(filename)
+ return (gsub(filename,"%.[%a%d]+$",""))
end
-function file.extname(name,default)
- return match(name,"^.+%.([^/\\]-)$") or default or ""
+function file.addsuffix(filename, suffix, criterium)
+ if not suffix or suffix == "" then
+ return filename
+ elseif criterium == true then
+ return filename .. "." .. suffix
+ elseif not criterium then
+ local n, s = splitname(filename)
+ if not s or s == "" then
+ return filename .. "." .. suffix
+ else
+ return filename
+ end
+ else
+ local n, s = splitname(filename)
+ if s and s ~= "" then
+ local t = type(criterium)
+ if t == "table" then
+ -- keep if in criterium
+ for i=1,#criterium do
+ if s == criterium[i] then
+ return filename
+ end
+ end
+ elseif t == "string" then
+ -- keep if criterium
+ if s == criterium then
+ return filename
+ end
+ end
+ end
+ return n .. "." .. suffix
+ end
end
-file.suffix = file.extname
+--~ print("1 " .. file.addsuffix("name","new") .. " -> name.new")
+--~ print("2 " .. file.addsuffix("name.old","new") .. " -> name.old")
+--~ print("3 " .. file.addsuffix("name.old","new",true) .. " -> name.old.new")
+--~ print("4 " .. file.addsuffix("name.old","new","new") .. " -> name.new")
+--~ print("5 " .. file.addsuffix("name.old","new","old") .. " -> name.old")
+--~ print("6 " .. file.addsuffix("name.old","new","foo") .. " -> name.new")
+--~ print("7 " .. file.addsuffix("name.old","new",{"foo","bar"}) .. " -> name.new")
+--~ print("8 " .. file.addsuffix("name.old","new",{"old","bar"}) .. " -> name.old")
+
+function file.replacesuffix(filename, suffix)
+ return (gsub(filename,"%.[%a%d]+$","")) .. "." .. suffix
+end
--~ function file.join(...)
--~ local pth = concat({...},"/")
@@ -1590,74 +1850,130 @@ end
--~ print(file.join("http:///a","/y"))
--~ print(file.join("//nas-1","/y"))
-function file.iswritable(name)
- local a = lfs.attributes(name) or lfs.attributes(file.dirname(name,"."))
+function file.is_writable(name)
+ local a = attributes(name) or attributes(dirname(name,"."))
return a and sub(a.permissions,2,2) == "w"
end
-function file.isreadable(name)
- local a = lfs.attributes(name)
+function file.is_readable(name)
+ local a = attributes(name)
return a and sub(a.permissions,1,1) == "r"
end
-file.is_readable = file.isreadable
-file.is_writable = file.iswritable
-
--- todo: lpeg
+file.isreadable = file.is_readable -- depricated
+file.iswritable = file.is_writable -- depricated
---~ function file.split_path(str)
---~ local t = { }
---~ str = gsub(str,"\\", "/")
---~ str = gsub(str,"(%a):([;/])", "%1\001%2")
---~ for name in gmatch(str,"([^;:]+)") do
---~ if name ~= "" then
---~ t[#t+1] = gsub(name,"\001",":")
---~ end
---~ end
---~ return t
---~ end
+-- todo: lpeg \\ / .. does not save much
local checkedsplit = string.checkedsplit
-function file.split_path(str,separator)
+function file.splitpath(str,separator) -- string
str = gsub(str,"\\","/")
return checkedsplit(str,separator or io.pathseparator)
end
-function file.join_path(tab)
- return concat(tab,io.pathseparator) -- can have trailing //
+function file.joinpath(tab,separator) -- table
+ return concat(tab,separator or io.pathseparator) -- can have trailing //
end
-- we can hash them weakly
-function file.collapse_path(str)
+--~ function file.collapsepath(str) -- fails on b.c/..
+--~ str = gsub(str,"\\","/")
+--~ if find(str,"/") then
+--~ str = gsub(str,"^%./",(gsub(getcurrentdir(),"\\","/")) .. "/") -- ./xx in qualified
+--~ str = gsub(str,"/%./","/")
+--~ local n, m = 1, 1
+--~ while n > 0 or m > 0 do
+--~ str, n = gsub(str,"[^/%.]+/%.%.$","")
+--~ str, m = gsub(str,"[^/%.]+/%.%./","")
+--~ end
+--~ str = gsub(str,"([^/])/$","%1")
+--~ -- str = gsub(str,"^%./","") -- ./xx in qualified
+--~ str = gsub(str,"/%.$","")
+--~ end
+--~ if str == "" then str = "." end
+--~ return str
+--~ end
+--~
+--~ The previous one fails on "a.b/c" so Taco came up with a split based
+--~ variant. After some skyping we got it sort of compatible with the old
+--~ one. After that the anchoring to currentdir was added in a better way.
+--~ Of course there are some optimizations too. Finally we had to deal with
+--~ windows drive prefixes and thinsg like sys://.
+
+function file.collapsepath(str,anchor)
+ if anchor and not find(str,"^/") and not find(str,"^%a:") then
+ str = getcurrentdir() .. "/" .. str
+ end
+ if str == "" or str =="." then
+ return "."
+ elseif find(str,"^%.%.") then
+ str = gsub(str,"\\","/")
+ return str
+ elseif not find(str,"%.") then
+ str = gsub(str,"\\","/")
+ return str
+ end
str = gsub(str,"\\","/")
- if find(str,"/") then
- str = gsub(str,"^%./",(gsub(lfs.currentdir(),"\\","/")) .. "/") -- ./xx in qualified
- str = gsub(str,"/%./","/")
- local n, m = 1, 1
- while n > 0 or m > 0 do
- str, n = gsub(str,"[^/%.]+/%.%.$","")
- str, m = gsub(str,"[^/%.]+/%.%./","")
- end
- str = gsub(str,"([^/])/$","%1")
- -- str = gsub(str,"^%./","") -- ./xx in qualified
- str = gsub(str,"/%.$","")
- end
- if str == "" then str = "." end
- return str
+ local starter, rest = match(str,"^(%a+:/*)(.-)$")
+ if starter then
+ str = rest
+ end
+ local oldelements = checkedsplit(str,"/")
+ local newelements = { }
+ local i = #oldelements
+ while i > 0 do
+ local element = oldelements[i]
+ if element == '.' then
+ -- do nothing
+ elseif element == '..' then
+ local n = i -1
+ while n > 0 do
+ local element = oldelements[n]
+ if element ~= '..' and element ~= '.' then
+ oldelements[n] = '.'
+ break
+ else
+ n = n - 1
+ end
+ end
+ if n < 1 then
+ insert(newelements,1,'..')
+ end
+ elseif element ~= "" then
+ insert(newelements,1,element)
+ end
+ i = i - 1
+ end
+ if #newelements == 0 then
+ return starter or "."
+ elseif starter then
+ return starter .. concat(newelements, '/')
+ elseif find(str,"^/") then
+ return "/" .. concat(newelements,'/')
+ else
+ return concat(newelements, '/')
+ end
end
---~ print(file.collapse_path("/a"))
---~ print(file.collapse_path("a/./b/.."))
---~ print(file.collapse_path("a/aa/../b/bb"))
---~ print(file.collapse_path("a/../.."))
---~ print(file.collapse_path("a/.././././b/.."))
---~ print(file.collapse_path("a/./././b/.."))
---~ print(file.collapse_path("a/b/c/../.."))
-
-function file.robustname(str)
- return (gsub(str,"[^%a%d%/%-%.\\]+","-"))
+--~ local function test(str)
+--~ print(string.format("%-20s %-15s %-15s",str,file.collapsepath(str),file.collapsepath(str,true)))
+--~ end
+--~ test("a/b.c/d") test("b.c/d") test("b.c/..")
+--~ test("/") test("c:/..") test("sys://..")
+--~ test("") test("./") test(".") test("..") test("./..") test("../..")
+--~ test("a") test("./a") test("/a") test("a/../..")
+--~ test("a/./b/..") test("a/aa/../b/bb") test("a/.././././b/..") test("a/./././b/..")
+--~ test("a/b/c/../..") test("./a/b/c/../..") test("a/b/c/../..")
+
+function file.robustname(str,strict)
+ str = gsub(str,"[^%a%d%/%-%.\\]+","-")
+ if strict then
+ return lower(gsub(str,"^%-*(.-)%-*$","%1"))
+ else
+ return str
+ end
end
file.readdata = io.loaddata
@@ -1669,31 +1985,31 @@ end
-- lpeg variants, slightly faster, not always
---~ local period = lpeg.P(".")
---~ local slashes = lpeg.S("\\/")
+--~ local period = P(".")
+--~ local slashes = S("\\/")
--~ local noperiod = 1-period
--~ local noslashes = 1-slashes
--~ local name = noperiod^1
---~ local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * lpeg.C(noperiod^1) * -1
+--~ local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * C(noperiod^1) * -1
--~ function file.extname(name)
--~ return lpegmatch(pattern,name) or ""
--~ end
---~ local pattern = lpeg.Cs(((period * noperiod^1 * -1)/"" + 1)^1)
+--~ local pattern = Cs(((period * noperiod^1 * -1)/"" + 1)^1)
--~ function file.removesuffix(name)
--~ return lpegmatch(pattern,name)
--~ end
---~ local pattern = (noslashes^0 * slashes)^1 * lpeg.C(noslashes^1) * -1
+--~ local pattern = (noslashes^0 * slashes)^1 * C(noslashes^1) * -1
--~ function file.basename(name)
--~ return lpegmatch(pattern,name) or name
--~ end
---~ local pattern = (noslashes^0 * slashes)^1 * lpeg.Cp() * noslashes^1 * -1
+--~ local pattern = (noslashes^0 * slashes)^1 * Cp() * noslashes^1 * -1
--~ function file.dirname(name)
--~ local p = lpegmatch(pattern,name)
@@ -1704,7 +2020,7 @@ end
--~ end
--~ end
---~ local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * lpeg.Cp() * noperiod^1 * -1
+--~ local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * Cp() * noperiod^1 * -1
--~ function file.addsuffix(name, suffix)
--~ local p = lpegmatch(pattern,name)
@@ -1715,7 +2031,7 @@ end
--~ end
--~ end
---~ local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * lpeg.Cp() * noperiod^1 * -1
+--~ local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * Cp() * noperiod^1 * -1
--~ function file.replacesuffix(name,suffix)
--~ local p = lpegmatch(pattern,name)
@@ -1726,7 +2042,7 @@ end
--~ end
--~ end
---~ local pattern = (noslashes^0 * slashes)^0 * lpeg.Cp() * ((noperiod^1 * period)^1 * lpeg.Cp() + lpeg.P(true)) * noperiod^1 * -1
+--~ local pattern = (noslashes^0 * slashes)^0 * Cp() * ((noperiod^1 * period)^1 * Cp() + P(true)) * noperiod^1 * -1
--~ function file.nameonly(name)
--~ local a, b = lpegmatch(pattern,name)
@@ -1758,11 +2074,14 @@ end
-- also rewrite previous
-local letter = lpeg.R("az","AZ") + lpeg.S("_-+")
-local separator = lpeg.P("://")
+local letter = R("az","AZ") + S("_-+")
+local separator = P("://")
-local qualified = lpeg.P(".")^0 * lpeg.P("/") + letter*lpeg.P(":") + letter^1*separator + letter^1 * lpeg.P("/")
-local rootbased = lpeg.P("/") + letter*lpeg.P(":")
+local qualified = P(".")^0 * P("/") + letter*P(":") + letter^1*separator + letter^1 * P("/")
+local rootbased = P("/") + letter*P(":")
+
+lpeg.patterns.qualified = qualified
+lpeg.patterns.rootbased = rootbased
-- ./name ../name /name c: :// name/name
@@ -1774,14 +2093,16 @@ function file.is_rootbased_path(filename)
return lpegmatch(rootbased,filename) ~= nil
end
-local slash = lpeg.S("\\/")
-local period = lpeg.P(".")
-local drive = lpeg.C(lpeg.R("az","AZ")) * lpeg.P(":")
-local path = lpeg.C(((1-slash)^0 * slash)^0)
-local suffix = period * lpeg.C(lpeg.P(1-period)^0 * lpeg.P(-1))
-local base = lpeg.C((1-suffix)^0)
+-- actually these are schemes
+
+local slash = S("\\/")
+local period = P(".")
+local drive = C(R("az","AZ")) * P(":")
+local path = C(((1-slash)^0 * slash)^0)
+local suffix = period * C(P(1-period)^0 * P(-1))
+local base = C((1-suffix)^0)
-local pattern = (drive + lpeg.Cc("")) * (path + lpeg.Cc("")) * (base + lpeg.Cc("")) * (suffix + lpeg.Cc(""))
+local pattern = (drive + Cc("")) * (path + Cc("")) * (base + Cc("")) * (suffix + Cc(""))
function file.splitname(str) -- returns drive, path, base, suffix
return lpegmatch(pattern,str)
@@ -1797,12 +2118,19 @@ end
--~ -- todo:
--~
--~ if os.type == "windows" then
---~ local currentdir = lfs.currentdir
---~ function lfs.currentdir()
+--~ local currentdir = getcurrentdir
+--~ function getcurrentdir()
--~ return (gsub(currentdir(),"\\","/"))
--~ end
--~ end
+-- for myself:
+
+function file.strip(name,dir)
+ local b, a = match(name,"^(.-)" .. dir .. "(.*)$")
+ return a ~= "" and a or name
+end
+
end -- closure
do -- begin closure to overcome local limits and interference
@@ -1815,7 +2143,10 @@ if not modules then modules = { } end modules ['l-io'] = {
license = "see context related readme files"
}
-local byte, find, gsub = string.byte, string.find, string.gsub
+local io = io
+local byte, find, gsub, format = string.byte, string.find, string.gsub, string.format
+local concat = table.concat
+local type = type
if string.find(os.getenv("PATH"),";") then
io.fileseparator, io.pathseparator = "\\", ";"
@@ -1826,9 +2157,7 @@ end
function io.loaddata(filename,textmode)
local f = io.open(filename,(textmode and 'r') or 'rb')
if f then
- -- collectgarbage("step") -- sometimes makes a big difference in mem consumption
local data = f:read('*all')
- -- garbagecollector.check(data)
f:close()
return data
else
@@ -1840,13 +2169,14 @@ function io.savedata(filename,data,joiner)
local f = io.open(filename,"wb")
if f then
if type(data) == "table" then
- f:write(table.join(data,joiner or ""))
+ f:write(concat(data,joiner or ""))
elseif type(data) == "function" then
data(f)
else
f:write(data or "")
end
f:close()
+ io.flush()
return true
else
return false
@@ -1875,12 +2205,19 @@ function io.size(filename)
end
function io.noflines(f)
- local n = 0
- for _ in f:lines() do
- n = n + 1
+ if type(f) == "string" then
+ local f = io.open(filename)
+ local n = f and io.noflines(f) or 0
+ assert(f:close())
+ return n
+ else
+ local n = 0
+ for _ in f:lines() do
+ n = n + 1
+ end
+ f:seek('set',0)
+ return n
end
- f:seek('set',0)
- return n
end
local nextchar = {
@@ -1906,8 +2243,6 @@ local nextchar = {
function io.characters(f,n)
if f then
return nextchar[n or 1], f
- else
- return nil, nil
end
end
@@ -1916,40 +2251,42 @@ local nextbyte = {
local a, b, c, d = f:read(1,1,1,1)
if d then
return byte(a), byte(b), byte(c), byte(d)
- else
- return nil, nil, nil, nil
+ end
+ end,
+ [3] = function(f)
+ local a, b, c = f:read(1,1,1)
+ if b then
+ return byte(a), byte(b), byte(c)
end
end,
[2] = function(f)
local a, b = f:read(1,1)
if b then
return byte(a), byte(b)
- else
- return nil, nil
end
end,
[1] = function (f)
local a = f:read(1)
if a then
return byte(a)
- else
- return nil
end
end,
[-2] = function (f)
local a, b = f:read(1,1)
if b then
return byte(b), byte(a)
- else
- return nil, nil
+ end
+ end,
+ [-3] = function(f)
+ local a, b, c = f:read(1,1,1)
+ if b then
+ return byte(c), byte(b), byte(a)
end
end,
[-4] = function(f)
local a, b, c, d = f:read(1,1,1,1)
if d then
return byte(d), byte(c), byte(b), byte(a)
- else
- return nil, nil, nil, nil
end
end
}
@@ -1966,12 +2303,13 @@ function io.ask(question,default,options)
while true do
io.write(question)
if options then
- io.write(string.format(" [%s]",table.concat(options,"|")))
+ io.write(format(" [%s]",concat(options,"|")))
end
if default then
- io.write(string.format(" [%s]",default))
+ io.write(format(" [%s]",default))
end
- io.write(string.format(" "))
+ io.write(format(" "))
+ io.flush()
local answer = io.read()
answer = gsub(answer,"^%s*(.*)%s*$","%1")
if answer == "" and default then
@@ -1995,56 +2333,134 @@ function io.ask(question,default,options)
end
end
+local function readnumber(f,n,m)
+ if m then
+ f:seek("set",n)
+ n = m
+ end
+ if n == 1 then
+ return byte(f:read(1))
+ elseif n == 2 then
+ local a, b = byte(f:read(2),1,2)
+ return 256 * a + b
+ elseif n == 3 then
+ local a, b, c = byte(f:read(3),1,3)
+ return 256*256 * a + 256 * b + c
+ elseif n == 4 then
+ local a, b, c, d = byte(f:read(4),1,4)
+ return 256*256*256 * a + 256*256 * b + 256 * c + d
+ elseif n == 8 then
+ local a, b = readnumber(f,4), readnumber(f,4)
+ return 256 * a + b
+ elseif n == 12 then
+ local a, b, c = readnumber(f,4), readnumber(f,4), readnumber(f,4)
+ return 256*256 * a + 256 * b + c
+ elseif n == -2 then
+ local b, a = byte(f:read(2),1,2)
+ return 256*a + b
+ elseif n == -3 then
+ local c, b, a = byte(f:read(3),1,3)
+ return 256*256 * a + 256 * b + c
+ elseif n == -4 then
+ local d, c, b, a = byte(f:read(4),1,4)
+ return 256*256*256 * a + 256*256 * b + 256*c + d
+ elseif n == -8 then
+ local h, g, f, e, d, c, b, a = byte(f:read(8),1,8)
+ return 256*256*256*256*256*256*256 * a +
+ 256*256*256*256*256*256 * b +
+ 256*256*256*256*256 * c +
+ 256*256*256*256 * d +
+ 256*256*256 * e +
+ 256*256 * f +
+ 256 * g +
+ h
+ else
+ return 0
+ end
+end
+
+io.readnumber = readnumber
+
+function io.readstring(f,n,m)
+ if m then
+ f:seek("set",n)
+ n = m
+ end
+ local str = gsub(f:read(n),"%z","")
+ return str
+end
+
end -- closure
do -- begin closure to overcome local limits and interference
-if not modules then modules = { } end modules ['luat-dum'] = {
- version = 1.001,
+if not modules then modules = { } end modules ['luat-basics-gen'] = {
+ 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"
}
+if context then
+ texio.write_nl("fatal error: this module is not for context")
+ os.exit()
+end
+
local dummyfunction = function() end
+local dummyreporter = function(c) return function(...) texio.write(c .. " : " .. string.format(...)) end end
statistics = {
register = dummyfunction,
starttiming = dummyfunction,
stoptiming = dummyfunction,
+ elapsedtime = nil,
}
+
directives = {
register = dummyfunction,
enable = dummyfunction,
disable = dummyfunction,
}
+
trackers = {
register = dummyfunction,
enable = dummyfunction,
disable = dummyfunction,
}
+
experiments = {
register = dummyfunction,
enable = dummyfunction,
disable = dummyfunction,
}
-storage = {
+
+storage = { -- probably no longer needed
register = dummyfunction,
shared = { },
}
+
logs = {
+ new = dummyreporter,
+ reporter = dummyreporter,
+ messenger = dummyreporter,
report = dummyfunction,
- simple = dummyfunction,
-}
-tasks = {
- new = dummyfunction,
- actions = dummyfunction,
- appendaction = dummyfunction,
- prependaction = dummyfunction,
}
+
callbacks = {
register = function(n,f) return callback.register(n,f) end,
+
+}
+
+utilities = {
+ storage = {
+ allocate = function(t) return t or { } end,
+ mark = function(t) return t or { } end,
+ },
+}
+
+characters = characters or {
+ data = { }
}
-- we need to cheat a bit here
@@ -2057,22 +2473,34 @@ local remapper = {
otf = "opentype fonts",
ttf = "truetype fonts",
ttc = "truetype fonts",
- dfont = "truetype dictionary",
+ dfont = "truetype fonts", -- "truetype dictionary",
cid = "cid maps",
fea = "font feature files",
}
-function resolvers.find_file(name,kind)
+function resolvers.findfile(name,fileformat)
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"))
+ fileformat = fileformat and string.lower(fileformat)
+ local found = kpse.find_file(name,(fileformat and fileformat ~= "" and (remapper[fileformat] or fileformat)) or file.extname(name,"tex"))
+ if not found or found == "" then
+ found = kpse.find_file(name,"other text files")
+ end
+ return found
end
-function resolvers.findbinfile(name,kind)
- if not kind or kind == "" then
- kind = file.extname(name) -- string.match(name,"%.([^%.]-)$")
+function resolvers.findbinfile(name,fileformat)
+ if not fileformat or fileformat == "" then
+ fileformat = file.extname(name) -- string.match(name,"%.([^%.]-)$")
end
- return resolvers.find_file(name,(kind and remapper[kind]) or kind)
+ return resolvers.findfile(name,(fileformat and remapper[fileformat]) or fileformat)
+end
+
+function resolvers.resolve(s)
+ return s
+end
+
+function resolvers.unresolve(s)
+ return s
end
-- Caches ... I will make a real stupid version some day when I'm in the
@@ -2081,57 +2509,124 @@ end
-- usage as I don't want any dependency at all. Also, ConTeXt might have
-- different needs and tricks added.
+--~ containers.usecache = true
+
caches = { }
---~ containers.usecache = true
+local writable, readables = nil, { }
+
+if not caches.namespace or caches.namespace == "" or caches.namespace == "context" then
+ caches.namespace = 'generic'
+end
-function caches.setpath(category,subcategory)
- local root = kpse.var_value("TEXMFCACHE") or ""
- if root == "" then
- root = kpse.var_value("VARTEXMF") or ""
+do
+
+ local cachepaths = kpse.expand_path('$TEXMFCACHE') or ""
+
+ if cachepaths == "" then
+ cachepaths = kpse.expand_path('$TEXMFVAR')
+ end
+
+ if cachepaths == "" then
+ cachepaths = kpse.expand_path('$VARTEXMF')
+ end
+
+ if cachepaths == "" then
+ cachepaths = "."
+ end
+
+ cachepaths = string.split(cachepaths,os.type == "windows" and ";" or ":")
+
+ for i=1,#cachepaths do
+ if file.is_writable(cachepaths[i]) then
+ writable = file.join(cachepaths[i],"luatex-cache")
+ lfs.mkdir(writable)
+ writable = file.join(writable,caches.namespace)
+ lfs.mkdir(writable)
+ break
+ end
+ end
+
+ for i=1,#cachepaths do
+ if file.is_readable(cachepaths[i]) then
+ readables[#readables+1] = file.join(cachepaths[i],"luatex-cache",caches.namespace)
+ end
end
- if root ~= "" then
- root = file.join(root,category)
- lfs.mkdir(root)
- root = file.join(root,subcategory)
- lfs.mkdir(root)
- return lfs.isdir(root) and root
+
+ if not writable then
+ texio.write_nl("quiting: fix your writable cache path")
+ os.exit()
+ elseif #readables == 0 then
+ texio.write_nl("quiting: fix your readable cache path")
+ 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-" and name -- clash prevention
+ name = "temp-" .. name -- clash prevention
return file.addsuffix(file.join(path,name),"lua")
end
end
-function caches.iswritable(path,name)
+function caches.is_writable(path,name)
local fullname = makefullname(path,name)
- return fullname and file.iswritable(fullname)
+ return fullname and file.is_writable(fullname)
end
-function caches.loaddata(path,name)
- local fullname = makefullname(path,name)
- if fullname then
- local data = loadfile(fullname)
- return data and data()
+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
- table.tofile(fullname,data,'return',false,true,false)
+ texio.write(string.format("(save: %s)",fullname))
+ table.tofile(fullname,data,true,{ reduce = true })
end
end
+--
+
+local table.setmetatableindex(t,f)
+ setmetatable(t,{ __index = f })
+end
+
end -- closure
do -- begin closure to overcome local limits and interference
if not modules then modules = { } end modules ['data-con'] = {
- version = 1.001,
+ version = 1.100,
comment = "companion to luat-lib.mkiv",
author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
copyright = "PRAGMA ADE / ConTeXt Development Team",
@@ -2157,50 +2652,63 @@ table structures without bothering about the disk cache.</p>
<p>Examples of usage can be found in the font related code.</p>
--ldx]]--
-containers = containers or { }
-
+containers = containers or { }
+local containers = containers
containers.usecache = true
+local report_containers = logs.reporter("resolvers","containers")
+
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')
+ report_containers("container: %s, tag: %s, name: %s",container.subcategory,tag,name or 'invalid')
end
end
local allocated = { }
--- tracing
+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,
+ __storage__ = true
+}
function containers.define(category, subcategory, version, enabled)
- return function()
- 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 1.000,
- trace = false,
- path = caches and caches.setpath and caches.setpath(category,subcategory),
- }
- c[subcategory] = s
- end
- return s
- else
- return nil
+ 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, -- after all, this is TeX
+ trace = false,
+ -- writable = caches.getwritablepath and caches.getwritablepath (category,subcategory) or { "." },
+ -- readables = caches.getreadablepaths and caches.getreadablepaths(category,subcategory) or { "." },
+ }
+ 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.path, name)
+ return container.enabled and caches and caches.is_writable(container.writable, name)
end
function containers.is_valid(container, name)
@@ -2213,18 +2721,20 @@ function containers.is_valid(container, name)
end
function containers.read(container,name)
- if container.enabled and caches and not container.storage[name] and containers.usecache then
- container.storage[name] = caches.loaddata(container.path,name)
- if containers.is_valid(container,name) then
+ 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
- container.storage[name] = nil
+ stored = nil
end
- end
- if container.storage[name] then
+ storage[name] = stored
+ elseif stored then
report(container,"reusing",name)
end
- return container.storage[name]
+ return stored
end
function containers.write(container, name, data)
@@ -2233,7 +2743,7 @@ function containers.write(container, name, data)
if container.enabled and caches then
local unique, shared = data.unique, data.shared
data.unique, data.shared = nil, nil
- caches.savedata(container.path, name, data)
+ caches.savedata(container.writable, name, data)
report(container,"saved",name)
data.unique, data.shared = unique, shared
end
@@ -2255,122 +2765,74 @@ end -- closure
do -- begin closure to overcome local limits and interference
-if not modules then modules = { } end modules ['node-ini'] = {
+if not modules then modules = { } end modules ['luatex-fonts-nod'] = {
version = 1.001,
- comment = "companion to node-ini.mkiv",
+ comment = "companion to luatex-fonts.lua",
author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
copyright = "PRAGMA ADE / ConTeXt Development Team",
license = "see context related readme files"
}
---[[ldx--
-<p>Most of the code that had accumulated here is now separated in
-modules.</p>
---ldx]]--
-
--- this module is being reconstructed
-
-local utf = unicode.utf8
-local next, type = next, type
-local format, concat, match, utfchar = string.format, table.concat, string.match, utf.char
-
-local chardata = characters and characters.data
+if context then
+ texio.write_nl("fatal error: this module is not for context")
+ os.exit()
+end
---[[ldx--
-<p>We start with a registration system for atributes so that we can use the
-symbolic names later on.</p>
---ldx]]--
+-- Don't depend on code here as it is only needed to complement the
+-- font handler code.
-attributes = attributes or { }
+-- Attributes:
-attributes.names = attributes.names or { }
-attributes.numbers = attributes.numbers or { }
-attributes.list = attributes.list or { }
-attributes.unsetvalue = -0x7FFFFFFF
+if tex.attribute[0] ~= 0 then
-storage.register("attributes/names", attributes.names, "attributes.names")
-storage.register("attributes/numbers", attributes.numbers, "attributes.numbers")
-storage.register("attributes/list", attributes.list, "attributes.list")
+ 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","! purposes so setting them at the TeX end might break the font handler.")
+ texio.write_nl("log","!")
-local names, numbers, list = attributes.names, attributes.numbers, attributes.list
+ tex.attribute[0] = 0 -- else no features
-function attributes.define(name,number) -- at the tex end
- if not numbers[name] then
- numbers[name], names[number], list[number] = number, name, { }
- end
end
---[[ldx--
-<p>We can use the attributes in the range 127-255 (outside user space). These
-are only used when no attribute is set at the \TEX\ end which normally
-happens in <l n='context'/>.</p>
---ldx]]--
+attributes = { }
+attributes.unsetvalue = -0x7FFFFFFF
-storage.shared.attributes_last_private = storage.shared.attributes_last_private or 127
+local numbers, last = { }, 127
-function attributes.private(name) -- at the lua end (hidden from user)
+function attributes.private(name)
local number = numbers[name]
if not number then
- local last = storage.shared.attributes_last_private or 127
if last < 255 then
last = last + 1
- storage.shared.attributes_last_private = last
end
number = last
- numbers[name], names[number], list[number] = number, name, { }
+ numbers[name] = number
end
return number
end
---[[ldx--
-<p>Access to nodes is what gives <l n='luatex'/> its power. Here we
-implement a few helper functions. These functions are rather optimized.</p>
---ldx]]--
+-- Nodes:
---[[ldx--
-<p>When manipulating node lists in <l n='context'/>, we will remove
-nodes and insert new ones. While node access was implemented, we did
-quite some experiments in order to find out if manipulating nodes
-in <l n='lua'/> was feasible from the perspective of performance.</p>
-
-<p>First of all, we noticed that the bottleneck is more with excessive
-callbacks (some gets called very often) and the conversion from and to
-<l n='tex'/>'s datastructures. However, at the <l n='lua'/> end, we
-found that inserting and deleting nodes in a table could become a
-bottleneck.</p>
-
-<p>This resulted in two special situations in passing nodes back to
-<l n='tex'/>: a table entry with value <type>false</type> is ignored,
-and when instead of a table <type>true</type> is returned, the
-original table is used.</p>
-
-<p>Insertion is handled (at least in <l n='context'/> as follows. When
-we need to insert a node at a certain position, we change the node at
-that position by a dummy node, tagged <type>inline</type> which itself
-has_attribute the original node and one or more new nodes. Before we pass
-back the list we collapse the list. Of course collapsing could be built
-into the <l n='tex'/> engine, but this is a not so natural extension.</p>
-
-<p>When we collapse (something that we only do when really needed), we
-also ignore the empty nodes. [This is obsolete!]</p>
---ldx]]--
+nodes = { }
+nodes.pool = { }
+nodes.handlers = { }
-nodes = nodes or { }
+local nodecodes = { } for k,v in next, node.types () do nodecodes[string.gsub(v,"_","")] = k end
+local whatcodes = { } for k,v in next, node.whatsits() do whatcodes[string.gsub(v,"_","")] = k end
+local glyphcodes = { [0] = "character", "glyph", "ligature", "ghost", "left", "right" }
-local hlist = node.id('hlist')
-local vlist = node.id('vlist')
-local glyph = node.id('glyph')
-local glue = node.id('glue')
-local penalty = node.id('penalty')
-local kern = node.id('kern')
-local whatsit = node.id('whatsit')
+nodes.nodecodes = nodecodes
+nodes.whatcodes = whatcodes
+nodes.whatsitcodes = whatcodes
+nodes.glyphcodes = glyphcodes
-local traverse_id = node.traverse_id
-local traverse = node.traverse
-local free_node = node.free
-local remove_node = node.remove
-local insert_node_before = node.insert_before
-local insert_node_after = node.insert_after
+local free_node = node.free
+local remove_node = node.remove
+local new_node = node.new
+
+nodes.handlers.protectglyphs = node.protect_glyphs
+nodes.handlers.unprotectglyphs = node.unprotect_glyphs
function nodes.remove(head, current, free_too)
local t = current
@@ -2390,1130 +2852,15 @@ function nodes.delete(head,current)
return nodes.remove(head,current,true)
end
-nodes.before = insert_node_before
-nodes.after = insert_node_after
-
--- we need to test this, as it might be fixed now
-
-function nodes.before(h,c,n)
- if c then
- if c == h then
- n.next = h
- n.prev = nil
- h.prev = n
- else
- local cp = c.prev
- n.next = c
- n.prev = cp
- if cp then
- cp.next = n
- end
- c.prev = n
- return h, n
- end
- end
- return n, n
-end
-
-function nodes.after(h,c,n)
- if c then
- local cn = c.next
- if cn then
- n.next = cn
- cn.prev = n
- else
- n.next = nil
- end
- c.next = n
- n.prev = c
- return h, n
- end
- return n, n
-end
-
--- local h, c = nodes.replace(head,current,new)
--- local c = nodes.replace(false,current,new)
--- local c = nodes.replace(current,new)
-
-function nodes.replace(head,current,new) -- no head returned if false
- if not new then
- head, current, new = false, head, current
- end
- local prev, next = current.prev, current.next
- if next then
- new.next, next.prev = next, new
- end
- if prev then
- new.prev, prev.next = prev, new
- end
- if head then
- if head == current then
- head = new
- end
- free_node(current)
- return head, new
- else
- free_node(current)
- return new
- end
-end
-
--- will move
-
-local function count(stack,flat)
- local n = 0
- while stack do
- local id = stack.id
- if not flat and id == hlist or id == vlist then
- local list = stack.list
- if list then
- n = n + 1 + count(list) -- self counts too
- else
- n = n + 1
- end
- else
- n = n + 1
- end
- stack = stack.next
- end
- return n
-end
-
-nodes.count = count
-
--- new, will move
-
-function attributes.ofnode(n)
- local a = n.attr
- if a then
- local names = attributes.names
- a = a.next
- while a do
- local number, value = a.number, a.value
- texio.write_nl(format("%s : attribute %3i, value %4i, name %s",tostring(n),number,value,names[number] or '?'))
- a = a.next
- end
- end
-end
-
-local left, space = lpeg.P("<"), lpeg.P(" ")
-
-nodes.filterkey = left * (1-left)^0 * left * space^0 * lpeg.C((1-space)^0)
-
-end -- closure
-
-do -- begin closure to overcome local limits and interference
-
-if not modules then modules = { } end modules ['node-res'] = {
- 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 gmatch, format = string.gmatch, string.format
-local copy_node, free_node, free_list, new_node, node_type, node_id = node.copy, node.free, node.flush_list, node.new, node.type, node.id
-local tonumber, round = tonumber, math.round
-
-local glyph_node = node_id("glyph")
-
---[[ldx--
-<p>The next function is not that much needed but in <l n='context'/> we use
-for debugging <l n='luatex'/> node management.</p>
---ldx]]--
-
-nodes = nodes or { }
-
-nodes.whatsits = { } -- table.swapped(node.whatsits())
-
-local reserved = { }
-local whatsits = nodes.whatsits
-
-for k, v in next, node.whatsits() do
- whatsits[k], whatsits[v] = v, k -- two way
-end
-
-local function register_node(n)
- reserved[#reserved+1] = n
- return n
-end
-
-nodes.register = register_node
+nodes.before = node.insert_before
+nodes.after = node.insert_after
-function nodes.cleanup_reserved(nofboxes) -- todo
- nodes.tracers.steppers.reset() -- todo: make a registration subsystem
- local nr, nl = #reserved, 0
- for i=1,nr do
- local ri = reserved[i]
- -- if not (ri.id == glue_spec and not ri.is_writable) then
- free_node(reserved[i])
- -- end
- end
- if nofboxes then
- local tb = tex.box
- for i=0,nofboxes do
- local l = tb[i]
- if l then
- free_node(tb[i])
- nl = nl + 1
- end
- end
- end
- reserved = { }
- return nr, nl, nofboxes -- can be nil
-end
-
-function nodes.usage()
- local t = { }
- for n, tag in gmatch(status.node_mem_usage,"(%d+) ([a-z_]+)") do
- t[tag] = n
- end
- return t
-end
-
-local disc = register_node(new_node("disc"))
-local kern = register_node(new_node("kern",1))
-local penalty = register_node(new_node("penalty"))
-local glue = register_node(new_node("glue")) -- glue.spec = nil
-local glue_spec = register_node(new_node("glue_spec"))
-local glyph = register_node(new_node("glyph",0))
-local textdir = register_node(new_node("whatsit",whatsits.dir)) -- 7 (6 is local par node)
-local rule = register_node(new_node("rule"))
-local latelua = register_node(new_node("whatsit",whatsits.late_lua)) -- 35
-local user_n = register_node(new_node("whatsit",whatsits.user_defined)) user_n.type = 100 -- 44
-local user_l = register_node(new_node("whatsit",whatsits.user_defined)) user_l.type = 110 -- 44
-local user_s = register_node(new_node("whatsit",whatsits.user_defined)) user_s.type = 115 -- 44
-local user_t = register_node(new_node("whatsit",whatsits.user_defined)) user_t.type = 116 -- 44
-local left_margin_kern = register_node(new_node("margin_kern",0))
-local right_margin_kern = register_node(new_node("margin_kern",1))
-local lineskip = register_node(new_node("glue",1))
-local baselineskip = register_node(new_node("glue",2))
-local leftskip = register_node(new_node("glue",8))
-local rightskip = register_node(new_node("glue",9))
-local temp = register_node(new_node("temp",0))
-
-function nodes.zeroglue(n)
- local s = n.spec
- return not writable or (
- s.width == 0
- and s.stretch == 0
- and s.shrink == 0
- and s.stretch_order == 0
- and s.shrink_order == 0
- )
-end
-
-function nodes.glyph(fnt,chr)
- local n = copy_node(glyph)
- if fnt then n.font = fnt end
- if chr then n.char = chr end
- return n
-end
-
-function nodes.penalty(p)
- local n = copy_node(penalty)
- n.penalty = p
- return n
-end
-
-function nodes.kern(k)
- local n = copy_node(kern)
+function nodes.pool.kern(k)
+ local n = new_node("kern",1)
n.kern = k
return n
end
-function nodes.glue_spec(width,stretch,shrink)
- local s = copy_node(glue_spec)
- s.width, s.stretch, s.shrink = width, stretch, shrink
- return s
-end
-
-local function someskip(skip,width,stretch,shrink)
- local n = copy_node(skip)
- if not width then
- -- no spec
- elseif tonumber(width) then
- local s = copy_node(glue_spec)
- s.width, s.stretch, s.shrink = width, stretch, shrink
- n.spec = s
- else
- -- shared
- n.spec = copy_node(width)
- end
- return n
-end
-
-function nodes.glue(width,stretch,shrink)
- return someskip(glue,width,stretch,shrink)
-end
-function nodes.leftskip(width,stretch,shrink)
- return someskip(leftskip,width,stretch,shrink)
-end
-function nodes.rightskip(width,stretch,shrink)
- return someskip(rightskip,width,stretch,shrink)
-end
-function nodes.lineskip(width,stretch,shrink)
- return someskip(lineskip,width,stretch,shrink)
-end
-function nodes.baselineskip(width,stretch,shrink)
- return someskip(baselineskip,width,stretch,shrink)
-end
-
-function nodes.disc()
- return copy_node(disc)
-end
-
-function nodes.textdir(dir)
- local t = copy_node(textdir)
- t.dir = dir
- return t
-end
-
-function nodes.rule(width,height,depth,dir)
- local n = copy_node(rule)
- if width then n.width = width end
- if height then n.height = height end
- if depth then n.depth = depth end
- if dir then n.dir = dir end
- return n
-end
-
-function nodes.latelua(code)
- local n = copy_node(latelua)
- n.data = code
- return n
-end
-
-function nodes.leftmarginkern(glyph,width)
- local n = copy_node(left_margin_kern)
- if not glyph then
- logs.fatal("nodes","invalid pointer to left margin glyph node")
- elseif glyph.id ~= glyph_node then
- logs.fatal("nodes","invalid node type %s for left margin glyph node",node_type(glyph))
- else
- n.glyph = glyph
- end
- if width then
- n.width = width
- end
- return n
-end
-
-function nodes.rightmarginkern(glyph,width)
- local n = copy_node(right_margin_kern)
- if not glyph then
- logs.fatal("nodes","invalid pointer to right margin glyph node")
- elseif glyph.id ~= glyph_node then
- logs.fatal("nodes","invalid node type %s for right margin glyph node",node_type(p))
- else
- n.glyph = glyph
- end
- if width then
- n.width = width
- end
- return n
-end
-
-function nodes.temp()
- return copy_node(temp)
-end
---[[
-<p>At some point we ran into a problem that the glue specification
-of the zeropoint dimension was overwritten when adapting a glue spec
-node. This is a side effect of glue specs being shared. After a
-couple of hours tracing and debugging Taco and I came to the
-conclusion that it made no sense to complicate the spec allocator
-and settled on a writable flag. This all is a side effect of the
-fact that some glues use reserved memory slots (with the zeropoint
-glue being a noticeable one). So, next we wrap this into a function
-and hide it for the user. And yes, LuaTeX now gives a warning as
-well.</p>
-]]--
-
-if tex.luatexversion > 51 then
-
- function nodes.writable_spec(n)
- local spec = n.spec
- if not spec then
- spec = copy_node(glue_spec)
- n.spec = spec
- elseif not spec.writable then
- spec = copy_node(spec)
- n.spec = spec
- end
- return spec
- end
-
-else
-
- function nodes.writable_spec(n)
- local spec = n.spec
- if not spec then
- spec = copy_node(glue_spec)
- else
- spec = copy_node(spec)
- end
- n.spec = spec
- return spec
- end
-
-end
-
-local cache = { }
-
-function nodes.usernumber(num)
- local n = cache[num]
- if n then
- return copy_node(n)
- else
- local n = copy_node(user_n)
- if num then n.value = num end
- return n
- end
-end
-
-function nodes.userlist(list)
- local n = copy_node(user_l)
- if list then n.value = list end
- return n
-end
-
-local cache = { } -- we could use the same cache
-
-function nodes.userstring(str)
- local n = cache[str]
- if n then
- return copy_node(n)
- else
- local n = copy_node(user_s)
- n.type = 115
- if str then n.value = str end
- return n
- end
-end
-
-function nodes.usertokens(tokens)
- local n = copy_node(user_t)
- if tokens then n.value = tokens end
- return n
-end
-
-statistics.register("cleaned up reserved nodes", function()
- return format("%s nodes, %s lists of %s", nodes.cleanup_reserved(tex.count["lastallocatedbox"]))
-end) -- \topofboxstack
-
-statistics.register("node memory usage", function() -- comes after cleanup !
- return status.node_mem_usage
-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"
-}
-
--- tricky ... fonts.ids is not yet defined .. to be solved (maybe general tex ini)
-
--- This is very experimental (this will change when we have luatex > .50 and
--- a few pending thingies are available. Also, Idris needs to make a few more
--- test fonts. Btw, future versions of luatex will have extended glyph properties
--- that can be of help.
-
-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 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 = { }
-
--- currently we do gpos/kern in a bit inofficial way but when we
--- have the extra fields in glyphnodes to manipulate ht/dp/wd
--- explicitly i will provide an alternative; also, we can share
--- tables
-
--- for the moment we pass the r2l key ... volt/arabtype tests
-
-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]
- -- dy = y - h
- 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]
- -- inefficient but singles have less, but weird anyway, needs checking
- 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 -- no bound
-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 }
- end
- return dx, bound
-end
-
-function nodes.set_mark(start,base,factor,rlmode,ba,ma,index) --ba=baseanchor, ma=markanchor
- 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 } }
- 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
-
--- todo: reuse tables (i.e. no collection), but will be extra fields anyway
-
-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 has_marks or has_cursives or has_kerns then
- if trace_injections then
- nodes.trace_injection(head)
- end
- -- in the future variant we will not copy items but refs to tables
- local done, ky, rl, valid, cx, wx, mk = false, { }, { }, { }, { }, { }, { }
- if has_kerns then -- move outside loop
- 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] -- could move in test
- 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
- -- we can assume done == true because we have cursives and marks
- local cx = { }
- if has_kerns and next(ky) then
- for n, k in next, ky do
- n.yoffset = k
- end
- end
- -- todo: reuse t and use maxt
- if has_cursives then
- local p_cursbase, p = nil, nil
- -- since we need valid[n+1] we can also use a "while true do"
- local t, d, maxt = { }, { }, 0
- for i=1,#valid do -- valid == glyphs
- 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
- -- if rlmode and rlmode < 0 then
- dy = -dy
- -- end
- 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] -- not used
- -- if rlmode and rlmode > 0 then
- -- todo
- -- 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
- -- todo : combine
- if next(wx) then
- for n, k in next, wx do
- -- only w can be nil, can be sped up when w == nil
- local rl, x, w, r2l = k[1], k[2] or 0, k[4] or 0, k[6]
- local wx = w - x
---~ if rl < 0 then
---~ 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 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
- 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
- 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 -- todo: h ?
- end
- if w then
- -- copied from above
- local r2l = kk[6]
- local wx = w - x
---~ if rl < 0 then
---~ if r2l then
---~ if x ~= 0 then
---~ insert_node_before(head,n,newkern(x))
---~ end
---~ if wx ~= 0 then
---~ insert_node_after(head,n,newkern(wx))
---~ end
---~ else
---~ if wx ~= 0 then
---~ insert_node_before(head,n,newkern(wx))
---~ end
---~ if x ~= 0 then
---~ insert_node_after (head,n,newkern(x))
---~ end
---~ end
---~ else
- 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
- else
- -- simple (e.g. kernclass kerns)
- if x ~= 0 then
- insert_node_before(head,n,newkern(x))
- end
- end
- end
- end
- end
- if not keep then
- kerns = { }
- end
- return head, true
- else
- -- no tracing needed
- end
- return head, false
-end
-
-end -- closure
-
-do -- begin closure to overcome local limits and interference
-
-if not modules then modules = { } end modules ['node-fnt'] = {
- 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 next, type = next, type
-
-local trace_characters = false trackers.register("nodes.characters", function(v) trace_characters = v end)
-
-local glyph = node.id('glyph')
-
-local traverse_id = node.traverse_id
-local has_attribute = node.has_attribute
-
-local starttiming, stoptiming = statistics.starttiming, statistics.stoptiming
-
-fonts = fonts or { }
-fonts.tfm = fonts.tfm or { }
-fonts.ids = fonts.ids or { }
-
-local fontdata = fonts.ids
-
--- some tests with using an array of dynamics[id] and processes[id] demonstrated
--- that there was nothing to gain (unless we also optimize other parts)
---
--- maybe getting rid of the intermediate shared can save some time
-
--- potential speedup: check for subtype < 256 so that we can remove that test
--- elsewhere, danger: injected nodes will not be dealt with but that does not
--- happen often; we could consider processing sublists but that might need mor
--- checking later on; the current approach also permits variants
-
-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 -- else no features
-
-end
-
--- this will be redone and split in a generic one and a context one
-
-function nodes.process_characters(head)
- -- either next or not, but definitely no already processed list
- starttiming(nodes)
- local usedfonts, attrfonts, done = { }, { }, false
- local a, u, prevfont, prevattr = 0, 0, nil, 0
- for n in traverse_id(glyph,head) do
- local font, attr = n.font, has_attribute(n,0) -- zero attribute is reserved for fonts in context
- if attr and attr > 0 then
- if font ~= prevfont or attr ~= prevattr then
- local used = attrfonts[font]
- if not used then
- used = { }
- attrfonts[font] = used
- end
- if not used[attr] then
- -- we do some testing outside the function
- local tfmdata = fontdata[font]
- local shared = tfmdata.shared
- if shared then
- local dynamics = shared.dynamics
- if dynamics then
- local d = shared.set_dynamics(font,dynamics,attr) -- still valid?
- if d then
- used[attr] = d
- a = a + 1
- end
- end
- end
- end
- prevfont, prevattr = font, attr
- end
- elseif font ~= prevfont then
- prevfont, prevattr = font, 0
- local used = usedfonts[font]
- if not used then
- local tfmdata = fontdata[font]
- if tfmdata then
- local shared = tfmdata.shared -- we need to check shared, only when same features
- if shared then
- local processors = shared.processes
- if processors and #processors > 0 then
- usedfonts[font] = processors
- u = u + 1
- end
- end
- else
- -- probably nullfont
- end
- end
- else
- prevattr = attr
- end
- end
- -- we could combine these and just make the attribute nil
- if u == 1 then
- local font, processors = next(usedfonts)
- local n = #processors
- if n > 0 then
- local h, d = processors[1](head,font,false)
- head, done = h or head, done or d
- if n > 1 then
- for i=2,n do
- local h, d = processors[i](head,font,false)
- head, done = h or head, done or d
- end
- end
- end
- elseif u > 0 then
- for font, processors in next, usedfonts do
- local n = #processors
- local h, d = processors[1](head,font,false)
- head, done = h or head, done or d
- if n > 1 then
- for i=2,n do
- local h, d = processors[i](head,font,false)
- head, done = h or head, done or d
- end
- end
- end
- end
- if a == 1 then
- local font, dynamics = next(attrfonts)
- for attribute, processors in next, dynamics do -- attr can switch in between
- local n = #processors
- local h, d = processors[1](head,font,attribute)
- head, done = h or head, done or d
- if n > 1 then
- for i=2,n do
- local h, d = processors[i](head,font,attribute)
- head, done = h or head, done or d
- end
- end
- end
- elseif a > 0 then
- for font, dynamics in next, attrfonts do
- for attribute, processors in next, dynamics do -- attr can switch in between
- local n = #processors
- local h, d = processors[1](head,font,attribute)
- head, done = h or head, done or d
- if n > 1 then
- for i=2,n do
- local h, d = processors[i](head,font,attribute)
- head, done = h or head, done or d
- end
- end
- end
- end
- end
- stoptiming(nodes)
- if trace_characters then
- nodes.report(head,done)
- end
- return head, true
-end
-
-if node.protect_glyphs then
-
- nodes.protect_glyphs = node.protect_glyphs
- nodes.unprotect_glyphs = node.unprotect_glyphs
-
-else do
-
- -- initial value subtype : X000 0001 = 1 = 0x01 = char
- --
- -- expected before linebreak : X000 0000 = 0 = 0x00 = glyph
- -- X000 0010 = 2 = 0x02 = ligature
- -- X000 0100 = 4 = 0x04 = ghost
- -- X000 1010 = 10 = 0x0A = leftboundary lig
- -- X001 0010 = 18 = 0x12 = rightboundary lig
- -- X001 1010 = 26 = 0x1A = both boundaries lig
- -- X000 1100 = 12 = 0x1C = leftghost
- -- X001 0100 = 20 = 0x14 = rightghost
-
- function nodes.protect_glyphs(head)
- local done = false
- for g in traverse_id(glyph,head) do
- local s = g.subtype
- if s == 1 then
- done, g.subtype = true, 256
- elseif s <= 256 then
- done, g.subtype = true, 256 + s
- end
- end
- return done
- end
-
- function nodes.unprotect_glyphs(head)
- local done = false
- for g in traverse_id(glyph,head) do
- local s = g.subtype
- if s > 256 then
- done, g.subtype = true, s - 256
- end
- end
- return done
- end
-
-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 { }
-
-function nodes.simple_font_handler(head)
--- lang.hyphenate(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
-
end -- closure
do -- begin closure to overcome local limits and interference
@@ -3526,99 +2873,42 @@ if not modules then modules = { } end modules ['font-ini'] = {
license = "see context related readme files"
}
+-- basemethods -> can also be in list
+-- presetcontext -> defaults
+-- hashfeatures -> ctx version
+
--[[ldx--
<p>Not much is happening here.</p>
--ldx]]--
-local utf = unicode.utf8
-local format, serialize = string.format, table.serialize
-local write_nl = texio.write_nl
+local lower = string.lower
+local allocate, mark = utilities.storage.allocate, utilities.storage.mark
-if not fontloader then fontloader = fontforge end
+local report_defining = logs.reporter("fonts","defining")
fontloader.totable = fontloader.to_table
--- vtf comes first
--- fix comes last
-
-fonts = fonts or { }
+fonts = fonts or { } -- already defined in context
+local fonts = fonts
-fonts.ids = fonts.ids or { } fonts.identifiers = fonts.ids -- aka fontdata
-fonts.chr = fonts.chr or { } fonts.characters = fonts.chr -- aka chardata
-fonts.qua = fonts.qua or { } fonts.quads = fonts.qua -- aka quaddata
+-- some of these might move to where they are used first:
-fonts.tfm = fonts.tfm or { }
+fonts.hashes = { identifiers = allocate() }
+fonts.analyzers = { } -- not needed here
+fonts.readers = { }
+fonts.tables = { }
+fonts.definers = { methods = { } }
+fonts.specifiers = fonts.specifiers or { } -- in format !
+fonts.loggers = { register = function() end }
+fonts.helpers = { }
-fonts.mode = 'base'
-fonts.private = 0xF0000 -- 0x10FFFF
-fonts.verbose = false -- more verbose cache tables
-
-fonts.ids[0] = { -- nullfont
- 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 { }
-
--- tracing
-
-if not fonts.color then
-
- fonts.color = {
- set = function() end,
- reset = function() end,
- }
-
-end
-
--- format identification
-
-fonts.formats = { }
-
-function fonts.fontformat(filename,default)
- local extname = file.extname(filename)
- local format = fonts.formats[extname]
- if format then
- return format
- else
- logs.report("fonts define","unable to detemine font format for '%s'",filename)
- return default
- end
-end
+fonts.tracers = { } -- for the moment till we have move to moduledata
end -- closure
do -- begin closure to overcome local limits and interference
-if not modules then modules = { } end modules ['font-tfm'] = {
+if not modules then modules = { } end modules ['font-con'] = {
version = 1.001,
comment = "companion to font-ini.mkiv",
author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
@@ -3626,83 +2916,56 @@ if not modules then modules = { } end modules ['font-tfm'] = {
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 next, tostring, rawget = next, tostring, rawget
+local format, match, lower, gsub = string.format, string.match, string.lower, string.gsub
+local utfbyte = utf.byte
+local sort, insert, concat, sortedkeys, serialize, fastcopy = table.sort, table.insert, table.concat, table.sortedkeys, table.serialize, table.fastcopy
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)
--- tfmdata has also fast access to indices and unicodes
--- to be checked: otf -> tfm -> tfmscaled
---
+local report_defining = logs.reporter("fonts","defining")
+
-- watch out: no negative depths and negative eights permitted in regular fonts
--[[ldx--
<p>Here we only implement a few helper functions.</p>
--ldx]]--
-fonts = fonts or { }
-fonts.tfm = fonts.tfm or { }
-fonts.ids = fonts.ids or { }
+local fonts = fonts
+local constructors = { }
+fonts.constructors = constructors
+local handlers = { }
+fonts.handlers = handlers
-local tfm = fonts.tfm
+local specifiers = fonts.specifiers
+local contextsetups = specifiers.contextsetups
+local contextnumbers = specifiers.contextnumbers
-fonts.loaded = fonts.loaded or { }
-fonts.dontembed = fonts.dontembed or { }
-fonts.triggers = fonts.triggers or { } -- brrr
-fonts.initializers = fonts.initializers or { }
-fonts.initializers.common = fonts.initializers.common or { }
+local allocate = utilities.storage.allocate
+local setmetatableindex = table.setmetatableindex
-local fontdata = fonts.ids
-local disc = node.id('disc')
-local glyph = node.id('glyph')
-local set_attribute = node.set_attribute
+-- will be directives
---[[ldx--
-<p>The next function encapsulates the standard <l n='tfm'/> loader as
-supplied by <l n='luatex'/>.</p>
---ldx]]--
+constructors.dontembed = allocate()
+constructors.mathactions = { }
+constructors.autocleanup = true
+constructors.namemode = "fullpath" -- will be a function
-tfm.resolve_vf = true -- false
-tfm.share_base_kerns = false -- true (.5 sec slower on mk but brings down mem from 410M to 310M, beware: then script/lang share too)
-tfm.mathactions = { }
-tfm.fontname_mode = "fullpath"
+constructors.version = 1.01
+constructors.cache = containers.define("fonts", "constructors", constructors.version, false)
-tfm.enhance = tfm.enhance or function() end
+constructors.privateoffset = 0xF0000 -- 0x10FFFF
-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) -- not cached, fast enough
- if tfmdata then
- tfmdata.descriptions = tfmdata.descriptions or { }
- if tfm.resolve_vf then
- fonts.logger.save(tfmdata,file.extname(fname),specification) -- strange, why here
- fname = resolvers.findbinfile(specification.name, 'ovf')
- if fname and fname ~= "" then
- local vfdata = font.read_vf(fname,specification.size) -- not cached, fast enough
- 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
+-- This might become an interface;
+
+local designsizes = allocate()
+constructors.designsizes = designsizes
+local loadedfonts = allocate()
+constructors.loadedfonts = loadedfonts
--[[ldx--
<p>We need to normalize the scale factor (in scaled points). This has to
@@ -3715,22 +2978,23 @@ local factors = {
bp = 65781.8,
}
-function tfm.setfactor(f)
- tfm.factor = factors[f or 'pt'] or factors.pt
+function constructors.setfactor(f)
+ constructors.factor = factors[f or 'pt'] or factors.pt
end
-tfm.setfactor()
+constructors.setfactor()
-function tfm.scaled(scaledpoints, designsize) -- handles designsize in sp as well
+function constructors.scaled(scaledpoints, designsize) -- handles designsize in sp as well
if scaledpoints < 0 then
if designsize then
- if designsize > tfm.factor then -- or just 1000 / when? mp?
+ local factor = constructors.factor
+ if designsize > factor then -- or just 1000 / when? mp?
return (- scaledpoints/1000) * designsize -- sp's
else
- return (- scaledpoints/1000) * designsize * tfm.factor
+ return (- scaledpoints/1000) * designsize * factor
end
else
- return (- scaledpoints/1000) * 10 * tfm.factor
+ return (- scaledpoints/1000) * 10 * factor
end
else
return scaledpoints
@@ -3738,200 +3002,326 @@ function tfm.scaled(scaledpoints, designsize) -- handles designsize in sp as wel
end
--[[ldx--
-<p>Before a font is passed to <l n='tex'/> we scale it. Here we also need
-to scale virtual characters.</p>
---ldx]]--
-
-function tfm.get_virtual_id(tfmdata)
- -- since we don't know the id yet, we use 0 as signal
- 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
-
---[[ldx--
<p>Beware, the boundingbox is passed as reference so we may not overwrite it
in the process; numbers are of course copies. Here 65536 equals 1pt. (Due to
excessive memory usage in CJK fonts, we no longer pass the boundingbox.)</p>
--ldx]]--
-fonts.trace_scaling = false
+-- The scaler is only used for otf and afm and virtual fonts. If
+-- a virtual font has italic correction make sure to set the
+-- italic_correction flag. Some more flags will be added in
+-- the future.
--- the following hack costs a bit of runtime but safes memory
+--[[ldx--
+<p>The reason why the scaler was originally split, is that for a while we experimented
+with a helper function. However, in practice the <l n='api'/> calls are too slow to
+make this profitable and the <l n='lua'/> based variant was just faster. A days
+wasted day but an experience richer.</p>
+--ldx]]--
+
+-- we can get rid of the tfm instance when we have fast access to the
+-- scaled character dimensions at the tex end, e.g. a fontobject.width
+-- actually we already have soem of that now as virtual keys in glyphs
--
--- basekerns are scaled and will be hashed by table id
--- sharedkerns are unscaled and are be hashed by concatenated indexes
-
---~ function tfm.check_base_kerns(tfmdata)
---~ if tfm.share_base_kerns then
---~ local sharedkerns = tfmdata.sharedkerns
---~ if sharedkerns then
---~ local basekerns = { }
---~ tfmdata.basekerns = basekerns
---~ return sharedkerns, basekerns
---~ end
---~ end
---~ return nil, nil
---~ end
+-- flushing the kern and ligature tables from memory saves a lot (only
+-- base mode) but it complicates vf building where the new characters
+-- demand this data .. solution: functions that access them
---~ function tfm.prepare_base_kerns(tfmdata)
---~ if tfm.share_base_kerns and not tfmdata.sharedkerns then
---~ local sharedkerns = { }
---~ tfmdata.sharedkerns = sharedkerns
---~ for u, chr in next, tfmdata.characters do
---~ local kerns = chr.kerns
---~ if kerns then
---~ local hash = concat(sortedkeys(kerns), " ")
---~ local base = sharedkerns[hash]
---~ if not base then
---~ sharedkerns[hash] = kerns
---~ else
---~ chr.kerns = base
---~ end
---~ end
---~ end
---~ end
---~ end
+function constructors.cleanuptable(tfmdata)
+ if constructors.autocleanup and tfmdata.properties.virtualized then
+ for k, v in next, tfmdata.characters do
+ if v.commands then v.commands = nil end
+ -- if v.kerns then v.kerns = nil end
+ end
+ end
+end
--- we can have cache scaled characters when we are in node mode and don't have
--- protruding and expansion: hash == fullname @ size @ protruding @ expansion
--- but in practice (except from mk) the otf hash will be enough already so it
--- makes no sense to mess up the code now
+-- experimental, sharing kerns (unscaled and scaled) saves memory
+-- local sharedkerns, basekerns = constructors.check_base_kerns(tfmdata)
+-- loop over descriptions (afm and otf have descriptions, tfm not)
+-- there is no need (yet) to assign a value to chr.tonunicode
-local charactercache = { }
+-- constructors.prepare_base_kerns(tfmdata) -- optimalization
--- The scaler is only used for otf and afm and virtual fonts. If
--- a virtual font has italic correction make sure to set the
--- has_italic flag. Some more flags will be added in the future.
+-- we have target.name=metricfile and target.fullname=RealName and target.filename=diskfilename
+-- when collapsing fonts, luatex looks as both target.name and target.fullname as ttc files
+-- can have multiple subfonts
-function tfm.calculate_scale(tfmtable, scaledpoints, relativeid)
+function constructors.calculatescale(tfmdata,scaledpoints)
+ local parameters = tfmdata.parameters
if scaledpoints < 0 then
- scaledpoints = (- scaledpoints/1000) * tfmtable.designsize -- already in sp
+ scaledpoints = (- scaledpoints/1000) * (tfmdata.designsize or parameters.designsize) -- already in sp
+ end
+ return scaledpoints, scaledpoints / (parameters.units or 1000) -- delta
+end
+
+function constructors.assignmathparameters(target,tfmdata)
+ -- when a tfm file is loaded, it has already been scaled
+ -- and it never enters the scaled so this is otf only
+ local mathparameters = original.mathparameters
+ if mathparameters and next(mathparameters) then
+ local targetparameters = target.parameters
+ local targetmathparameters = { }
+ local factor = targetparameters.factor
+ for name, value in next, mathparameters do
+ if name == "RadicalDegreeBottomRaisePercent" then
+ targetmathparameters[name] = value
+ else
+ targetmathparameters[name] = value * factor
+ end
+ end
+ if not targetmathparameters.AccentBaseHeight then
+ targetmathparameters.AccentBaseHeight = nil -- safeguard, still needed?
+ end
+ if not targetmathparameters.FractionDelimiterSize then
+ targetmathparameters.FractionDelimiterSize = 0
+ end
+ if not mathparameters.FractionDelimiterDisplayStyleSize then
+ targetmathparameters.FractionDelimiterDisplayStyleSize = 0
+ end
+ target.mathparameters = targetmathparameters
end
- local units = tfmtable.units or 1000
- local delta = scaledpoints/units -- brr, some open type fonts have 2048
- return scaledpoints, delta, units
end
-function tfm.do_scale(tfmtable, scaledpoints, relativeid)
- -- tfm.prepare_base_kerns(tfmtable) -- optimalization
- local t = { } -- the new table
- local scaledpoints, delta, units = tfm.calculate_scale(tfmtable, scaledpoints, relativeid)
- t.units_per_em = units or 1000
- local hdelta, vdelta = delta, delta
- -- unicoded unique descriptions shared cidinfo characters changed parameters indices
- for k,v in next, tfmtable do
- if type(v) == "table" then
- -- print(k)
- else
- t[k] = v
- end
+function constructors.scale(tfmdata,specification)
+ local target = { } -- the new table
+ --
+ if tonumber(specification) then
+ specification = { size = specification }
+ end
+ --
+ local scaledpoints = specification.size
+ local relativeid = specification.relativeid
+ --
+ local properties = tfmdata.properties or { }
+ local goodies = tfmdata.goodies or { }
+ local resources = tfmdata.resources or { }
+ local descriptions = tfmdata.descriptions or { } -- bad news if empty
+ local characters = tfmdata.characters or { } -- bad news if empty
+ local changed = tfmdata.changed or { } -- for base mode
+ local shared = tfmdata.shared or { }
+ local parameters = tfmdata.parameters or { }
+ local mathparameters = tfmdata.mathparameters or { }
+ --
+ local targetcharacters = { }
+ local targetdescriptions = table.derive(descriptions)
+ local targetparameters = table.derive(parameters)
+ -- local targetmathparameters = table.fastcopy(mathparameters) -- happens elsewhere
+ local targetproperties = table.derive(properties)
+ local targetgoodies = goodies -- we need to loop so no metatable
+ target.characters = targetcharacters
+ target.descriptions = targetdescriptions
+ target.parameters = targetparameters
+ -- target.mathparameters = targetmathparameters -- happens elsewhere
+ target.properties = targetproperties
+ target.goodies = targetgoodies
+ target.shared = shared
+ target.resources = resources
+ target.unscaled = tfmdata -- the original unscaled one
+ --
+ -- specification.mathsize : 1=text 2=script 3=scriptscript
+ -- specification.textsize : natural (text)size
+ -- parameters.mathsize : 1=text 2=script 3=scriptscript >1000 enforced size (feature value other than yes)
+ --
+ local mathsize = tonumber(specification.mathsize) or 0
+ local textsize = tonumber(specification.textsize) or scaledpoints
+ local forcedsize = tonumber(parameters.mathsize ) or 0
+ if (mathsize == 2 or forcedsize == 2) and parameters.scriptpercentage then
+ scaledpoints = parameters.scriptpercentage * textsize / 100
+ elseif (mathsize == 3 or forcedsize == 3) and parameters.scriptscriptpercentage then
+ scaledpoints = parameters.scriptscriptpercentage * textsize / 100
+ elseif forcedsize > 1000 then -- safeguard
+ scaledpoints = forcedsize
+ end
+ --
+ local tounicode = resources.tounicode
+ local defaultwidth = resources.defaultwidth or 0
+ local defaultheight = resources.defaultheight or 0
+ local defaultdepth = resources.defaultdepth or 0
+ local units = parameters.units or 1000
+ --
+ if target.fonts then
+ target.fonts = fastcopy(target.fonts) -- maybe we virtualize more afterwards
+ end
+ --
+ -- boundary keys are no longer needed as we now have a string 'right_boundary'
+ -- that can be used in relevant tables (kerns and ligatures) ... not that I ever
+ -- used them
+ --
+ -- boundarychar_label = 0, -- not needed
+ -- boundarychar = 65536, -- there is now a string 'right_boundary'
+ -- false_boundarychar = 65536, -- produces invalid tfm in luatex
+ --
+ targetproperties.language = properties.language or "dflt" -- inherited
+ targetproperties.script = properties.script or "dflt" -- inherited
+ targetproperties.mode = properties.mode or "base" -- inherited
+ --
+ local askedscaledpoints = scaledpoints
+ local scaledpoints, delta = constructors.calculatescale(tfmdata,scaledpoints) -- no shortcut, dan be redefined
+ --
+ local hdelta = delta
+ local vdelta = delta
+ --
+ target.designsize = parameters.designsize -- not really needed so it muight become obsolete
+ target.units_per_em = units -- just a trigger for the backend (does luatex use this? if not it will go)
+ --
+ local direction = properties.direction or tfmdata.direction or 0 -- pointless, as we don't use omf fonts at all
+ target.direction = direction
+ properties.direction = direction
+ --
+ target.size = scaledpoints
+ --
+ target.encodingbytes = properties.encodingbytes or 1
+ target.embedding = properties.embedding or "subset"
+ target.tounicode = 1
+ target.cidinfo = properties.cidinfo
+ target.format = properties.format
+ --
+ local fontname = properties.fontname or tfmdata.fontname -- for the moment we fall back on
+ local fullname = properties.fullname or tfmdata.fullname -- names in the tfmdata although
+ local filename = properties.filename or tfmdata.filename -- that is not the right place to
+ local psname = properties.psname or tfmdata.psname -- pass them
+ local name = properties.name or tfmdata.name
+ --
+ if not psname or psname == "" then
+ -- name used in pdf file as well as for selecting subfont in ttc/dfont
+ psname = fontname or (fullname and fonts.names.cleanname(fullname))
end
- local extend_factor = tfmtable.extend_factor or 0
+ target.fontname = fontname
+ target.fullname = fullname
+ target.filename = filename
+ target.psname = psname
+ target.name = name
+ --
+ properties.fontname = fontname
+ properties.fullname = fullname
+ properties.filename = filename
+ properties.psname = psname
+ properties.name = name
+ -- expansion (hz)
+ local expansion = parameters.expansion
+ if expansion then
+ target.stretch = expansion.stretch
+ target.shrink = expansion.shrink
+ target.step = expansion.step
+ target.auto_expand = expansion.auto
+ end
+ -- protrusion
+ local protrusion = parameters.protrusion
+ if protrusion then
+ target.auto_protrude = protrusion.auto
+ end
+ -- widening
+ local extend_factor = parameters.extend_factor or 0
if extend_factor ~= 0 and extend_factor ~= 1 then
hdelta = hdelta * extend_factor
- t.extend = extend_factor * 1000
+ target.extend = extend_factor * 1000 -- extent ?
else
- t.extend = 1000
+ target.extend = 1000 -- extent ?
end
- local slant_factor = tfmtable.slant_factor or 0
+ -- slanting
+ local slant_factor = parameters.slant_factor or 0
if slant_factor ~= 0 then
- t.slant = slant_factor * 1000
+ target.slant = slant_factor * 1000
else
- t.slant = 0
- end
- -- status
- 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
+ target.slant = 0
+ end
+ --
+ targetparameters.factor = delta
+ targetparameters.hfactor = hdelta
+ targetparameters.vfactor = vdelta
+ targetparameters.size = scaledpoints
+ targetparameters.units = units
+ targetparameters.scaledpoints = askedscaledpoints
+ --
+ local isvirtual = properties.virtualized or tfmdata.type == "virtual"
+ local hasquality = target.auto_expand or target.auto_protrude
+ local hasitalic = properties.italic_correction
+ local stackmath = not properties.no_stackmath
+ local nonames = properties.noglyphnames
+ local nodemode = properties.mode == "node"
+ --
+ if changed and not next(changed) then
+ changed = false
+ end
+ --
+ target.type = isvirtual and "virtual" or "real"
+ -- this will move to some subtable so that it is copied at once
+ target.postprocessors = tfmdata.postprocessors
+ --
+ local targetslant = (parameters.slant or parameters[1] or 0)
+ local targetspace = (parameters.space or parameters[2] or 0)*hdelta
+ local targetspace_stretch = (parameters.space_stretch or parameters[3] or 0)*hdelta
+ local targetspace_shrink = (parameters.space_shrink or parameters[4] or 0)*hdelta
+ local targetx_height = (parameters.x_height or parameters[5] or 0)*vdelta
+ local targetquad = (parameters.quad or parameters[6] or 0)*hdelta
+ local targetextra_space = (parameters.extra_space or parameters[7] or 0)*hdelta
+ --
+ targetparameters.slant = targetslant
+ targetparameters.space = targetspace
+ targetparameters.space_stretch = targetspace_stretch
+ targetparameters.space_shrink = targetspace_shrink
+ targetparameters.x_height = targetx_height
+ targetparameters.quad = targetquad
+ targetparameters.extra_space = targetextra_space
--
- t.parameters = { }
- t.characters = { }
- t.MathConstants = { }
- -- fast access
- 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.embedding = tfmtable.embedding
- t.descriptions = descriptions
- if tfmtable.fonts then
- t.fonts = table.fastcopy(tfmtable.fonts) -- hm also at the end
- end
- local tp = t.parameters
- local mp = t.math_parameters
- local tfmp = tfmtable.parameters -- let's check for indexes
+ local ascender = parameters.ascender
+ if ascender then
+ targetparameters.ascender = delta * ascender
+ end
+ local descender = parameters.descender
+ if descender then
+ targetparameters.descender = delta * descender
+ end
+ -- copies, might disappear
+ targetparameters.xheight = targetparameters.xheight or parameters.x_height
+ targetparameters.extraspace = targetparameters.extraspace or parameters.extra_space
+ targetparameters.spacestretch = targetparameters.spacestretch or parameters.space_stretch
+ targetparameters.spaceshrink = targetparameters.spaceshrink or parameters.space_shrink
--
- 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 --hack
- local changed = tfmtable.changed or { } -- for base mode
- 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
- -- experimental, sharing kerns (unscaled and scaled) saves memory
- -- local sharedkerns, basekerns = tfm.check_base_kerns(tfmtable)
- -- loop over descriptions (afm and otf have descriptions, tfm not)
- -- there is no need (yet) to assign a value to chr.tonunicode
- 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 protrusionfactor = (targetquad ~= 0 and 1000/targetquad) or 0
+ local scaledwidth = defaultwidth * hdelta
+ local scaledheight = defaultheight * vdelta
+ local scaleddepth = defaultdepth * vdelta
+ --
+ local hasmath = (properties.has_math or next(mathparameters)) and true
+ if hasmath then
+ if trace_defining then
+ report_defining("math enabled for: name '%s', fullname: '%s', filename: '%s'",
+ name or "noname",fullname or "nofullname",filename or "nofilename")
+ end
+ constructors.assignmathparameters(target,tfmdata) -- does scaling and whatever is needed
+ properties.has_math = true
+ target.nomath = false
+ target.MathConstants = target.mathparameters
+ else
+ if trace_defining then
+ report_defining("math disabled for: name '%s', fullname: '%s', filename: '%s'",
+ name or "noname",fullname or "nofullname",filename or "nofilename")
+ end
+ properties.has_math = false
+ target.nomath = true
+ target.mathparameters = nil -- nop
+ end
+ --
+ local sharedkerns = { }
+ --
+ for unicode, character in next, characters do
local chr, description, index
- if ischanged then
+ if changed then
-- basemode hack
- local c = changed[k]
+ local c = changed[unicode]
if c then
- description = descriptions[c] or v
- v = characters[c] or v
- index = (indices and indices[c]) or c
+ description = descriptions[c] or character
+ character = characters[c] or character
+ index = description.index or c
else
- description = descriptions[k] or v
- index = (indices and indices[k]) or k
+ description = descriptions[unicode] or character
+ index = description.index or unicode
end
else
- description = descriptions[k] or v
- index = (indices and indices[k]) or k
+ description = descriptions[unicode] or character
+ index = description.index or unicode
end
local width = description.width
local height = description.height
@@ -3941,9 +3331,8 @@ t.colorscheme = tfmtable.colorscheme
-- if depth then depth = vdelta*depth else depth = scaleddepth end
if depth and depth ~= 0 then
depth = delta*depth
- if nameneeded then
+ if nonames then
chr = {
- name = description.name,
index = index,
height = height,
depth = depth,
@@ -3951,6 +3340,7 @@ t.colorscheme = tfmtable.colorscheme
}
else
chr = {
+ name = description.name,
index = index,
height = height,
depth = depth,
@@ -3959,15 +3349,15 @@ t.colorscheme = tfmtable.colorscheme
end
else
-- this saves a little bit of memory time and memory, esp for big cjk fonts
- if nameneeded then
+ if nonames then
chr = {
- name = description.name,
index = index,
height = height,
width = width,
}
else
chr = {
+ name = description.name,
index = index,
height = height,
width = width,
@@ -3975,7 +3365,7 @@ t.colorscheme = tfmtable.colorscheme
end
end
-- if trace_scaling then
- -- logs.report("define font","t=%s, u=%s, i=%s, n=%s c=%s",k,chr.tounicode or k,description.index,description.name or '-',description.class or '-')
+ -- report_defining("t=%s, u=%s, i=%s, n=%s c=%s",k,chr.tounicode or "",index or 0,description.name or '-',description.class or '-')
-- end
if tounicode then
local tu = tounicode[index] -- nb: index!
@@ -3985,22 +3375,22 @@ t.colorscheme = tfmtable.colorscheme
end
if hasquality then
-- we could move these calculations elsewhere (saves calculations)
- local ve = v.expansion_factor
+ local ve = character.expansion_factor
if ve then
chr.expansion_factor = ve*1000 -- expansionfactor, hm, can happen elsewhere
end
- local vl = v.left_protruding
+ local vl = character.left_protruding
if vl then
chr.left_protruding = protrusionfactor*width*vl
end
- local vr = v.right_protruding
+ local vr = character.right_protruding
if vr then
chr.right_protruding = protrusionfactor*width*vr
end
end
-- todo: hasitalic
if hasitalic then
- local vi = description.italic or v.italic
+ local vi = description.italic or character.italic
if vi and vi ~= 0 then
chr.italic = vi*hdelta
end
@@ -4008,11 +3398,14 @@ t.colorscheme = tfmtable.colorscheme
-- to be tested
if hasmath then
-- todo, just operate on descriptions.math
- local vn = v.next
+ local vn = character.next
if vn then
chr.next = vn
+ -- if character.vert_variants or character.horiz_variants then
+ -- report_defining("glyph U+%05X has combination of next, vert_variants and horiz_variants",index)
+ -- end
else
- local vv = v.vert_variants
+ local vv = character.vert_variants
if vv then
local t = { }
for i=1,#vv do
@@ -4026,13 +3419,8 @@ t.colorscheme = tfmtable.colorscheme
}
end
chr.vert_variants = t
- --~ local ic = v.vert_italic_correction
- --~ if ic then
- --~ chr.italic = ic * hdelta
- --~ print(format("0x%05X -> %s",k,chr.italic))
- --~ end
else
- local hv = v.horiz_variants
+ local hv = character.horiz_variants
if hv then
local t = { }
for i=1,#hv do
@@ -4049,12 +3437,12 @@ t.colorscheme = tfmtable.colorscheme
end
end
end
- local vt = description.top_accent
- if vt then
- chr.top_accent = vdelta*vt
+ local va = character.top_accent
+ if va then
+ chr.top_accent = vdelta*va
end
if stackmath then
- local mk = v.mathkerns
+ local mk = character.mathkerns -- not in math ?
if mk then
local kerns = { }
local v = mk.top_right if v then local k = { } for i=1,#v do local vi = v[i]
@@ -4069,26 +3457,13 @@ t.colorscheme = tfmtable.colorscheme
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 -- singular
+ chr.mathkern = kerns -- singular -> should be patched in luatex !
end
end
end
if not nodemode then
- local vk = v.kerns
+ local vk = character.kerns
if vk then
- --~ if sharedkerns then
- --~ local base = basekerns[vk] -- hashed by table id, not content
- --~ if not base then
- --~ base = {}
- --~ for k,v in next, vk do base[k] = v*hdelta end
- --~ basekerns[vk] = base
- --~ end
- --~ chr.kerns = base
- --~ else
- --~ local tt = {}
- --~ for k,v in next, vk do tt[k] = v*hdelta end
- --~ chr.kerns = tt
- --~ end
local s = sharedkerns[vk]
if not s then
s = { }
@@ -4097,7 +3472,7 @@ t.colorscheme = tfmtable.colorscheme
end
chr.kerns = s
end
- local vl = v.ligatures
+ local vl = character.ligatures
if vl then
if true then
chr.ligatures = vl -- shared
@@ -4111,7 +3486,7 @@ t.colorscheme = tfmtable.colorscheme
end
end
if isvirtual then
- local vc = v.commands
+ local vc = character.commands
if vc then
-- we assume non scaled commands here
-- tricky .. we need to scale pseudo math glyphs too
@@ -4130,230 +3505,577 @@ t.colorscheme = tfmtable.colorscheme
local ivc = vc[i]
local key = ivc[1]
if key == "right" then
- tt[#tt+1] = { key, ivc[2]*hdelta }
+ tt[i] = { key, ivc[2]*hdelta }
elseif key == "down" then
- tt[#tt+1] = { key, ivc[2]*vdelta }
+ tt[i] = { key, ivc[2]*vdelta }
elseif key == "rule" then
- tt[#tt+1] = { key, ivc[2]*vdelta, ivc[3]*hdelta }
+ tt[i] = { key, ivc[2]*vdelta, ivc[3]*hdelta }
else -- not comment
- tt[#tt+1] = ivc -- shared since in cache and untouched
+ tt[i] = ivc -- shared since in cache and untouched
end
end
chr.commands = tt
else
chr.commands = vc
end
+ chr.index = nil
end
end
- tc[k] = chr
+ targetcharacters[unicode] = chr
end
- -- t.encodingbytes, t.filename, t.fullname, t.name: elsewhere
- t.size = scaledpoints
- t.factor = delta
- t.hfactor = hdelta
- t.vfactor = vdelta
- if t.fonts then
- t.fonts = table.fastcopy(t.fonts) -- maybe we virtualize more afterwards
+ return target
+end
+
+function constructors.finalize(tfmdata)
+ if tfmdata.properties and tfmdata.properties.finalized then
+ return
end
- if hasmath then
- -- mathematics.extras.copy(t) -- can be done elsewhere if needed
- local ma = tfm.mathactions
- for i=1,#ma do
- ma[i](t,tfmtable,delta,hdelta,vdelta) -- what delta?
- end
+ --
+ if not tfmdata.characters then
+ return nil
end
- -- needed for \high cum suis
- local tpx = tp.x_height
- if hasmath then
- if not tp[13] then tp[13] = .86*tpx end -- mathsupdisplay
- if not tp[14] then tp[14] = .86*tpx end -- mathsupnormal
- if not tp[15] then tp[15] = .86*tpx end -- mathsupcramped
- if not tp[16] then tp[16] = .48*tpx end -- mathsubnormal
- if not tp[17] then tp[17] = .48*tpx end -- mathsubcombined
- if not tp[22] then tp[22] = 0 end -- mathaxisheight
- if t.MathConstants then t.MathConstants.AccentBaseHeight = nil end -- safeguard
- end
- t.tounicode = 1
- t.cidinfo = tfmtable.cidinfo
- -- we have t.name=metricfile and t.fullname=RealName and t.filename=diskfilename
- -- when collapsing fonts, luatex looks as both t.name and t.fullname as ttc files
- -- can have multiple subfonts
- 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")
+ --
+ if not tfmdata.goodies then
+ tfmdata.goodies = { } -- context specific
+ end
+ --
+ local parameters = tfmdata.parameters
+ if not parameters then
+ return nil
+ end
+ --
+ if not parameters.expansion then
+ parameters.expansion = {
+ stretch = tfmdata.stretch or 0,
+ shrink = tfmdata.shrink or 0,
+ step = tfmdata.step or 0,
+ auto = tfmdata.auto_expand or false,
+ }
+ end
+ --
+ if not parameters.protrusion then
+ parameters.protrusion = {
+ auto = auto_protrude
+ }
+ end
+ --
+ if not parameters.size then
+ parameters.size = tfmdata.size
+ end
+ --
+ if not parameters.extend_factor then
+ parameters.extend_factor = tfmdata.extend or 0
+ end
+ --
+ if not parameters.slant_factor then
+ parameters.slant_factor = tfmdata.slant or 0
+ end
+ --
+ if not parameters.designsize then
+ parameters.designsize = tfmdata.designsize or 655360
+ end
+ --
+ if not parameters.units then
+ parameters.units = tfmdata.units_per_em or 1000
+ end
+ --
+ if not tfmdata.descriptions then
+ local descriptions = { } -- yes or no
+ setmetatableindex(descriptions, function(t,k) local v = { } t[k] = v return v end)
+ tfmdata.descriptions = descriptions
+ end
+ --
+ local properties = tfmdata.properties
+ if not properties then
+ properties = { }
+ tfmdata.properties = properties
+ end
+ --
+ if not properties.virtualized then
+ properties.virtualized = tfmdata.type == "virtual"
+ end
+ --
+ if not tfmdata.properties then
+ tfmdata.properties = {
+ fontname = tfmdata.fontname,
+ filename = tfmdata.filename,
+ fullname = tfmdata.fullname,
+ name = tfmdata.name,
+ psname = tfmdata.psname,
+ --
+ encodingbytes = tfmdata.encodingbytes or 1,
+ embedding = tfmdata.embedding or "subset",
+ tounicode = tfmdata.tounicode or 1,
+ cidinfo = tfmdata.cidinfo or nil,
+ format = tfmdata.format or "type1",
+ direction = tfmdata.direction or 0,
+ }
+ end
+ if not tfmdata.resources then
+ tfmdata.resources = { }
+ end
+ if not tfmdata.shared then
+ tfmdata.shared = { }
+ end
+ --
+ -- tfmdata.fonts
+ -- tfmdata.unscaled
+ --
+ if not properties.has_math then
+ properties.has_math = not tfmdata.nomath
+ end
+ --
+ tfmdata.MathConstants = nil
+ tfmdata.postprocessors = nil
+ --
+ tfmdata.fontname = nil
+ tfmdata.filename = nil
+ tfmdata.fullname = nil
+ tfmdata.name = nil -- most tricky part
+ tfmdata.psname = nil
+ --
+ tfmdata.encodingbytes = nil
+ tfmdata.embedding = nil
+ tfmdata.tounicode = nil
+ tfmdata.cidinfo = nil
+ tfmdata.format = nil
+ tfmdata.direction = nil
+ tfmdata.type = nil
+ tfmdata.nomath = nil
+ tfmdata.designsize = nil
+ --
+ tfmdata.size = nil
+ tfmdata.stretch = nil
+ tfmdata.shrink = nil
+ tfmdata.step = nil
+ tfmdata.auto_expand = nil
+ tfmdata.auto_protrude = nil
+ tfmdata.extend = nil
+ tfmdata.slant = nil
+ tfmdata.units_per_em = nil
+ --
+ properties.finalized = true
+ --
+ return tfmdata
+end
+
+--[[ldx--
+<p>A unique hash value is generated by:</p>
+--ldx]]--
+
+local hashmethods = { }
+constructors.hashmethods = hashmethods
+
+function constructors.hashfeatures(specification) -- will be overloaded
+ local features = specification.features
+ if features then
+ local t, tn = { }, 0
+ for category, list in next, features do
+ if next(list) then
+ local hasher = hashmethods[category]
+ if hasher then
+ local hash = hasher(list)
+ if hash then
+ tn = tn + 1
+ t[tn] = category .. ":" .. hash
+ end
+ end
+ end
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")
+ if tn > 0 then
+ return concat(t," & ")
end
- t.nomath, t.MathConstants = true, nil
end
- if not t.psname then
- -- name used in pdf file as well as for selecting subfont in ttc/dfont
- t.psname = t.fontname or (t.fullname and fonts.names.cleanname(t.fullname))
+ return "unknown"
+end
+
+hashmethods.normal = function(list)
+ local s = { }
+ local n = 0
+ for k, v in next, list do
+ if k ~= "number" and k ~= "features" then -- I need to figure this out, features
+ n = n + 1
+ s[n] = k
+ end
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")
+ if n > 0 then
+ sort(s)
+ for i=1,n do
+ local k = s[i]
+ s[i] = k .. '=' .. tostring(list[k])
+ end
+ return concat(s,"+")
end
---~ print(t.fontname,table.serialize(t.MathConstants))
- return t, delta
end
--[[ldx--
-<p>The reason why the scaler is split, is that for a while we experimented
-with a helper function. However, in practice the <l n='api'/> calls are too slow to
-make this profitable and the <l n='lua'/> based variant was just faster. A days
-wasted day but an experience richer.</p>
+<p>In principle we can share tfm tables when we are in node for a font, but then
+we need to define a font switch as an id/attr switch which is no fun, so in that
+case users can best use dynamic features ... so, we will not use that speedup. Okay,
+when we get rid of base mode we can optimize even further by sharing, but then we
+loose our testcases for <l n='luatex'/>.</p>
--ldx]]--
-tfm.auto_cleanup = true
+function constructors.hashinstance(specification,force)
+ local hash, size, fallbacks = specification.hash, specification.size, specification.fallbacks
+ if force or not hash then
+ hash = constructors.hashfeatures(specification)
+ specification.hash = hash
+ end
+ if size < 1000 and designsizes[hash] then
+ size = math.round(constructors.scaled(size,designsizes[hash]))
+ specification.size = size
+ end
+ -- local mathsize = specification.mathsize or 0
+ -- if mathsize > 0 then
+ -- local textsize = specification.textsize
+ -- if fallbacks then
+ -- return hash .. ' @ ' .. tostring(size) .. ' [ ' .. tostring(mathsize) .. ' : ' .. tostring(textsize) .. ' ] @ ' .. fallbacks
+ -- else
+ -- return hash .. ' @ ' .. tostring(size) .. ' [ ' .. tostring(mathsize) .. ' : ' .. tostring(textsize) .. ' ]'
+ -- end
+ -- else
+ if fallbacks then
+ return hash .. ' @ ' .. tostring(size) .. ' @ ' .. fallbacks
+ else
+ return hash .. ' @ ' .. tostring(size)
+ end
+ -- end
+end
-local lastfont = nil
+function constructors.setname(tfmdata,specification) -- todo: get specification from tfmdata
+ if constructors.namemode == "specification" then
+ -- not to be used in context !
+ local specname = specification.specification
+ if specname then
+ tfmdata.properties.name = specname
+ if trace_defining then
+ report_otf("overloaded fontname: '%s'",specname)
+ end
+ end
+ end
+end
--- we can get rid of the tfm instance when we have fast access to the
--- scaled character dimensions at the tex end, e.g. a fontobject.width
---
--- flushing the kern and ligature tables from memory saves a lot (only
--- base mode) but it complicates vf building where the new characters
--- demand this data .. solution: functions that access them
+function constructors.checkedfilename(data)
+ local foundfilename = data.foundfilename
+ if not foundfilename then
+ local askedfilename = data.filename or ""
+ if askedfilename ~= "" then
+ askedfilename = resolvers.resolve(askedfilename) -- no shortcut
+ foundfilename = resolvers.findbinfile(askedfilename,"") or ""
+ if foundfilename == "" then
+ report_defining("source file '%s' is not found",askedfilename)
+ foundfilename = resolvers.findbinfile(file.basename(askedfilename),"") or ""
+ if foundfilename ~= "" then
+ report_defining("using source file '%s' (cache mismatch)",foundfilename)
+ end
+ end
+ end
+ data.foundfilename = foundfilename
+ end
+ return foundfilename
+end
+
+local formats = allocate()
+fonts.formats = formats
+
+setmetatableindex(formats, function(t,k)
+ local l = lower(k)
+ if rawget(t,k) then
+ t[k] = l
+ return l
+ end
+ return rawget(t,file.extname(l))
+end)
+
+local locations = { }
-function tfm.cleanup_table(tfmdata) -- we need a cleanup callback, now we miss the last one
- if tfm.auto_cleanup then -- ok, we can hook this into everyshipout or so ... todo
- if tfmdata.type == 'virtual' or tfmdata.virtualized then
- for k, v in next, tfmdata.characters do
- if v.commands then v.commands = nil end
- -- if v.kerns then v.kerns = nil end
+local function setindeed(mode,target,group,name,action,position)
+ local t = target[mode]
+ if not t then
+ report_defining("fatal error in setting feature '%s', group '%s', mode '%s'",name or "?",group or "?",mode)
+ os.exit()
+ elseif position then
+ -- todo: remove existing
+ insert(t, position, { name = name, action = action })
+ else
+ for i=1,#t do
+ local ti = t[i]
+ if ti.name == name then
+ ti.action = action
+ return
end
- else
- -- for k, v in next, tfmdata.characters do
- -- if v.kerns then v.kerns = nil end
- -- end
end
+ insert(t, { name = name, action = action })
end
end
-function tfm.cleanup(tfmdata) -- we need a cleanup callback, now we miss the last one
+local function set(group,name,target,source)
+ target = target[group]
+ if not target then
+ report_defining("fatal target error in setting feature '%s', group '%s'",name or "?",group or "?")
+ os.exit()
+ end
+ local source = source[group]
+ if not source then
+ report_defining("fatal source error in setting feature '%s', group '%s'",name or "?",group or "?")
+ os.exit()
+ end
+ local node = source.node
+ local base = source.base
+ local position = source.position
+ if node then
+ setindeed("node",target,group,name,node,position)
+ end
+ if base then
+ setindeed("base",target,group,name,base,position)
+ end
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 {})
---~ print("scaling", t.name, t.factor) -- , tfm.hash_features(tfmtable.specification))
- tfm.cleanup(t)
- return t
+local function register(where,specification)
+ local name = specification.name
+ if name and name ~= "" then
+ local default = specification.default
+ local description = specification.description
+ local initializers = specification.initializers
+ local processors = specification.processors
+ local manipulators = specification.manipulators
+ local modechecker = specification.modechecker
+ if default then
+ where.defaults[name] = default
+ end
+ if description and description ~= "" then
+ where.descriptions[name] = description
+ end
+ if initializers then
+ set('initializers',name,where,specification)
+ end
+ if processors then
+ set('processors', name,where,specification)
+ end
+ if manipulators then
+ set('manipulators',name,where,specification)
+ end
+ if modechecker then
+ where.modechecker = modechecker
+ end
+ end
end
---[[ldx--
-<p>Analyzers run per script and/or language and are needed in order to
-process features right.</p>
---ldx]]--
+constructors.registerfeature = register
-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 { }
+function constructors.getfeatureaction(what,where,mode,name)
+ what = handlers[what].features
+ if what then
+ where = what[where]
+ if where then
+ mode = where[mode]
+ if mode then
+ for i=1,#mode do
+ local m = mode[i]
+ if m.name == name then
+ return m.action
+ end
+ end
+ end
+ end
+ end
+end
--- todo: analyzers per script/lang, cross font, so we need an font id hash -> script
--- e.g. latin -> hyphenate, arab -> 1/2/3 analyze
+function constructors.newfeatures(what)
+ local features = handlers[what].features
+ if not features then
+ local tables = handlers[what].tables -- can be preloaded
+ features = allocate {
+ defaults = { },
+ descriptions = tables and tables.features or { },
+ initializers = { base = { }, node = { } },
+ processors = { base = { }, node = { } },
+ manipulators = { base = { }, node = { } },
+ }
+ features.register = function(specification) return register(features,specification) end
+ handlers[what].features = features -- will also become hidden
+ end
+ return features
+end
--- an example analyzer (should move to font-ota.lua)
+--[[ldx--
+<p>We need to check for default features. For this we provide
+a helper function.</p>
+--ldx]]--
-local state = attributes.private('state')
+function constructors.checkedfeatures(what,features)
+ local defaults = handlers[what].features.defaults
+ if features and next(features) then
+ features = fastcopy(features) -- can be inherited
+ for key, value in next, defaults do
+ if features[key] == nil then
+ features[key] = value
+ end
+ end
+ return features
+ else
+ return fastcopy(defaults) -- we can change features in place
+ end
+end
-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 -- maybe make n boolean
- 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) -- mark
- elseif n == 0 then
- first, last, n = current, current, 1
- set_attribute(current,state,1) -- init
- else
- last, n = current, n+1
- set_attribute(current,state,2) -- medi
+-- before scaling
+
+function constructors.initializefeatures(what,tfmdata,features,trace,report)
+ if features and next(features) then
+ local properties = tfmdata.properties or { } -- brrr
+ local whathandler = handlers[what]
+ local whatfeatures = whathandler.features
+ local whatinitializers = whatfeatures.initializers
+ local whatmodechecker = whatfeatures.modechecker
+ -- properties.mode can be enforces (for instance in font-otd)
+ local mode = properties.mode or (whatmodechecker and whatmodechecker(tfmdata,features,features.mode)) or features.mode or "base"
+ properties.mode = mode -- also status
+ features.mode = mode -- both properties.mode or features.mode can be changed
+ --
+ local done = { }
+ while true do
+ local redo = false
+ local initializers = whatfeatures.initializers[mode]
+ if initializers then
+ for i=1,#initializers do
+ local step = initializers[i]
+ local feature = step.name
+-- we could intercept mode here .. needs a rewrite of this whole loop then btu it's cleaner that way
+ local value = features[feature]
+ if not value then
+ -- disabled
+ elseif done[feature] then
+ -- already done
+ else
+ local action = step.action
+ if trace then
+ report("initializing feature %s to %s for mode %s for font %s",feature,
+ tostring(value),mode or 'unknown', tfmdata.properties.fullname or 'unknown')
+ end
+ action(tfmdata,value,features) -- can set mode (e.g. goodies) so it can trigger a restart
+ if mode ~= properties.mode or mode ~= features.mode then
+ if whatmodechecker then
+ properties.mode = whatmodechecker(tfmdata,features,properties.mode) -- force checking
+ features.mode = properties.mode
+ end
+ if mode ~= properties.mode then
+ mode = properties.mode
+ redo = true
+ end
+ end
+ done[feature] = true
+ end
+ if redo then
+ break
+ end
end
- else -- finish
- if first and first == last then
- set_attribute(last,state,4) -- isol
- elseif last then
- set_attribute(last,state,3) -- fina
+ if not redo then
+ break
end
- first, last, n = nil, nil, 0
- end
- elseif id == disc then
- -- always in the middle
- set_attribute(current,state,2) -- midi
- last = current
- else -- finish
- if first and first == last then
- set_attribute(last,state,4) -- isol
- elseif last then
- set_attribute(last,state,3) -- fina
+ else
+ break
end
- first, last, n = nil, nil, 0
end
- current = current.next
- end
- if first and first == last then
- set_attribute(last,state,4) -- isol
- elseif last then
- set_attribute(last,state,3) -- fina
+ properties.mode = mode -- to be sure
+ return true
+ else
+ return false
end
- return head, done
end
-function tfm.replacements(tfm,value)
- -- tfm.characters[0x0022] = table.fastcopy(tfm.characters[0x201D])
- -- tfm.characters[0x0027] = table.fastcopy(tfm.characters[0x2019])
- -- tfm.characters[0x0060] = table.fastcopy(tfm.characters[0x2018])
- -- tfm.characters[0x0022] = tfm.characters[0x201D]
- tfm.characters[0x0027] = tfm.characters[0x2019]
- -- tfm.characters[0x0060] = tfm.characters[0x2018]
+-- while typesetting
+
+function constructors.collectprocessors(what,tfmdata,features,trace,report)
+ local processes, nofprocesses = { }, 0
+ if features and next(features) then
+ local properties = tfmdata.properties
+ local whathandler = handlers[what]
+ local whatfeatures = whathandler.features
+ local whatprocessors = whatfeatures.processors
+ local processors = whatprocessors[properties.mode]
+ if processors then
+ for i=1,#processors do
+ local step = processors[i]
+ local feature = step.name
+ if features[feature] then
+ local action = step.action
+ if trace then
+ report("installing feature processor %s for mode %s for font %s",feature,
+ mode or 'unknown', tfmdata.properties.fullname or 'unknown')
+ end
+ if action then
+ nofprocesses = nofprocesses + 1
+ processes[nofprocesses] = action
+ end
+ end
+ end
+ end
+ end
+ return processes
end
--- checking
+-- after scaling
-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)
+function constructors.applymanipulators(what,tfmdata,features,trace,report)
+ if features and next(features) then
+ local properties = tfmdata.properties
+ local whathandler = handlers[what]
+ local whatfeatures = whathandler.features
+ local whatmanipulators = whatfeatures.manipulators
+ local manipulators = whatmanipulators[properties.mode]
+ if manipulators then
+ for i=1,#manipulators do
+ local step = manipulators[i]
+ local feature = step.name
+ local value = features[feature]
+ if value then
+ local action = step.action
+ if trace then
+ report("applying feature manipulator %s for mode %s for font %s",feature,
+ mode or 'unknown', tfmdata.properties.fullname or 'unknown')
+ end
+ if action then
+ action(tfmdata,feature,value)
+ end
end
end
- elseif whatever then
- logs.report("fonts","no source file for '%s'",whatever)
- foundfilename = ""
end
- metadata.foundfilename = foundfilename
- -- logs.report("fonts","using source file '%s'",foundfilename)
end
- return foundfilename
end
--- status info
+end -- closure
+
+do -- begin closure to overcome local limits and interference
+
+if not modules then modules = { } end modules ['luatex-font-enc'] = {
+ 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"
+}
+
+if context then
+ texio.write_nl("fatal error: this module is not for context")
+ os.exit()
+end
+
+local fonts = fonts
+fonts.encodings = { }
+fonts.encodings.agl = { }
+
+setmetatable(fonts.encodings.agl, { __index = function(t,k)
+ if k == "unicodes" then
+ texio.write(" <loading (extended) adobe glyph list>")
+ local unicodes = dofile(resolvers.findfile("font-agl.lua"))
+ fonts.encodings.agl = { unicodes = unicodes }
+ return unicodes
+ else
+ return nil
+ end
+end })
-statistics.register("fonts load time", function()
- return statistics.elapsedseconds(fonts)
-end)
end -- closure
@@ -4369,15 +4091,19 @@ if not modules then modules = { } end modules ['font-cid'] = {
local format, match, lower = string.format, string.match, string.lower
local tonumber = tonumber
-local lpegmatch = lpeg.match
+local P, S, R, C, V, lpegmatch = lpeg.P, lpeg.S, lpeg.R, lpeg.C, lpeg.V, lpeg.match
-local trace_loading = false trackers.register("otf.loading", function(v) trace_loading = v end)
+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 report_otf = logs.reporter("fonts","otf loading")
+local fonts = fonts
+
+local cid = { }
+fonts.cid = cid
+
+local cidmap = { }
+local cidmax = 10
-- original string parser: 0.109, lpeg parser: 0.036 seconds for Adobe-CNS1-4.cidmap
--
@@ -4386,14 +4112,14 @@ fonts.cid.max = fonts.cid.max or 10
-- 1..95 0020
-- 99 3000
-local number = lpeg.C(lpeg.R("09","af","AF")^1)
-local space = lpeg.S(" \n\r\t")
+local number = C(R("09","af","AF")^1)
+local space = S(" \n\r\t")
local spaces = space^0
-local period = lpeg.P(".")
+local period = P(".")
local periods = period * period
-local name = lpeg.P("/") * lpeg.C((1-space)^1)
+local name = P("/") * C((1-space)^1)
-local unicodes, names = { }, { }
+local unicodes, names = { }, { } -- we could use Carg now
local function do_one(a,b)
unicodes[tonumber(a)] = tonumber(b,16)
@@ -4411,15 +4137,15 @@ 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,
+local grammar = P { "start",
+ start = number * spaces * number * V("series"),
+ series = (spaces * (V("one") + V("range") + 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 function loadcidfile(filename)
local data = io.loaddata(filename)
if data then
unicodes, names = { }, { }
@@ -4433,1045 +4159,98 @@ function fonts.cid.load(filename)
unicodes = unicodes,
names = names
}
- else
- return nil
end
end
-local template = "%s-%s-%s.cidmap"
+cid.loadfile = loadcidfile -- we use the frozen variant
+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
+ local found = cidmap[hashname]
+ if not found then
if trace_loading then
- logs.report("load otf","checking cidmap, registry: %s, ordering: %s, supplement: %s, filename: %s",registry,ordering,supplement,filename)
+ report_otf("checking cidmap, registry: %s, ordering: %s, supplement: %s, filename: %s",registry,ordering,supplement,filename)
end
- local fullname = resolvers.find_file(filename,'cid') or ""
+ local fullname = resolvers.findfile(filename,'cid') or ""
if fullname ~= "" then
- cidmap = fonts.cid.load(fullname)
- if cidmap then
+ found = loadcidfile(fullname)
+ if found then
if trace_loading then
- logs.report("load otf","using cidmap file %s",filename)
+ report_otf("using cidmap file %s",filename)
end
- fonts.cid.map[hashname] = cidmap
- cidmap.usedname = file.basename(filename)
- return cidmap
+ cidmap[hashname] = found
+ found.usedname = file.basename(filename)
end
end
end
- return cidmap
+ return found
end
-function fonts.cid.getmap(registry,ordering,supplement)
- -- cf Arthur R. we can safely scan upwards since cids are downward compatible
- local supplement = tonumber(supplement)
+-- cf Arthur R. we can safely scan upwards since cids are downward compatible
+
+function cid.getmap(specification)
+ if not specification then
+ report_otf("invalid cidinfo specification (table expected)")
+ return
+ end
+ local registry = specification.registry
+ local ordering = specification.ordering
+ local supplement = specification.supplement
+ -- check for already loaded file
+ local filename = format(registry,ordering,supplement)
+ local found = cidmap[lower(filename)]
+ if found then
+ return found
+ end
if trace_loading then
- logs.report("load otf","needed cidmap, registry: %s, ordering: %s, supplement: %s",registry,ordering,supplement)
+ report_otf("needed cidmap, registry: %s, ordering: %s, supplement: %s",registry,ordering,supplement)
end
- local cidmap = locate(registry,ordering,supplement)
- if not cidmap then
+ found = locate(registry,ordering,supplement)
+ if not found then
+ local supnum = tonumber(supplement)
local cidnum = nil
-- next highest (alternatively we could start high)
- if supplement < fonts.cid.max then
- for supplement=supplement+1,fonts.cid.max do
- local c = locate(registry,ordering,supplement)
+ if supnum < cidmax then
+ for s=supnum+1,cidmax do
+ local c = locate(registry,ordering,s)
if c then
- cidmap, cidnum = c, supplement
+ found, cidnum = c, s
break
end
end
end
-- next lowest (least worse fit)
- if not cidmap and supplement > 0 then
- for supplement=supplement-1,0,-1 do
- local c = locate(registry,ordering,supplement)
+ if not found and supnum > 0 then
+ for s=supnum-1,0,-1 do
+ local c = locate(registry,ordering,s)
if c then
- cidmap, cidnum = c, supplement
+ found, cidnum = c, s
break
end
end
end
- -- prevent further lookups
- if cidmap and cidnum > 0 then
+ -- prevent further lookups -- somewhat tricky
+ registry = lower(registry)
+ ordering = lower(ordering)
+ if found 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 -- copy of ref
+ local filename = format(template,registry,ordering,s)
+ if not cidmap[filename] then
+ cidmap[filename] = found
end
end
end
end
- return cidmap
+ return found
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', -- math
- ['expt'] = 'Expert Forms',
- ['falt'] = 'Final glyph Alternates',
- ['fin2'] = 'Terminal Forms #2',
- ['fin3'] = 'Terminal Forms #3',
- ['fina'] = 'Terminal Forms',
- ['flac'] = 'Flattened Accents Over Capitals', -- math
- ['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', -- 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', -- math
- ['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'
-}
-
--- can be sped up by local tables
-
-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%-]","")
- k = language
- 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
-
--- When I feel the need ...
-
---~ otf.tables.aat = {
---~ [ 0] = {
---~ name = "allTypographicFeaturesType",
---~ [ 0] = "allTypeFeaturesOnSelector",
---~ [ 1] = "allTypeFeaturesOffSelector",
---~ },
---~ [ 1] = {
---~ name = "ligaturesType",
---~ [0 ] = "requiredLigaturesOnSelector",
---~ [1 ] = "requiredLigaturesOffSelector",
---~ [2 ] = "commonLigaturesOnSelector",
---~ [3 ] = "commonLigaturesOffSelector",
---~ [4 ] = "rareLigaturesOnSelector",
---~ [5 ] = "rareLigaturesOffSelector",
---~ [6 ] = "logosOnSelector ",
---~ [7 ] = "logosOffSelector ",
---~ [8 ] = "rebusPicturesOnSelector",
---~ [9 ] = "rebusPicturesOffSelector",
---~ [10] = "diphthongLigaturesOnSelector",
---~ [11] = "diphthongLigaturesOffSelector",
---~ [12] = "squaredLigaturesOnSelector",
---~ [13] = "squaredLigaturesOffSelector",
---~ [14] = "abbrevSquaredLigaturesOnSelector",
---~ [15] = "abbrevSquaredLigaturesOffSelector",
---~ },
---~ [ 2] = {
---~ name = "cursiveConnectionType",
---~ [ 0] = "unconnectedSelector",
---~ [ 1] = "partiallyConnectedSelector",
---~ [ 2] = "cursiveSelector ",
---~ },
---~ [ 3] = {
---~ name = "letterCaseType",
---~ [ 0] = "upperAndLowerCaseSelector",
---~ [ 1] = "allCapsSelector ",
---~ [ 2] = "allLowerCaseSelector",
---~ [ 3] = "smallCapsSelector ",
---~ [ 4] = "initialCapsSelector",
---~ [ 5] = "initialCapsAndSmallCapsSelector",
---~ },
---~ [ 4] = {
---~ name = "verticalSubstitutionType",
---~ [ 0] = "substituteVerticalFormsOnSelector",
---~ [ 1] = "substituteVerticalFormsOffSelector",
---~ },
---~ [ 5] = {
---~ name = "linguisticRearrangementType",
---~ [ 0] = "linguisticRearrangementOnSelector",
---~ [ 1] = "linguisticRearrangementOffSelector",
---~ },
---~ [ 6] = {
---~ name = "numberSpacingType",
---~ [ 0] = "monospacedNumbersSelector",
---~ [ 1] = "proportionalNumbersSelector",
---~ },
---~ [ 7] = {
---~ name = "appleReserved1Type",
---~ },
---~ [ 8] = {
---~ name = "smartSwashType",
---~ [ 0] = "wordInitialSwashesOnSelector",
---~ [ 1] = "wordInitialSwashesOffSelector",
---~ [ 2] = "wordFinalSwashesOnSelector",
---~ [ 3] = "wordFinalSwashesOffSelector",
---~ [ 4] = "lineInitialSwashesOnSelector",
---~ [ 5] = "lineInitialSwashesOffSelector",
---~ [ 6] = "lineFinalSwashesOnSelector",
---~ [ 7] = "lineFinalSwashesOffSelector",
---~ [ 8] = "nonFinalSwashesOnSelector",
---~ [ 9] = "nonFinalSwashesOffSelector",
---~ },
---~ [ 9] = {
---~ name = "diacriticsType",
---~ [ 0] = "showDiacriticsSelector",
---~ [ 1] = "hideDiacriticsSelector",
---~ [ 2] = "decomposeDiacriticsSelector",
---~ },
---~ [10] = {
---~ name = "verticalPositionType",
---~ [ 0] = "normalPositionSelector",
---~ [ 1] = "superiorsSelector ",
---~ [ 2] = "inferiorsSelector ",
---~ [ 3] = "ordinalsSelector ",
---~ },
---~ [11] = {
---~ name = "fractionsType",
---~ [ 0] = "noFractionsSelector",
---~ [ 1] = "verticalFractionsSelector",
---~ [ 2] = "diagonalFractionsSelector",
---~ },
---~ [12] = {
---~ name = "appleReserved2Type",
---~ },
---~ [13] = {
---~ name = "overlappingCharactersType",
---~ [ 0] = "preventOverlapOnSelector",
---~ [ 1] = "preventOverlapOffSelector",
---~ },
---~ [14] = {
---~ name = "typographicExtrasType",
---~ [0 ] = "hyphensToEmDashOnSelector",
---~ [1 ] = "hyphensToEmDashOffSelector",
---~ [2 ] = "hyphenToEnDashOnSelector",
---~ [3 ] = "hyphenToEnDashOffSelector",
---~ [4 ] = "unslashedZeroOnSelector",
---~ [5 ] = "unslashedZeroOffSelector",
---~ [6 ] = "formInterrobangOnSelector",
---~ [7 ] = "formInterrobangOffSelector",
---~ [8 ] = "smartQuotesOnSelector",
---~ [9 ] = "smartQuotesOffSelector",
---~ [10] = "periodsToEllipsisOnSelector",
---~ [11] = "periodsToEllipsisOffSelector",
---~ },
---~ [15] = {
---~ name = "mathematicalExtrasType",
---~ [ 0] = "hyphenToMinusOnSelector",
---~ [ 1] = "hyphenToMinusOffSelector",
---~ [ 2] = "asteriskToMultiplyOnSelector",
---~ [ 3] = "asteriskToMultiplyOffSelector",
---~ [ 4] = "slashToDivideOnSelector",
---~ [ 5] = "slashToDivideOffSelector",
---~ [ 6] = "inequalityLigaturesOnSelector",
---~ [ 7] = "inequalityLigaturesOffSelector",
---~ [ 8] = "exponentsOnSelector",
---~ [ 9] = "exponentsOffSelector",
---~ },
---~ [16] = {
---~ name = "ornamentSetsType",
---~ [ 0] = "noOrnamentsSelector",
---~ [ 1] = "dingbatsSelector ",
---~ [ 2] = "piCharactersSelector",
---~ [ 3] = "fleuronsSelector ",
---~ [ 4] = "decorativeBordersSelector",
---~ [ 5] = "internationalSymbolsSelector",
---~ [ 6] = "mathSymbolsSelector",
---~ },
---~ [17] = {
---~ name = "characterAlternativesType",
---~ [ 0] = "noAlternatesSelector",
---~ },
---~ [18] = {
---~ name = "designComplexityType",
---~ [ 0] = "designLevel1Selector",
---~ [ 1] = "designLevel2Selector",
---~ [ 2] = "designLevel3Selector",
---~ [ 3] = "designLevel4Selector",
---~ [ 4] = "designLevel5Selector",
---~ },
---~ [19] = {
---~ name = "styleOptionsType",
---~ [ 0] = "noStyleOptionsSelector",
---~ [ 1] = "displayTextSelector",
---~ [ 2] = "engravedTextSelector",
---~ [ 3] = "illuminatedCapsSelector",
---~ [ 4] = "titlingCapsSelector",
---~ [ 5] = "tallCapsSelector ",
---~ },
---~ [20] = {
---~ name = "characterShapeType",
---~ [0 ] = "traditionalCharactersSelector",
---~ [1 ] = "simplifiedCharactersSelector",
---~ [2 ] = "jis1978CharactersSelector",
---~ [3 ] = "jis1983CharactersSelector",
---~ [4 ] = "jis1990CharactersSelector",
---~ [5 ] = "traditionalAltOneSelector",
---~ [6 ] = "traditionalAltTwoSelector",
---~ [7 ] = "traditionalAltThreeSelector",
---~ [8 ] = "traditionalAltFourSelector",
---~ [9 ] = "traditionalAltFiveSelector",
---~ [10] = "expertCharactersSelector",
---~ },
---~ [21] = {
---~ name = "numberCaseType",
---~ [ 0] = "lowerCaseNumbersSelector",
---~ [ 1] = "upperCaseNumbersSelector",
---~ },
---~ [22] = {
---~ name = "textSpacingType",
---~ [ 0] = "proportionalTextSelector",
---~ [ 1] = "monospacedTextSelector",
---~ [ 2] = "halfWidthTextSelector",
---~ [ 3] = "normallySpacedTextSelector",
---~ },
---~ [23] = {
---~ name = "transliterationType",
---~ [ 0] = "noTransliterationSelector",
---~ [ 1] = "hanjaToHangulSelector",
---~ [ 2] = "hiraganaToKatakanaSelector",
---~ [ 3] = "katakanaToHiraganaSelector",
---~ [ 4] = "kanaToRomanizationSelector",
---~ [ 5] = "romanizationToHiraganaSelector",
---~ [ 6] = "romanizationToKatakanaSelector",
---~ [ 7] = "hanjaToHangulAltOneSelector",
---~ [ 8] = "hanjaToHangulAltTwoSelector",
---~ [ 9] = "hanjaToHangulAltThreeSelector",
---~ },
---~ [24] = {
---~ name = "annotationType",
---~ [ 0] = "noAnnotationSelector",
---~ [ 1] = "boxAnnotationSelector",
---~ [ 2] = "roundedBoxAnnotationSelector",
---~ [ 3] = "circleAnnotationSelector",
---~ [ 4] = "invertedCircleAnnotationSelector",
---~ [ 5] = "parenthesisAnnotationSelector",
---~ [ 6] = "periodAnnotationSelector",
---~ [ 7] = "romanNumeralAnnotationSelector",
---~ [ 8] = "diamondAnnotationSelector",
---~ },
---~ [25] = {
---~ name = "kanaSpacingType",
---~ [ 0] = "fullWidthKanaSelector",
---~ [ 1] = "proportionalKanaSelector",
---~ },
---~ [26] = {
---~ name = "ideographicSpacingType",
---~ [ 0] = "fullWidthIdeographsSelector",
---~ [ 1] = "proportionalIdeographsSelector",
---~ },
---~ [103] = {
---~ name = "cjkRomanSpacingType",
---~ [ 0] = "halfWidthCJKRomanSelector",
---~ [ 1] = "proportionalCJKRomanSelector",
---~ [ 2] = "defaultCJKRomanSelector",
---~ [ 3] = "fullWidthCJKRomanSelector",
---~ },
---~ }
-
-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",
@@ -5480,15 +4259,18 @@ if not modules then modules = { } end modules ['font-map'] = {
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 P, R, S, C, Ct, Cc, lpegmatch = lpeg.P, lpeg.R, lpeg.S, lpeg.C, lpeg.Ct, lpeg.Cc, 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 trace_loading = false trackers.register("fonts.loading", function(v) trace_loading = v end)
+local trace_mapping = false trackers.register("fonts.mapping", function(v) trace_unimapping = v end)
-local ctxcatcodes = tex and tex.ctxcatcodes
+local report_fonts = logs.reporter("fonts","loading") -- not otf only
+
+local fonts = fonts
+local mappings = { }
+fonts.mappings = mappings
--[[ldx--
<p>Eventually this code will disappear because map files are kind
@@ -5496,50 +4278,46 @@ of obsolete. Some code may move to runtime or auxiliary modules.</p>
<p>The name to unciode related code will stay of course.</p>
--ldx]]--
-fonts = fonts or { }
-fonts.map = fonts.map or { }
-
-local function load_lum_table(filename) -- will move to font goodies
+local function loadlumtable(filename) -- will move to font goodies
local lumname = file.replacesuffix(file.basename(filename),"lum")
- local lumfile = resolvers.find_file(lumname,"map") or ""
+ local lumfile = resolvers.findfile(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)
+ if trace_loading or trace_mapping then
+ report_fonts("enhance: loading %s ",lumfile)
end
lumunic = dofile(lumfile)
return lumunic, lumfile
end
end
-local hex = lpeg.R("AF","09")
+local hex = 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 dec = (R("09")^1) / tonumber
+local period = P(".")
+local unicode = P("uni") * (hexfour * (period + P(-1)) * Cc(false) + Ct(hexfour^1) * Cc(true))
+local ucode = P("u") * (hexsix * (period + P(-1)) * Cc(false) + Ct(hexsix ^1) * Cc(true))
+local index = P("index") * dec * Cc(false)
local parser = unicode + ucode + index
local parsers = { }
-local function make_name_parser(str)
+local function makenameparser(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)
+ p = P(str) * period * dec * Cc(false)
parsers[str] = p
end
return p
end
end
---~ local parser = fonts.map.make_name_parser("Japan1")
---~ local parser = fonts.map.make_name_parser()
+--~ local parser = mappings.makenameparser("Japan1")
+--~ local parser = mappings.makenameparser()
--~ local function test(str)
--~ local b, a = lpegmatch(parser,str)
--~ print((a and table.serialize(b)) or b)
@@ -5580,7 +4358,7 @@ end
--~
--~ local cache = { }
--~
---~ function fonts.map.tounicode16(unicode)
+--~ function mappings.tounicode16(unicode)
--~ local s = cache[unicode]
--~ if not s then
--~ if unicode < 0x10000 then
@@ -5593,14 +4371,14 @@ end
--~ return s
--~ 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
+mappings.loadlumtable = loadlumtable
+mappings.makenameparser = makenameparser
+mappings.tounicode16 = tounicode16
+mappings.tounicode16sequence = tounicode16sequence
-local separator = lpeg.S("_.")
-local other = lpeg.C((1 - separator)^1)
-local ligsplitter = lpeg.Ct(other * (separator * other)^0)
+local separator = S("_.")
+local other = C((1 - separator)^1)
+local ligsplitter = Ct(other * (separator * other)^0)
--~ print(table.serialize(lpegmatch(ligsplitter,"this")))
--~ print(table.serialize(lpegmatch(ligsplitter,"this.that")))
@@ -5608,8 +4386,11 @@ local ligsplitter = lpeg.Ct(other * (separator * other)^0)
--~ print(table.serialize(lpegmatch(ligsplitter,"such_so_more")))
--~ print(table.serialize(lpegmatch(ligsplitter,"such_so_more.that")))
-fonts.map.add_to_unicode = function(data,filename)
- local unicodes = data.luatex and data.luatex.unicodes
+function mappings.addtounicode(data,filename)
+ local resources = data.resources
+ local properties = data.properties
+ local descriptions = data.descriptions
+ local unicodes = resources.unicodes
if not unicodes then
return
end
@@ -5619,28 +4400,35 @@ fonts.map.add_to_unicode = function(data,filename)
unicodes['zwj'] = unicodes['zwj'] or 0x200D
unicodes['zwnj'] = unicodes['zwnj'] or 0x200C
-- the tounicode mapping is sparse and only needed for alternatives
- local tounicode, originals, ns, nl, private, unknown = { }, { }, 0, 0, fonts.private, format("%04X",utfbyte("?"))
- data.luatex.tounicode, data.luatex.originals = tounicode, originals
+ local private = fonts.constructors.privateoffset
+ local unknown = format("%04X",utfbyte("?"))
+ local unicodevector = fonts.encodings.agl.unicodes -- loaded runtime in context
+ local tounicode = { }
+ local originals = { }
+ resources.tounicode = tounicode
+ resources.originals = originals
local lumunic, uparser, oparser
+ local cidinfo, cidnames, cidcodes, usedmap
if false then -- will become an option
- lumunic = load_lum_table(filename)
+ lumunic = loadlumtable(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]
+ --
+ cidinfo = properties.cidinfo
+ usedmap = cidinfo and fonts.cid.getmap(cidinfo)
+ --
if usedmap then
- oparser = usedmap and make_name_parser(cidinfo.ordering)
+ oparser = usedmap and makenameparser(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 -- play safe
+ uparser = makenameparser()
+ local ns, nl = 0, 0
+ for unic, glyph in next, descriptions do
+ local index = glyph.index
+ local name = glyph.name
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])
+ local unicode = lumunic and lumunic[name] or unicodevector[name]
if unicode then
originals[index], tounicode[index], ns = unicode, tounicode16(unicode), ns + 1
end
@@ -5684,7 +4472,7 @@ fonts.map.add_to_unicode = function(data,filename)
-- skip
elseif nplit == 1 then
local base = split[1]
- unicode = unicodes[base] or (aglmap and aglmap[base])
+ unicode = unicodes[base] or unicodevector[base]
if unicode then
if type(unicode) == "table" then
unicode = unicode[1]
@@ -5692,19 +4480,25 @@ fonts.map.add_to_unicode = function(data,filename)
originals[index], tounicode[index], ns = unicode, tounicode16(unicode), ns + 1
end
else
- local t = { }
+ local t, n = { }, 0
for l=1,nplit do
local base = split[l]
- local u = unicodes[base] or (aglmap and aglmap[base])
+ local u = unicodes[base] or unicodevector[base]
if not u then
break
elseif type(u) == "table" then
- t[#t+1] = u[1]
+ n = n + 1
+ t[n] = u[1]
else
- t[#t+1] = u
+ n = n + 1
+ t[n] = u
end
end
- if #t > 0 then -- done then
+ if n == 0 then -- done then
+ -- nothing
+ elseif n == 1 then
+ originals[index], tounicode[index], nl, unicode = t[1], tounicode16(t[1]), nl + 1, true
+ else
originals[index], tounicode[index], nl, unicode = t, tounicode16sequence(t), nl + 1, true
end
end
@@ -5725,129 +4519,159 @@ fonts.map.add_to_unicode = function(data,filename)
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 -- play safe
+ if trace_mapping then
+ for unic, glyph in table.sortedhash(descriptions) do
+ local name = glyph.name
+ local index = glyph.index
+ local toun = tounicode[index]
if toun then
- logs.report("load otf","internal: 0x%05X, name: %s, unicode: 0x%05X, tounicode: %s",index,name,unic,toun)
+ report_fonts("internal: 0x%05X, name: %s, unicode: U+%05X, tounicode: %s",index,name,unic,toun)
else
- logs.report("load otf","internal: 0x%05X, name: %s, unicode: 0x%05X",index,name,unic)
+ report_fonts("internal: 0x%05X, name: %s, unicode: U+%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)
+ report_fonts("enhance: %s tounicode entries added (%s ligatures)",nl+ns, ns)
end
end
--- the following is sort of obsolete
---
--- fonts.map.data = fonts.map.data or { }
--- fonts.map.encodings = fonts.map.encodings or { }
--- fonts.map.loaded = fonts.map.loaded or { }
--- fonts.map.line = fonts.map.line or { }
---
--- function fonts.map.line.pdftex(e)
--- if e.name and e.fontfile then
--- local fullname = e.fullname or ""
--- if e.slant and e.slant ~= 0 then
--- if e.encoding then
--- pdf.mapline(format('= %s %s "%g SlantFont" <%s <%s',e.name,fullname,e.slant,e.encoding,e.fontfile)))
--- else
--- pdf.mapline(format('= %s %s "%g SlantFont" <%s',e.name,fullname,e.slant,e.fontfile)))
--- end
--- elseif e.extend and e.extend ~= 1 and e.extend ~= 0 then
--- if e.encoding then
--- pdf.mapline(format('= %s %s "%g ExtendFont" <%s <%s',e.name,fullname,e.extend,e.encoding,e.fontfile)))
--- else
--- pdf.mapline(format('= %s %s "%g ExtendFont" <%s',e.name,fullname,e.extend,e.fontfile)))
--- end
--- else
--- if e.encoding then
--- pdf.mapline(format('= %s %s <%s <%s',e.name,fullname,e.encoding,e.fontfile)))
--- else
--- pdf.mapline(format('= %s %s <%s',e.name,fullname,e.fontfile)))
--- end
--- end
--- else
--- return nil
--- end
--- end
+end -- closure
+
+do -- begin closure to overcome local limits and interference
+
+if not modules then modules = { } end modules ['luatex-fonts-syn'] = {
+ 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"
+}
+
+if context then
+ texio.write_nl("fatal error: this module is not for context")
+ os.exit()
+end
+
+-- Generic font names support.
--
--- function fonts.map.flush(backend) -- will also erase the accumulated data
--- local flushline = fonts.map.line[backend or "pdftex"] or fonts.map.line.pdftex
--- for _, e in next, fonts.map.data do
--- flushline(e)
--- end
--- fonts.map.data = { }
--- end
+-- Watch out, the version number is the same as the one used in
+-- the mtx-fonts.lua function scripts.fonts.names as we use a
+-- simplified font database in the plain solution and by using
+-- a different number we're less dependent on context.
--
--- fonts.map.line.dvips = fonts.map.line.pdftex
--- fonts.map.line.dvipdfmx = function() end
+-- mtxrun --script font --reload --simple
--
--- function fonts.map.convert_entries(filename)
--- if not fonts.map.loaded[filename] then
--- fonts.map.data, fonts.map.encodings = fonts.map.load_file(filename,fonts.map.data, fonts.map.encodings)
--- fonts.map.loaded[filename] = true
--- end
--- end
+-- The format of the file is as follows:
--
--- function fonts.map.load_file(filename, entries, encodings)
--- entries = entries or { }
--- encodings = encodings or { }
--- local f = io.open(filename)
--- if f then
--- local data = f:read("*a")
--- if data then
--- for line in gmatch(data,"(.-)[\n\t]") do
--- if find(line,"^[%#%%%s]") then
--- -- print(line)
--- else
--- local extend, slant, name, fullname, fontfile, encoding
--- line = gsub(line,'"(.+)"', function(s)
--- extend = find(s,'"([^"]+) ExtendFont"')
--- slant = find(s,'"([^"]+) SlantFont"')
--- return ""
--- end)
--- if not name then
--- -- name fullname encoding fontfile
--- name, fullname, encoding, fontfile = match(line,"^(%S+)%s+(%S*)[%s<]+(%S*)[%s<]+(%S*)%s*$")
--- end
--- if not name then
--- -- name fullname (flag) fontfile encoding
--- name, fullname, fontfile, encoding = match(line,"^(%S+)%s+(%S*)[%d%s<]+(%S*)[%s<]+(%S*)%s*$")
--- end
--- if not name then
--- -- name fontfile
--- name, fontfile = match(line,"^(%S+)%s+[%d%s<]+(%S*)%s*$")
--- end
--- if name then
--- if encoding == "" then encoding = nil end
--- entries[name] = {
--- name = name, -- handy
--- fullname = fullname,
--- encoding = encoding,
--- fontfile = fontfile,
--- slant = tonumber(slant),
--- extend = tonumber(extend)
--- }
--- encodings[name] = encoding
--- elseif line ~= "" then
--- -- print(line)
--- end
--- end
--- end
--- end
--- f:close()
--- end
--- return entries, encodings
--- end
+-- return {
+-- ["version"] = 1.001,
+-- ["mappings"] = {
+-- ["somettcfontone"] = { "Some TTC Font One", "SomeFontA.ttc", 1 },
+-- ["somettcfonttwo"] = { "Some TTC Font Two", "SomeFontA.ttc", 2 },
+-- ["somettffont"] = { "Some TTF Font", "SomeFontB.ttf" },
+-- ["someotffont"] = { "Some OTF Font", "SomeFontC.otf" },
+-- },
+-- }
+
+local fonts = fonts
+fonts.names = fonts.names or { }
+
+fonts.names.version = 1.001 -- not the same as in context
+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.findfile(basename,format) or ""
+ if foundname ~= "" then
+ data = dofile(foundname)
+ texio.write("<font database loaded: ",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 -- fallback to filename
+ end
+ end
+end
+
+fonts.names.resolvespec = fonts.names.resolve -- only supported in mkiv
+
+function fonts.names.getfilename(askedname,suffix) -- only supported in mkiv
+ return ""
+end
end -- closure
do -- begin closure to overcome local limits and interference
-if not modules then modules = { } end modules ['font-otf'] = {
+if not modules then modules = { } end modules ['luatex-fonts-tfm'] = {
+ 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"
+}
+
+if context then
+ texio.write_nl("fatal error: this module is not for context")
+ os.exit()
+end
+
+local fonts = fonts
+local tfm = { }
+fonts.handlers.tfm = tfm
+fonts.formats.tfm = "type1" -- we need to have at least a value here
+
+function fonts.readers.tfm(specification)
+ local fullname = specification.filename or ""
+ if fullname == "" then
+ local forced = specification.forced or ""
+ if forced ~= "" then
+ fullname = specification.name .. "." .. forced
+ else
+ fullname = specification.name
+ end
+ end
+ local foundname = resolvers.findbinfile(fullname, 'tfm') or ""
+ if foundname == "" then
+ foundname = resolvers.findbinfile(fullname, 'ofm') or ""
+ end
+ if foundname ~= "" then
+ specification.filename = foundname
+ specification.format = "ofm"
+ return font.read_tfm(specification.filename,specification.size)
+ end
+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",
@@ -5855,120 +4679,243 @@ if not modules then modules = { } end modules ['font-otf'] = {
license = "see context related readme files"
}
-local utf = unicode.utf8
+local lower = string.lower
-local concat, getn, utfbyte = table.concat, table.getn, 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 lpegmatch = lpeg.match
+local allocate = utilities.storage.allocate
-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)
+local fonts = fonts
+local otf = { }
+fonts.handlers.otf = otf
---~ trackers.enable("otf.loading")
+local otffeatures = fonts.constructors.newfeatures("otf")
+local registerotffeature = otffeatures.register
---[[ldx--
-<p>The fontforge table has organized lookups in a certain way. A first implementation
-of this code was organized featurewise: information related to features was
-collected and processing boiled down to a run over the features. The current
-implementation honors the order in the main feature table. Since we can reorder this
-table as we want, we can eventually support several models of processing. We kept
-the static as well as dynamic feature processing, because it had proved to be
-rather useful. The formerly three loop variants have beem discarded but will
-reapear at some time.</p>
-
-<itemize>
-<item>we loop over all lookups</item>
-<item>for each lookup we do a run over the list of glyphs</item>
-<item>but we only process them for features that are enabled</item>
-<item>if we're dealing with a contextual lookup, we loop over all contexts</item>
-<item>in that loop we quit at a match and then process the list of sublookups</item>
-<item>we always continue after the match</item>
-</itemize>
-
-<p>In <l n='context'/> we do this for each font that is used in a list, so in
-practice we have quite some nested loops.</p>
-
-<p>We process the whole list and then consult the glyph nodes. An alternative approach
-is to collect strings of characters using the same font including spaces (because some
-lookups involve spaces). However, we then need to reconstruct the list which is no fun.
-Also, we need to carry quite some information, like attributes, so eventually we don't
-gain much (if we gain something at all).</p>
-
-<p>Another consideration has been to operate on sublists (subhead, subtail) but again
-this would complicate matters as we then neext to keep track of a changing subhead
-and subtail. On the other hand, this might save some runtime. The number of changes
-involved is not that large. This only makes sense when we have many fonts in a list
-and don't change to frequently.</p>
---ldx]]--
+registerotffeature {
+ name = "features",
+ description = "initialization of feature handler",
+ default = true,
+}
+
+-- these are later hooked into node and base initializaters
+
+local otftables = otf.tables -- not always defined
+
+local function setmode(tfmdata,value)
+ if value then
+ tfmdata.properties.mode = lower(value)
+ end
+end
-fonts = fonts or { }
-fonts.otf = fonts.otf or { }
-fonts.tfm = fonts.tfm or { }
+local function setlanguage(tfmdata,value)
+ if value then
+ local cleanvalue = lower(value)
+ local languages = otftables and otftables.languages
+ local properties = tfmdata.properties
+ if not languages then
+ properties.language = cleanvalue
+ elseif languages[value] then
+ properties.language = cleanvalue
+ else
+ properties.language = "dflt"
+ end
+ end
+end
-local otf = fonts.otf
-local tfm = fonts.tfm
+local function setscript(tfmdata,value)
+ if value then
+ local cleanvalue = lower(value)
+ local scripts = otftables and otftables.scripts
+ local properties = tfmdata.properties
+ if not scripts then
+ properties.script = cleanvalue
+ elseif scripts[value] then
+ properties.script = cleanvalue
+ else
+ properties.script = "dflt"
+ end
+ end
+end
-local fontdata = fonts.ids
+registerotffeature {
+ name = "mode",
+ description = "mode",
+ initializers = {
+ base = setmode,
+ node = setmode,
+ }
+}
-otf.tables = otf.tables or { } -- defined in font-ott.lua
-otf.meanings = otf.meanings or { } -- defined in font-ott.lua
-otf.tables.features = otf.tables.features or { } -- defined in font-ott.lua
-otf.tables.languages = otf.tables.languages or { } -- defined in font-ott.lua
-otf.tables.scripts = otf.tables.scripts or { } -- defined in font-ott.lua
+registerotffeature {
+ name = "language",
+ description = "language",
+ initializers = {
+ base = setlanguage,
+ node = setlanguage,
+ }
+}
-otf.features = otf.features or { }
-otf.features.list = otf.features.list or { }
-otf.features.default = otf.features.default or { }
+registerotffeature {
+ name = "script",
+ description = "script",
+ initializers = {
+ base = setscript,
+ node = setscript,
+ }
+}
-otf.enhancers = otf.enhancers or { }
-otf.glists = { "gsub", "gpos" }
-otf.version = 2.650 -- beware: also sync font-mis.lua
-otf.pack = true -- beware: also sync font-mis.lua
-otf.syncspace = true
-otf.notdef = false
-otf.cache = containers.define("fonts", "otf", otf.version, true)
-otf.cleanup_aat = false -- only context
+end -- closure
-local wildcard = "*"
-local default = "dflt"
+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"
+}
+
+-- langs -> languages enz
+-- anchor_classes vs kernclasses
+-- modification/creationtime in subfont is runtime dus zinloos
+-- to_table -> totable
+-- ascent descent
+
+local utf = unicode.utf8
+
+local utfbyte = 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 reversed, concat, remove = table.reversed, table.concat, table.remove
+local ioflush = io.flush
+local fastcopy, tohash = table.fastcopy, table.tohash
+
+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_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 report_otf = logs.reporter("fonts","otf loading")
+
+local fonts = fonts
+local otf = fonts.handlers.otf
+
+otf.glists = { "gsub", "gpos" }
+
+otf.version = 2.730 -- beware: also sync font-mis.lua
+otf.cache = containers.define("fonts", "otf", otf.version, true)
+
+local fontdata = fonts.hashes.identifiers
+local chardata = characters and characters.data -- not used
+
+local otffeatures = fonts.constructors.newfeatures("otf")
+local registerotffeature = otffeatures.register
+
+local enhancers = allocate()
+otf.enhancers = enhancers
+local patches = { }
+enhancers.patches = patches
+
+local definers = fonts.definers
+local readers = fonts.readers
+local constructors = fonts.constructors
+
+local forceload = false
+local cleanup = 0 -- mk: 0=885M 1=765M 2=735M (regular run 730M)
+local usemetatables = false -- .4 slower on mk but 30 M less mem so we might change the default -- will be directive
+local packdata = true
+local syncspace = true
+local forcenotdef = false
+
+local wildcard = "*"
+local default = "dflt"
+
+local fontloaderfields = fontloader.fields
+local mainfields = nil
+local glyphfields = nil -- not used yet
+
+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.usemetatables", function(v) usemetatables = 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)
+
+local function load_featurefile(raw,featurefile)
+ if featurefile and featurefile ~= "" then
+ if trace_loading then
+ report_otf("featurefile: %s", featurefile)
+ end
+ fontloader.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 %s 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 [%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
+ report_otf(" %s: %s",feature,concat(tt," "))
+ end
+ end
+ end
+ end
+ if trace_loading then
+ report_otf("\n")
+ end
+ elseif trace_loading then
+ report_otf("font %s has no sequences",filename)
+ end
+end
--[[ldx--
<p>We start with a lot of tables and related functions.</p>
--ldx]]--
-otf.tables.global_fields = table.tohash {
- "lookups",
- "glyphs",
- "subfonts",
- "luatex",
- "pfminfo",
- "cidinfo",
- "tables",
- "names",
- "unicodes",
- "names",
---~ "math",
- "anchor_classes",
- "kern_classes",
- "gpos",
- "gsub"
-}
-
-otf.tables.valid_fields = {
- "anchor_classes",
+local valid_fields = table.tohash {
+ -- "anchor_classes",
"ascent",
- "cache_version",
+ -- "cache_version",
"cidinfo",
"copyright",
- "creationtime",
+ -- "creationtime",
"descent",
"design_range_bottom",
"design_range_top",
@@ -5977,32 +4924,32 @@ otf.tables.valid_fields = {
"extrema_bound",
"familyname",
"fontname",
+ "fontname",
"fontstyle_id",
"fontstyle_name",
"fullname",
- "glyphs",
+ -- "glyphs",
"hasvmetrics",
- "head_optimized_for_cleartype",
+ -- "head_optimized_for_cleartype",
"horiz_base",
"issans",
"isserif",
"italicangle",
- "kerns",
- "lookups",
- -- "luatex",
+ -- "kerns",
+ -- "lookups",
"macstyle",
- "modificationtime",
+ -- "modificationtime",
"onlybitmaps",
"origname",
"os2_version",
"pfminfo",
- "private",
+ -- "private",
"serifcheck",
"sfd_version",
-- "size",
"strokedfont",
"strokewidth",
- "subfonts",
+ -- "subfonts",
"table_version",
-- "tables",
-- "ttf_tab_saved",
@@ -6013,282 +4960,704 @@ otf.tables.valid_fields = {
"upos",
"use_typo_metrics",
"uwidth",
- "validation_state",
- "verbose",
+ -- "validation_state",
"version",
"vert_base",
"weight",
"weight_width_slope_only",
- "xuid",
+ -- "xuid",
+}
+
+local ordered_enhancers = {
+ "prepare tables",
+ "prepare glyphs",
+ "prepare lookups",
+
+ "analyze glyphs",
+ "analyze math",
+
+ "prepare tounicode", -- maybe merge with prepare
+
+ "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",
+ "check extra features", -- after metadata
+
+ "add duplicates",
+ "check encoding",
+
+ "cleanup tables",
}
--[[ldx--
<p>Here we go.</p>
--ldx]]--
-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)
+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("enhance: %s (%s)",name,filename)
+ ioflush()
end
+ enhancer(data,filename,raw)
+ elseif trace_loading then
+ -- report_otf("enhance: %s is undefined",name)
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)
+function enhancers.apply(data,filename,raw)
+ local basename = file.basename(lower(filename))
+ if trace_loading then
+ report_otf("start enhancing: %s",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
- enhancer(data,filename)
+ 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("stop enhancing")
end
+ ioflush() -- we want instant messages
end
-local enhancers = {
- -- pack and unpack are handled separately; they might even be moved
- -- away from the enhancers namespace
- "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", -- moved here
- "flatten glyph lookups", "flatten anchor tables", "flatten feature tables",
- "simplify glyph lookups", -- some saving
- "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",
-}
+-- patches.register("before","migrate metadata","cambria",function() end)
+
+function patches.register(what,where,pattern,action)
+ local ww = what[where]
+ if ww then
+ ww[pattern] = action
+ else
+ ww = { [pattern] = action}
+ end
+end
+
+function patches.report(fmt,...)
+ if trace_loading then
+ report_otf("patching: " ..fmt,...)
+ end
+end
+
+function enhancers.register(what,action) -- only already registered can be overloaded
+ actions[what] = action
+end
function otf.load(filename,format,sub,featurefile)
local name = file.basename(file.removesuffix(filename))
+ 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
- if sub == "" then sub = false 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)
- local size = lfs.attributes(filename,"size") or 0
- if not data or data.verbose ~= fonts.verbose or data.size ~= size then
- logs.report("load otf","loading: %s (hash: %s)",filename,hash)
- local ff, messages
+ 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: no featurefile '%s'",s)
+ else
+ local attr = lfs.attributes(name)
+ featurefiles[#featurefiles+1] = {
+ name = name,
+ size = size,
+ time = time,
+ }
+ 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("loading: forced reload due to hard coded flag")
+ 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: %s",featurefile or "--")
+ end
+ end
+ if reload then
+ report_otf("loading: %s (hash: %s)",filename,hash)
+ local fontdata, messages
if sub then
- ff, messages = fontloader.open(filename,sub)
+ fontdata, messages = fontloader.open(filename,sub)
else
- ff, messages = fontloader.open(filename)
+ fontdata, messages = fontloader.open(filename)
+ end
+ if fontdata then
+ mainfields = mainfields or (fontloaderfields and fontloaderfields(fontdata))
end
if trace_loading and messages and #messages > 0 then
if type(messages) == "string" then
- logs.report("load otf","warning: %s",messages)
+ report_otf("warning: %s",messages)
else
for m=1,#messages do
- logs.report("load otf","warning: %s",tostring(messages[m]))
+ report_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() -- we want instant messages
- end
- if otf.pack and not fonts.verbose then
- otf.enhance("pack",data,filename)
- end
- data.size = size
- data.verbose = fonts.verbose
- logs.report("load otf","saving in cache: %s",filename)
- data = containers.write(otf.cache(), hash, data)
+ report_otf("font loaded okay")
+ 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,
+ format = format,
+ featuredata = featurefiles,
+ resources = {
+ filename = resolvers.unresolve(filename), -- no shortcut
+ version = otf.version,
+ creator = "context mkiv",
+ unicodes = unicodes,
+ indices = {
+ -- index to unicodes
+ },
+ duplicates = {
+ -- alternative unicodes
+ },
+ lookuptypes = {
+ },
+ },
+ metadata = {
+ -- raw metadata, not to be used
+ },
+ properties = {
+ -- normalized metadata
+ },
+ descriptions = {
+ },
+ goodies = {
+ },
+ helpers = {
+ tounicodelist = splitter,
+ tounicodetable = lpeg.Ct(splitter),
+ },
+ }
+ starttiming(data)
+ report_otf("file size: %s", size)
+ enhancers.apply(data,filename,fontdata)
+ if packdata then
+ if cleanup > 0 then
+ collectgarbage("collect")
+ end
+ enhance("pack",data,filename,nil)
+ end
+ report_otf("saving in cache: %s",filename)
+ data = containers.write(otf.cache, hash, data)
+ if cleanup > 1 then
collectgarbage("collect")
- data = containers.read(otf.cache(), hash) -- this frees the old table and load the sparse one
+ end
+ stoptiming(data)
+ if elapsedtime then -- not in generic
+ report_otf("preprocessing and caching took %s seconds",elapsedtime(data))
+ end
+ fontloader.close(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")
- else
- logs.report("load otf","loading failed (table conversion error)")
end
else
- logs.report("load otf","loading failed (file read error)")
+ data = nil
+ report_otf("loading failed (file read error)")
end
end
if data then
if trace_defining then
- logs.report("define font","loading from cache: %s",hash)
+ report_otf("loading from cache: %s",hash)
end
- otf.enhance("unpack",data,filename,false) -- no message here
- otf.add_dimensions(data)
+ enhance("unpack",data,filename,nil,false)
+ enhance("add dimensions",data,filename,nil,false)
if trace_sequences then
- otf.show_feature_order(data,filename)
+ showfeatureorder(data,filename)
end
end
return data
end
-function otf.add_dimensions(data)
+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.italic_correction = 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 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
- -- no need to set it and no negative heights, nil == 0
+ 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
+ if usemetatables then
+ for _, d in next, descriptions do
+ local wd = d.width
+ if not wd then
+ d.width = defaultwidth
+ elseif trace_markwidth and wd ~= 0 and d.class == "mark" then
+ report_otf("mark with width %s (%s) in %s",wd,d.name or "<noname>",file.basename(filename))
+ -- d.width = -wd
+ end
+ setmetatable(d,mt)
+ end
+ else
+ for _, d in next, descriptions do
+ local bb, wd = d.boundingbox, d.width
+ if not wd then
+ d.width = defaultwidth
+ elseif trace_markwidth and wd ~= 0 and d.class == "mark" then
+ report_otf("mark with width %s (%s) in %s",wd,d.name or "<noname>",file.basename(filename))
+ -- d.width = -wd
+ end
+ -- if forcenotdef 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
+ -- 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
- d.height = ht
+ new[k] = v
end
- if dp == 0 or dp < 0 then
- -- no negative depths and no negative depths, nil == 0
+ 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
- d.depth = dp
+ new[k] = v
end
end
end
+ return new
+ else
+ return { }
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
+-- not setting italic_correction and class (when nil) during
+-- table cronstruction can save some mem
+
+actions["prepare glyphs"] = function(data,filename,raw)
+ 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
+
+ if rawsubfonts then
+
+ metadata.subfonts = { }
+ properties.cidinfo = rawcidinfo
+
+ if rawcidinfo.registry then
+ local cidmap = fonts.cid.getmap(rawcidinfo)
+ if cidmap then
+ rawcidinfo.usedname = cidmap.usedname
+ local nofnames, nofunicodes = 0, 0
+ local cidunicodes, cidnames = cidmap.unicodes, cidmap.names
+ for cidindex=1,#rawsubfonts do
+ local subfont = rawsubfonts[cidindex]
+ local cidglyphs = subfont.glyphs
+ metadata.subfonts[cidindex] = somecopy(subfont)
+ for index=0,subfont.glyphcnt-1 do -- we could take the previous glyphcnt instead of 0
+ local glyph = cidglyphs[index]
+ if glyph then
+ local unicode = glyph.unicode
+ local name = glyph.name or cidnames[index]
+ if not unicode or unicode == -1 or unicode >= criterium then
+ unicode = cidunicodes[index]
+ end
+ if not unicode or unicode == -1 or unicode >= criterium then
+ if not name then
+ name = format("u%06X",private)
+ end
+ unicode = private
+ unicodes[name] = private
+ if trace_private then
+ report_otf("enhance: glyph %s at index 0x%04X is moved to private unicode slot U+%05X",name,index,private)
+ end
+ private = private + 1
+ nofnames = nofnames + 1
+ else
+ if not name then
+ name = format("u%06X",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
+ cidindex = cidindex,
+ index = index,
+ glyph = glyph,
+ }
+
+ descriptions[unicode] = description
+ else
+ -- report_otf("potential problem: glyph 0x%04X is used but empty",index)
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
+ 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 %s",filename)
end
+ elseif trace_loading then
+ report_otf("font %s has no glyphs",filename)
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
--- todo: normalize, design_size => designsize
+ else
-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
+ for index=0,raw.glyphcnt-1 do -- not raw.glyphmax-1 (as that will crash)
+ local glyph = rawglyphs[index]
+ if glyph then
+ local unicode = glyph.unicode
+ local name = glyph.name
+ if not unicode or unicode == -1 or unicode >= criterium then
+ unicode = private
+ unicodes[name] = private
+ if trace_private then
+ report_otf("enhance: glyph %s at index 0x%04X is moved to private unicode slot U+%05X",name,index,private)
end
+ private = private + 1
else
- t[us] = true
+ unicodes[name] = unicode
+ end
+ indices[index] = unicode
+ if not name then
+ name = format("u%06X",unicode)
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
+ d[#d+1] = altuni[i].unicode
+ end
+ duplicates[unicode] = d
+ end
+ else
+ report_otf("potential problem: glyph 0x%04X is used but empty",index)
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"
+ resources.private = private
+
end
-otf.enhancers["cleanup aat"] = function(data,filename)
- if otf.cleanup_aat then
+-- the next one is still messy but will get better when we have
+-- flattened map/enc tables in the font loader
+
+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 whwn cidmap)
+
+ local mapdata = raw.map or { }
+ local unicodetoindex = mapdata and mapdata.map 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
+
+ -- end of messy
+
+ if find(encname,"unicode") then -- unicodebmp, unicodefull, ...
+ if trace_loading then
+ report_otf("checking embedded unicode map '%s'",encname)
+ end
+ for unicode, index in next, unicodetoindex do -- altuni already covers this
+ if unicode <= criterium and not descriptions[unicode] then
+ local parent = indices[index] -- why nil?
+ if parent then
+ report_otf("weird, unicode U+%05X points to U+%05X with index 0x%04X",unicode,parent,index)
+ else
+ report_otf("weird, unicode U+%05X points to nowhere with index 0x%04X",unicode,index)
+ end
+ end
+ end
+ elseif properties.cidinfo then
+ report_otf("warning: no unicode map, used cidmap '%s'",properties.cidinfo.usedname or "?")
+ else
+ report_otf("warning: non unicode map '%s', only using glyph unicode data",encname or "whatever")
+ end
+
+ if mapdata then
+ mapdata.map = { } -- clear some memory
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
- -- scripts and tag
- local tag = f[k].tag
- if not done[tag] then
- t[#t+1] = tag
- done[tag] = true
+-- for the moment we assume that a fotn with lookups will not use
+-- altuni so we stick to kerns only
+
+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
+ for i=1,#d do
+ local u = d[i]
+ if not descriptions[u] then
+ local description = descriptions[unicode]
+ local duplicate = table.copy(description) -- else packing problem
+ duplicate.comment = format("copy of U+%05X", unicode)
+ descriptions[u] = duplicate
+ local n = 0
+ for _, description in next, descriptions do
+ if kerns then
+ local kerns = description.kerns
+ 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 trace_loading then
+ report_otf("duplicating U+%05X to U+%05X with index 0x%04X (%s kerns)",unicode,u,description.index,n)
end
end
end
- if #t > 0 then
- return t
+ 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 italic_correction = false
+ local widths = { }
+ local marks = { }
+ for unicode, description in next, descriptions do
+ local glyph = description.glyph
+ local italic = glyph.italic_correction
+ if not italic then
+ -- skip
+ elseif italic == 0 then
+ -- skip
+ else
+ description.italic = italic
+ italic_correction = 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.italic_correction = italic_correction
+ -- 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
- return nil
end
-otf.enhancers["analyse features"] = function(data,filename)
- -- local luatex = data.luatex
- -- luatex.gposfeatures = analyze_features(data.gpos)
- -- luatex.gsubfeatures = analyze_features(data.gsub)
+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
-otf.enhancers["rehash features"] = function(data,filename)
+actions["reorganize features"] = function(data,filename,raw) -- combine with other
local features = { }
- data.luatex.features = features
+ data.resources.features = features
for k, what in next, otf.glists do
- local dw = data[what]
+ local dw = raw[what]
if dw then
local f = { }
features[what] = f
@@ -6299,10 +5668,16 @@ otf.enhancers["rehash features"] = function(data,filename)
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 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))
+ 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
@@ -6315,279 +5690,349 @@ otf.enhancers["rehash features"] = function(data,filename)
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
+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 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
+ 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
+ if l then
+ l[anchor] = true
+ else
+ l = { [anchor] = true }
+ lookup_to_anchor[lookup] = l
+ end
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
+actions["prepare tounicode"] = function(data,filename,raw)
+ fonts.mappings.addtounicode(data,filename)
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
- -- scripts, tag, ismac
- local t = { }
- for f=1,#features do
- local feature = features[f]
- local hash = { }
- -- only script and langs matter
- 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
+actions["reorganize subtables"] = function(data,filename,raw)
+ local resources = data.resources
+ local sequences = { }
+ local lookups = { }
+ local chainedfeatures = { }
+ resources.sequences = sequences
+ resources.lookups = lookups
+ for _, what in next, otf.glists do
+ local dw = raw[what]
+ if dw then
+ for k=1,#dw do
+ local gk = dw[k]
+ local typ = gk.type
+ local chain =
+ (typ == "gsub_contextchain" or typ == "gpos_contextchain") and 1 or
+ (typ == "gsub_reversecontextchain" or typ == "gpos_reversecontextchain") and -1 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
+ --
+ local features = gk.features
+ if features then
+ -- scripts, tag, ismac
+ local f = { }
+ 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 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
- 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 = { -- 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,
- }
- if flags.mark_class then
- gk.markclass = luatex.markclasses[flags.mark_class]
+ sequences[#sequences+1] = {
+ type = typ,
+ chain = chain,
+ flags = flags,
+ name = name,
+ subtables = subtables,
+ markclass = markclass,
+ features = f,
+ }
+ else
+ lookups[name] = {
+ type = typ,
+ chain = chain,
+ flags = flags,
+ subtables = subtables,
+ markclass = markclass,
+ }
end
end
end
end
end
-otf.enhancers["merge cid fonts"] = function(data,filename)
- -- we can also move the names to data.luatex.names which might
- -- save us some more memory (at the cost of harder tracing)
- 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
- -- dummy entry
- else
- local unicode, name = unicodes[index], names[index]
- g.cidindex = n
- g.boundingbox = g.boundingbox -- or zerobox
- 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
+-- test this:
+--
+-- for _, what in next, otf.glists do
+-- raw[what] = nil
+-- end
+
+actions["prepare lookups"] = function(data,filename,raw)
+ local lookups = raw.lookups
+ if lookups then
+ data.lookups = lookups
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
+-- The reverse handler does a bit redundant splitting but it's seldom
+-- seen so we don' tbother 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
- -- beware: the indices table is used to initialize the tfm table
- 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 -- or 0
- 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
+ return result
+end
+
+local function t_hashed(t,cache)
+ if t then
+ local h = { }
+ for i=1,#t do
+ local ti = t[i]
+ local h = cache[ti]
+ if not h then
+ h = { }
+ for i=1,#ti do
+ h[ti] = true
end
end
+ cache[ti] = h
end
+ return h
+ else
+ return nil
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
+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
- data.ttf_tab_saved = nil
end
-otf.enhancers["compact glyphs"] = function(data,filename)
- table.compact(data.glyphs) -- needed?
- if data.subfonts then
- for _, subfont in next, data.subfonts do
- table.compact(subfont.glyphs) -- needed?
+local s_hashed = t_hashed
+
+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
-otf.enhancers["reverse coverage"] = function(data,filename)
+actions["reorganize lookups"] = function(data,filename,raw)
-- we prefer the before lookups in a normal order
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)
+ local splitter = data.helpers.tounicodetable
+ local cache, h_cache = { }, { }
+ 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,cache,reversed(before_class))
+ end
+ local current_class = lookup.current_class
+ if current_class then
+ current_class = t_uncover(splitter,cache,current_class)
+ end
+ local after_class = lookup.after_class
+ if after_class then
+ after_class = t_uncover(splitter,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,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 { }
+ if lookups and not lookups[i] then
+ lookups[i] = false
+ end
+ end
+ rule.current = t_hashed(current,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,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,cache,reversed(before))
+ rule.before = t_hashed(before,h_cache)
+ end
+ local current = coverage.current
+ if current then
+ current = t_uncover(splitter,cache,current)
+ rule.current = t_hashed(current,h_cache)
+ end
+ local after = coverage.after
+ if after then
+ after = t_uncover(splitter,cache,after)
+ rule.after = t_hashed(after,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,cache,reversed(before))
+ rule.before = t_hashed(before,h_cache)
+ end
+ local current = reversecoverage.current
+ if current then
+ current = t_uncover(splitter,cache,current)
+ rule.current = t_hashed(current,h_cache)
+ end
+ local after = reversecoverage.after
+ if after then
+ after = t_uncover(splitter,cache,after)
+ rule.after = t_hashed(after,h_cache)
+ end
+ local replacements = reversecoverage.replacements
+ if replacements then
+ rule.replacements = r_uncover(splitter,cache,current,replacements)
+ end
+ rule.reversecoverage = nil
+ end
+ end
+ elseif format == "glyphs" then
+ for i=1,#rules do
+ local rule = rules[i]
+ local glyphs = rule.glyphs
+ if glyphs then
+ local fore = glyphs.fore
+ if fore then
+ fore = s_uncover(splitter,cache,fore)
+ rule.before = s_hashed(fore,h_cache)
+ end
+ local back = glyphs.back
+ if back then
+ back = s_uncover(splitter,cache,back)
+ rule.after = s_hashed(back,h_cache)
+ end
+ local names = glyphs.names
+ if names then
+ names = s_uncover(splitter,cache,names)
+ rule.current = s_hashed(names,h_cache)
+ end
+ rule.glyphs = nil
+ end
end
end
end
@@ -6595,385 +6040,222 @@ otf.enhancers["reverse coverage"] = function(data,filename)
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
+-- to be checked italic_correction
+
+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
+ report_otf("skipping cyclic reference U+%05X in math variant U+%05X",g,unicode)
+ elseif n == 0 then
+ n = 1
+ variants = { g }
+ else
+ n = n + 1
+ variants[n] = g
+ 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
- glyph.italic_correction = nil
- ok = true
+ else
+ parts = nil
end
end
- -- we can use this to avoid calculations
- otf.tables.valid_fields[#otf.tables.valid_fields+1] = "has_italic"
- data.has_italic = true
+ local italic_correction = the_variants.italic_correction
+ if italic_correction and italic_correction == 0 then
+ italic_correction = nil
+ end
+ return variants, parts, italic_correction
end
-otf.enhancers["check math"] = function(data,filename)
- if data.math then
- -- we move the math stuff into a math subtable because we then can
- -- test faster in the tfm copy
- 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
+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 horiz_variants = glyph.horiz_variants
+ local vert_variants = glyph.vert_variants
+ local top_accent = glyph.top_accent
+ if mathkerns or horiz_variants or vert_variants or top_accent then
local math = { }
- glyph.math = math
- if mk then
- for k, v in next, mk do
+ if top_accent then
+ math.top_accent = top_accent
+ end
+ if mathkerns then
+ for k, v in next, mathkerns 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 then
- if #p>0 then
- for i=1,#p do
- local pi = p[i]
- pi.glyph = unicodes[pi.component] or 0
+ 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
- math.horiz_parts = p
end
end
- local ic = hv.italic_correction
- if ic and ic ~= 0 then
- math.horiz_italic_correction = ic
- end
- glyph.horiz_variants = nil
+ math.kerns = mathkerns
end
- if vv then
- local uc = unicodes[index]
- math.vert_variants = vv.variants
- local p = vv.parts
- if p then
- if #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
- end
- local ic = vv.italic_correction
- if ic and ic ~= 0 then
- math.vert_italic_correction = ic
- end
- glyph.vert_variants = nil
+ if horiz_variants then
+ math.horiz_variants, math.horiz_parts, math.horiz_italic_correction = check_variants(unicode,horiz_variants,splitter,unicodes)
end
- local ic = glyph.italic_correction
- if ic then
- if ic ~= 0 then
- math.italic_correction = ic
- end
- glyph.italic_correction = nil
+ if vert_variants then
+ math.vert_variants, math.vert_parts, math.vert_italic_correction = check_variants(unicode,vert_variants,splitter,unicodes)
end
+ local italic_correction = description.italic
+ if italic_correction and italic_correction ~= 0 then
+ math.italic_correction = italic_correction
+ end
+ description.math = math
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
- -- share 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
- 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
-
--- kern: ttf has a table with kerns
-
---~ otf.enhancers["reorganize kerns"] = function(data,filename)
---~ local glyphs, mapmap, unicodes = data.glyphs, data.luatex.indices, data.luatex.unicodes
---~ local mkdone = false
---~ for index, glyph in next, data.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 -- brrr, wrong! we miss the non unicode ones
---~ 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 -- saves space and time
---~ 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
---~ 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 -- name is inconsistent with anchor_classes
---~ if kernclass then -- the next one is quite slow
---~ for k=1,#kernclass do
---~ local kcl = kernclass[k]
---~ local firsts, seconds, offsets, lookups = kcl.firsts, kcl.seconds, kcl.offsets, kcl.lookup -- singular
---~ if type(lookups) ~= "table" then
---~ lookups = { lookups }
---~ end
---~ for l=1,#lookups do
---~ local lookup = lookups[l]
---~ -- weird, as maxfirst and maxseconds can have holes
---~ local maxfirsts, maxseconds = getn(firsts), getn(seconds)
---~ if trace_loading then
---~ logs.report("load otf", "adding kernclass %s with %s times %s pairs",lookup, maxfirsts, maxseconds)
---~ end
---~ for fk, fv in next, firsts do
---~ for first in gmatch(fv,"[^ ]+") do
---~ local first_unicode = unicodes[first]
---~ if type(first_unicode) == "number" then
---~ first_unicode = { first_unicode }
---~ end
---~ for f=1,#first_unicode do
---~ local glyph = glyphs[mapmap[first_unicode[f]]]
---~ if glyph then
---~ local mykerns = glyph.mykerns
---~ if not mykerns then
---~ mykerns = { } -- unicode indexed !
---~ glyph.mykerns = mykerns
---~ end
---~ local lookupkerns = mykerns[lookup]
---~ if not lookupkerns then
---~ lookupkerns = { }
---~ mykerns[lookup] = lookupkerns
---~ end
---~ for sk, sv in next, seconds do
---~ local offset = offsets[(fk-1) * maxseconds + sk]
---~ --~ local offset = offsets[sk] -- (fk-1) * maxseconds + sk]
---~ for second in gmatch(sv,"[^ ]+") do
---~ local second_unicode = unicodes[second]
---~ if type(second_unicode) == "number" then
---~ lookupkerns[second_unicode] = offset
---~ else
---~ for s=1,#second_unicode do
---~ lookupkerns[second_unicode[s]] = offset
---~ end
---~ end
---~ end
---~ end
---~ elseif trace_loading then
---~ logs.report("load otf", "no glyph data for U+%04X", first_unicode[f])
---~ 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["reorganize kerns"] = function(data,filename)
- local glyphs, mapmap, unicodes = data.glyphs, data.luatex.indices, data.luatex.unicodes
- local mkdone = false
- for index, glyph in next, data.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 -- brrr, wrong! we miss the non unicode ones
- 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
+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
- mkl[uvc] = vo
+ newkerns[lookup] = { [unicode] = offset }
end
end
+ elseif trace_loading then
+ report_otf("problems with unicode %s of kern %s of glyph U+%05X",name,k,unicode)
end
end
end
- glyph.mykerns = mykerns
- glyph.kerns = nil -- saves space and time
- mkdone = true
+ description.kerns = newkerns
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]
+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
+ for gp=1,#gposlist do
+ local gpos = gposlist[gp]
local subtables = gpos.subtables
if subtables then
for s=1,#subtables do
local subtable = subtables[s]
local kernclass = subtable.kernclass -- name is inconsistent with anchor_classes
if kernclass then -- the next one is quite slow
+ local split = { } -- saves time
for k=1,#kernclass do
local kcl = kernclass[k]
- local firsts, seconds, offsets, lookups = kcl.firsts, kcl.seconds, kcl.offsets, kcl.lookup -- singular
+ local firsts = kcl.firsts
+ local seconds = kcl.seconds
+ local offsets = kcl.offsets
+ local lookups = kcl.lookup -- singular
if type(lookups) ~= "table" then
lookups = { lookups }
end
- local split = { }
+ -- we can check the max in the loop
+ -- local maxseconds = getn(seconds)
+ 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 l=1,#lookups do
local lookup = lookups[l]
- -- weird, as maxfirst and maxseconds can have holes, first seems to be indexed, seconds starts at 2
- local maxfirsts, maxseconds = getn(firsts), getn(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
- if trace_loading then
- logs.report("load otf", "adding kernclass %s with %s times %s pairs",lookup, maxfirsts, maxseconds)
- end
- local function do_it(fk,first_unicode)
- local glyph = glyphs[mapmap[first_unicode]]
- if glyph then
- local mykerns = glyph.mykerns
- if not mykerns then
- mykerns = { } -- unicode indexed !
- glyph.mykerns = mykerns
- end
- local lookupkerns = mykerns[lookup]
- if not lookupkerns then
- lookupkerns = { }
- mykerns[lookup] = lookupkerns
- 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=2,maxseconds do
- local sv = seconds[sk]
- local offset = offsets[baseoffset + sk]
- --~ local offset = offsets[sk] -- (fk-1) * maxseconds + sk]
+ -- for sk=2,maxseconds do
+ -- local sv = seconds[sk]
+ for sk, sv in next, seconds do
local splt = split[sv]
- if splt then
- for i=1,#splt do
- local second_unicode = splt[i]
- if tonumber(second_unicode) then
- lookupkerns[second_unicode] = offset
- else
- for s=1,#second_unicode do
- lookupkerns[second_unicode[s]] = offset
- end
+ 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
- elseif trace_loading then
- logs.report("load otf", "no glyph data for U+%04X", first_unicode)
- end
- end
- for fk=1,#firsts do
- local fv = firsts[fk]
- local splt = split[fv]
- if splt then
for i=1,#splt do
local first_unicode = splt[i]
- if tonumber(first_unicode) then
- do_it(fk,first_unicode)
- else
- for f=1,#first_unicode do
- do_it(fk,first_unicode[f])
+ 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
+ for second_unicode, kern in next, extrakerns do
+ lookupkerns[second_unicode] = kern
+ end
+ elseif trace_loading then
+ report_otf("no glyph data for U+%05X", first_unicode)
end
end
end
end
end
end
- subtable.comment = "The kernclass table is merged into mykerns in the indexed glyph tables."
subtable.kernclass = { }
end
end
@@ -6982,470 +6264,311 @@ otf.enhancers["reorganize kerns"] = function(data,filename)
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]
- -- looks like this is done twice ... bug?
- if code then
- local vu = v.unicode
- if not vu then
- v.unicode = code
- elseif type(vu) == "table" then
- if vu[#vu] == code then
- -- weird
- else
- vu[#vu+1] = code
- end
- elseif vu ~= code then
- v.unicode = { vu, code }
- 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 _
+
+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
- 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 -- funny names for editors
- data.glyphcnt = nil
- data.glyphmax = nil
- if true then
- data.gpos = nil
- data.gsub = nil
- data.anchor_classes = nil
+ -- 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
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
- -- goodies
- local pfminfo = data.pfminfo
- metadata.isfixedpitch = metadata.isfixedpitch or (pfminfo.panose and pfminfo.panose["proportion"] == "Monospaced")
- metadata.charwidth = pfminfo and pfminfo.avgwidth
+actions["cleanup tables"] = function(data,filename,raw)
+ data.resources.indices = nil -- not needed
+ data.helpers = nil
end
-local private_math_parameters = {
- "FractionDelimiterSize",
- "FractionDelimiterDisplayStyleSize",
-}
+-- 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).
-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
+-- we can share { } as it is never set
+
+--- ligatures have an extra specification.char entry that we don't use
+
+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
-otf.enhancers["flatten glyph lookups"] = function(data,filename)
- for k, v in next, data.glyphs do
- local lookups = v.lookups
+ for unicode, description in next, descriptions do
+ local lookups = description.glyph.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
+ 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: %s => %s and %s",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
- if two then
- vv[kkk] = { "pair", paired, { }, { two.x or 0, two.y or 0, two.h or 0, two.v or 0} } -- maybe nil instead of { }
- else
- vv[kkk] = { "pair", paired }
- end
+ lookuplist[l] = { paired, { one.x or 0, one.y or 0, one.h or 0, one.v or 0 } }
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
+ 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
- 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
+ for tag, lookuplist in next, lookups do
+ if #lookuplist == 1 then
+ if slookups then
+ slookups[tag] = lookuplist[1]
+ else
+ slookups = { [tag] = lookuplist[1] }
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 }
+ if mlookups then
+ mlookups[tag] = lookuplist
else
- for kkkk=1,#vvv do
- local vvvv = vvv[kkkk]
- vvv[kkkk] = { vvvv.x or 0, vvvv.y or 0 }
- end
+ mlookups = { [tag] = lookuplist }
end
end
end
- end
- end
-end
-
-otf.enhancers["flatten feature tables"] = function(data,filename)
- -- is this needed? do we still use them at all?
- for _, tag in next, otf.glists do
- if data[tag] then
- if trace_loading then
- logs.report("load otf", "flattening %s table", tag)
+ if slookups then
+ description.slookups = slookups
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
+ if mlookups then
+ description.mlookups = mlookups
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
--- tex features
-
-fonts.otf.enhancers["enrich with features"] = function(data,filename)
- -- later, ctx only
-end
-
-function otf.features.register(name,default)
- otf.features.list[#otf.features.list+1] = name
- otf.features.default[name] = default
-end
-
--- for context this will become a task handler
-
-function otf.set_features(tfmdata,features)
- local processes = { }
- if features and next(features) then
- local lists = { -- why local
- fonts.triggers,
- fonts.processors,
- fonts.manipulators,
- }
- local mode = tfmdata.mode or fonts.mode -- or features.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 -- brr
- if not done[f] then -- so, we can move some to triggers
- 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) -- can set mode (no need to pass otf)
- mode = tfmdata.mode or fonts.mode -- keep this, mode can be set local !
- local im = initializers[mode]
- if im then
- fiotf = initializers[mode].otf
- end
- done[f] = true
- end
- end
+actions["reorganize glyph anchors"] = function(data,filename,raw) -- when we replace inplace we safe entries
+ 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
end
- end
- end
- end
- local fm = fonts.methods[mode] -- todo: zonder node/mode otf/...
- 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 -- brr
- 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
+ else
+ for tag, specification in next, data do
+ data[tag] = { specification.x or 0, specification.y or 0 }
end
end
end
- else
- -- message
+ description.anchors = anchors
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)
---~ print(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 { } -- combine
- local shared = tfmdata.shared
- shared.otfdata = otfdata
- shared.features = features -- default
- shared.dynamics = { }
- shared.processes = { }
- shared.set_dynamics = otf.set_dynamics -- fast access and makes other modules independent
- -- this will be done later anyway, but it's convenient to have
- -- them already for fast access
- 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)
+-- 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
- return tfmdata
end
---~ {
---~ ['boundingbox']={ 95, -458, 733, 1449 },
---~ ['class']="base",
---~ ['name']="braceleft",
---~ ['unicode']=123,
---~ ['vert_variants']={
---~ ['italic_correction']=0,
---~ ['parts']={
---~ { ['component']="uni23A9", ['endConnectorLength']=1000, ['fullAdvance']=2546, ['is_extender']=0, ['startConnectorLength']=0, }, -- bot
---~ { ['component']="uni23AA", ['endConnectorLength']=2500, ['fullAdvance']=2501, ['is_extender']=1, ['startConnectorLength']=2500, }, -- rep
---~ { ['component']="uni23A8", ['endConnectorLength']=1000, ['fullAdvance']=4688, ['is_extender']=0, ['startConnectorLength']=1000, }, -- mid
---~ { ['component']="uni23AA", ['endConnectorLength']=2500, ['fullAdvance']=2501, ['is_extender']=1, ['startConnectorLength']=2500, }, -- rep
---~ { ['component']="uni23A7", ['endConnectorLength']=0, ['fullAdvance']=2546, ['is_extender']=0, ['startConnectorLength']=1000, }, -- top
---~ },
---~ ['variants']="braceleft braceleft.vsize1 braceleft.vsize2 braceleft.vsize3 braceleft.vsize4 braceleft.vsize5 braceleft.vsize6 braceleft.vsize7",
---~ },
---~ ['width']=793,
---~ },
-
--- 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
-
-fonts.formats.dfont = "truetype"
-fonts.formats.ttc = "truetype"
-fonts.formats.ttf = "truetype"
-fonts.formats.otf = "opentype"
-
-function otf.copy_to_tfm(data,cache_id) -- we can save a copy when we reorder the tma to unicode (nasty due to one->many)
+-- 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 assing 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 glyphs, pfminfo, metadata = data.glyphs or { }, data.pfminfo or { }, data.metadata or { }
- local luatex = data.luatex
- local unicodes = luatex.unicodes -- names to unicodes
- local indices = luatex.indices
- local characters, parameters, math_parameters, descriptions = { }, { }, { }, { }
- local designsize = metadata.designsize or metadata.design_size or 100
+ local metadata = data.metadata
+ local resources = data.resources
+ local properties = table.derive(data.properties)
+ local descriptions = table.derive(data.descriptions)
+ local goodies = table.derive(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 mathspecs = metadata.math
+ --
if designsize == 0 then
designsize = 100
end
- local spaceunits = 500
- -- indices maps from unicodes to indices
- for u, i in next, indices do
- characters[u] = { } -- we need this because for instance we add protruding info and loop over characters
- descriptions[u] = glyphs[i]
- end
- -- math
- if metadata.math then
- -- parameters
- for name, value in next, metadata.math do
- math_parameters[name] = value
- end
- -- we could use a subset
- for u, char in next, characters do
- local d = descriptions[u]
+ 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
- -- we have them shared because that packs nicer
- -- we could prepare the variants and keep 'm in descriptions
if m then
+ -- watch out: luatex uses horiz_variants for the parts
local variants = m.horiz_variants
+ local parts = m.horiz_parts
+ -- local done = { [unicode] = true }
if variants then
- local c = char
- for n in gmatch(variants,"[^ ]+") do
- local un = unicodes[n]
- if un and u ~= un then
+ local c = character
+ for i=1,#variants do
+ local un = variants[i]
+ -- if done[un] then
+ -- -- report_otf("skipping cyclic reference U+%05X in math variant U+%05X",un,unicode)
+ -- else
c.next = un
c = characters[un]
- end
- end
- c.horiz_variants = m.horiz_parts
- else
- local variants = m.vert_variants
- if variants then
- local c = char
- for n in gmatch(variants,"[^ ]+") do
- local un = unicodes[n]
- if un and u ~= un then
- c.next = un
- c = characters[un]
- end
- end
- c.vert_variants = m.vert_parts
- c.vert_italic_correction = m.vert_italic_correction
- end
+ -- done[un] = true
+ -- end
+ end -- c is now last in chain
+ c.horiz_variants = parts
+ elseif parts then
+ character.horiz_variants = parts
+ end
+ local variants = m.vert_variants
+ local parts = m.vert_parts
+ -- 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+%05X in math variant U+%05X",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
+ local italic_correction = m.vert_italic_correction
+ if italic_correction then
+ character.vert_italic_correction = italic_correction -- was c.
+ end
+ local top_accent = m.top_accent
+ if top_accent then
+ character.top_accent = top_accent
end
local kerns = m.kerns
if kerns then
- char.mathkerns = kerns
+ character.mathkerns = kerns
end
end
end
end
-- end math
- local endash, emdash, space = 0x20, 0x2014, "space" -- unicodes['space'], unicodes['emdash']
- if metadata.isfixedpitch then
- if descriptions[endash] then
- spaceunits, spacer = descriptions[endash].width, "space"
+ local monospaced = metadata.isfixedpitch or (pfminfo.panose and pfminfo.panose.proportion == "Monospaced")
+ local charwidth = pfminfo.avgwidth -- or unset
+ local italicangle = metadata.italicangle
+ local charxheight = pfminfo.os2_xheight and pfminfo.os2_xheight > 0 and pfminfo.os2_xheight
+ properties.monospaced = monospaced
+ parameters.italicangle = italicangle
+ parameters.charwidth = charwidth
+ parameters.charxheight = charxheight
+ --
+ local space = 0x0020 -- unicodes['space'], unicodes['emdash']
+ local emdash = 0x2014 -- unicodes['space'], unicodes['emdash']
+ 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 metadata.charwidth then
- spaceunits, spacer = metadata.charwidth, "charwidth"
+ if not spaceunits and charwidth then
+ spaceunits, spacer = charwidth, "charwidth"
end
else
- if descriptions[endash] then
- spaceunits, spacer = descriptions[endash].width, "space"
+ 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 metadata.charwidth then
- spaceunits, spacer = metadata.charwidth, "charwidth"
+ if not spaceunits and charwidth then
+ spaceunits, spacer = charwidth, "charwidth"
end
end
- spaceunits = tonumber(spaceunits) or tfm.units/2 -- 500 -- brrr
+ spaceunits = tonumber(spaceunits) or 500 -- brrr
-- we need a runtime lookup because of running from cdrom or zip, brrr (shouldn't we use the basename then?)
- local filename = fonts.tfm.checked_filename(luatex)
+ local filename = constructors.checkedfilename(resources)
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 "" -- weird here, fix upstream
- --
parameters.slant = 0
parameters.space = spaceunits -- 3.333 (cmr10)
parameters.space_stretch = units/2 -- 500 -- 1.666 (cmr10)
@@ -7455,20 +6578,21 @@ function otf.copy_to_tfm(data,cache_id) -- we can save a copy when we reorder th
if spaceunits < 2*units/5 then
-- todo: warning
end
- local italicangle = metadata.italicangle
- if italicangle then -- maybe also in afm _
- parameters.slant = parameters.slant - math.round(math.tan(italicangle*math.pi/180))
+ if italicangle then
+ parameters.italicangle = italicangle
+ parameters.italicfactor = math.cos(math.rad(90+italicangle))
+ parameters.slant = - math.round(math.tan(italicangle*math.pi/180))
end
- if metadata.isfixedpitch then
+ if monospaced then
parameters.space_stretch = 0
parameters.space_shrink = 0
- elseif otf.syncspace then --
+ elseif syncspace then --
parameters.space_stretch = spaceunits/2
parameters.space_shrink = spaceunits/3
end
parameters.extra_space = parameters.space_shrink -- 1.111 (cmr10)
- if pfminfo.os2_xheight and pfminfo.os2_xheight > 0 then
- parameters.x_height = pfminfo.os2_xheight
+ if charxheight then
+ parameters.x_height = charxheight
else
local x = 0x78 -- unicodes['x']
if x then
@@ -7479,99 +6603,109 @@ function otf.copy_to_tfm(data,cache_id) -- we can save a copy when we reorder th
end
end
--
+ parameters.designsize = (designsize/10)*65536
+ parameters.ascender = abs(metadata.ascent or 0)
+ parameters.descender = abs(metadata.descent or 0)
+ parameters.units = units
+ --
+ properties.space = spacer
+ properties.encodingbytes = 2
+ properties.format = data.format or fonts.formats[filename] or "opentype"
+ properties.noglyphnames = true
+ properties.filename = filename
+ properties.fontname = fontname
+ properties.fullname = fullname
+ properties.psname = fontname or fullname
+ properties.name = filename or fullname
+ --
+ -- properties.name = specification.name
+ -- properties.sub = specification.sub
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,
+ characters = characters,
+ descriptions = descriptions,
+ parameters = parameters,
+ mathparameters = mathparameters,
+ resources = resources,
+ properties = properties,
+ goodies = goodies,
}
- 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
- -- this will move to a function
- 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
- -- not to be used in context !
- local specname = specification.specification
- if specname then
- tfmtable.name = specname
- if trace_defining then
- logs.report("define font","overloaded fontname: '%s'",specname)
+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,format,sub,features and features.featurefile)
+ if rawdata and next(rawdata) then
+ 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
- fonts.logger.save(tfmtable,file.extname(specification.filename),specification)
+ 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)
+ constructors.applymanipulators("otf",tfmdata,specification.features.normal,trace_features,report_otf)
+ constructors.setname(tfmdata,specification) -- only otf?
+ fonts.loggers.register(tfmdata,file.extname(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
---~ print(tfmtable.fullname)
- return tfmtable
end
+registerotffeature {
+ name = "mathsize",
+ description = "apply mathsize as specified in the font",
+ initializers = {
+ base = checkmathsize,
+ node = checkmathsize,
+ }
+}
+
-- helpers
-function otf.collect_lookups(otfdata,kind,script,language)
- -- maybe store this in the font
- local sequences = otfdata.luatex.sequences
+function otf.collectlookups(rawdata,kind,script,language)
+ local sequences = rawdata.resources.sequences
if sequences then
local featuremap, featurelist = { }, { }
for s=1,#sequences do
@@ -7600,527 +6734,1102 @@ function otf.collect_lookups(otfdata,kind,script,language)
return nil, nil
end
+-- readers
+
+local function check_otf(forced,specification,suffix,what)
+ local name = specification.name
+ if forced then
+ name = file.addsuffix(name,suffix,true)
+ end
+ local fullname, tfmdata = findbinfile(name,suffix) or "", nil -- one shot
+ if fullname == "" then
+ fullname = fonts.names.getfilename(name,suffix)
+ end
+ if fullname ~= "" then
+ specification.filename, specification.format = fullname, what -- hm, so we do set the filename, then
+ tfmdata = read_from_otf(specification) -- we need to do it for all matches / todo
+ end
+ return tfmdata
+end
+
+local function opentypereader(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
+
+readers.opentype = opentypereader
+
+local formats = fonts.formats
+
+formats.otf = "opentype"
+formats.ttf = "truetype"
+formats.ttc = "truetype"
+formats.dfont = "truetype"
+
+function readers.otf (specification) return opentypereader(specification,"otf",formats.otf ) end
+function readers.ttf (specification) return opentypereader(specification,"ttf",formats.ttf ) end
+function readers.ttc (specification) return opentypereader(specification,"ttf",formats.ttc ) end
+function readers.dfont(specification) return opentypereader(specification,"ttf",formats.dfont) end
+
+-- this will be overloaded
+
+function otf.scriptandlanguage(tfmdata,attr)
+ local properties = tfmdata.properties
+ return properties.script or "dflt", properties.language or "dflt"
+end
+
end -- closure
do -- begin closure to overcome local limits and interference
-if not modules then modules = { } end modules ['font-otd'] = {
+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 utfchar = utf.char
-local trace_dynamics = false trackers.register("otf.dynamics", function(v) trace_dynamics = v end)
-
-fonts = fonts or { }
-fonts.otf = fonts.otf or { }
+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 otf = fonts.otf
-local fontdata = fonts.ids
+local report_prepare = logs.reporter("fonts","otf prepare")
-otf.features = otf.features or { }
-otf.features.default = otf.features.default or { }
+local fonts = fonts
+local otf = fonts.handlers.otf
-local context_setups = fonts.define.specify.context_setups
-local context_numbers = fonts.define.specify.context_numbers
+local otffeatures = fonts.constructors.newfeatures("otf")
+local registerotffeature = otffeatures.register
-local a_to_script = { } otf.a_to_script = a_to_script
-local a_to_language = { } otf.a_to_language = a_to_language
+local wildcard = "*"
+local default = "dflt"
-function otf.set_dynamics(font,dynamics,attribute)
- features = context_setups[context_numbers[attribute]] -- can be moved to caller
- 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
- -- if trace_dynamics then
- -- logs.report("otf define","using dynamics %s: attribute %s, script %s, language %s",context_numbers[attribute],attribute,script,language)
- -- end
- return dsla
+local function gref(descriptions,n)
+ if type(n) == "number" then
+ local name = descriptions[n].name
+ if name then
+ return format("U+%05X (%s)",n,name)
else
- local tfmdata = fontdata[font]
- a_to_script [attribute] = script
- a_to_language[attribute] = language
- -- we need to save some values
- 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 = { }
- -- end of save
- dsla = otf.set_features(tfmdata,fonts.define.check(features,otf.features.default))
- if trace_dynamics then
- logs.report("otf define","setting dynamics %s: attribute %s, script %s, language %s",context_numbers[attribute],attribute,script,language)
+ return format("U+%05X")
+ end
+ elseif n then
+ local num, nam = { }, { }
+ for i=2,#n do -- first is likely a key
+ local ni = n[i]
+ num[i] = format("U+%05X",ni)
+ nam[i] = descriptions[ni].name or "?"
+ end
+ return format("%s (%s)",concat(num," "), concat(nam," "))
+ else
+ return "?"
+ end
+end
+
+local function cref(feature,lookupname)
+ if lookupname then
+ return format("feature %s, lookup %s",feature,lookupname)
+ else
+ return format("feature %s",feature)
+ end
+end
+
+local basemethods = { }
+local basemethod = "<unset>"
+
+local function applybasemethod(what,...)
+ local m = basemethods[basemethod][what]
+ if m then
+ return m(...)
+ end
+end
+
+-- We need to make sure that luatex sees the difference between
+-- base fonts that have different glyphs in the same slots in fonts
+-- that have the same fullname (or filename). LuaTeX will merge fonts
+-- eventually (and subset later on). If needed we can use a more
+-- verbose name as long as we don't use <()<>[]{}/%> and the length
+-- is < 128.
+
+local basehash, basehashes, applied = { }, 1, { }
+
+local function registerbasehash(tfmdata)
+ local properties = tfmdata.properties
+ local hash = concat(applied," ")
+ local base = basehash[hash]
+ if not base then
+ basehashes = basehashes + 1
+ base = basehashes
+ basehash[hash] = base
+ end
+ properties.basehash = base
+ properties.fullname = properties.fullname .. "-" .. base
+ -- report_prepare("fullname base hash: '%s', featureset '%s'",tfmdata.properties.fullname,hash)
+ applied = { }
+end
+
+local function registerbasefeature(feature,value)
+ applied[#applied+1] = feature .. "=" .. tostring(value)
+end
+
+-- The original basemode ligature builder used the names of components
+-- and did some expression juggling to get the chain right. The current
+-- variant starts with unicodes but still uses names to make the chain.
+-- This is needed because we have to create intermediates when needed
+-- but use predefined snippets when available. To some extend the
+-- current builder is more stupid but I don't worry that much about it
+-- as ligatures are rather predicatable.
+--
+-- Personally I think that an ff + i == ffi rule as used in for instance
+-- latin modern is pretty weird as no sane person will key that in and
+-- expect a glyph for that ligature plus the following character. Anyhow,
+-- as we need to deal with this, we do, but no guarantes are given.
+--
+-- latin modern dejavu
+--
+-- f+f 102 102 102 102
+-- f+i 102 105 102 105
+-- f+l 102 108 102 108
+-- f+f+i 102 102 105
+-- f+f+l 102 102 108 102 102 108
+-- ff+i 64256 105 64256 105
+-- ff+l 64256 108
+--
+-- As you can see here, latin modern is less complete than dejavu but
+-- in practice one will not notice it.
+--
+-- The while loop is needed because we need to resolve for instance
+-- pseudo names like hyphen_hyphen to endash so in practice we end
+-- up with a bit too many definitions but the overhead is neglectable.
+--
+-- Todo: if changed[first] or changed[second] then ... end
+
+local trace = false
+
+local function finalize_ligatures(tfmdata,ligatures)
+ local nofligatures = #ligatures
+ if nofligatures > 0 then
+ local characters = tfmdata.characters
+ local descriptions = tfmdata.descriptions
+ local resources = tfmdata.resources
+ local unicodes = resources.unicodes
+ local private = resources.private
+ local alldone = false
+ while not alldone do
+ local done = 0
+ for i=1,nofligatures do
+ local ligature = ligatures[i]
+ if ligature then
+ local unicode, lookupdata = ligature[1], ligature[2]
+ if trace then
+ print("BUILDING",concat(lookupdata," "),unicode)
+ end
+ local size = #lookupdata
+ local firstcode = lookupdata[1] -- [2]
+ local firstdata = characters[firstcode]
+ local okay = false
+ if firstdata then
+ local firstname = "ctx_" .. firstcode
+ for i=1,size-1 do -- for i=2,size-1 do
+ local firstdata = characters[firstcode]
+ if not firstdata then
+ firstcode = private
+ if trace then
+ print(" DEFINING",firstname,firstcode)
+ end
+ unicodes[firstname] = firstcode
+ firstdata = { intermediate = true, ligatures = { } }
+ characters[firstcode] = firstdata
+ descriptions[firstcode] = { name = firstname }
+ private = private + 1
+ end
+ local target
+ local secondcode = lookupdata[i+1]
+ local secondname = firstname .. "_" .. secondcode
+ if i == size - 1 then
+ target = unicode
+ if not unicodes[secondname] then
+ unicodes[secondname] = unicode -- map final ligature onto intermediates
+ end
+ okay = true
+ else
+ target = unicodes[secondname]
+ if not target then
+ break
+ end
+ end
+ if trace then
+ print("CODES",firstname,firstcode,secondname,secondcode,target)
+ end
+ local firstligs = firstdata.ligatures
+ if firstligs then
+ firstligs[secondcode] = { char = target }
+ else
+ firstdata.ligatures = { [secondcode] = { char = target } }
+ end
+ firstcode = target
+ firstname = secondname
+ end
+ end
+ if okay then
+ ligatures[i] = false
+ done = done + 1
+ end
+ end
+ end
+ alldone = done == 0
+ end
+ if trace then
+ for k, v in next, characters do
+ if v.ligatures then table.print(v,k) end
end
- -- we need to restore some values
- tfmdata.script = saved.script
- tfmdata.language = saved.language
- tfmdata.mode = saved.mode
- tfmdata.shared.features = saved.features
- -- end of restore
- dynamics[script][language][attribute] = dsla -- cache
- return dsla
end
+ tfmdata.resources.private = private
end
- return nil -- { }
end
-end -- closure
+local function preparesubstitutions(tfmdata,feature,value,validlookups,lookuplist)
+ local characters = tfmdata.characters
+ local descriptions = tfmdata.descriptions
+ local resources = tfmdata.resources
+ local changed = tfmdata.changed
+ local unicodes = resources.unicodes
+ local lookuphash = resources.lookuphash
+ local lookuptypes = resources.lookuptypes
-do -- begin closure to overcome local limits and interference
+ local ligatures = { }
-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 actions = {
+ substitution = function(lookupdata,lookupname,description,unicode)
+ if trace_baseinit and trace_singles then
+ report_prepare("%s: base substitution %s => %s",cref(feature,lookupname),
+ gref(descriptions,unicode),gref(descriptions,replacement))
+ end
+ changed[unicode] = lookupdata
+ end,
+ alternate = function(lookupdata,lookupname,description,unicode)
+ local replacement = lookupdata[value] or lookupdata[#lookupdata]
+ if trace_baseinit and trace_alternatives then
+ report_prepare("%s: base alternate %s %s => %s",cref(feature,lookupname),
+ tostring(value),gref(descriptions,unicode),gref(descriptions,replacement))
+ end
+ changed[unicode] = replacement
+ end,
+ ligature = function(lookupdata,lookupname,description,unicode)
+ if trace_baseinit and trace_alternatives then
+ report_prepare("%s: base ligature %s %s => %s",cref(feature,lookupname),
+ tostring(value),gref(descriptions,lookupdata),gref(descriptions,unicode))
+ end
+ ligatures[#ligatures+1] = { unicode, lookupdata }
+ end,
+ }
--- i need to check features=yes|no also in relation to hashing
+ for unicode, character in next, characters do
+ local description = descriptions[unicode]
+ local lookups = description.slookups
+ if lookups then
+ for l=1,#lookuplist do
+ local lookupname = lookuplist[l]
+ local lookupdata = lookups[lookupname]
+ if lookupdata then
+ local lookuptype = lookuptypes[lookupname]
+ local action = actions[lookuptype]
+ if action then
+ action(lookupdata,lookupname,description,unicode)
+ end
+ end
+ end
+ end
+ local lookups = description.mlookups
+ if lookups then
+ for l=1,#lookuplist do
+ local lookupname = lookuplist[l]
+ local lookuplist = lookups[lookupname]
+ if lookuplist then
+ local lookuptype = lookuptypes[lookupname]
+ local action = actions[lookuptype]
+ if action then
+ for i=1,#lookuplist do
+ action(lookuplist[i],lookupname,description,unicode)
+ end
+ end
+ end
+ end
+ end
+ end
-local lower = string.lower
+ finalize_ligatures(tfmdata,ligatures)
+end
-local otf = fonts.otf
+local function preparepositionings(tfmdata,feature,value,validlookups,lookuplist) -- todo what kind of kerns, currently all
+ local characters = tfmdata.characters
+ local descriptions = tfmdata.descriptions
+ local resources = tfmdata.resources
+ local unicodes = resources.unicodes
+ local sharedkerns = { }
+ local traceindeed = trace_baseinit and trace_kerns
+ for unicode, character in next, characters do
+ local description = descriptions[unicode]
+ local rawkerns = description.kerns -- shared
+ if rawkerns then
+ local s = sharedkerns[rawkerns]
+ if s == false then
+ -- skip
+ elseif s then
+ character.kerns = s
+ else
+ local newkerns = character.kerns
+ local done = false
+ for l=1,#lookuplist do
+ local lookup = lookuplist[l]
+ local kerns = rawkerns[lookup]
+ if kerns then
+ for otherunicode, value in next, kerns do
+ if value == 0 then
+ -- maybe no 0 test here
+ elseif not newkerns then
+ newkerns = { [otherunicode] = value }
+ done = true
+ if traceindeed then
+ report_prepare("%s: base kern %s + %s => %s",cref(feature,lookup),
+ gref(descriptions,unicode),gref(descriptions,otherunicode),value)
+ end
+ elseif not newkerns[otherunicode] then -- first wins
+ newkerns[otherunicode] = value
+ done = true
+ if traceindeed then
+ report_prepare("%s: base kern %s + %s => %s",cref(feature,lookup),
+ gref(descriptions,unicode),gref(descriptions,otherunicode),value)
+ end
+ end
+ end
+ end
+ end
+ if done then
+ sharedkerns[rawkerns] = newkerns
+ character.kerns = newkerns -- no empty assignments
+ else
+ sharedkerns[rawkerns] = false
+ end
+ end
+ end
+ end
+end
-otf.default_language = 'latn'
-otf.default_script = 'dflt'
+basemethods.independent = {
+ preparesubstitutions = preparesubstitutions,
+ preparepositionings = preparepositionings,
+}
-local languages = otf.tables.languages
-local scripts = otf.tables.scripts
+local function makefake(tfmdata,name,present)
+ local resources = tfmdata.resources
+ local private = resources.private
+ local character = { intermediate = true, ligatures = { } }
+ resources.unicodes[name] = private
+ tfmdata.characters[private] = character
+ tfmdata.descriptions[private] = { name = name }
+ resources.private = private + 1
+ present[name] = private
+ return character
+end
+
+local function make_1(present,tree,name)
+ for k, v in next, tree do
+ if k == "ligature" then
+ present[name] = v
+ else
+ make_1(present,v,name .. "_" .. k)
+ end
+ end
+end
-function otf.features.language(tfmdata,value)
- if value then
- value = lower(value)
- if languages[value] then
- tfmdata.language = value
+local function make_2(present,tfmdata,characters,tree,name,preceding,unicode,done,lookupname)
+ for k, v in next, tree do
+ if k == "ligature" then
+ local character = characters[preceding]
+ if not character then
+ if trace_baseinit then
+ report_prepare("weird ligature in lookup %s: U+%05X (%s), preceding U+%05X (%s)",lookupname,v,utfchar(v),preceding,utfchar(preceding))
+ end
+ character = makefake(tfmdata,name,present)
+ end
+ local ligatures = character.ligatures
+ if ligatures then
+ ligatures[unicode] = { char = v }
+ else
+ character.ligatures = { [unicode] = { char = v } }
+ end
+ if done then
+ local d = done[lookupname]
+ if not d then
+ done[lookupname] = { "dummy", v }
+ else
+ d[#d+1] = v
+ end
+ end
+ else
+ local code = present[name] or unicode
+ local name = name .. "_" .. k
+ make_2(present,tfmdata,characters,v,name,code,k,done,lookupname)
end
end
end
-function otf.features.script(tfmdata,value)
- if value then
- value = lower(value)
- if scripts[value] then
- tfmdata.script = value
+local function preparesubstitutions(tfmdata,feature,value,validlookups,lookuplist)
+ local characters = tfmdata.characters
+ local descriptions = tfmdata.descriptions
+ local resources = tfmdata.resources
+ local changed = tfmdata.changed
+ local lookuphash = resources.lookuphash
+ local lookuptypes = resources.lookuptypes
+
+ local ligatures = { }
+
+ for l=1,#lookuplist do
+ local lookupname = lookuplist[l]
+ local lookupdata = lookuphash[lookupname]
+ local lookuptype = lookuptypes[lookupname]
+ for unicode, data in next, lookupdata do
+ if lookuptype == "substitution" then
+ if trace_baseinit and trace_singles then
+ report_prepare("%s: base substitution %s => %s",cref(feature,lookupname),
+ gref(descriptions,unicode),gref(descriptions,data))
+ end
+ changed[unicode] = data
+ elseif lookuptype == "alternate" then
+ local replacement = data[value] or data[#data]
+ if trace_baseinit and trace_alternatives then
+ report_prepare("%s: base alternate %s %s => %s",cref(feature,lookupname),
+ tostring(value),gref(descriptions,unicode),gref(descriptions,replacement))
+ end
+ changed[unicode] = replacement
+ elseif lookuptype == "ligature" then
+ ligatures[#ligatures+1] = { unicode, data, lookupname }
+ end
+ end
+ end
+
+ local nofligatures = #ligatures
+
+ if nofligatures > 0 then
+
+ local characters = tfmdata.characters
+ local present = { }
+ local done = trace_baseinit and trace_ligatures and { }
+
+ for i=1,nofligatures do
+ local ligature = ligatures[i]
+ local unicode, tree = ligature[1], ligature[2]
+ make_1(present,tree,"ctx_"..unicode)
+ end
+
+ for i=1,nofligatures do
+ local ligature = ligatures[i]
+ local unicode, tree, lookupname = ligature[1], ligature[2], ligature[3]
+ make_2(present,tfmdata,characters,tree,"ctx_"..unicode,unicode,unicode,done,lookupname)
+ end
+
+ if done then
+ for lookupname, list in next, done do
+ report_prepare("%s: base ligatures %s => %s",cref(feature,lookupname),
+ tostring(value),gref(descriptions,done))
+ end
end
+
end
+
end
-function otf.features.mode(tfmdata,value)
- if value then
- tfmdata.mode = lower(value)
+local function preparepositionings(tfmdata,feature,value,validlookups,lookuplist)
+ local characters = tfmdata.characters
+ local descriptions = tfmdata.descriptions
+ local resources = tfmdata.resources
+ local lookuphash = resources.lookuphash
+ local traceindeed = trace_baseinit and trace_kerns
+
+ -- check out this sharedkerns trickery
+
+ for l=1,#lookuplist do
+ local lookupname = lookuplist[l]
+ local lookupdata = lookuphash[lookupname]
+ for unicode, data in next, lookupdata do
+ local character = characters[unicode]
+ local kerns = character.kerns
+ if not kerns then
+ kerns = { }
+ character.kerns = kerns
+ end
+ if traceindeed then
+ for otherunicode, kern in next, data do
+ if not kerns[otherunicode] and kern ~= 0 then
+ kerns[otherunicode] = kern
+ report_prepare("%s: base kern %s + %s => %s",cref(feature,lookup),
+ gref(descriptions,unicode),gref(descriptions,otherunicode),kern)
+ end
+ end
+ else
+ for otherunicode, kern in next, data do
+ if not kerns[otherunicode] and kern ~= 0 then
+ kerns[otherunicode] = kern
+ end
+ end
+ end
+ end
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
+local function initializehashes(tfmdata)
+ nodeinitializers.features(tfmdata)
+end
-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
+basemethods.shared = {
+ initializehashes = initializehashes,
+ preparesubstitutions = preparesubstitutions,
+ preparepositionings = preparepositionings,
+}
-otf.features.register("features",true) -- we always do features
-table.insert(fonts.processors,"features") -- we need a proper function for doing this
+basemethod = "independent"
+local function featuresinitializer(tfmdata,value)
+ if true then -- value then
+ local t = trace_preparing and os.clock()
+ local features = tfmdata.shared.features
+ if features then
+ applybasemethod("initializehashes",tfmdata)
+ local collectlookups = otf.collectlookups
+ local rawdata = tfmdata.shared.rawdata
+ local properties = tfmdata.properties
+ local script = properties.script
+ local language = properties.language
+ local basesubstitutions = rawdata.resources.features.gsub
+ local basepositionings = rawdata.resources.features.gpos
+ if basesubstitutions then
+ for feature, data in next, basesubstitutions do
+ local value = features[feature]
+ if value then
+ local validlookups, lookuplist = collectlookups(rawdata,feature,script,language)
+ if validlookups then
+ applybasemethod("preparesubstitutions",tfmdata,feature,value,validlookups,lookuplist)
+ registerbasefeature(feature,value)
+ end
+ end
+ end
+ end
+ if basepositions then
+ for feature, data in next, basepositions do
+ local value = features[feature]
+ if value then
+ local validlookups, lookuplist = collectlookups(rawdata,feature,script,language)
+ if validlookups then
+ applybasemethod("preparepositionings",tfmdata,feature,features[feature],validlookups,lookuplist)
+ registerbasefeature(feature,value)
+ end
+ end
+ end
+ end
+ registerbasehash(tfmdata)
+ end
+ if trace_preparing then
+ report_prepare("preparation time is %0.3f seconds for %s",os.clock()-t,tfmdata.properties.fullname or "?")
+ end
+ end
+end
+
+registerotffeature {
+ name = "features",
+ description = "features",
+ default = true,
+ initializers = {
+--~ position = 1, -- after setscript (temp hack ... we need to force script / language to 1
+ base = featuresinitializer,
+ }
+}
+
+-- independent : collect lookups independently (takes more runtime ... neglectable)
+-- shared : shares lookups with node mode (takes more memory ... noticeable)
+
+directives.register("fonts.otf.loader.basemethod", function(v)
+ if basemethods[v] then
+ basemethod = v
+ end
+end)
end -- closure
do -- begin closure to overcome local limits and interference
-if not modules then modules = { } end modules ['font-otb'] = {
+if not modules then modules = { } end modules ['node-inj'] = {
version = 1.001,
- comment = "companion to font-ini.mkiv",
+ 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 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
+-- This is very experimental (this will change when we have luatex > .50 and
+-- a few pending thingies are available. Also, Idris needs to make a few more
+-- test fonts. Btw, future versions of luatex will have extended glyph properties
+-- that can be of help.
-local otf = fonts.otf
-local tfm = fonts.tfm
+local next = next
-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 trace_injections = false trackers.register("nodes.injections", function(v) trace_injections = v end)
-local wildcard = "*"
-local default = "dflt"
+local report_injections = logs.reporter("nodes","injections")
-local split_at_space = lpeg.Ct(lpeg.splitat(" ")) -- no trailing or multiple spaces anyway
+local attributes, nodes, node = attributes, nodes, node
-local pcache, fcache = { }, { } -- could be weak
+fonts = fonts
+local fontdata = fonts.hashes.identifiers
-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)
+nodes.injections = nodes.injections or { }
+local injections = nodes.injections
+
+local nodecodes = nodes.nodecodes
+local glyph_code = nodecodes.glyph
+local nodepool = nodes.pool
+local newkern = nodepool.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 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 = { }
+
+-- currently we do gpos/kern in a bit inofficial way but when we
+-- have the extra fields in glyphnodes to manipulate ht/dp/wd
+-- explicitly i will provide an alternative; also, we can share
+-- tables
+
+-- for the moment we pass the r2l key ... volt/arabtype tests
+
+function injections.setcursive(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 injections.setpair(current,factor,rlmode,r2lflag,spec,tfmchr)
+ local x, y, w, h = factor*spec[1], factor*spec[2], factor*spec[3], factor*spec[4]
+ -- dy = y - h
+ 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]
+ -- inefficient but singles have less, but weird anyway, needs checking
+ 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
- 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 "?"
+ bound = #kerns + 1
+ set_attribute(current,kernpair,bound)
+ kerns[bound] = { rlmode, x, y, w, h, r2lflag, tfmchr.width }
end
- return format("%s (%s)",concat(num," "), concat(nam," "))
- else
- return "?"
+ return x, y, w, h, bound
end
+ return x, y, w, h -- no bound
end
-local function cref(kind,lookupname)
- if lookupname then
- return format("feature %s, lookup %s",kind,lookupname)
+function injections.setkern(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 format("feature %s",kind)
+ return 0, 0
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))
- -- some kind of error
+function injections.setmark(start,base,factor,rlmode,ba,ma,index) --ba=baseanchor, ma=markanchor
+ 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
+ report_injections("possible problem, U+%05X is base mark 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
+
+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 trace(head)
+ report_injections("begin run")
+ for n in traverse_id(glyph_code,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_injections("char U+%05X, font=%s",n.char,n.font)
+ if kp then
+ local k = kerns[kp]
+ if k[3] then
+ report_injections(" 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_injections(" kern: dir=%s, dx=%s",dir(k[1]),k[2] or "?")
+ end
+ end
+ if mb then
+ report_injections(" markbase: bound=%s",mb)
+ end
+ if mm then
+ local m = marks[mm]
+ if mb then
+ local m = m[mb]
+ if m then
+ report_injections(" markmark: bound=%s, index=%s, dx=%s, dy=%s",mm,md or "?",m[1] or "?",m[2] or "?")
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] } -- can this still happen?
- 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
+ report_injections(" markmark: bound=%s, missing index",mm)
end
- ok, done[lig] = true, descriptions[uc].name
+ else
+ m = m[1]
+ report_injections(" markmark: bound=%s, dx=%s, dy=%s",mm,m[1] or "?",m[2] or "?")
end
end
- end
- if ok then
- -- done has "a b c" = "a_b_c" and ligatures the already set ligatures: "a b" = 123
- -- and here we add extras (f i i = fi + i and alike)
- --
- -- we could use a hash for fnc and pattern
- --
- -- this might be interfering !
- 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
+ if cb then
+ report_injections(" cursbase: bound=%s",cb)
+ end
+ if cc then
+ local c = cursives[cc]
+ report_injections(" curscurs: bound=%s, dir=%s, dx=%s, dy=%s",cc,dir(c[1]),c[2] or "?",c[3] or "?")
end
- else
- break
end
end
+ report_injections("end run")
end
-local splitter = lpeg.splitat(" ")
+-- todo: reuse tables (i.e. no collection), but will be extra fields anyway
+-- todo: check for attribute
-function prepare_base_substitutions(tfmdata,kind,value) -- we can share some code with the node features
- 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 -- names to 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] -- p.variant
- if pv then
- local upv = unicodes[pv]
- if upv then
- if type(upv) == "table" then
- upv = upv[1]
+-- we can have a fast test on a font being processed, so we can check faster for marks etc
+
+function injections.handler(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
+ trace(head)
+ end
+ -- in the future variant we will not copy items but refs to tables
+ local done, ky, rl, valid, cx, wx, mk, nofvalid = false, { }, { }, { }, { }, { }, { }, 0
+ if has_kerns then -- move outside loop
+ local nf, tm = nil, nil
+ for n in traverse_id(glyph_code,head) do -- only needed for relevant fonts
+ if n.subtype < 256 then
+ nofvalid = nofvalid + 1
+ valid[nofvalid] = n
+ if n.font ~= nf then
+ nf = n.font
+ tm = fontdata[nf].resources.marks
+ end
+ if tm then
+ mk[n] = tm[n.char]
+ end
+ 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 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
+ if w ~= 0 or x ~= 0 then
+ wx[n] = kk
end
+ rl[n] = kk[1] -- could move in test
end
end
- end,
- alternate = function(p,lookup,k,glyph,unicode)
- local pc = p[2] -- p.components
- if pc then
- -- a bit optimized ugliness
- 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
+ end
+ else
+ local nf, tm = nil, nil
+ for n in traverse_id(glyph_code,head) do
+ if n.subtype < 256 then
+ nofvalid = nofvalid + 1
+ valid[nofvalid] = n
+ if n.font ~= nf then
+ nf = n.font
+ tm = fontdata[nf].resources.marks
+ end
+ if tm then
+ mk[n] = tm[n.char]
+ end
+ end
+ end
+ end
+ if nofvalid > 0 then
+ -- we can assume done == true because we have cursives and marks
+ local cx = { }
+ if has_kerns and next(ky) then
+ for n, k in next, ky do
+ n.yoffset = k
+ end
+ end
+ -- todo: reuse t and use maxt
+ if has_cursives then
+ local p_cursbase, p = nil, nil
+ -- since we need valid[n+1] we can also use a "while true do"
+ local t, d, maxt = { }, { }, 0
+ for i=1,nofvalid do -- valid == glyphs
+ 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
- changed[k] = upc
+ if dx ~= 0 then
+ cx[n] = dx
+ rl[n] = rlmode
+ end
+ -- if rlmode and rlmode < 0 then
+ dy = -dy
+ -- end
+ 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
- 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
- -- we assume that it's no table
- 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)
+ 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
- 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
+ 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
- resolve_ligatures(tfmdata,ligatures,kind)
- end
- else
- tfmdata.ligatures = tfmdata.ligatures or { } -- left over from what ?
- end
-end
-
-local function prepare_base_kerns(tfmdata,kind,value) -- todo what kind of kerns, currently all
- 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 -- names to 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 -- shared
- if dk then
- local s = sharedkerns[dk]
- if s == false then
- -- skip
- 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 -- maybe no 0 test here
- 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
+ if has_marks then
+ for i=1,nofvalid 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_code,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
+ -- new per 2010-10-06, width adapted per 2010-02-03
+ -- we used to negate the width of marks because in tfm
+ -- that makes sense but we no longer do that so as a
+ -- consequence the sign of p.width was changed (we need
+ -- to keep an eye on it as we don't have that many fonts
+ -- that enter this branch .. i'm still not sure if this
+ -- one is right
+ 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]
+ -- lucida U\char"032F (default+mark)
+ n.xoffset = p.xoffset - p.width + d[1] -- 01-05-2011
+ 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
- end
- if done then
- sharedkerns[dk] = t
- chr.kerns = t -- no empty assignments
else
- sharedkerns[dk] = false
+ break
end
end
end
end
+ if not keep then
+ marks = { }
+ end
end
- end
- end
-end
-
--- In principle we could register each feature individually which was
--- what we did in earlier versions. However, after the rewrite it
--- made more sense to collect them in an overall features initializer
--- just as with the node variant. There it was needed because we need
--- to do complete mixed runs and not run featurewise (as we did before).
-
-local supported_gsub = {
- 'liga', 'dlig', 'rlig', 'hlig',
- 'pnum', 'onum', 'tnum', 'lnum',
- 'zero',
- 'smcp', 'cpsp', 'c2sc', 'ornm', 'aalt',
- 'hwid', 'fwid',
- 'ssty', 'rtlm', -- math
--- 'tlig', 'trep',
-}
-
-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 -- value then
- -- not shared
- 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)
+ -- todo : combine
+ if next(wx) then
+ for n, k in next, wx do
+ -- only w can be nil, can be sped up when w == nil
+ 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
- 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)
+ 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
- local hash = concat(h," ")
- local base = basehash[hash]
- if not base then
- basehashes = basehashes + 1
- base = basehashes
- basehash[hash] = base
+ if not keep then
+ kerns = { }
end
- -- We need to make sure that luatex sees the difference between
- -- base fonts that have different glyphs in the same slots in fonts
- -- that have the same fullname (or filename). LuaTeX will merge fonts
- -- eventually (and subset later on). If needed we can use a more
- -- verbose name as long as we don't use <()<>[]{}/%> and the length
- -- is < 128.
- tfmdata.fullname = tfmdata.fullname .. "-" .. base -- tfmdata.psname is the original
- --~ logs.report("otf define","fullname base hash: '%s', featureset '%s'",tfmdata.fullname,hash)
+ return head, true
+ elseif not keep then
+ kerns, cursives, marks = { }, { }, { }
end
- if trace_preparing then
- logs.report("otf define","preparation time is %0.3f seconds for %s",os.clock()-t,tfmdata.fullname or "?")
+ elseif has_kerns then
+ if trace_injections then
+ trace(head)
+ end
+ for n in traverse_id(glyph_code,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 -- todo: h ?
+ end
+ if w then
+ -- copied from above
+ 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
+ -- simple (e.g. kernclass kerns)
+ 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
+ -- no tracing needed
end
+ return head, false
end
end -- closure
@@ -8139,14 +7848,6 @@ if not modules then modules = { } end modules ['font-otn'] = {
-- much functionality could only be implemented thanks to the husayni font
-- of Idris Samawi Hamid to who we dedicate this module.
--- I'm in the process of cleaning up the code (which happens in another
--- file) so don't rely on things staying the same.
-
--- some day when we can jit this, we can use more functions
-
--- we can use more lpegs when lpeg is extended with function args and so
--- resolving to unicode does not gain much
-
-- in retrospect it always looks easy but believe it or not, it took a lot
-- of work to get proper open type support done: buggy fonts, fuzzy specs,
-- special made testfonts, many skype sessions between taco, idris and me,
@@ -8161,10 +7862,7 @@ if not modules then modules = { } end modules ['font-otn'] = {
-- alternative loop quitters
-- check cursive and r2l
-- find out where ignore-mark-classes went
--- remove unused tables
--- slide tail (always glue at the end so only needed once
-- default features (per language, script)
--- cleanup kern(class) code, remove double info
-- handle positions (we need example fonts)
-- handle gpos_single (we might want an extra width field in glyph nodes because adding kerns might interfere)
@@ -8240,6 +7938,8 @@ results in different tables.</p>
-- gpos_context ok --
-- gpos_contextchain ok --
--
+-- todo: contextpos and contextsub and class stuff
+--
-- actions:
--
-- handler : actions triggered by lookup
@@ -8249,13 +7949,20 @@ results in different tables.</p>
-- remark: the 'not implemented yet' variants will be done when we have fonts that use them
-- remark: we need to check what to do with discretionaries
+-- We used to have independent hashes for lookups but as the tags are unique
+-- we now use only one hash. If needed we can have multiple again but in that
+-- case I will probably prefix (i.e. rename) the lookups in the cached font file.
+
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 random = math.random
-local otf = fonts.otf
-local tfm = fonts.tfm
+local logs, trackers, nodes, attributes = logs, trackers, nodes, attributes
+
+local fonts = fonts
+local otf = fonts.handlers.otf
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)
@@ -8274,6 +7981,12 @@ local trace_steps = false trackers.register("otf.steps", function
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)
+local report_direct = logs.reporter("fonts","otf direct")
+local report_subchain = logs.reporter("fonts","otf subchain")
+local report_chain = logs.reporter("fonts","otf chain")
+local report_process = logs.reporter("fonts","otf process")
+local report_prepare = logs.reporter("fonts","otf prepare")
+
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)
@@ -8284,83 +7997,84 @@ 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 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 setmetatableindex = table.setmetatableindex
-local split_at_space = lpeg.splitters[" "] or lpeg.Ct(lpeg.splitat(" ")) -- no trailing or multiple spaces anyway
+local zwnj = 0x200C
+local zwj = 0x200D
+local wildcard = "*"
+local default = "dflt"
-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 nodecodes = nodes.nodecodes
+local whatcodes = nodes.whatcodes
+local glyphcodes = nodes.glyphcodes
-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 glyph_code = nodecodes.glyph
+local glue_code = nodecodes.glue
+local disc_code = nodecodes.disc
+local whatsit_code = nodecodes.whatsit
+
+local dir_code = whatcodes.dir
+local localpar_code = whatcodes.localpar
+
+local ligature_code = glyphcodes.ligature
-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 privateattribute = attributes.private
-local markonce = true
-local cursonce = true
-local kernonce = true
+local state = privateattribute('state')
+local markbase = privateattribute('markbase')
+local markmark = privateattribute('markmark')
+local markdone = privateattribute('markdone')
+local cursbase = privateattribute('cursbase')
+local curscurs = privateattribute('curscurs')
+local cursdone = privateattribute('cursdone')
+local kernpair = privateattribute('kernpair')
-local fontdata = fonts.ids
+local injections = nodes.injections
+local setmark = injections.setmark
+local setcursive = injections.setcursive
+local setkern = injections.setkern
+local setpair = injections.setpair
-otf.features.process = { }
+local markonce = true
+local cursonce = true
+local kernonce = true
+
+local fonthashes = fonts.hashes
+local fontdata = fonthashes.identifiers
+
+local otffeatures = fonts.constructors.newfeatures("otf")
+local registerotffeature = otffeatures.register
+
+local onetimemessage = fonts.loggers.onetimemessage
-- we share some vars here, after all, we have no nested lookups and
-- less code
-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
-
--- we cheat a bit and assume that a font,attr combination are kind of ranged
-
-local context_setups = fonts.define.specify.context_setups
-local context_numbers = fonts.define.specify.context_numbers
-local context_merged = fonts.define.specify.context_merged
-
--- we cannot optimize with "start = first_character(head)" because then we don't
+local tfmdata = false
+local characters = false
+local descriptions = false
+local resources = false
+local marks = false
+local currentfont = false
+local lookuptable = false
+local anchorlookups = false
+local lookuptypes = false
+local handlers = { }
+local rlmode = 0
+local featurevalue = false
+
+-- we cannot optimize with "start = first_glyph(head)" because then we don't
-- know which rlmode we're in which messes up cursive handling later on
--
-- head is always a whatsit so we can safely assume that head is not changed
-local special_attributes = {
- init = 1,
- medi = 2,
- fina = 3,
- isol = 4
-}
-
-- we use this for special testing and documentation
local checkstep = (nodes and nodes.tracers and nodes.tracers.steppers.check) or function() end
@@ -8371,10 +8085,11 @@ local function logprocess(...)
if trace_steps then
registermessage(...)
end
- logs.report("otf direct",...)
+ report_direct(...)
end
+
local function logwarning(...)
- logs.report("otf direct",...)
+ report_direct(...)
end
local function gref(n)
@@ -8382,9 +8097,9 @@ local function gref(n)
local description = descriptions[n]
local name = description and description.name
if name then
- return format("U+%04X (%s)",n,name)
+ return format("U+%05X (%s)",n,name)
else
- return format("U+%04X",n)
+ return format("U+%05X",n)
end
elseif not n then
return "<error in tracing>"
@@ -8392,9 +8107,11 @@ local function gref(n)
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 "?"
+ if tonumber(ni) then -- later we will start at 2
+ local di = descriptions[ni]
+ num[i] = format("U+%05X",ni)
+ nam[i] = di and di.name or "?"
+ end
end
return format("%s (%s)",concat(num," "), concat(nam," "))
end
@@ -8432,89 +8149,77 @@ local function markstoligature(kind,lookupname,start,stop,char)
snext.prev = current
end
start.prev, stop.next = nil, nil
- current.char, current.subtype, current.components = char, 2, start
+ current.char, current.subtype, current.components = char, ligature_code, start
return keep
end
local function toligature(kind,lookupname,start,stop,char,markflag,discfound) -- brr head
- if start ~= stop then
---~ if discfound then
---~ local lignode = copy_node(start)
---~ lignode.font = start.font
---~ lignode.char = char
---~ lignode.subtype = 2
---~ start = node.do_ligature_n(start, stop, lignode)
---~ if start.id == disc then
---~ local prev = start.prev
---~ start = start.next
---~ end
- if discfound then
- -- print("start->stop",nodes.tosequence(start,stop))
- 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
- -- print("start->end",nodes.tosequence(start))
- else -- start is the ligature
- 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
+ if start == stop then
+ start.char = char
+ elseif discfound then
+ -- print("start->stop",nodes.tosequence(start,stop))
+ local lignode = copy_node(start)
+ lignode.font, lignode.char, lignode.subtype = start.font, char, ligature_code
+ 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
+ -- print("start->end",nodes.tosequence(start))
+ else -- start is the ligature
+ 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, ligature_code, start
+ local head = current
+ if deletemarks then
+ if trace_marks then
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
+ logwarning("%s: remove mark %s",pref(kind,lookupname),gref(start.char))
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
+ 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_code 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
- start = start.next
+ else
+ break
end
+ start = start.next
end
- return head
end
- else
- start.char = char
+ return head
end
return start
end
@@ -8530,7 +8235,7 @@ end
local function alternative_glyph(start,alternatives,kind,chainname,chainlookupname,lookupname) -- chainname and chainlookupname optional
local value, choice, n = featurevalue or tfmdata.shared.features[kind], nil, #alternatives -- global value, brrr
if value == "random" then
- local r = math.random(1,n)
+ local r = 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]
@@ -8557,6 +8262,33 @@ local function alternative_glyph(start,alternatives,kind,chainname,chainlookupna
return choice, value
end
+local function multiple_glyphs(start,multiple)
+ local nofmultiples = #multiple
+ if nofmultiples > 0 then
+ start.char = multiple[1]
+ if nofmultiples > 1 then
+ local sn = start.next
+ for k=2,nofmultiples do -- todo: use insert_node
+ local n = copy_node(start)
+ n.char = multiple[k]
+ n.next = sn
+ n.prev = start
+ if sn then
+ sn.prev = n
+ end
+ start.next = n
+ start = n
+ end
+ end
+ return start, true
+ else
+ if trace_multiples then
+ logprocess("no multiple for %s",gref(start.char))
+ end
+ return start, false
+ end
+end
+
function handlers.gsub_alternate(start,kind,lookupname,alternative,sequence)
local choice, index = alternative_glyph(start,alternative,kind,lookupname)
if trace_alternatives then
@@ -8570,41 +8302,21 @@ 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
+ return multiple_glyphs(start,multiple)
end
-function handlers.gsub_ligature(start,kind,lookupname,ligature,sequence) --or maybe pass lookup ref
+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
+ if id == glyph_code and s.subtype<256 and s.font == currentfont then
+ local lg = ligature[s.char]
+ if lg then
+ stop = s
+ ligature = lg
+ s = s.next
else
break
end
@@ -8612,54 +8324,64 @@ function handlers.gsub_ligature(start,kind,lookupname,ligature,sequence) --or ma
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))
+ if stop then
+ local lig = ligature.ligature
+ if lig then
+ if trace_ligatures then
+ local stopchar = stop.char
+ start = markstoligature(kind,lookupname,start,stop,lig)
+ 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,lig)
+ end
+ return start, true
else
- start = markstoligature(kind,lookupname,start,stop,ligature[2])
+ -- ok, goto next lookup
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 id == glyph_code 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
+ local lg = ligature[char]
+ if lg then
stop = s
ligature = lg
s = s.next
+ else
+ break
end
end
else
break
end
- elseif id == disc then
+ elseif id == disc_code 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))
+ if stop then
+ local lig = ligature.ligature
+ if lig then
+ if trace_ligatures then
+ local stopchar = stop.char
+ start = toligature(kind,lookupname,start,stop,lig,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,lig,skipmark,discfound)
+ end
+ return start, true
else
- start = toligature(kind,lookupname,start,stop,ligature[2],skipmark,discfound)
+ -- ok, goto next lookup
end
- return start, true
end
end
return start, false
@@ -8674,12 +8396,12 @@ function handlers.gpos_mark2base(start,kind,lookupname,markanchors,sequence)
local markchar = start.char
if marks[markchar] then
local base = start.prev -- [glyph] [start=mark]
- if base and base.id == glyph and base.subtype<256 and base.font == currentfont then
+ if base and base.id == glyph_code 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
+ if base and base.id == glyph_code and base.subtype<256 and base.font == currentfont then
basechar = base.char
if not marks[basechar] then
break
@@ -8704,7 +8426,7 @@ function handlers.gpos_mark2base(start,kind,lookupname,markanchors,sequence)
if al[anchor] then
local ma = markanchors[anchor]
if ma then
- local dx, dy, bound = set_mark(start,base,tfmdata.factor,rlmode,ba,ma)
+ local dx, dy, bound = setmark(start,base,tfmdata.parameters.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)
@@ -8719,7 +8441,7 @@ function handlers.gpos_mark2base(start,kind,lookupname,markanchors,sequence)
end
else -- if trace_bugs then
-- logwarning("%s: char %s is missing in font",pref(kind,lookupname),gref(basechar))
- fonts.register_message(currentfont,basechar,"no base anchors")
+ onetimemessage(currentfont,basechar,"no base anchors",report_fonts)
end
elseif trace_bugs then
logwarning("%s: prev node is no char",pref(kind,lookupname))
@@ -8736,13 +8458,13 @@ function handlers.gpos_mark2ligature(start,kind,lookupname,markanchors,sequence)
if marks[markchar] then
local base = start.prev -- [glyph] [optional marks] [start=mark]
local index = 1
- if base and base.id == glyph and base.subtype<256 and base.font == currentfont then
+ if base and base.id == glyph_code 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
+ if base and base.id == glyph_code and base.subtype<256 and base.font == currentfont then
basechar = base.char
if marks[basechar] then
index = index + 1
@@ -8772,7 +8494,7 @@ function handlers.gpos_mark2ligature(start,kind,lookupname,markanchors,sequence)
if ma then
ba = ba[index]
if ba then
- local dx, dy, bound = set_mark(start,base,tfmdata.factor,rlmode,ba,ma,index)
+ local dx, dy, bound = setmark(start,base,tfmdata.parameters.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)
@@ -8789,7 +8511,7 @@ function handlers.gpos_mark2ligature(start,kind,lookupname,markanchors,sequence)
end
else -- if trace_bugs then
-- logwarning("%s: char %s is missing in font",pref(kind,lookupname),gref(basechar))
- fonts.register_message(currentfont,basechar,"no base anchors")
+ onetimemessage(currentfont,basechar,"no base anchors",report_fonts)
end
elseif trace_bugs then
logwarning("%s: prev node is no char",pref(kind,lookupname))
@@ -8806,7 +8528,7 @@ function handlers.gpos_mark2mark(start,kind,lookupname,markanchors,sequence)
--~ local alreadydone = markonce and has_attribute(start,markmark)
--~ if not alreadydone then
local base = start.prev -- [glyph] [basemark] [start=mark]
- if base and base.id == glyph and base.subtype<256 and base.font == currentfont then -- subtype test can go
+ if base and base.id == glyph_code and base.subtype<256 and base.font == currentfont then -- subtype test can go
local basechar = base.char
local baseanchors = descriptions[basechar]
if baseanchors then
@@ -8819,7 +8541,7 @@ function handlers.gpos_mark2mark(start,kind,lookupname,markanchors,sequence)
if al[anchor] then
local ma = markanchors[anchor]
if ma then
- local dx, dy, bound = set_mark(start,base,tfmdata.factor,rlmode,ba,ma)
+ local dx, dy, bound = setmark(start,base,tfmdata.parameters.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)
@@ -8835,7 +8557,7 @@ function handlers.gpos_mark2mark(start,kind,lookupname,markanchors,sequence)
end
else -- if trace_bugs then
-- logwarning("%s: char %s is missing in font",pref(kind,lookupname),gref(basechar))
- fonts.register_message(currentfont,basechar,"no base anchors")
+ onetimemessage(currentfont,basechar,"no base anchors",report_fonts)
end
elseif trace_bugs then
logwarning("%s: prev node is no mark",pref(kind,lookupname))
@@ -8860,7 +8582,7 @@ function handlers.gpos_cursive(start,kind,lookupname,exitanchors,sequence) -- to
end
else
local nxt = start.next
- while not done and nxt and nxt.id == glyph and nxt.subtype<256 and nxt.font == currentfont do
+ while not done and nxt and nxt.id == glyph_code and nxt.subtype<256 and nxt.font == currentfont do
local nextchar = nxt.char
if marks[nextchar] then
-- should not happen (maybe warning)
@@ -8877,7 +8599,7 @@ function handlers.gpos_cursive(start,kind,lookupname,exitanchors,sequence) -- to
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])
+ local dx, dy, bound = setcursive(start,nxt,tfmdata.parameters.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
@@ -8890,7 +8612,7 @@ function handlers.gpos_cursive(start,kind,lookupname,exitanchors,sequence) -- to
end
else -- if trace_bugs then
-- logwarning("%s: char %s is missing in font",pref(kind,lookupname),gref(startchar))
- fonts.register_message(currentfont,startchar,"no entry anchors")
+ onetimemessage(currentfont,startchar,"no entry anchors",report_fonts)
end
break
end
@@ -8907,7 +8629,7 @@ 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])
+ local dx, dy, w, h = setpair(start,tfmdata.parameters.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
@@ -8922,10 +8644,11 @@ function handlers.gpos_pair(start,kind,lookupname,kerns,sequence)
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 factor = tfmdata.parameters.factor
+ local lookuptype = lookuptypes[lookupname]
+ while snext and snext.id == glyph_code and snext.subtype<256 and snext.font == currentfont do
local nextchar = snext.char
-local krn = kerns[nextchar]
+ local krn = kerns[nextchar]
if not krn and marks[nextchar] then
prev = snext
snext = snext.next
@@ -8934,38 +8657,38 @@ local krn = kerns[nextchar]
if not krn then
-- skip
elseif type(krn) == "table" then
- if krn[1] == "pair" then
- local a, b = krn[3], krn[4]
+ if lookuptype == "pair" then -- probably not needed
+ local a, b = krn[2], krn[3]
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])
+ local x, y, w, h = setpair(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])
+ local x, y, w, h = setpair(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
+ else -- wrong ... position has different entries
+ report_process("%s: check this out (old kern stuff)",pref(kind,lookupname))
+ -- local a, b = krn[2], krn[6]
+ -- if a and a ~= 0 then
+ -- local k = setkern(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)
+ local k = setkern(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
@@ -8990,59 +8713,51 @@ local function logprocess(...)
if trace_steps then
registermessage(...)
end
- logs.report("otf subchain",...)
-end
-local function logwarning(...)
- logs.report("otf subchain",...)
+ report_subchain(...)
end
--- ['coverage']={
--- ['after']={ "r" },
--- ['before']={ "q" },
--- ['current']={ "a", "b", "c" },
--- },
--- ['lookups']={ "ls_l_1", "ls_l_1", "ls_l_1" },
+local logwarning = report_subchain
-function chainmores.chainsub(start,stop,kind,chainname,currentcontext,cache,lookuplist,chainlookupname,n)
+function chainmores.chainsub(start,stop,kind,chainname,currentcontext,lookuphash,lookuplist,chainlookupname,n)
logprocess("%s: a direct call to chainsub cannot happen",cref(kind,chainname,chainlookupname))
return start, false
end
-- handled later:
--
--- function chainmores.gsub_single(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname,n)
--- return chainprocs.gsub_single(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname,n)
+-- function chainmores.gsub_single(start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,n)
+-- return chainprocs.gsub_single(start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,n)
-- end
-function chainmores.gsub_multiple(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname,n)
+function chainmores.gsub_multiple(start,stop,kind,chainname,currentcontext,lookuphash,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)
+
+function chainmores.gsub_alternate(start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,n)
logprocess("%s: gsub_alternate not yet supported",cref(kind,chainname,chainlookupname))
return start, false
end
-- handled later:
--
--- function chainmores.gsub_ligature(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname,n)
--- return chainprocs.gsub_ligature(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname,n)
+-- function chainmores.gsub_ligature(start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,n)
+-- return chainprocs.gsub_ligature(start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,n)
-- end
local function logprocess(...)
if trace_steps then
registermessage(...)
end
- logs.report("otf chain",...)
-end
-local function logwarning(...)
- logs.report("otf chain",...)
+ report_chain(...)
end
+local logwarning = report_chain
+
-- We could share functions but that would lead to extra function calls with many
-- arguments, redundant tests and confusing messages.
-function chainprocs.chainsub(start,stop,kind,chainname,currentcontext,cache,lookuplist,chainlookupname)
+function chainprocs.chainsub(start,stop,kind,chainname,currentcontext,lookuphash,lookuplist,chainlookupname)
logwarning("%s: a direct call to chainsub cannot happen",cref(kind,chainname,chainlookupname))
return start, false
end
@@ -9051,7 +8766,7 @@ end
-- in a bit weird way. There is no lookup and the replacement comes from the lookup
-- itself. It is meant mostly for dealing with Urdu.
-function chainprocs.reversesub(start,stop,kind,chainname,currentcontext,cache,replacements)
+function chainprocs.reversesub(start,stop,kind,chainname,currentcontext,lookuphash,replacements)
local char = start.char
local replacement = replacements[char]
if replacement then
@@ -9084,11 +8799,10 @@ as less as needed but that would also mke the code even more messy.</p>
local function delete_till_stop(start,stop,ignoremarks)
if start ~= stop then
-- todo keep marks
- local done = false
- while not done do
- done = start == stop
- delete_node(start,start.next)
- end
+ repeat
+ local next = start.next
+ delete_node(start,next)
+ until next == stop
end
end
@@ -9097,18 +8811,22 @@ end
match.</p>
--ldx]]--
-function chainprocs.gsub_single(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname,chainindex)
+function chainprocs.gsub_single(start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex)
-- todo: marks ?
- if not chainindex then
- delete_till_stop(start,stop) -- ,currentlookup.flags[1])
- end
+--~ if not chainindex then
+--~ delete_till_stop(start,stop) -- ,currentlookup.flags[1]
+--~ stop = start
+--~ end
local current = start
local subtables = currentlookup.subtables
+ if #subtables > 1 then
+ logwarning("todo: check if we need to loop over the replacements: %s",concat(subtables," "))
+ end
while current do
- if current.id == glyph then
+ if current.id == glyph_code then
local currentchar = current.char
- local lookupname = subtables[1]
- local replacement = cache.gsub_single[lookupname]
+ local lookupname = subtables[1] -- only 1
+ local replacement = lookuphash[lookupname]
if not replacement then
if trace_bugs then
logwarning("%s: no single hits",cref(kind,chainname,chainlookupname,lookupname,chainindex))
@@ -9143,12 +8861,12 @@ chainmores.gsub_single = chainprocs.gsub_single
the match.</p>
--ldx]]--
-function chainprocs.gsub_multiple(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname)
+function chainprocs.gsub_multiple(start,stop,kind,chainname,currentcontext,lookuphash,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]
+ local replacements = lookuphash[lookupname]
if not replacements then
if trace_bugs then
logwarning("%s: no multiple hits",cref(kind,chainname,chainlookupname,lookupname))
@@ -9163,21 +8881,7 @@ function chainprocs.gsub_multiple(start,stop,kind,chainname,currentcontext,cache
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) -- maybe delete the components and such
- 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
+ return multiple_glyphs(start,replacements)
end
end
return start, false
@@ -9187,16 +8891,16 @@ end
<p>Here we replace start by new glyph. First we delete the rest of the match.</p>
--ldx]]--
-function chainprocs.gsub_alternate(start,stop,kind,lookupname,currentcontext,cache,currentlookup)
+function chainprocs.gsub_alternate(start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname)
-- todo: marks ?
delete_till_stop(start,stop)
local current = start
local subtables = currentlookup.subtables
while current do
- if current.id == glyph then
+ if current.id == glyph_code then
local currentchar = current.char
local lookupname = subtables[1]
- local alternatives = cache.gsub_alternate[lookupname]
+ local alternatives = lookuphash[lookupname]
if not alternatives then
if trace_bugs then
logwarning("%s: no alternative hits",cref(kind,chainname,chainlookupname,lookupname))
@@ -9231,11 +8935,11 @@ this function (move code inline and handle the marks by a separate function). We
assume rather stupid ligatures (no complex disc nodes).</p>
--ldx]]--
-function chainprocs.gsub_ligature(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname,chainindex)
+function chainprocs.gsub_ligature(start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex)
local startchar = start.char
local subtables = currentlookup.subtables
local lookupname = subtables[1]
- local ligatures = cache.gsub_ligature[lookupname]
+ local ligatures = lookuphash[lookupname]
if not ligatures then
if trace_bugs then
logwarning("%s: no ligature hits",cref(kind,chainname,chainlookupname,lookupname,chainindex))
@@ -9250,7 +8954,7 @@ function chainprocs.gsub_ligature(start,stop,kind,chainname,currentcontext,cache
local s, discfound, last, nofreplacements = start.next, false, stop, 0
while s do
local id = s.id
- if id == disc then
+ if id == disc_code then
s = s.next
discfound = true
else
@@ -9258,21 +8962,21 @@ function chainprocs.gsub_ligature(start,stop,kind,chainname,currentcontext,cache
if marks[schar] then -- marks
s = s.next
else
- local lg = ligatures[1][schar]
- if not lg then
- break
- else
+ local lg = ligatures[schar]
+ if lg then
ligatures, last, nofreplacements = lg, s, nofreplacements + 1
if s == stop then
break
else
s = s.next
end
+ else
+ break
end
end
end
end
- local l2 = ligatures[2]
+ local l2 = ligatures.ligature
if l2 then
if chainindex then
stop = last
@@ -9284,7 +8988,7 @@ function chainprocs.gsub_ligature(start,stop,kind,chainname,currentcontext,cache
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,lookup,start,stop,l2,currentlookup.flags[1],discfound)
+ start = toligature(kind,lookupname,start,stop,l2,currentlookup.flags[1],discfound)
return start, true, nofreplacements
elseif trace_bugs then
if start == stop then
@@ -9300,23 +9004,23 @@ end
chainmores.gsub_ligature = chainprocs.gsub_ligature
-function chainprocs.gpos_mark2base(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname)
+function chainprocs.gpos_mark2base(start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname)
local markchar = start.char
if marks[markchar] then
local subtables = currentlookup.subtables
local lookupname = subtables[1]
- local markanchors = cache.gpos_mark2base[lookupname]
+ local markanchors = lookuphash[lookupname]
if markanchors then
markanchors = markanchors[markchar]
end
if markanchors then
local base = start.prev -- [glyph] [start=mark]
- if base and base.id == glyph and base.subtype<256 and base.font == currentfont then
+ if base and base.id == glyph_code 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
+ if base and base.id == glyph_code and base.subtype<256 and base.font == currentfont then
basechar = base.char
if not marks[basechar] then
break
@@ -9338,7 +9042,7 @@ function chainprocs.gpos_mark2base(start,stop,kind,chainname,currentcontext,cach
if al[anchor] then
local ma = markanchors[anchor]
if ma then
- local dx, dy, bound = set_mark(start,base,tfmdata.factor,rlmode,ba,ma)
+ local dx, dy, bound = setmark(start,base,tfmdata.parameters.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)
@@ -9364,25 +9068,25 @@ function chainprocs.gpos_mark2base(start,stop,kind,chainname,currentcontext,cach
return start, false
end
-function chainprocs.gpos_mark2ligature(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname)
+function chainprocs.gpos_mark2ligature(start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname)
local markchar = start.char
if marks[markchar] then
local subtables = currentlookup.subtables
local lookupname = subtables[1]
- local markanchors = cache.gpos_mark2ligature[lookupname]
+ local markanchors = lookuphash[lookupname]
if markanchors then
markanchors = markanchors[markchar]
end
if markanchors then
local base = start.prev -- [glyph] [optional marks] [start=mark]
local index = 1
- if base and base.id == glyph and base.subtype<256 and base.font == currentfont then
+ if base and base.id == glyph_code 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
+ if base and base.id == glyph_code and base.subtype<256 and base.font == currentfont then
basechar = base.char
if marks[basechar] then
index = index + 1
@@ -9411,7 +9115,7 @@ function chainprocs.gpos_mark2ligature(start,stop,kind,chainname,currentcontext,
if ma then
ba = ba[index]
if ba then
- local dx, dy, bound = set_mark(start,base,tfmdata.factor,rlmode,ba,ma,index)
+ local dx, dy, bound = setmark(start,base,tfmdata.parameters.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)
@@ -9438,7 +9142,7 @@ function chainprocs.gpos_mark2ligature(start,stop,kind,chainname,currentcontext,
return start, false
end
-function chainprocs.gpos_mark2mark(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname)
+function chainprocs.gpos_mark2mark(start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname)
local markchar = start.char
if marks[markchar] then
--~ local alreadydone = markonce and has_attribute(start,markmark)
@@ -9446,13 +9150,13 @@ function chainprocs.gpos_mark2mark(start,stop,kind,chainname,currentcontext,cach
-- local markanchors = descriptions[markchar].anchors markanchors = markanchors and markanchors.mark
local subtables = currentlookup.subtables
local lookupname = subtables[1]
- local markanchors = cache.gpos_mark2mark[lookupname]
+ local markanchors = lookuphash[lookupname]
if markanchors then
markanchors = markanchors[markchar]
end
if markanchors then
local base = start.prev -- [glyph] [basemark] [start=mark]
- if base and base.id == glyph and base.subtype<256 and base.font == currentfont then -- subtype test can go
+ if base and base.id == glyph_code and base.subtype<256 and base.font == currentfont then -- subtype test can go
local basechar = base.char
local baseanchors = descriptions[basechar].anchors
if baseanchors then
@@ -9463,7 +9167,7 @@ function chainprocs.gpos_mark2mark(start,stop,kind,chainname,currentcontext,cach
if al[anchor] then
local ma = markanchors[anchor]
if ma then
- local dx, dy, bound = set_mark(start,base,tfmdata.factor,rlmode,ba,ma)
+ local dx, dy, bound = setmark(start,base,tfmdata.parameters.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)
@@ -9494,13 +9198,13 @@ end
-- ! ! ! untested ! ! !
-function chainprocs.gpos_cursive(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname)
+function chainprocs.gpos_cursive(start,stop,kind,chainname,currentcontext,lookuphash,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]
+ local exitanchors = lookuphash[lookupname]
if exitanchors then
exitanchors = exitanchors[startchar]
end
@@ -9512,7 +9216,7 @@ function chainprocs.gpos_cursive(start,stop,kind,chainname,currentcontext,cache,
end
else
local nxt = start.next
- while not done and nxt and nxt.id == glyph and nxt.subtype<256 and nxt.font == currentfont do
+ while not done and nxt and nxt.id == glyph_code and nxt.subtype<256 and nxt.font == currentfont do
local nextchar = nxt.char
if marks[nextchar] then
-- should not happen (maybe warning)
@@ -9529,7 +9233,7 @@ function chainprocs.gpos_cursive(start,stop,kind,chainname,currentcontext,cache,
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])
+ local dx, dy, bound = setcursive(start,nxt,tfmdata.parameters.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
@@ -9542,7 +9246,7 @@ function chainprocs.gpos_cursive(start,stop,kind,chainname,currentcontext,cache,
end
else -- if trace_bugs then
-- logwarning("%s: char %s is missing in font",pref(kind,lookupname),gref(startchar))
- fonts.register_message(currentfont,startchar,"no entry anchors")
+ onetimemessage(currentfont,startchar,"no entry anchors",report_fonts)
end
break
end
@@ -9559,16 +9263,16 @@ function chainprocs.gpos_cursive(start,stop,kind,chainname,currentcontext,cache,
return start, false
end
-function chainprocs.gpos_single(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname,chainindex,sequence)
- -- untested
+function chainprocs.gpos_single(start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex,sequence)
+ -- untested .. needs checking for the new model
local startchar = start.char
local subtables = currentlookup.subtables
local lookupname = subtables[1]
- local kerns = cache.gpos_single[lookupname]
+ local kerns = lookuphash[lookupname]
if kerns then
- kerns = kerns[startchar]
+ kerns = kerns[startchar] -- needed ?
if kerns then
- local dx, dy, w, h = set_pair(start,tfmdata.factor,rlmode,sequence.flags[4],kerns,characters[startchar])
+ local dx, dy, w, h = setpair(start,tfmdata.parameters.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
@@ -9579,20 +9283,21 @@ end
-- when machines become faster i will make a shared function
-function chainprocs.gpos_pair(start,stop,kind,chainname,currentcontext,cache,currentlookup,chainlookupname,chainindex,sequence)
+function chainprocs.gpos_pair(start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex,sequence)
-- logwarning("%s: gpos_pair not yet supported",cref(kind,chainname,chainlookupname))
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]
+ local kerns = lookuphash[lookupname]
if kerns then
kerns = kerns[startchar]
if kerns then
+ local lookuptype = lookuptypes[lookupname]
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 factor = tfmdata.parameters.factor
+ while snext and snext.id == glyph_code and snext.subtype<256 and snext.font == currentfont do
local nextchar = snext.char
local krn = kerns[nextchar]
if not krn and marks[nextchar] then
@@ -9602,27 +9307,27 @@ function chainprocs.gpos_pair(start,stop,kind,chainname,currentcontext,cache,cur
if not krn then
-- skip
elseif type(krn) == "table" then
- if krn[1] == "pair" then
- local a, b = krn[3], krn[4]
+ if lookuptype == "pair" then
+ local a, b = krn[2], krn[3]
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])
+ local x, y, w, h = setpair(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])
+ local x, y, w, h = setpair(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]
+ report_process("%s: check this out (old kern stuff)",cref(kind,chainname,chainlookupname))
+ local a, b = krn[2], krn[6]
if a and a ~= 0 then
- local k = set_kern(snext,factor,rlmode,a)
+ local k = setkern(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
@@ -9633,7 +9338,7 @@ function chainprocs.gpos_pair(start,stop,kind,chainname,currentcontext,cache,cur
end
done = true
elseif krn ~= 0 then
- local k = set_kern(snext,factor,rlmode,krn)
+ local k = setkern(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
@@ -9665,7 +9370,7 @@ local function show_skip(kind,chainname,char,ck,class)
end
end
-local function normal_handle_contextchain(start,kind,chainname,contexts,sequence,cache)
+local function normal_handle_contextchain(start,kind,chainname,contexts,sequence,lookuphash)
-- local rule, lookuptype, sequence, f, l, lookups = ck[1], ck[2] ,ck[3], ck[4], ck[5], ck[6]
local flags, done = sequence.flags, false
local skipmark, skipligature, skipbase = flags[1], flags[2], flags[3]
@@ -9680,11 +9385,12 @@ local function normal_handle_contextchain(start,kind,chainname,contexts,sequence
-- f..l = mid string
if s == 1 then
-- never happens
- match = current.id == glyph and current.subtype<256 and current.font == currentfont and seq[1][current.char]
+ match = current.id == glyph_code and current.subtype<256 and current.font == currentfont and seq[1][current.char]
else
-- todo: better space check (maybe check for glue)
local f, l = ck[4], ck[5]
- if f == l then
+ -- current match
+ if f == 1 and f == l then
-- already a hit
match = true
else
@@ -9694,12 +9400,12 @@ local function normal_handle_contextchain(start,kind,chainname,contexts,sequence
-- we cannot optimize for n=2 because there can be disc nodes
-- if not someskip and n == l then
-- -- n=2 and no skips then faster loop
- -- match = last and last.id == glyph and last.subtype<256 and last.font == currentfont and seq[n][last.char]
+ -- match = last and last.id == glyph_code and last.subtype<256 and last.font == currentfont and seq[n][last.char]
-- else
while n <= l do
if last then
local id = last.id
- if id == glyph then
+ if id == glyph_code then
if last.subtype<256 and last.font == currentfont then
local char = last.char
local ccd = descriptions[char]
@@ -9725,7 +9431,7 @@ local function normal_handle_contextchain(start,kind,chainname,contexts,sequence
else
match = false break
end
- elseif id == disc then -- what to do with kerns?
+ elseif id == disc_code then -- what to do with kerns?
last = last.next
else
match = false break
@@ -9736,15 +9442,15 @@ local function normal_handle_contextchain(start,kind,chainname,contexts,sequence
end
-- end
end
+ -- before
if match and f > 1 then
- -- before
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 id == glyph_code then
if prev.subtype<256 and prev.font == currentfont then -- normal char
local char = prev.char
local ccd = descriptions[char]
@@ -9766,7 +9472,7 @@ local function normal_handle_contextchain(start,kind,chainname,contexts,sequence
else
match = false break
end
- elseif id == disc then
+ elseif id == disc_code then
-- skip 'm
elseif seq[n][32] then
n = n -1
@@ -9774,7 +9480,7 @@ local function normal_handle_contextchain(start,kind,chainname,contexts,sequence
match = false break
end
prev = prev.prev
- elseif seq[n][32] then
+ elseif seq[n][32] then -- somehat special, as zapfino can have many preceding spaces
n = n -1
else
match = false break
@@ -9790,16 +9496,16 @@ local function normal_handle_contextchain(start,kind,chainname,contexts,sequence
end
end
end
+ -- after
if match and s > l then
- -- after
- local current = last.next
+ local current = last and last.next
if current then
-- removed optimization for s-l == 1, we have to deal with marks anyway
local n = l + 1
while n <= s do
if current then
local id = current.id
- if id == glyph then
+ if id == glyph_code then
if current.subtype<256 and current.font == currentfont then -- normal char
local char = current.char
local ccd = descriptions[char]
@@ -9821,7 +9527,7 @@ local function normal_handle_contextchain(start,kind,chainname,contexts,sequence
else
match = false break
end
- elseif id == disc then
+ elseif id == disc_code then
-- skip 'm
elseif seq[n][32] then -- brrr
n = n + 1
@@ -9852,9 +9558,11 @@ local function normal_handle_contextchain(start,kind,chainname,contexts,sequence
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])
+ 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)
+ 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]
@@ -9866,59 +9574,35 @@ local function normal_handle_contextchain(start,kind,chainname,contexts,sequence
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)
+ start, done = cp(start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence)
else
logprocess("%s: %s is not yet supported",cref(kind,chainname,chainlookupname),chainlookup.type)
end
else
- -- actually this needs a more complex treatment for which we will use chainmores
---~ local i = 1
---~ repeat
---~ 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)
---~ -- messy since last can be changed !
---~ if ok then
---~ done = true
---~ start = start.next
---~ if n then
---~ -- skip next one(s) if ligature
---~ i = i + n - 1
---~ end
---~ end
---~ else
---~ logprocess("%s: multiple subchains for %s are not yet supported",cref(kind,chainname,chainlookupname),chainlookup.type)
---~ end
---~ i = i + 1
---~ until i > nofchainlookups
-
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
+ 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)
+ start, ok, n = cp(start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,i,sequence)
-- messy since last can be changed !
if ok then
done = true
@@ -9933,12 +9617,11 @@ end
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) -- sequence
+ start, done = chainprocs.reversesub(start,last,kind,chainname,ck,lookuphash,replacements) -- sequence
else
done = true -- can be meant to be skipped
if trace_contexts then
@@ -9993,18 +9676,17 @@ local function logprocess(...)
if trace_steps then
registermessage(...)
end
- logs.report("otf process",...)
-end
-local function logwarning(...)
- logs.report("otf process",...)
+ report_process(...)
end
+local logwarning = report_process
+
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)
+ logwarning("missing cache for lookup %s of type %s in font %s (%s)",lookup,typ,currentfont,tfmdata.properties.fullname)
end
end
@@ -10012,135 +9694,144 @@ local resolved = { } -- we only resolve a font,script,language pair once
-- todo: pass all these 'locals' in a table
-function fonts.methods.node.otf.features(head,font,attr)
+local lookuphashes = { }
+
+setmetatableindex(lookuphashes, function(t,font)
+ local lookuphash = fontdata[font].resources.lookuphash
+ if not lookuphash or not next(lookuphash) then
+ lookuphash = false
+ end
+ t[font] = lookuphash
+ return lookuphash
+end)
+
+-- fonts.hashes.lookups = lookuphashes
+
+local special_attributes = {
+ init = 1,
+ medi = 2,
+ fina = 3,
+ isol = 4
+}
+
+local function initialize(sequence,script,language,enabled)
+ local features = sequence.features
+ if features then
+ for kind, scripts in next, features do
+ local valid = enabled[kind]
+ if valid then
+ local languages = scripts[script] or scripts[wildcard]
+ if languages and (languages[language] or languages[wildcard]) then
+ return { valid, special_attributes[kind] or false, sequence.chain or 0, kind }
+ end
+ end
+ end
+ end
+ return false
+end
+
+function otf.dataset(ftfmdata,sequences,font) -- generic variant, overloaded in context
+ local shared = tfmdata.shared
+ local properties = tfmdata.properties
+ local language = properties.language or "dflt"
+ local script = properties.script or "dflt"
+ local enabled = shared.features
+ 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
+ setmetatableindex(rl, function(t,k)
+ local v = enabled and initialize(sequences[k],script,language,enabled)
+ t[k] = v
+ return v
+ end)
+ end
+ return rl
+end
+
+local function featuresprocessor(head,font,attr)
+
+ local lookuphash = lookuphashes[font] -- we can also check sequences here
+
+ if not lookuphash then
+ return head, false
+ end
+
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 -- can be made local to closure
- 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]] -- could be a direct list
- dyn = context_merged[attr] or 0
- language, script = features.language or "dflt", features.script or "dflt"
- a_enabled = features -- shared.features -- can be made local to the resolver
- if dyn == 2 or dyn == -2 then
- -- font based
- s_enabled = shared.features
- end
- else
- language, script = tfmdata.language or "dflt", tfmdata.script or "dflt"
- s_enabled = shared.features -- can be made local to the resolver
- dyn = 0
- end
- -- we can save some runtime by caching feature tests
- 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 -- attr can be false
- -- sequences always > 1 so no need for optimization
+
+ tfmdata = fontdata[font]
+ descriptions = tfmdata.descriptions
+ characters = tfmdata.characters
+ resources = tfmdata.resources
+
+ marks = resources.marks
+ anchorlookups = resources.lookup_to_anchor
+ lookuptable = resources.lookups
+ lookuptypes = resources.lookuptypes
+
+ currentfont = font
+ rlmode = 0
+
+ local sequences = resources.sequences
+ local done = false
+ local datasets = otf.dataset(tfmdata,sequences,font,attr)
+
for s=1,#sequences do
- local pardir, txtdir = 0, { }
- local success = false
+ local pardir, txtdir, success = 0, { }, false -- we could reuse txtdir and use a top pointer
local sequence = sequences[s]
- local r = ra[s] -- cache
- if r == nil then
- --
- -- this bit will move to font-ctx and become a function
- ---
- local chain = sequence.chain or 0
- local features = sequence.features
- if not features then
- -- indirect lookup, part of chain (todo: make this a separate table)
- r = false -- { false, false, chain }
- else
- local valid, attribute, kind, what = false, false
- for k,v in next, features do
- -- we can quit earlier but for the moment we want the tracing
- 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
- -- not l[language] or l[default] or l[wildcard] because we want tracing
- -- only first attribute match check, so we assume simple fina's
- -- default can become a font feature itself
- if l[language] then
- valid, what = s_e or a_e, language
- -- elseif l[default] then
- -- valid, what = true, default
- 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 -- { valid, attribute, chain, "generic" } -- false anyway, could be flag instead of table
- end
- end
- ra[s] = r
- end
- featurevalue = r and r[1] -- todo: pass to function instead of using a global
+ local dataset = datasets[s] -- cache
+ featurevalue = dataset and dataset[1] -- todo: pass to function instead of using a global
if featurevalue then
- local attribute, chain, typ, subtables = r[2], r[3], sequence.type, sequence.subtables
+ local attribute, chain, typ, subtables = dataset[2], dataset[3], sequence.type, sequence.subtables
+--~ print(typ)
+--~ table.print(table.keys(sequence))
if chain < 0 then
-- this is a limited case, no special treatments like 'init' etc
local handler = handlers[typ]
- local thecache = featuredata[typ] or { }
-- we need to get rid of this slide !
local start = find_node_tail(head) -- slow (we can store tail because there's always a skip at the end): todo
while start do
local id = start.id
- if id == glyph then
- if start.subtype<256 and start.font == font and (not attr or has_attribute(start,0,attr)) then
---~ if start.subtype<256 and start.font == font and has_attribute(start,0,attr) 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
+ if id == glyph_code 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 = lookuphash[lookupname]
+ if lookupcache then
+ local lookupmatch = lookupcache[start.char]
+ if lookupmatch then
+ start, success = handler(start,dataset[4],lookupname,lookupmatch,sequence,lookuphash,i)
+ if success then
+ break
+ end
end
+ else
+ report_missing_cache(typ,lookupname)
end
- else
- report_missing_cache(typ,lookupname)
end
+ if start then start = start.prev end
+ else
+ start = start.prev
end
- if start then start = start.prev end
else
start = start.prev
end
@@ -10151,34 +9842,42 @@ function fonts.methods.node.otf.features(head,font,attr)
else
local handler = handlers[typ]
local ns = #subtables
- local thecache = featuredata[typ] or { }
local start = head -- local ?
rlmode = 0 -- to be checked ?
if ns == 1 then
local lookupname = subtables[1]
- local lookupcache = thecache[lookupname]
+ local lookupcache = lookuphash[lookupname]
if not lookupcache then
report_missing_cache(typ,lookupname)
else
while start do
local id = start.id
- if id == glyph then
---~ if start.font == font and start.subtype<256 and has_attribute(start,0,attr) and (not attribute or has_attribute(start,state,attribute)) then
- if start.font == font and start.subtype<256 and (not attr or has_attribute(start,0,attr)) and (not attribute or has_attribute(start,state,attribute)) then
- local lookupmatch = lookupcache[start.char]
- if lookupmatch then
- -- sequence kan weg
- local ok
- start, ok = handler(start,r[4],lookupname,lookupmatch,sequence,featuredata,1)
- if ok then
- success = true
+ if id == glyph_code 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
+ -- sequence kan weg
+ local ok
+ start, ok = handler(start,dataset[4],lookupname,lookupmatch,sequence,lookuphash,1)
+ if ok then
+ success = true
+ end
end
+ if start then start = start.next end
+ else
+ start = start.next
end
- if start then start = start.next end
else
start = start.next
end
- -- elseif id == glue then
+ -- elseif id == glue_code then
-- if p[5] then -- chain
-- local pc = pp[32]
-- if pc then
@@ -10193,9 +9892,9 @@ function fonts.methods.node.otf.features(head,font,attr)
-- else
-- start = start.next
-- end
- elseif id == whatsit then
+ elseif id == whatsit_code then
local subtype = start.subtype
- if subtype == 7 then
+ if subtype == dir_code then
local dir = start.dir
if dir == "+TRT" or dir == "+TLT" then
insert(txtdir,dir)
@@ -10211,9 +9910,9 @@ function fonts.methods.node.otf.features(head,font,attr)
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)
+ report_process("directions after textdir %s: pardir=%s, txtdir=%s:%s, rlmode=%s",dir,pardir,#txtdir,txtdir[#txtdir] or "unset",rlmode)
end
- elseif subtype == 6 then
+ elseif subtype == localpar_code then
local dir = start.dir
if dir == "TRT" then
pardir = -1
@@ -10225,7 +9924,7 @@ function fonts.methods.node.otf.features(head,font,attr)
rlmode = pardir
--~ txtdir = { }
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)
+ report_process("directions after pardir %s: pardir=%s, txtdir=%s:%s, rlmode=%s",dir,pardir,#txtdir,txtdir[#txtdir] or "unset",rlmode)
end
end
start = start.next
@@ -10237,32 +9936,41 @@ function fonts.methods.node.otf.features(head,font,attr)
else
while start do
local id = start.id
- if id == glyph then
- if start.subtype<256 and start.font == font and (not attr or has_attribute(start,0,attr)) and (not attribute or has_attribute(start,state,attribute)) then
---~ if start.subtype<256 and start.font == font and has_attribute(start,0,attr) and (not attribute or has_attribute(start,state,attribute)) 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
- -- we could move all code inline but that makes things even more unreadable
- local ok
- start, ok = handler(start,r[4],lookupname,lookupmatch,sequence,featuredata,i)
- if ok then
- success = true
- break
+ if id == glyph_code 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 = lookuphash[lookupname]
+ if lookupcache then
+ local lookupmatch = lookupcache[start.char]
+ if lookupmatch then
+ -- we could move all code inline but that makes things even more unreadable
+ local ok
+ start, ok = handler(start,dataset[4],lookupname,lookupmatch,sequence,lookuphash,i)
+ if ok then
+ success = true
+ break
+ end
end
+ else
+ report_missing_cache(typ,lookupname)
end
- else
- report_missing_cache(typ,lookupname)
end
+ if start then start = start.next end
+ else
+ start = start.next
end
- if start then start = start.next end
else
start = start.next
end
- -- elseif id == glue then
+ -- elseif id == glue_code then
-- if p[5] then -- chain
-- local pc = pp[32]
-- if pc then
@@ -10277,10 +9985,9 @@ function fonts.methods.node.otf.features(head,font,attr)
-- else
-- start = start.next
-- end
- elseif id == whatsit then
- local subtype = start.subtype
+ elseif id == whatsit_code then
local subtype = start.subtype
- if subtype == 7 then
+ if subtype == dir_code then
local dir = start.dir
if dir == "+TRT" or dir == "+TLT" then
insert(txtdir,dir)
@@ -10296,9 +10003,9 @@ function fonts.methods.node.otf.features(head,font,attr)
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)
+ report_process("directions after textdir %s: pardir=%s, txtdir=%s:%s, rlmode=%s",dir,pardir,#txtdir,txtdir[#txtdir] or "unset",rlmode)
end
- elseif subtype == 6 then
+ elseif subtype == localpar_code then
local dir = start.dir
if dir == "TRT" then
pardir = -1
@@ -10310,7 +10017,7 @@ function fonts.methods.node.otf.features(head,font,attr)
rlmode = pardir
--~ txtdir = { }
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)
+ report_process("directions after pardir %s: pardir=%s, txtdir=%s:%s, rlmode=%s",dir,pardir,#txtdir,txtdir[#txtdir] or "unset",rlmode)
end
end
start = start.next
@@ -10331,463 +10038,1049 @@ function fonts.methods.node.otf.features(head,font,attr)
return head, done
end
-otf.features.prepare = { }
+local function generic(lookupdata,lookupname,unicode,lookuphash)
+ local target = lookuphash[lookupname]
+ if target then
+ target[unicode] = lookupdata
+ else
+ lookuphash[lookupname] = { [unicode] = lookupdata }
+ end
+end
--- we used to share code in the following functions but that costs a lot of
--- memory due to extensive calls to functions (easily hundreds of thousands per
--- document)
+local action = {
-local function split(replacement,original,cache,unicodes)
- -- we can cache this too, but not the same (although unicode is a unique enough hash)
- local o, t, n = { }, { }, 0
- for s in gmatch(original,"[^ ]+") do
- local us = unicodes[s]
- if type(us) == "number" then -- tonumber(us)
- o[#o+1] = us
- else
- o[#o+1] = us[1]
+ substitution = generic,
+ multiple = generic,
+ alternate = generic,
+ position = generic,
+
+ ligature = function(lookupdata,lookupname,unicode,lookuphash)
+ local target = lookuphash[lookupname]
+ if not target then
+ target = { }
+ lookuphash[lookupname] = target
end
- end
- for s in gmatch(replacement,"[^ ]+") do
- n = n + 1
- local us = unicodes[s]
- if type(us) == "number" then -- tonumber(us)
- t[o[n]] = us
- else
- t[o[n]] = us[1]
+ for i=1,#lookupdata do
+ local li = lookupdata[i]
+ local tu = target[li]
+ if not tu then
+ tu = { }
+ target[li] = tu
+ end
+ target = tu
end
- end
- return t
-end
+ target.ligature = unicode
+ end,
-local function uncover(covers,result,cache,unicodes)
- -- lpeg hardly faster (.005 sec on mk)
- 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
+ pair = function(lookupdata,lookupname,unicode,lookuphash)
+ local target = lookuphash[lookupname]
+ if not target then
+ target = { }
+ lookuphash[lookupname] = target
+ end
+ local others = target[unicode]
+ local paired = lookupdata[1]
+ if others then
+ others[paired] = lookupdata
else
- result[#result+1] = cc
+ others = { [paired] = lookupdata }
+ target[unicode] = others
end
- 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 -- names to unicodes
- local indices = tfmdata.indices
- local descriptions = tfmdata.descriptions
- --
- -- we can change the otf table after loading but then we need to adapt base mode
- -- as well (no big deal)
- --
- local action = {
- substitution = function(p,lookup,k,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
- --~ if trace_lookups then
- --~ logs.report("define otf","lookup %s: substitution %s => %s",lookup,old,new)
- --~ end
- end,
- multiple = function (p,lookup,k,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
- --~ if trace_lookups then
- --~ logs.report("define otf","lookup %s: multiple %s => %s",lookup,old,concat(new," "))
- --~ end
- end,
- alternate = function(p,lookup,k,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
- --~ if trace_lookups then
- --~ logs.report("define otf","lookup %s: alternate %s => %s",lookup,old,concat(new,"|"))
- --~ end
- end,
- ligature = function (p,lookup,k,glyph,unicode)
- --~ if trace_lookups then
- --~ logs.report("define otf","lookup %s: ligature %s => %s",lookup,p[2],glyph.name)
- --~ end
- 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,k,glyph,unicode)
- -- not used
- local s = position[lookup]
- if not s then s = { } position[lookup] = s end
- s[unicode] = p[2] -- direct pointer to kern spec
- end,
- pair = function(p,lookup,k,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
- -- todo: fast check for space
- 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 -- direct pointer to main table
- else
- for i=1,#upc do
- others[upc[i]] = p -- direct pointer to main table
- end
- end
- end
- elseif type(upc) == "number" then
- others[upc] = p -- direct pointer to main table
- else
- for i=1,#upc do
- others[upc[i]] = p -- direct pointer to main table
- end
- end
- --~ if trace_lookups then
- --~ logs.report("define otf","lookup %s: pair for U+%04X",lookup,unicode)
- --~ end
- end,
- }
- --
- for unicode, glyph in next, descriptions do
- local lookups = glyph.slookups
+
+ local rawdata = tfmdata.shared.rawdata
+ local resources = rawdata.resources
+ local lookuphash = resources.lookuphash
+ local anchor_to_lookup = resources.anchor_to_lookup
+ local lookup_to_anchor = resources.lookup_to_anchor
+ local lookuptypes = resources.lookuptypes
+ local characters = tfmdata.characters
+ local descriptions = tfmdata.descriptions
+
+ -- we cannot free the entries in the descriptions as sometimes we access
+ -- then directly (for instance anchors) ... selectively freeing does save
+ -- much memory as it's only a reference to a table and the slot in the
+ -- description hash is not freed anyway
+
+ for unicode, character in next, characters do -- we cannot loop over descriptions !
+
+ local description = descriptions[unicode]
+
+ local lookups = description.slookups
if lookups then
- for lookup, p in next, lookups do
- action[p[1]](p,lookup,k,glyph,unicode)
+ for lookupname, lookupdata in next, lookups do
+ action[lookuptypes[lookupname]](lookupdata,lookupname,unicode,lookuphash)
end
end
- local lookups = glyph.mlookups
+
+ local lookups = description.mlookups
if lookups then
- for lookup, whatever in next, lookups do
- for i=1,#whatever do -- normaly one
- local p = whatever[i]
- action[p[1]](p,lookup,k,glyph,unicode)
+ for lookupname, lookuplist in next, lookups do
+ local lookuptype = lookuptypes[lookupname]
+ for l=1,#lookuplist do
+ local lookupdata = lookuplist[l]
+ action[lookuptype](lookupdata,lookupname,unicode,lookuphash)
end
end
end
- local list = glyph.mykerns
+
+ local list = description.kerns
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 -- ref to glyph, saves lookup
- --~ if trace_lookups then
- --~ logs.report("define otf","lookup %s: kern for U+%04X",lookup,unicode)
- --~ end
+ for lookup, krn in next, list do -- ref to glyph, saves lookup
+ local target = lookuphash[lookup]
+ if target then
+ target[unicode] = krn
+ else
+ lookuphash[lookup] = { [unicode] = krn }
+ end
end
end
- local oanchor = glyph.anchors
- if oanchor then
- for typ, anchors in next, oanchor do -- types
- 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 -- ref to glyph, saves lookup
- --~ if trace_lookups then
- --~ logs.report("define otf","lookup %s: mark anchor %s for U+%04X",lookup,name,unicode)
- --~ end
- end
- end
- end
- elseif typ == "cexit" then -- or entry?
+
+ local list = description.anchors
+ if list then
+ for typ, anchors in next, list do -- types
+ if typ == "mark" or typ == "cexit" then -- or entry?
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 -- ref to glyph, saves lookup
- --~ if trace_lookups then
- --~ logs.report("define otf","lookup %s: exit anchor %s for U+%04X",lookup,name,unicode)
- --~ end
+ local target = lookuphash[lookup]
+ if target then
+ target[unicode] = anchors
+ else
+ lookuphash[lookup] = { [unicode] = anchors }
+ end
end
end
end
end
end
end
+
end
+
end
--- local cache = { }
-luatex = luatex or {} -- this has to change ... we need a better one
+local function split(replacement,original)
+ local result = { }
+ for i=1,#replacement do
+ result[original[i]] = replacement[i]
+ end
+ return result
+end
+
+-- not shared as we hook into lookups now
+
+--~ local function uncover_1(covers,result) -- multiple covers
+--~ local nofresults = #result
+--~ for n=1,#covers do
+--~ nofresults = nofresults + 1
+--~ local u = { }
+--~ local c = covers[n]
+--~ for i=1,#c do
+--~ u[c[i]] = true
+--~ end
+--~ result[nofresults] = u
+--~ end
+--~ end
+
+--~ local function uncover_2(covers,result) -- single covers (turned into multiple with n=1)
+--~ local nofresults = #result
+--~ for n=1,#covers do
+--~ nofresults = nofresults + 1
+--~ result[nofresults] = { [covers[n]] = true }
+--~ end
+--~ end
+
+--~ local function uncover_1(covers,result) -- multiple covers
+--~ local nofresults = #result
+--~ for n=1,#covers do
+--~ nofresults = nofresults + 1
+--~ result[nofresults] = covers[n]
+--~ end
+--~ end
+
+--~ local function prepare_contextchains(tfmdata)
+--~ local rawdata = tfmdata.shared.rawdata
+--~ local resources = rawdata.resources
+--~ local lookuphash = resources.lookuphash
+--~ local lookups = rawdata.lookups
+--~ if lookups then
+--~ for lookupname, lookupdata in next, rawdata.lookups do
+--~ local lookuptype = lookupdata.type
+--~ if not lookuptype then
+--~ report_prepare("missing lookuptype for %s",lookupname)
+--~ else -- => lookuphash[lookupname][unicode]
+--~ local rules = lookupdata.rules
+--~ if rules then
+--~ local fmt = lookupdata.format
+--~ -- if fmt == "coverage" then
+--~ if fmt == "coverage" or fmt == "glyphs" then
+--~ if lookuptype ~= "chainsub" and lookuptype ~= "chainpos" then
+--~ -- todo: dejavu-serif has one (but i need to see what use it has)
+--~ report_prepare("unsupported coverage %s for %s",lookuptype,lookupname)
+--~ else
+--~ local contexts = lookuphash[lookupname]
+--~ if not contexts then
+--~ contexts = { }
+--~ lookuphash[lookupname] = contexts
+--~ end
+--~ local t, nt = { }, 0
+--~ for nofrules=1,#rules do -- does #rules>1 happen often?
+--~ local rule = rules[nofrules]
+--~ local current = rule.current
+--~ local before = rule.before
+--~ local after = rule.after
+--~ local sequence = { }
+--~ if before then
+--~ uncover_1(before,sequence)
+--~ end
+--~ local start = #sequence + 1
+--~ uncover_1(current,sequence)
+--~ local stop = #sequence
+--~ if after then
+--~ uncover_1(after,sequence)
+--~ end
+--~ if sequence[1] then
+--~ nt = nt + 1
+--~ t[nt] = { 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
+--~ elseif fmt == "reversecoverage" then -- we could combine both branches (only dufference is replacements)
+--~ if lookuptype ~= "reversesub" then
+--~ report_prepare("unsupported reverse coverage %s for %s",lookuptype,lookupname)
+--~ else
+--~ local contexts = lookuphash[lookupname]
+--~ if not contexts then
+--~ contexts = { }
+--~ lookuphash[lookupname] = contexts
+--~ end
+--~ local t, nt = { }, 0
+--~ for nofrules=1,#rules do
+--~ local rule = rules[nofrules]
+--~ local current = rule.current
+--~ local before = rule.before
+--~ local after = rule.after
+--~ local replacements = rule.replacements
+--~ local sequence = { }
+--~ if before then
+--~ uncover_1(before,sequence)
+--~ end
+--~ local start = #sequence + 1
+--~ uncover_1(current,sequence)
+--~ local stop = #sequence
+--~ if after then
+--~ uncover_1(after,sequence)
+--~ end
+--~ if sequence[1] then
+--~ nt = nt + 1
+--~ t[nt] = { 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
+--~ -- elseif fmt == "glyphs" then --maybe just make then before = { fore } and share with coverage
+--~ -- if lookuptype ~= "chainsub" and lookuptype ~= "chainpos" then
+--~ -- report_prepare("unsupported coverage %s for %s",lookuptype,lookupname)
+--~ -- else
+--~ -- local contexts = lookuphash[lookupname]
+--~ -- if not contexts then
+--~ -- contexts = { }
+--~ -- lookuphash[lookupname] = contexts
+--~ -- end
+--~ -- local t, nt = { }, 0
+--~ -- for nofrules=1,#rules do -- we can make glyphs a special case (less tables)
+--~ -- local rule = rules[nofrules]
+--~ -- local current = rule.names
+--~ -- local before = rule.fore
+--~ -- local after = rule.back
+--~ -- local sequence = { }
+--~ -- if before then
+--~ -- uncover_1(before,sequence)
+--~ -- end
+--~ -- local start = #sequence + 1
+--~ -- uncover_1(current,sequence)
+--~ -- local stop = #sequence
+--~ -- if after then
+--~ -- uncover_1(after,sequence)
+--~ -- end
+--~ -- if sequence then
+--~ -- nt = nt + 1
+--~ -- t[nt] = { 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
+
+local valid = {
+ coverage = { chainsub = true, chainpos = true },
+ reversecoverage = { reversesub = true },
+ glyphs = { chainsub = true, chainpos = true },
+}
-function prepare_contextchains(tfmdata)
- local otfdata = tfmdata.shared.otfdata
- local lookups = otfdata.lookups
+local function prepare_contextchains(tfmdata)
+ local rawdata = tfmdata.shared.rawdata
+ local resources = rawdata.resources
+ local lookuphash = resources.lookuphash
+ local lookups = rawdata.lookups
if lookups then
- local featuredata = otfdata.shared.featuredata
- local contextchain = featuredata.gsub_contextchain -- shared with gpos
- local reversecontextchain = featuredata.gsub_reversecontextchain -- shared with gpos
- 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
+ for lookupname, lookupdata in next, rawdata.lookups do
local lookuptype = lookupdata.type
- if not lookuptype then
- logs.report("otf process","missing lookuptype for %s",lookupname)
- else
+ if lookuptype then
local rules = lookupdata.rules
if rules then
- local fmt = lookupdata.format
- -- contextchain[lookupname][unicode]
- 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 -- does #rules>1 happen often?
- 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
+ local format = lookupdata.format
+ local validformat = valid[format]
+ if not validformat then
+ report_prepare("unsupported format %s",format)
+ elseif not validformat[lookuptype] then
+ -- todo: dejavu-serif has one (but i need to see what use it has)
+ report_prepare("unsupported %s %s for %s",format,lookuptype,lookupname)
+ else
+ local contexts = lookuphash[lookupname]
+ if not contexts then
+ contexts = { }
+ lookuphash[lookupname] = contexts
+ end
+ local t, nt = { }, 0
+ for nofrules=1,#rules do
+ local rule = rules[nofrules]
+ local current = rule.current
+ local before = rule.before
+ local after = rule.after
+ local replacements = rule.replacements
+ local sequence = { }
+ local nofsequences = 0
+ -- Wventually we can store start, stop and sequence in the cached file
+ -- but then less sharing takes place so best not do that without a lot
+ -- of profiling so let's forget about it.
+ if before then
+ for n=1,#before do
+ nofsequences = nofsequences + 1
+ sequence[nofsequences] = before[n]
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
+ local start = nofsequences + 1
+ for n=1,#current do
+ nofsequences = nofsequences + 1
+ sequence[nofsequences] = current[n]
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
- -- this is different from normal coverage, we assume only replacements
- 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
+ local stop = nofsequences
+ if after then
+ for n=1,#after do
+ nofsequences = nofsequences + 1
+ sequence[nofsequences] = after[n]
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
- -- nearly the same as coverage so we could as well rename it
- 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
+ if sequence[1] then
+ -- Replacements only happen with reverse lookups as they are single only. We
+ -- could pack them into current (replacement value instead of true) and then
+ -- use sequence[start] instead but it's somewhat ugly.
+ nt = nt + 1
+ t[nt] = { 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
+ else
+ -- no rules
end
+ else
+ report_prepare("missing lookuptype for %s",lookupname)
end
end
end
end
-function fonts.initializers.node.otf.features(tfmdata,value)
+-- we can consider lookuphash == false (initialized but empty) vs lookuphash == table
+
+local function featuresinitializer(tfmdata,value)
if true then -- value 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
- -- caches
- 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
- --
+ -- beware we need to use the topmost properties table
+ local rawdata = tfmdata.shared.rawdata
+ local properties = rawdata.properties
+ if not properties.initialized then
+ local starttime = trace_preparing and os.clock()
+ local resources = rawdata.resources
+ resources.lookuphash = resources.lookuphash or { }
prepare_contextchains(tfmdata)
prepare_lookups(tfmdata)
- otfdata.shared.initialized = true
+ properties.initialized = true
if trace_preparing then
- logs.report("otf process","preparation time is %0.3f seconds for %s",os.clock()-t,tfmdata.fullname or "?")
+ report_prepare("preparation time is %0.3f seconds for %s",os.clock()-starttime,tfmdata.properties.fullname or "?")
end
end
end
end
+registerotffeature {
+ name = "features",
+ description = "features",
+ default = true,
+ initializers = {
+ position = 1,
+ node = featuresinitializer,
+ },
+ processors = {
+ node = featuresprocessor,
+ }
+}
+
+end -- closure
+
+do -- begin closure to overcome local limits and interference
+
+if not modules then modules = { } end modules ['luatex-fonts-chr'] = {
+ version = 1.001,
+ comment = "companion to luatex-fonts.lua",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+if context then
+ texio.write_nl("fatal error: this module is not for context")
+ os.exit()
+end
+
+characters = characters or { }
+characters.categories = {
+ [0x0300]="mn",
+ [0x0301]="mn",
+ [0x0302]="mn",
+ [0x0303]="mn",
+ [0x0304]="mn",
+ [0x0305]="mn",
+ [0x0306]="mn",
+ [0x0307]="mn",
+ [0x0308]="mn",
+ [0x0309]="mn",
+ [0x030A]="mn",
+ [0x030B]="mn",
+ [0x030C]="mn",
+ [0x030D]="mn",
+ [0x030E]="mn",
+ [0x030F]="mn",
+ [0x0310]="mn",
+ [0x0311]="mn",
+ [0x0312]="mn",
+ [0x0313]="mn",
+ [0x0314]="mn",
+ [0x0315]="mn",
+ [0x0316]="mn",
+ [0x0317]="mn",
+ [0x0318]="mn",
+ [0x0319]="mn",
+ [0x031A]="mn",
+ [0x031B]="mn",
+ [0x031C]="mn",
+ [0x031D]="mn",
+ [0x031E]="mn",
+ [0x031F]="mn",
+ [0x0320]="mn",
+ [0x0321]="mn",
+ [0x0322]="mn",
+ [0x0323]="mn",
+ [0x0324]="mn",
+ [0x0325]="mn",
+ [0x0326]="mn",
+ [0x0327]="mn",
+ [0x0328]="mn",
+ [0x0329]="mn",
+ [0x032A]="mn",
+ [0x032B]="mn",
+ [0x032C]="mn",
+ [0x032D]="mn",
+ [0x032E]="mn",
+ [0x032F]="mn",
+ [0x0330]="mn",
+ [0x0331]="mn",
+ [0x0332]="mn",
+ [0x0333]="mn",
+ [0x0334]="mn",
+ [0x0335]="mn",
+ [0x0336]="mn",
+ [0x0337]="mn",
+ [0x0338]="mn",
+ [0x0339]="mn",
+ [0x033A]="mn",
+ [0x033B]="mn",
+ [0x033C]="mn",
+ [0x033D]="mn",
+ [0x033E]="mn",
+ [0x033F]="mn",
+ [0x0340]="mn",
+ [0x0341]="mn",
+ [0x0342]="mn",
+ [0x0343]="mn",
+ [0x0344]="mn",
+ [0x0345]="mn",
+ [0x0346]="mn",
+ [0x0347]="mn",
+ [0x0348]="mn",
+ [0x0349]="mn",
+ [0x034A]="mn",
+ [0x034B]="mn",
+ [0x034C]="mn",
+ [0x034D]="mn",
+ [0x034E]="mn",
+ [0x034F]="mn",
+ [0x0350]="mn",
+ [0x0351]="mn",
+ [0x0352]="mn",
+ [0x0353]="mn",
+ [0x0354]="mn",
+ [0x0355]="mn",
+ [0x0356]="mn",
+ [0x0357]="mn",
+ [0x0358]="mn",
+ [0x0359]="mn",
+ [0x035A]="mn",
+ [0x035B]="mn",
+ [0x035C]="mn",
+ [0x035D]="mn",
+ [0x035E]="mn",
+ [0x035F]="mn",
+ [0x0360]="mn",
+ [0x0361]="mn",
+ [0x0362]="mn",
+ [0x0363]="mn",
+ [0x0364]="mn",
+ [0x0365]="mn",
+ [0x0366]="mn",
+ [0x0367]="mn",
+ [0x0368]="mn",
+ [0x0369]="mn",
+ [0x036A]="mn",
+ [0x036B]="mn",
+ [0x036C]="mn",
+ [0x036D]="mn",
+ [0x036E]="mn",
+ [0x036F]="mn",
+ [0x0483]="mn",
+ [0x0484]="mn",
+ [0x0485]="mn",
+ [0x0486]="mn",
+ [0x0591]="mn",
+ [0x0592]="mn",
+ [0x0593]="mn",
+ [0x0594]="mn",
+ [0x0595]="mn",
+ [0x0596]="mn",
+ [0x0597]="mn",
+ [0x0598]="mn",
+ [0x0599]="mn",
+ [0x059A]="mn",
+ [0x059B]="mn",
+ [0x059C]="mn",
+ [0x059D]="mn",
+ [0x059E]="mn",
+ [0x059F]="mn",
+ [0x05A0]="mn",
+ [0x05A1]="mn",
+ [0x05A2]="mn",
+ [0x05A3]="mn",
+ [0x05A4]="mn",
+ [0x05A5]="mn",
+ [0x05A6]="mn",
+ [0x05A7]="mn",
+ [0x05A8]="mn",
+ [0x05A9]="mn",
+ [0x05AA]="mn",
+ [0x05AB]="mn",
+ [0x05AC]="mn",
+ [0x05AD]="mn",
+ [0x05AE]="mn",
+ [0x05AF]="mn",
+ [0x05B0]="mn",
+ [0x05B1]="mn",
+ [0x05B2]="mn",
+ [0x05B3]="mn",
+ [0x05B4]="mn",
+ [0x05B5]="mn",
+ [0x05B6]="mn",
+ [0x05B7]="mn",
+ [0x05B8]="mn",
+ [0x05B9]="mn",
+ [0x05BA]="mn",
+ [0x05BB]="mn",
+ [0x05BC]="mn",
+ [0x05BD]="mn",
+ [0x05BF]="mn",
+ [0x05C1]="mn",
+ [0x05C2]="mn",
+ [0x05C4]="mn",
+ [0x05C5]="mn",
+ [0x05C7]="mn",
+ [0x0610]="mn",
+ [0x0611]="mn",
+ [0x0612]="mn",
+ [0x0613]="mn",
+ [0x0614]="mn",
+ [0x0615]="mn",
+ [0x064B]="mn",
+ [0x064C]="mn",
+ [0x064D]="mn",
+ [0x064E]="mn",
+ [0x064F]="mn",
+ [0x0650]="mn",
+ [0x0651]="mn",
+ [0x0652]="mn",
+ [0x0653]="mn",
+ [0x0654]="mn",
+ [0x0655]="mn",
+ [0x0656]="mn",
+ [0x0657]="mn",
+ [0x0658]="mn",
+ [0x0659]="mn",
+ [0x065A]="mn",
+ [0x065B]="mn",
+ [0x065C]="mn",
+ [0x065D]="mn",
+ [0x065E]="mn",
+ [0x0670]="mn",
+ [0x06D6]="mn",
+ [0x06D7]="mn",
+ [0x06D8]="mn",
+ [0x06D9]="mn",
+ [0x06DA]="mn",
+ [0x06DB]="mn",
+ [0x06DC]="mn",
+ [0x06DF]="mn",
+ [0x06E0]="mn",
+ [0x06E1]="mn",
+ [0x06E2]="mn",
+ [0x06E3]="mn",
+ [0x06E4]="mn",
+ [0x06E7]="mn",
+ [0x06E8]="mn",
+ [0x06EA]="mn",
+ [0x06EB]="mn",
+ [0x06EC]="mn",
+ [0x06ED]="mn",
+ [0x0711]="mn",
+ [0x0730]="mn",
+ [0x0731]="mn",
+ [0x0732]="mn",
+ [0x0733]="mn",
+ [0x0734]="mn",
+ [0x0735]="mn",
+ [0x0736]="mn",
+ [0x0737]="mn",
+ [0x0738]="mn",
+ [0x0739]="mn",
+ [0x073A]="mn",
+ [0x073B]="mn",
+ [0x073C]="mn",
+ [0x073D]="mn",
+ [0x073E]="mn",
+ [0x073F]="mn",
+ [0x0740]="mn",
+ [0x0741]="mn",
+ [0x0742]="mn",
+ [0x0743]="mn",
+ [0x0744]="mn",
+ [0x0745]="mn",
+ [0x0746]="mn",
+ [0x0747]="mn",
+ [0x0748]="mn",
+ [0x0749]="mn",
+ [0x074A]="mn",
+ [0x07A6]="mn",
+ [0x07A7]="mn",
+ [0x07A8]="mn",
+ [0x07A9]="mn",
+ [0x07AA]="mn",
+ [0x07AB]="mn",
+ [0x07AC]="mn",
+ [0x07AD]="mn",
+ [0x07AE]="mn",
+ [0x07AF]="mn",
+ [0x07B0]="mn",
+ [0x07EB]="mn",
+ [0x07EC]="mn",
+ [0x07ED]="mn",
+ [0x07EE]="mn",
+ [0x07EF]="mn",
+ [0x07F0]="mn",
+ [0x07F1]="mn",
+ [0x07F2]="mn",
+ [0x07F3]="mn",
+ [0x0901]="mn",
+ [0x0902]="mn",
+ [0x093C]="mn",
+ [0x0941]="mn",
+ [0x0942]="mn",
+ [0x0943]="mn",
+ [0x0944]="mn",
+ [0x0945]="mn",
+ [0x0946]="mn",
+ [0x0947]="mn",
+ [0x0948]="mn",
+ [0x094D]="mn",
+ [0x0951]="mn",
+ [0x0952]="mn",
+ [0x0953]="mn",
+ [0x0954]="mn",
+ [0x0962]="mn",
+ [0x0963]="mn",
+ [0x0981]="mn",
+ [0x09BC]="mn",
+ [0x09C1]="mn",
+ [0x09C2]="mn",
+ [0x09C3]="mn",
+ [0x09C4]="mn",
+ [0x09CD]="mn",
+ [0x09E2]="mn",
+ [0x09E3]="mn",
+ [0x0A01]="mn",
+ [0x0A02]="mn",
+ [0x0A3C]="mn",
+ [0x0A41]="mn",
+ [0x0A42]="mn",
+ [0x0A47]="mn",
+ [0x0A48]="mn",
+ [0x0A4B]="mn",
+ [0x0A4C]="mn",
+ [0x0A4D]="mn",
+ [0x0A70]="mn",
+ [0x0A71]="mn",
+ [0x0A81]="mn",
+ [0x0A82]="mn",
+ [0x0ABC]="mn",
+ [0x0AC1]="mn",
+ [0x0AC2]="mn",
+ [0x0AC3]="mn",
+ [0x0AC4]="mn",
+ [0x0AC5]="mn",
+ [0x0AC7]="mn",
+ [0x0AC8]="mn",
+ [0x0ACD]="mn",
+ [0x0AE2]="mn",
+ [0x0AE3]="mn",
+ [0x0B01]="mn",
+ [0x0B3C]="mn",
+ [0x0B3F]="mn",
+ [0x0B41]="mn",
+ [0x0B42]="mn",
+ [0x0B43]="mn",
+ [0x0B4D]="mn",
+ [0x0B56]="mn",
+ [0x0B82]="mn",
+ [0x0BC0]="mn",
+ [0x0BCD]="mn",
+ [0x0C3E]="mn",
+ [0x0C3F]="mn",
+ [0x0C40]="mn",
+ [0x0C46]="mn",
+ [0x0C47]="mn",
+ [0x0C48]="mn",
+ [0x0C4A]="mn",
+ [0x0C4B]="mn",
+ [0x0C4C]="mn",
+ [0x0C4D]="mn",
+ [0x0C55]="mn",
+ [0x0C56]="mn",
+ [0x0CBC]="mn",
+ [0x0CBF]="mn",
+ [0x0CC6]="mn",
+ [0x0CCC]="mn",
+ [0x0CCD]="mn",
+ [0x0CE2]="mn",
+ [0x0CE3]="mn",
+ [0x0D41]="mn",
+ [0x0D42]="mn",
+ [0x0D43]="mn",
+ [0x0D4D]="mn",
+ [0x0DCA]="mn",
+ [0x0DD2]="mn",
+ [0x0DD3]="mn",
+ [0x0DD4]="mn",
+ [0x0DD6]="mn",
+ [0x0E31]="mn",
+ [0x0E34]="mn",
+ [0x0E35]="mn",
+ [0x0E36]="mn",
+ [0x0E37]="mn",
+ [0x0E38]="mn",
+ [0x0E39]="mn",
+ [0x0E3A]="mn",
+ [0x0E47]="mn",
+ [0x0E48]="mn",
+ [0x0E49]="mn",
+ [0x0E4A]="mn",
+ [0x0E4B]="mn",
+ [0x0E4C]="mn",
+ [0x0E4D]="mn",
+ [0x0E4E]="mn",
+ [0x0EB1]="mn",
+ [0x0EB4]="mn",
+ [0x0EB5]="mn",
+ [0x0EB6]="mn",
+ [0x0EB7]="mn",
+ [0x0EB8]="mn",
+ [0x0EB9]="mn",
+ [0x0EBB]="mn",
+ [0x0EBC]="mn",
+ [0x0EC8]="mn",
+ [0x0EC9]="mn",
+ [0x0ECA]="mn",
+ [0x0ECB]="mn",
+ [0x0ECC]="mn",
+ [0x0ECD]="mn",
+ [0x0F18]="mn",
+ [0x0F19]="mn",
+ [0x0F35]="mn",
+ [0x0F37]="mn",
+ [0x0F39]="mn",
+ [0x0F71]="mn",
+ [0x0F72]="mn",
+ [0x0F73]="mn",
+ [0x0F74]="mn",
+ [0x0F75]="mn",
+ [0x0F76]="mn",
+ [0x0F77]="mn",
+ [0x0F78]="mn",
+ [0x0F79]="mn",
+ [0x0F7A]="mn",
+ [0x0F7B]="mn",
+ [0x0F7C]="mn",
+ [0x0F7D]="mn",
+ [0x0F7E]="mn",
+ [0x0F80]="mn",
+ [0x0F81]="mn",
+ [0x0F82]="mn",
+ [0x0F83]="mn",
+ [0x0F84]="mn",
+ [0x0F86]="mn",
+ [0x0F87]="mn",
+ [0x0F90]="mn",
+ [0x0F91]="mn",
+ [0x0F92]="mn",
+ [0x0F93]="mn",
+ [0x0F94]="mn",
+ [0x0F95]="mn",
+ [0x0F96]="mn",
+ [0x0F97]="mn",
+ [0x0F99]="mn",
+ [0x0F9A]="mn",
+ [0x0F9B]="mn",
+ [0x0F9C]="mn",
+ [0x0F9D]="mn",
+ [0x0F9E]="mn",
+ [0x0F9F]="mn",
+ [0x0FA0]="mn",
+ [0x0FA1]="mn",
+ [0x0FA2]="mn",
+ [0x0FA3]="mn",
+ [0x0FA4]="mn",
+ [0x0FA5]="mn",
+ [0x0FA6]="mn",
+ [0x0FA7]="mn",
+ [0x0FA8]="mn",
+ [0x0FA9]="mn",
+ [0x0FAA]="mn",
+ [0x0FAB]="mn",
+ [0x0FAC]="mn",
+ [0x0FAD]="mn",
+ [0x0FAE]="mn",
+ [0x0FAF]="mn",
+ [0x0FB0]="mn",
+ [0x0FB1]="mn",
+ [0x0FB2]="mn",
+ [0x0FB3]="mn",
+ [0x0FB4]="mn",
+ [0x0FB5]="mn",
+ [0x0FB6]="mn",
+ [0x0FB7]="mn",
+ [0x0FB8]="mn",
+ [0x0FB9]="mn",
+ [0x0FBA]="mn",
+ [0x0FBB]="mn",
+ [0x0FBC]="mn",
+ [0x0FC6]="mn",
+ [0x102D]="mn",
+ [0x102E]="mn",
+ [0x102F]="mn",
+ [0x1030]="mn",
+ [0x1032]="mn",
+ [0x1036]="mn",
+ [0x1037]="mn",
+ [0x1039]="mn",
+ [0x1058]="mn",
+ [0x1059]="mn",
+ [0x135F]="mn",
+ [0x1712]="mn",
+ [0x1713]="mn",
+ [0x1714]="mn",
+ [0x1732]="mn",
+ [0x1733]="mn",
+ [0x1734]="mn",
+ [0x1752]="mn",
+ [0x1753]="mn",
+ [0x1772]="mn",
+ [0x1773]="mn",
+ [0x17B7]="mn",
+ [0x17B8]="mn",
+ [0x17B9]="mn",
+ [0x17BA]="mn",
+ [0x17BB]="mn",
+ [0x17BC]="mn",
+ [0x17BD]="mn",
+ [0x17C6]="mn",
+ [0x17C9]="mn",
+ [0x17CA]="mn",
+ [0x17CB]="mn",
+ [0x17CC]="mn",
+ [0x17CD]="mn",
+ [0x17CE]="mn",
+ [0x17CF]="mn",
+ [0x17D0]="mn",
+ [0x17D1]="mn",
+ [0x17D2]="mn",
+ [0x17D3]="mn",
+ [0x17DD]="mn",
+ [0x180B]="mn",
+ [0x180C]="mn",
+ [0x180D]="mn",
+ [0x18A9]="mn",
+ [0x1920]="mn",
+ [0x1921]="mn",
+ [0x1922]="mn",
+ [0x1927]="mn",
+ [0x1928]="mn",
+ [0x1932]="mn",
+ [0x1939]="mn",
+ [0x193A]="mn",
+ [0x193B]="mn",
+ [0x1A17]="mn",
+ [0x1A18]="mn",
+ [0x1B00]="mn",
+ [0x1B01]="mn",
+ [0x1B02]="mn",
+ [0x1B03]="mn",
+ [0x1B34]="mn",
+ [0x1B36]="mn",
+ [0x1B37]="mn",
+ [0x1B38]="mn",
+ [0x1B39]="mn",
+ [0x1B3A]="mn",
+ [0x1B3C]="mn",
+ [0x1B42]="mn",
+ [0x1B6B]="mn",
+ [0x1B6C]="mn",
+ [0x1B6D]="mn",
+ [0x1B6E]="mn",
+ [0x1B6F]="mn",
+ [0x1B70]="mn",
+ [0x1B71]="mn",
+ [0x1B72]="mn",
+ [0x1B73]="mn",
+ [0x1DC0]="mn",
+ [0x1DC1]="mn",
+ [0x1DC2]="mn",
+ [0x1DC3]="mn",
+ [0x1DC4]="mn",
+ [0x1DC5]="mn",
+ [0x1DC6]="mn",
+ [0x1DC7]="mn",
+ [0x1DC8]="mn",
+ [0x1DC9]="mn",
+ [0x1DCA]="mn",
+ [0x1DFE]="mn",
+ [0x1DFF]="mn",
+ [0x20D0]="mn",
+ [0x20D1]="mn",
+ [0x20D2]="mn",
+ [0x20D3]="mn",
+ [0x20D4]="mn",
+ [0x20D5]="mn",
+ [0x20D6]="mn",
+ [0x20D7]="mn",
+ [0x20D8]="mn",
+ [0x20D9]="mn",
+ [0x20DA]="mn",
+ [0x20DB]="mn",
+ [0x20DC]="mn",
+ [0x20E1]="mn",
+ [0x20E5]="mn",
+ [0x20E6]="mn",
+ [0x20E7]="mn",
+ [0x20E8]="mn",
+ [0x20E9]="mn",
+ [0x20EA]="mn",
+ [0x20EB]="mn",
+ [0x20EC]="mn",
+ [0x20ED]="mn",
+ [0x20EE]="mn",
+ [0x20EF]="mn",
+ [0x302A]="mn",
+ [0x302B]="mn",
+ [0x302C]="mn",
+ [0x302D]="mn",
+ [0x302E]="mn",
+ [0x302F]="mn",
+ [0x3099]="mn",
+ [0x309A]="mn",
+ [0xA806]="mn",
+ [0xA80B]="mn",
+ [0xA825]="mn",
+ [0xA826]="mn",
+ [0xFB1E]="mn",
+ [0xFE00]="mn",
+ [0xFE01]="mn",
+ [0xFE02]="mn",
+ [0xFE03]="mn",
+ [0xFE04]="mn",
+ [0xFE05]="mn",
+ [0xFE06]="mn",
+ [0xFE07]="mn",
+ [0xFE08]="mn",
+ [0xFE09]="mn",
+ [0xFE0A]="mn",
+ [0xFE0B]="mn",
+ [0xFE0C]="mn",
+ [0xFE0D]="mn",
+ [0xFE0E]="mn",
+ [0xFE0F]="mn",
+ [0xFE20]="mn",
+ [0xFE21]="mn",
+ [0xFE22]="mn",
+ [0xFE23]="mn",
+}
+
end -- closure
do -- begin closure to overcome local limits and interference
@@ -10807,49 +11100,109 @@ local type, tostring, match, format, concat = type, tostring, string.match, stri
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")
+local fonts, nodes, node = fonts, nodes, node
-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 allocate = utilities.storage.allocate
-local otf = fonts.otf
-local tfm = fonts.tfm
+local otf = fonts.handlers.otf
-local initializers = fonts.analyzers.initializers
-local methods = fonts.analyzers.methods
+local analyzers = fonts.analyzers
+local initializers = allocate()
+local methods = allocate()
-local glyph = node.id('glyph')
-local glue = node.id('glue')
-local penalty = node.id('penalty')
+analyzers.initializers = initializers
+analyzers.methods = methods
+analyzers.useunicodemarks = false
-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 nodecodes = nodes.nodecodes
+local glyph_code = nodecodes.glyph
-local fontdata = fonts.ids
-local state = attributes.private('state')
+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 fcs = (fonts.color and fonts.color.set) or function() end
-local fcr = (fonts.color and fonts.color.reset) or function() end
+local fontdata = fonts.hashes.identifiers
+local state = attributes.private('state')
+local categories = characters and characters.categories or { } -- sorry, only in context
-local a_to_script = otf.a_to_script
-local a_to_language = otf.a_to_language
+local tracers = nodes.tracers
+local colortracers = tracers and tracers.colors
+local setnodecolor = colortracers and colortracers.set or function() end
+local resetnodecolor = colortracers and colortracers.reset or function() end
+
+local otffeatures = fonts.constructors.newfeatures("otf")
+local registerotffeature = otffeatures.register
+
+--[[ldx--
+<p>Analyzers run per script and/or language and are needed in order to
+process features right.</p>
+--ldx]]--
+
+-- todo: analyzers per script/lang, cross font, so we need an font id hash -> script
+-- e.g. latin -> hyphenate, arab -> 1/2/3 analyze -- its own namespace
+
+local state = attributes.private('state')
+
+function analyzers.setstate(head,font)
+ local useunicodemarks = analyzers.useunicodemarks
+ local tfmdata = fontdata[font]
+ local characters = tfmdata.characters
+ local descriptions = tfmdata.descriptions
+ local first, last, current, n, done = nil, nil, head, 0, false -- maybe make n boolean
+ while current do
+ local id = current.id
+ if id == glyph_code and current.font == font then
+ local char = current.char
+ local d = descriptions[char]
+ if d then
+ if d.class == "mark" or (useunicodemarks and categories[char] == "mn") then
+ done = true
+ set_attribute(current,state,5) -- mark
+ elseif n == 0 then
+ first, last, n = current, current, 1
+ set_attribute(current,state,1) -- init
+ else
+ last, n = current, n+1
+ set_attribute(current,state,2) -- medi
+ end
+ else -- finish
+ if first and first == last then
+ set_attribute(last,state,4) -- isol
+ elseif last then
+ set_attribute(last,state,3) -- fina
+ end
+ first, last, n = nil, nil, 0
+ end
+ elseif id == disc_code then
+ -- always in the middle
+ set_attribute(current,state,2) -- midi
+ last = current
+ else -- finish
+ if first and first == last then
+ set_attribute(last,state,4) -- isol
+ elseif last then
+ set_attribute(last,state,3) -- fina
+ end
+ first, last, n = nil, nil, 0
+ end
+ current = current.next
+ end
+ if first and first == last then
+ set_attribute(last,state,4) -- isol
+ elseif last then
+ set_attribute(last,state,3) -- fina
+ end
+ return head, done
+end
-- in the future we will use language/script attributes instead of the
-- font related value, but then we also need dynamic features which is
-- somewhat slower; and .. we need a chain of them
-function fonts.initializers.node.otf.analyze(tfmdata,value,attr)
- 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 function analyzeinitializer(tfmdata,value) -- attr
+ local script, language = otf.scriptandlanguage(tfmdata) -- attr
local action = initializers[script]
if action then
if type(action) == "function" then
@@ -10861,17 +11214,11 @@ function fonts.initializers.node.otf.analyze(tfmdata,value,attr)
end
end
end
- return nil
end
-function fonts.methods.node.otf.analyze(head,font,attr)
+local function analyzeprocessor(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 script, language = otf.scriptandlanguage(tfmdata,attr)
local action = methods[script]
if action then
if type(action) == "function" then
@@ -10886,14 +11233,25 @@ function fonts.methods.node.otf.analyze(head,font,attr)
return head, false
end
-otf.features.register("analyze",true) -- we always analyze
-table.insert(fonts.triggers,"analyze") -- we need a proper function for doing this
+registerotffeature {
+ name = "analyze",
+ description = "analysis of (for instance) character classes",
+ default = true,
+ initializers = {
+ node = analyzeinitializer,
+ },
+ processors = {
+ position = 1,
+ node = analyzeprocessor,
+ }
+}
-- latin
-fonts.analyzers.methods.latn = fonts.analyzers.aux.setstate
+methods.latn = analyzers.setstate
--- this info eventually will go into char-def
+-- this info eventually will go into char-def adn we will have a state
+-- table for generic then
local zwnj = 0x200C
local zwj = 0x200D
@@ -10968,15 +11326,15 @@ 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)
+ log.report("analyze","arab: character %s (U+%05X) 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
+function methods.nocolor(head,font,attr)
+ for n in traverse_id(glyph_code,head) do
if not font or n.font == font then
- fcr(n)
+ resetnodecolor(n)
end
end
return head, true
@@ -10988,22 +11346,22 @@ local function finish(first,last)
local fc = first.char
if isol_fina_medi_init[fc] or isol_fina[fc] then
set_attribute(first,state,4) -- isol
- if trace_analyzing then fcs(first,"font:isol") end
+ if trace_analyzing then setnodecolor(first,"font:isol") end
else
warning(first,"isol")
set_attribute(first,state,0) -- error
- if trace_analyzing then fcr(first) end
+ if trace_analyzing then resetnodecolor(first) end
end
else
local lc = last.char
if isol_fina_medi_init[lc] or isol_fina[lc] then -- why isol here ?
-- if laststate == 1 or laststate == 2 or laststate == 4 then
set_attribute(last,state,3) -- fina
- if trace_analyzing then fcs(last,"font:fina") end
+ if trace_analyzing then setnodecolor(last,"font:fina") end
else
warning(last,"fina")
set_attribute(last,state,0) -- error
- if trace_analyzing then fcr(last) end
+ if trace_analyzing then resetnodecolor(last) end
end
end
first, last = nil, nil
@@ -11012,41 +11370,42 @@ local function finish(first,last)
local fc = first.char
if isol_fina_medi_init[fc] or isol_fina[fc] then
set_attribute(first,state,4) -- isol
- if trace_analyzing then fcs(first,"font:isol") end
+ if trace_analyzing then setnodecolor(first,"font:isol") end
else
warning(first,"isol")
set_attribute(first,state,0) -- error
- if trace_analyzing then fcr(first) end
+ if trace_analyzing then resetnodecolor(first) end
end
first = nil
end
return first, last
end
-function fonts.analyzers.methods.arab(head,font,attr) -- maybe make a special version with no trace
+function methods.arab(head,font,attr) -- maybe make a special version with no trace
+ local useunicodemarks = analyzers.useunicodemarks
local tfmdata = fontdata[font]
- local marks = tfmdata.marks
+ local marks = tfmdata.resources.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
+ if current.id == glyph_code 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
+ if marks[char] or (useunicodemarks and categories[char] == "mn") then
set_attribute(current,state,5) -- mark
- if trace_analyzing then fcs(current,"font:mark") end
+ if trace_analyzing then setnodecolor(current,"font:mark") end
elseif isol[char] then -- can be zwj or zwnj too
first, last = finish(first,last)
set_attribute(current,state,4) -- isol
- if trace_analyzing then fcs(current,"font:isol") end
+ if trace_analyzing then setnodecolor(current,"font:isol") end
first, last = nil, nil
elseif not first then
if isol_fina_medi_init[char] then
set_attribute(current,state,1) -- init
- if trace_analyzing then fcs(current,"font:init") end
+ if trace_analyzing then setnodecolor(current,"font:init") end
first, last = first or current, current
elseif isol_fina[char] then
set_attribute(current,state,4) -- isol
- if trace_analyzing then fcs(current,"font:isol") end
+ if trace_analyzing then setnodecolor(current,"font:isol") end
first, last = nil, nil
else -- no arab
first, last = finish(first,last)
@@ -11054,18 +11413,18 @@ function fonts.analyzers.methods.arab(head,font,attr) -- maybe make a special ve
elseif isol_fina_medi_init[char] then
first, last = first or current, current
set_attribute(current,state,2) -- medi
- if trace_analyzing then fcs(current,"font:medi") end
+ if trace_analyzing then setnodecolor(current,"font:medi") end
elseif isol_fina[char] then
if not has_attribute(last,state,1) then
-- tricky, we need to check what last may be !
set_attribute(last,state,2) -- medi
- if trace_analyzing then fcs(last,"font:medi") end
+ if trace_analyzing then setnodecolor(last,"font:medi") end
end
set_attribute(current,state,3) -- fina
- if trace_analyzing then fcs(current,"font:fina") end
+ if trace_analyzing then setnodecolor(current,"font:fina") end
first, last = nil, nil
elseif char >= 0x0600 and char <= 0x06FF then
- if trace_analyzing then fcs(current,"font:rest") end
+ if trace_analyzing then setnodecolor(current,"font:rest") end
first, last = finish(first,last)
else --no
first, last = finish(first,last)
@@ -11079,224 +11438,48 @@ function fonts.analyzers.methods.arab(head,font,attr) -- maybe make a special ve
return head, done
end
+directives.register("otf.analyze.useunicodemarks",function(v)
+ analyzers.useunicodemarks = v
+end)
+
end -- closure
do -- begin closure to overcome local limits and interference
-if not modules then modules = { } end modules ['font-otc'] = {
+if not modules then modules = { } end modules ['luatex-fonts-lua'] = {
version = 1.001,
- comment = "companion to font-otf.lua (context)",
+ 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 format, insert = string.format, table.insert
-local type, next = type, next
-
--- we assume that the other otf stuff is loaded already
-
-local trace_loading = false trackers.register("otf.loading", function(v) trace_loading = v end)
-
-local otf = fonts.otf
-local tfm = fonts.tfm
-
--- instead of "script = "DFLT", langs = { 'dflt' }" we now use wildcards (we used to
--- have always); some day we can write a "force always when true" trick for other
--- features as well
---
--- we could have a tnum variant as well
-
-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",
- },
- },
- trep = {
- {
- -- [0x0022] = 0x201D,
- [0x0027] = 0x2019,
- -- [0x0060] = 0x2018,
- },
- },
- anum = {
- { -- arabic
- [0x0030] = 0x0660,
- [0x0031] = 0x0661,
- [0x0032] = 0x0662,
- [0x0033] = 0x0663,
- [0x0034] = 0x0664,
- [0x0035] = 0x0665,
- [0x0036] = 0x0666,
- [0x0037] = 0x0667,
- [0x0038] = 0x0668,
- [0x0039] = 0x0669,
- },
- { -- persian
- [0x0030] = 0x06F0,
- [0x0031] = 0x06F1,
- [0x0032] = 0x06F2,
- [0x0033] = 0x06F3,
- [0x0034] = 0x06F4,
- [0x0035] = 0x06F5,
- [0x0036] = 0x06F6,
- [0x0037] = 0x06F7,
- [0x0038] = 0x06F8,
- [0x0039] = 0x06F9,
- },
- },
-}
-
-local extra_features = { -- maybe just 1..n so that we prescribe order
- 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", "FAR" }, } }, 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 = { "URD" }, } }, 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)
- -- todo
+if context then
+ texio.write_nl("fatal error: this module is not for context")
+ os.exit()
end
-fonts.otf.enhancers["enrich with features"] = function(data,filename)
- -- could be done elsewhere (true can be #)
- 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
+local fonts = fonts
+fonts.formats.lua = "lua"
+
+function fonts.readers.lua(specification)
+ local fullname = specification.filename or ""
+ if fullname == "" then
+ local forced = specification.forced or ""
+ if forced ~= "" then
+ fullname = specification.name .. "." .. forced
+ else
+ fullname = specification.name
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] = { "ligature", ligature, glyph.name }
- 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] = { { "substitution", glyphs[replacement].name } }
- 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)) -- right order
- 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
+ local fullname = resolvers.findfile(fullname) or ""
+ if fullname ~= "" then
+ local loader = loadfile(fullname)
+ loader = loader and loader()
+ return loader and loader(specification)
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')
-
--- the functionality is defined elsewhere
-
-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
@@ -11309,58 +11492,53 @@ if not modules then modules = { } end modules ['font-def'] = {
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 concat = table.concat
+local format, gmatch, match, find, lower, gsub = string.format, string.gmatch, string.match, string.find, string.lower, string.gsub
local tostring, next = tostring, next
local lpegmatch = lpeg.match
+local allocate = utilities.storage.allocate
+
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.*")
+local report_defining = logs.reporter("fonts","defining")
+
--[[ldx--
<p>Here we deal with defining fonts. We do so by intercepting the
default loader that only handles <l n='tfm'/>.</p>
--ldx]]--
-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 fonts = fonts
+local fontdata = fonts.hashes.identifiers
+local readers = fonts.readers
+local definers = fonts.definers
+local specifiers = fonts.specifiers
+local constructors = fonts.constructors
-local tfm = fonts.tfm
-local vf = fonts.vf
-local define = fonts.define
+readers.sequence = allocate { 'otf', 'ttf', 'afm', 'tfm', 'lua' } -- dfont ttc
-tfm.version = 1.01
-tfm.cache = containers.define("fonts", "tfm", tfm.version, false) -- better in font-tfm
+local variants = allocate()
+specifiers.variants = variants
-define.method = "afm or tfm" -- afm, tfm, afm or tfm, tfm or afm
-define.specify = fonts.define.specify or { }
-define.methods = fonts.define.methods or { }
+definers.methods = definers.methods or { }
-tfm.fonts = tfm.fonts or { }
-tfm.readers = tfm.readers or { }
-tfm.internalized = tfm.internalized or { } -- internal tex numbers
+local internalized = allocate() -- internal tex numbers (private)
-tfm.readers.sequence = { 'otf', 'ttf', 'afm', 'tfm' }
-tfm.auto_afm = true
-
-local readers = tfm.readers
-local sequence = readers.sequence
+local loadedfonts = constructors.loadedfonts
+local designsizes = constructors.designsizes
--[[ldx--
<p>We hardly gain anything when we cache the final (pre scaled)
-<l n='tfm'/> table. But it can be handy for debugging.</p>
+<l n='tfm'/> table. But it can be handy for debugging, so we no
+longer carry this code along. Also, we now have quite some reference
+to other tables so we would end up with lots of catches.</p>
--ldx]]--
-fonts.version = 1.05
-fonts.cache = containers.define("fonts", "def", fonts.version, false)
-
--[[ldx--
<p>We can prefix a font specification by <type>name:</type> or
<type>file:</type>. The first case will result in a lookup in the
@@ -11380,7 +11558,7 @@ and prepares a table that will move along as we proceed.</p>
-- name name(sub) name(sub)*spec name*spec
-- name@spec*oeps
-local splitter, specifiers = nil, ""
+local splitter, splitspecifiers = nil, ""
local P, C, S, Cc = lpeg.P, lpeg.C, lpeg.S, lpeg.Cc
@@ -11389,13 +11567,13 @@ local right = P(")")
local colon = P(":")
local space = P(" ")
-define.defaultlookup = "file"
+definers.defaultlookup = "file"
local prefixpattern = P(false)
-function define.add_specifier(symbol)
- specifiers = specifiers .. symbol
- local method = S(specifiers)
+local function addspecifier(symbol)
+ splitspecifiers = splitspecifiers .. symbol
+ local method = S(splitspecifiers)
local lookup = C(prefixpattern) * colon
local sub = left * C(P(1-left-right-method)^1) * right
local specification = C(method) * C(P(1)^1)
@@ -11403,36 +11581,39 @@ function define.add_specifier(symbol)
splitter = P((lookup + Cc("")) * name * (sub + Cc("")) * (specification + Cc("")))
end
-function define.add_lookup(str,default)
+local function addlookup(str,default)
prefixpattern = prefixpattern + P(str)
end
-define.add_lookup("file")
-define.add_lookup("name")
-define.add_lookup("spec")
+definers.addlookup = addlookup
-function define.get_specification(str)
+addlookup("file")
+addlookup("name")
+addlookup("spec")
+
+local function getspecification(str)
return lpegmatch(splitter,str)
end
-function define.register_split(symbol,action)
- define.add_specifier(symbol)
- define.specify[symbol] = action
+definers.getspecification = getspecification
+
+function definers.registersplit(symbol,action,verbosename)
+ addspecifier(symbol)
+ variants[symbol] = action
+ if verbosename then
+ variants[verbosename] = action
+ end
end
-function define.makespecification(specification, lookup, name, sub, method, detail, size)
+function definers.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",
+ report_defining("%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 specification.lookup then
---~ lookup = specification.lookup -- can come from xetex [] syntax
---~ specification.lookup = nil
---~ end
if not lookup or lookup == "" then
- lookup = define.defaultlookup
+ lookup = definers.defaultlookup
end
local t = {
lookup = lookup, -- forced type
@@ -11449,112 +11630,49 @@ function define.makespecification(specification, lookup, name, sub, method, deta
return t
end
-function define.analyze(specification, size)
+function definers.analyze(specification, size)
-- can be optimized with locals
- local lookup, name, sub, method, detail = define.get_specification(specification or "")
- return define.makespecification(specification, lookup, name, sub, method, detail, size)
+ local lookup, name, sub, method, detail = getspecification(specification or "")
+ return definers.makespecification(specification, lookup, name, sub, method, detail, size)
end
--[[ldx--
-<p>A unique hash value is generated by:</p>
+<p>We can resolve the filename using the next function:</p>
--ldx]]--
-local sortedhashkeys = table.sortedhashkeys
+definers.resolvers = definers.resolvers or { }
+local resolvers = definers.resolvers
-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 -- i need to figure this out, features
- 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 specification.mathsize then
---~ t[#t+1] = "mathsize=" .. specification.mathsize
---~ end
- if #t > 0 then
- return concat(t,"+")
- end
- end
- return "unknown"
-end
-
-fonts.designsizes = { }
+-- todo: reporter
---[[ldx--
-<p>In principle we can share tfm tables when we are in node for a font, but then
-we need to define a font switch as an id/attr switch which is no fun, so in that
-case users can best use dynamic features ... so, we will not use that speedup. Okay,
-when we get rid of base mode we can optimize even further by sharing, but then we
-loose our testcases for <l n='luatex'/>.</p>
---ldx]]--
-
-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
+function resolvers.file(specification)
+ local suffix = file.suffix(specification.name)
+ if fonts.formats[suffix] then
+ specification.forced = suffix
+ specification.name = file.removesuffix(specification.name)
end
- if size < 1000 and fonts.designsizes[hash] then
- size = math.round(tfm.scaled(size, fonts.designsizes[hash]))
- specification.size = size
- end
---~ local mathsize = specification.mathsize or 0
---~ if mathsize > 0 then
---~ local textsize = specification.textsize
---~ if fallbacks then
---~ return hash .. ' @ ' .. tostring(size) .. ' [ ' .. tostring(mathsize) .. ' : ' .. tostring(textsize) .. ' ] @ ' .. fallbacks
---~ else
---~ return hash .. ' @ ' .. tostring(size) .. ' [ ' .. tostring(mathsize) .. ' : ' .. tostring(textsize) .. ' ]'
---~ end
---~ else
- if fallbacks then
- return hash .. ' @ ' .. tostring(size) .. ' @ ' .. fallbacks
- else
- return hash .. ' @ ' .. tostring(size)
- end
---~ end
end
---[[ldx--
-<p>We can resolve the filename using the next function:</p>
---ldx]]--
-
-define.resolvers = resolvers
-
-function define.resolvers.file(specification)
- specification.forced = file.extname(specification.name)
- specification.name = file.removesuffix(specification.name)
-end
-
-function define.resolvers.name(specification)
+function resolvers.name(specification)
local resolve = fonts.names.resolve
if resolve then
- specification.resolved, specification.sub = fonts.names.resolve(specification.name,specification.sub)
- if specification.resolved then
- specification.forced = file.extname(specification.resolved)
- specification.name = file.removesuffix(specification.resolved)
+ local resolved, sub = fonts.names.resolve(specification.name,specification.sub)
+ 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)
+ resolvers.file(specification)
end
end
-function define.resolvers.spec(specification)
+function resolvers.spec(specification)
local resolvespec = fonts.names.resolvespec
if resolvespec then
specification.resolved, specification.sub = fonts.names.resolvespec(specification.name,specification.sub)
@@ -11563,13 +11681,13 @@ function define.resolvers.spec(specification)
specification.name = file.removesuffix(specification.resolved)
end
else
- define.resolvers.name(specification)
+ resolvers.name(specification)
end
end
-function define.resolve(specification)
+function definers.resolve(specification)
if not specification.resolved or specification.resolved == "" then -- resolved itself not per se in mapping hash
- local r = define.resolvers[specification.lookup]
+ local r = resolvers[specification.lookup]
if r then
r(specification)
end
@@ -11579,7 +11697,18 @@ function define.resolve(specification)
else
specification.forced = specification.forced
end
- specification.hash = lower(specification.name .. ' @ ' .. tfm.hash_features(specification))
+ -- for the moment here (goodies set outside features)
+ local goodies = specification.goodies
+ if goodies and goodies ~= "" then
+ local normal = specification.features.normal
+ if not normal then
+ specification.features.normal = { goodies = goodies }
+ elseif not normal.goodies then
+ normal.goodies = goodies
+ end
+ end
+ --
+ specification.hash = lower(specification.name .. ' @ ' .. constructors.hashfeatures(specification))
if specification.sub and specification.sub ~= "" then
specification.hash = specification.sub .. ' @ ' .. specification.hash
end
@@ -11602,25 +11731,48 @@ features (esp in virtual fonts) so let's not do that now.</p>
specification yet.</p>
--ldx]]--
-function tfm.read(specification)
- local hash = tfm.hash_instance(specification)
- local tfmtable = tfm.fonts[hash] -- hashes by size !
- if not tfmtable then
+-- not in context, at least not now:
+--
+-- function definers.applypostprocessors(tfmdata)
+-- local postprocessors = tfmdata.postprocessors
+-- if postprocessors then
+-- for i=1,#postprocessors do
+-- local extrahash = postprocessors[i](tfmdata) -- after scaling etc
+-- if type(extrahash) == "string" and extrahash ~= "" then
+-- -- e.g. a reencoding needs this
+-- extrahash = gsub(lower(extrahash),"[^a-z]","-")
+-- tfmdata.properties.fullname = format("%s-%s",tfmdata.properties.fullname,extrahash)
+-- end
+-- end
+-- end
+-- return tfmdata
+-- end
+
+function definers.applypostprocessors(tfmdata)
+ return tfmdata
+end
+
+function definers.loadfont(specification)
+ local hash = constructors.hashinstance(specification)
+ local tfmdata = loadedfonts[hash] -- hashes by size !
+ if not tfmdata 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)
+ local reader = readers[lower(forced)]
+ tfmdata = reader and reader(specification)
+ if not tfmdata then
+ report_defining("forced type %s of %s not found",forced,specification.name)
end
else
- for s=1,#sequence do -- reader sequence
+ local sequence = readers.sequence -- can be overloaded so only a shortcut here
+ for s=1,#sequence do
local reader = sequence[s]
- if readers[reader] then -- not really needed
+ if readers[reader] then -- we skip not loaded readers
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")
+ report_defining("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
+ tfmdata = readers[reader](specification)
+ if tfmdata then
break
else
specification.filename = nil
@@ -11628,196 +11780,60 @@ function tfm.read(specification)
end
end
end
- if tfmtable then
+ if tfmdata then
+ local properties = tfmdata.properties
+ local embedding
if directive_embedall then
- tfmtable.embedding = "full"
- elseif tfmtable.filename and fonts.dontembed[tfmtable.filename] then
- tfmtable.embedding = "no"
+ embedding = "full"
+ elseif properties.filename and constructors.dontembed[properties.filename] then
+ embedding = "no"
+ else
+ embedding = "subset"
+ end
+ if properties then
+ properties.embedding = embedding
else
- tfmtable.embedding = "subset"
+ tfmdata.properties = { embedding = embedding }
end
- tfm.fonts[hash] = tfmtable
- fonts.designsizes[specification.hash] = tfmtable.designsize -- we only know this for sure after loading once
- --~ tfmtable.mode = specification.features.normal.mode or "base"
+ tfmdata = definers.applypostprocessors(tfmdata)
+ loadedfonts[hash] = tfmdata
+ designsizes[specification.hash] = tfmdata.parameters.designsize
end
end
- if not tfmtable then
- logs.report("define font","font with name %s is not found",specification.name)
+ if not tfmdata then
+ report_defining("font with asked name '%s' is not found using lookup '%s'",specification.name,specification.lookup)
end
- return tfmtable
+ return tfmdata
end
--[[ldx--
<p>For virtual fonts we need a slightly different approach:</p>
--ldx]]--
-function tfm.read_and_define(name,size) -- no id
- local specification = define.analyze(name,size)
+function constructors.readanddefine(name,size) -- no id -- maybe a dummy first
+ local specification = definers.analyze(name,size)
local method = specification.method
- if method and define.specify[method] then
- specification = define.specify[method](specification)
+ if method and variants[method] then
+ specification = variants[method](specification)
end
- specification = define.resolve(specification)
- local hash = tfm.hash_instance(specification)
- local id = define.registered(hash)
+ specification = definers.resolve(specification)
+ local hash = constructors.hashinstance(specification)
+ local id = definers.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)
+ local tfmdata = definers.loadfont(specification)
+ if tfmdata then
+ tfmdata.properties.hash = hash
+ id = font.define(tfmdata)
+ definers.register(tfmdata,id)
else
id = 0 -- signal
end
end
- return fonts.ids[id], id
-end
-
---[[ldx--
-<p>Next follow the readers. This code was written while <l n='luatex'/>
-evolved. Each one has its own way of dealing with its format.</p>
---ldx]]--
-
-local function check_tfm(specification,fullname)
- -- ofm directive blocks local path search unless set; btw, in context we
- -- don't support ofm files anyway as this format is obsolete
- local foundname = resolvers.findbinfile(fullname, 'tfm') or "" -- just to be sure
- if foundname == "" then
- foundname = resolvers.findbinfile(fullname, 'ofm') or "" -- bonus for usage outside context
- 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 "" -- just to be sure
- if foundname == "" and tfm.auto_afm then
- local encoding, shortname = match(fullname,"^(.-)%-(.*)$") -- context: encoding-name.*
- if encoding and shortname and fonts.enc.known[encoding] then
- shortname = resolvers.findbinfile(shortname,'afm') or "" -- just to be sure
- if shortname ~= "" then
- foundname = shortname
- -- tfm.set_normal_feature(specification,'encoding',encoding) -- will go away
- 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 -- method == "afm or tfm" or method == "" then
- tfmtable = check_afm(specification,specification.name) or check_tfm(specification,specification.name)
- end
- end
- else
- tfmtable = check_afm(specification,fullname)
- end
- return tfmtable
+ return fontdata[id], id
end
--- maybe some day a set of names
-
-local function check_otf(forced,specification,suffix,what)
- local name = specification.name
- if forced then
- name = file.addsuffix(name,suffix)
- end
- local fullname, tfmtable = resolvers.findbinfile(name,suffix) or "", nil -- one shot
- 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 -- hm, so we do set the filename, then
- tfmtable = tfm.read_from_open_type(specification) -- we need to do it for all matches / todo
- 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 -- !!
-
--[[ldx--
-<p>We need to check for default features. For this we provide
-a helper function.</p>
---ldx]]--
-
-function define.check(features,defaults) -- nb adapts features !
- 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 -- done signals a change
-end
-
---[[ldx--
-<p>So far the specifyers. Now comes the real definer. Here we cache
+<p>So far the specifiers. Now comes the real definer. Here we cache
based on id's. Here we also intercept the virtual font handler. Since
it evolved stepwise I may rewrite this bit (combine code).</p>
@@ -11828,238 +11844,146 @@ not gain much. By the way, passing id's back to in the callback was
introduced later in the development.</p>
--ldx]]--
-define.last = nil
+local lastdefined = nil -- we don't want this one to end up in s-tra-02
+local internalized = { }
-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
- -- todo: extra functions, e.g. setdigitwidth etc in list
- tfm.internalized[hash] = id
- end
- end
+function definers.current() -- or maybe current
+ return lastdefined
end
-function define.registered(hash)
- local id = tfm.internalized[hash]
- return id, id and fonts.ids[id]
+function definers.registered(hash)
+ local id = internalized[hash]
+ return id, id and fontdata[id]
end
-local cache_them = false
-
-function tfm.make(specification)
- -- currently fonts are scaled while constructing the font, so we
- -- have to do scaling of commands in the vf at that point using
- -- e.g. "local scale = g.factor or 1" after all, we need to work
- -- with copies anyway and scaling needs to be done at some point;
- -- however, when virtual tricks are used as feature (makes more
- -- sense) we scale the commands in fonts.tfm.scale (and set the
- -- factor there)
- local fvm = define.methods[specification.features.vtf.preset]
- if fvm then
- return fvm(specification)
- else
- return nil
+function definers.register(tfmdata,id)
+ if tfmdata and id then
+ local hash = tfmdata.properties.hash
+ if not internalized[hash] then
+ internalized[hash] = id
+ if trace_defining then
+ report_defining("registering font, id: %s, hash: %s",id or "?",hash or "?")
+ end
+ fontdata[id] = tfmdata
+ end
end
end
-function define.read(specification,size,id) -- id can be optional, name can already be table
+function definers.read(specification,size,id) -- id can be optional, name can already be table
statistics.starttiming(fonts)
if type(specification) == "string" then
- specification = define.analyze(specification,size)
+ specification = definers.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) -- for tracing purposes
- end
- local fontdata = define.registered(hash) -- id
- 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) -- for tracing purposes
- end
- if fontdata then
- fontdata.hash = hash
- fontdata.cache = "no"
- if id then
- define.register(fontdata,id)
- end
- end
+ if method and variants[method] then
+ specification = variants[method](specification)
end
- define.last = fontdata or id -- todo ! ! ! ! !
- 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 "?"))
-
+ specification = definers.resolve(specification)
+ local hash = constructors.hashinstance(specification)
+ local tfmdata = definers.registered(hash) -- id
+ if not tfmdata then
+ tfmdata = definers.loadfont(specification) -- can be overloaded
+ if tfmdata then
+--~ constructors.checkvirtualid(tfmdata) -- interferes
+ tfmdata.properties.hash = hash
+ if id then
+ definers.register(tfmdata,id)
+ end
+ end
+ end
+ lastdefined = tfmdata or id -- todo ! ! ! ! !
+ if not tfmdata then -- or id?
+ report_defining( "unknown font %s, loading aborted",specification.name)
+ elseif trace_defining and type(tfmdata) == "table" then
+ local properties = tfmdata.properties or { }
+ local parameters = tfmdata.parameters or { }
+ report_defining("using %s font with id %s, name:%s size:%s bytes:%s encoding:%s fullname:%s filename:%s",
+ properties.type or "unknown",
+ id or "?",
+ properties.name or "?",
+ parameters.size or "default",
+ properties.encodingbytes or "?",
+ properties.encodingname or "unicode",
+ properties.fullname or "?",
+ file.basename(properties.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
+ return tfmdata
end
--[[ldx--
-<p>We overload both the <l n='tfm'/> and <l n='vf'/> readers.</p>
+<p>We overload the <l n='tfm'/> reader.</p>
--ldx]]--
-callbacks.register('define_font' , define.read, "definition of fonts (tfmtable preparation)")
-callbacks.register('find_vf_file', vf.find , "locating virtual fonts, insofar needed") -- not that relevant any more
+callbacks.register('define_font', definers.read, "definition of fonts (tfmdata preparation)")
end -- closure
do -- begin closure to overcome local limits and interference
-if not modules then modules = { } end modules ['font-xtx'] = {
+if not modules then modules = { } end modules ['luatex-font-def'] = {
version = 1.001,
- comment = "companion to font-ini.mkiv",
+ 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 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
+if context then
+ texio.write_nl("fatal error: this module is not for context")
+ os.exit()
+end
-local trace_defining = false trackers.register("fonts.defining", function(v) trace_defining = v end)
+local fonts = fonts
---[[ldx--
-<p>Choosing a font by name and specififying its size is only part of the
-game. In order to prevent complex commands, <l n='xetex'/> introduced
-a method to pass feature information as part of the font name. At the
-risk of introducing nasty parsing and compatinility problems, this
-syntax was expanded over time.</p>
-
-<p>For the sake of users who have defined fonts using that syntax, we
-will support it, but we will provide additional methods as well.
-Normally users will not use this direct way, but use a more abstract
-interface.</p>
-
-<p>The next one is the official one. However, in the plain
-variant we need to support the crappy [] specification as
-well and that does not work too well with the general design
-of the specifier.</p>
---ldx]]--
+-- A bit of tuning for definitions.
---~ function fonts.define.specify.colonized(specification) -- xetex mode
---~ local list = { }
---~ if specification.detail and specification.detail ~= "" then
---~ for v in gmatch(specification.detail,"%s*([^;]+)%s*") do
---~ local a, b = match(v,"^(%S*)%s*=%s*(%S*)$")
---~ if a and b then
---~ list[a] = b:is_boolean()
---~ if type(list[a]) == "nil" then
---~ list[a] = b
---~ end
---~ else
---~ local a, b = match(v,"^([%+%-]?)%s*(%S+)$")
---~ if a and b then
---~ list[b] = a ~= "-"
---~ end
---~ end
---~ end
---~ end
---~ specification.features.normal = list
---~ return specification
---~ end
+fonts.constructors.namemode = "specification" -- somehow latex needs this (changed name!) => will change into an overload
---~ check("oeps/BI:+a;-b;c=d")
---~ check("[oeps]/BI:+a;-b;c=d")
---~ check("file:oeps/BI:+a;-b;c=d")
---~ check("name:oeps/BI:+a;-b;c=d")
+-- tricky: we sort of bypass the parser and directly feed all into
+-- the sub parser
-local list = { }
+function fonts.definers.getspecification(str)
+ return "", str, "", ":", str
+end
-fonts.define.specify.colonized_default_lookup = "file"
+-- the generic name parser (different from context!)
+
+local list = { }
-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 issome () list.lookup = 'name' end -- xetex mode prefers name (not in context!)
+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 iscrap (s) list.crap = string.lower(s) end
-local function istrue (s) list[s] = 'yes' end
-local function isfalse(s) list[s] = 'no' end
-local function iskey (k,v) 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 -- was: (1-lpeg.S("/: ("))^0
-local crapspec = spaces * lpeg.P("/") * (((1-lpeg.P(":"))^0)/iscrap) * spaces
-local filename = (lpeg.P("file:")/isfile * (namespec/thename)) + (lpeg.P("[") * lpeg.P(true)/isname * (((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.C(sometext) * spaces * lpeg.P("=") * spaces * lpeg.C(sometext))/iskey
+local function iskey (k,v) list[k] = v end
+local function istrue (s) list[s] = true end
+local function isfalse(s) list[s] = false end
+
+local P, S, R, C = lpeg.P, lpeg.S, lpeg.R, lpeg.C
+
+local spaces = P(" ")^0
+local namespec = (1-S("/:("))^0 -- was: (1-S("/: ("))^0
+local crapspec = spaces * P("/") * (((1-P(":"))^0)/iscrap) * spaces
+local filename_1 = P("file:")/isfile * (namespec/thename)
+local filename_2 = P("[") * P(true)/isname * (((1-P("]"))^0)/thename) * P("]")
+local fontname_1 = P("name:")/isname * (namespec/thename)
+local fontname_2 = P(true)/issome * (namespec/thename)
+local sometext = (R("az","AZ","09") + S("+-."))^1
+local truevalue = P("+") * spaces * (sometext/istrue)
+local falsevalue = P("-") * spaces * (sometext/isfalse)
+local keyvalue = (C(sometext) * spaces * P("=") * spaces * C(sometext))/iskey
local somevalue = sometext/istrue
-local subvalue = lpeg.P("(") * (lpeg.C(lpeg.P(1-lpeg.S("()"))^1)/issub) * lpeg.P(")") -- for Kim
+local subvalue = P("(") * (C(P(1-S("()"))^1)/issub) * P(")") -- for Kim
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 options = P(":") * spaces * (P(";")^0 * option)^0
-local normalize_meanings = fonts.otf.meanings.normalize
+local pattern = (filename_1 + filename_2 + fontname_1 + fontname_2) * subvalue^0 * crapspec^0 * options^0
-function fonts.define.specify.colonized(specification) -- xetex mode
+local function colonized(specification) -- xetex mode
list = { }
- lpegmatch(pattern,specification.specification)
---~ for k, v in next, list do
---~ list[k] = v:is_boolean()
---~ if type(list[a]) == "nil" then
---~ list[k] = v
---~ end
---~ end
+ lpeg.match(pattern,specification.specification)
list.crap = nil -- style not supported, maybe some day
if list.name then
specification.name = list.name
@@ -12073,18 +11997,33 @@ function fonts.define.specify.colonized(specification) -- xetex mode
specification.sub = list.sub
list.sub = nil
end
--- specification.features.normal = list
- specification.features.normal = normalize_meanings(list)
+ specification.features.normal = fonts.handlers.otf.features.normalize(list)
return specification
end
-fonts.define.register_split(":", fonts.define.specify.colonized)
+fonts.definers.registersplit(":",colonized,"cryptic")
+fonts.definers.registersplit("", colonized,"more cryptic") -- catches \font\text=[names]
+
+function fonts.definers.applypostprocessors(tfmdata)
+ local postprocessors = tfmdata.postprocessors
+ if postprocessors then
+ for i=1,#postprocessors do
+ local extrahash = postprocessors[i](tfmdata) -- after scaling etc
+ if type(extrahash) == "string" and extrahash ~= "" then
+ -- e.g. a reencoding needs this
+ extrahash = string.gsub(lower(extrahash),"[^a-z]","-")
+ tfmdata.properties.fullname = format("%s-%s",tfmdata.properties.fullname,extrahash)
+ end
+ end
+ end
+ return tfmdata
+end
end -- closure
do -- begin closure to overcome local limits and interference
-if not modules then modules = { } end modules ['font-dum'] = {
+if not modules then modules = { } end modules ['luatex-fonts-ext'] = {
version = 1.001,
comment = "companion to luatex-*.tex",
author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
@@ -12092,120 +12031,46 @@ if not modules then modules = { } end modules ['font-dum'] = {
license = "see context related readme files"
}
-fonts = fonts or { }
-
--- general
-
-fonts.otf.pack = false -- only makes sense in context
-fonts.tfm.resolve_vf = false -- no sure about this
-fonts.tfm.fontname_mode = "specification" -- somehow latex needs this
-
--- readers
-
-fonts.tfm.readers = fonts.tfm.readers or { }
-fonts.tfm.readers.sequence = { 'otf', 'ttf', 'tfm' }
-fonts.tfm.readers.afm = nil
-
--- define
-
-fonts.define = fonts.define or { }
-
---~ fonts.define.method = "tfm"
-
-fonts.define.specify.colonized_default_lookup = "name"
-
-function fonts.define.get_specification(str)
- return "", str, "", ":", str
+if context then
+ texio.write_nl("fatal error: this module is not for context")
+ os.exit()
end
--- logger
-
-fonts.logger = fonts.logger or { }
-
-function fonts.logger.save()
-end
-
--- names
---
--- Watch out, the version number is the same as the one used in
--- the mtx-fonts.lua function scripts.fonts.names as we use a
--- simplified font database in the plain solution and by using
--- a different number we're less dependent on context.
-
-fonts.names = fonts.names or { }
-
-fonts.names.version = 1.001 -- not the same as in context
-fonts.names.basename = "luatex-fonts-names.lua"
-fonts.names.new_to_old = { }
-fonts.names.old_to_new = { }
-
-local data, loaded = nil, false
+local fonts = fonts
+local otffeatures = fonts.constructors.newfeatures("otf")
-local fileformats = { "lua", "tex", "other text files" }
+-- A few generic extensions.
-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 -- fallback to filename
- end
- end
-end
-
-fonts.names.resolvespec = fonts.names.resolve -- only supported in mkiv
-
--- For the moment we put this (adapted) pseudo feature here.
-
-table.insert(fonts.triggers,"itlc")
-
-local function itlc(tfmdata,value)
+local function initializeitlc(tfmdata,value)
if value then
-- the magic 40 and it formula come from Dohyun Kim
- 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
+ local parameters = tfmdata.parameters
+ local italicangle = parameters.italicangle
+ if italicangle and italicangle ~= 0 then
+ local uwidth = (parameters.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
- tfmdata.has_italic = true
end
+ tfmdata.properties.italic_correction = true
end
end
end
-fonts.initializers.base.otf.itlc = itlc
-fonts.initializers.node.otf.itlc = itlc
+otffeatures.register {
+ name = "itlc",
+ description = "italic correction",
+ initializers = {
+ base = initializeitlc,
+ node = initializeitlc,
+ }
+}
-- slant and extend
-function fonts.initializers.common.slant(tfmdata,value)
+local function initializeslant(tfmdata,value)
value = tonumber(value)
if not value then
value = 0
@@ -12214,10 +12079,19 @@ function fonts.initializers.common.slant(tfmdata,value)
elseif value < -1 then
value = -1
end
- tfmdata.slant_factor = value
+ tfmdata.parameters.slant_factor = value
end
-function fonts.initializers.common.extend(tfmdata,value)
+otffeatures.register {
+ name = "slant",
+ description = "slant glyphs",
+ initializers = {
+ base = initializeslant,
+ node = initializeslant,
+ }
+}
+
+local function initializeextend(tfmdata,value)
value = tonumber(value)
if not value then
value = 0
@@ -12226,31 +12100,34 @@ function fonts.initializers.common.extend(tfmdata,value)
elseif value < -10 then
value = -10
end
- tfmdata.extend_factor = value
+ tfmdata.parameters.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
+otffeatures.register {
+ name = "extend",
+ description = "scale glyphs horizontally",
+ initializers = {
+ base = initializeextend,
+ node = initializeextend,
+ }
+}
-- expansion and protrusion
fonts.protrusions = fonts.protrusions or { }
fonts.protrusions.setups = fonts.protrusions.setups or { }
-local setups = fonts.protrusions.setups
+local setups = fonts.protrusions.setups
-function fonts.initializers.common.protrusion(tfmdata,value)
+local function initializeprotrusion(tfmdata,value)
if 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
+ tfmdata.parameters.protrusion = {
+ auto = true,
+ }
for i, chr in next, tfmdata.characters do
local v, pl, pr = setup[i], nil, nil
if v then
@@ -12263,17 +12140,31 @@ function fonts.initializers.common.protrusion(tfmdata,value)
end
end
+otffeatures.register {
+ name = "protrusion",
+ description = "shift characters into the left and or right margin",
+ initializers = {
+ base = initializeprotrusion,
+ node = initializeprotrusion,
+ }
+}
+
fonts.expansions = fonts.expansions or { }
fonts.expansions.setups = fonts.expansions.setups or { }
local setups = fonts.expansions.setups
-function fonts.initializers.common.expansion(tfmdata,value)
+local function initializeexpansion(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
+ local factor = setup.factor or 1
+ tfmdata.parameters.expansion = {
+ stretch = 10 * (setup.stretch or 0),
+ shrink = 10 * (setup.shrink or 0),
+ step = 10 * (setup.step or 0),
+ auto = true,
+ }
for i, chr in next, tfmdata.characters do
local v = setup[i]
if v and v ~= 0 then
@@ -12286,18 +12177,18 @@ function fonts.initializers.common.expansion(tfmdata,value)
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
+otffeatures.register {
+ name = "expansion",
+ description = "apply hz optimization",
+ initializers = {
+ base = initializeexpansion,
+ node = initializeexpansion,
+ }
+}
-- left over
-function fonts.register_message()
-end
+function fonts.loggers.onetimemessage() end
-- example vectors
@@ -12339,51 +12230,147 @@ fonts.protrusions.setups['default'] = {
-- normalizer
-fonts.otf.meanings = fonts.otf.meanings or { }
-
-fonts.otf.meanings.normalize = fonts.otf.meanings.normalize or function(t)
+fonts.handlers.otf.features.normalize = function(t)
if t.rand then
t.rand = "random"
end
+ return t
end
-- bonus
-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])
+function fonts.helpers.nametoslot(name)
+ local t = type(name)
+ if t == "string" then
+ local tfmdata = fonts.hashes.identifiers[currentfont()]
+ local shared = tfmdata and tfmdata.shared
+ local fntdata = shared and shared.rawdata
+ return fntdata and fntdata.resources.unicodes[name]
+ elseif t == "number" then
+ return n
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
-
--- another one:
-
-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,
+-- \font\test=file:somefont:reencode=mymessup
+--
+-- fonts.encodings.reencodings.mymessup = {
+-- [109] = 110, -- m
+-- [110] = 109, -- n
+-- }
+
+fonts.encodings = fonts.encodings or { }
+local reencodings = { }
+fonts.encodings.reencodings = reencodings
+
+local function specialreencode(tfmdata,value)
+ -- we forget about kerns as we assume symbols and we
+ -- could issue a message if ther are kerns but it's
+ -- a hack anyway so we odn't care too much here
+ local encoding = value and reencodings[value]
+ if encoding then
+ local temp = { }
+ local char = tfmdata.characters
+ for k, v in next, encoding do
+ temp[k] = char[v]
+ end
+ for k, v in next, temp do
+ char[k] = temp[k]
+ end
+ -- if we use the font otherwise luatex gets confused so
+ -- we return an additional hash component for fullname
+ return string.format("reencoded:%s",value)
+ end
+end
+
+local function reencode(tfmdata,value)
+ tfmdata.postprocessors = tfmdata.postprocessors or { }
+ table.insert(tfmdata.postprocessors,
+ function(tfmdata)
+ return specialreencode(tfmdata,value)
+ end
+ )
+end
+
+otffeatures.register {
+ name = "reencode",
+ description = "reencode characters",
+ manipulators = {
+ base = reencode,
+ node = reencode,
+ }
+}
+
+end -- closure
+
+do -- begin closure to overcome local limits and interference
+
+if not modules then modules = { } end modules ['luatex-fonts-cbk'] = {
+ 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"
}
+if context then
+ texio.write_nl("fatal error: this module is not for context")
+ os.exit()
+end
+
+local fonts = fonts
+local nodes = nodes
+
+-- Fonts: (might move to node-gef.lua)
+
+local traverse_id = node.traverse_id
+local glyph_code = nodes.nodecodes.glyph
+
+function nodes.handlers.characters(head)
+ local fontdata = fonts.hashes.identifiers
+ if fontdata then
+ local usedfonts, done, prevfont = { }, false, nil
+ for n in traverse_id(glyph_code,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 -- we need to check shared, only when same features
+ 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
+ else
+ return head, false
+ end
+end
+
+function nodes.simple_font_handler(head)
+-- lang.hyphenate(head)
+ head = nodes.handlers.characters(head)
+ nodes.injections.handler(head)
+ nodes.handlers.protectglyphs(head)
+ head = node.ligaturing(head)
+ head = node.kerning(head)
+ return head
+end
end -- closure