diff options
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.lua | 14505 |
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 |