-- merged file : luatex-fonts-merged.lua
-- parent file : luatex-fonts.lua
-- merge date : 11/04/13 11:52:02
do -- begin closure to overcome local limits and interference
if not modules then modules={} end modules ['l-lua']={
version=1.001,
comment="companion to luat-lib.mkiv",
author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
copyright="PRAGMA ADE / ConTeXt Development Team",
license="see context related readme files"
}
local major,minor=string.match(_VERSION,"^[^%d]+(%d+)%.(%d+).*$")
_MAJORVERSION=tonumber(major) or 5
_MINORVERSION=tonumber(minor) or 1
_LUAVERSION=_MAJORVERSION+_MINORVERSION/10
if not lpeg then
lpeg=require("lpeg")
end
if loadstring then
local loadnormal=load
function load(first,...)
if type(first)=="string" then
return loadstring(first,...)
else
return loadnormal(first,...)
end
end
else
loadstring=load
end
if not ipairs then
local function iterate(a,i)
i=i+1
local v=a[i]
if v~=nil then
return i,v
end
end
function ipairs(a)
return iterate,a,0
end
end
if not pairs then
function pairs(t)
return next,t
end
end
if not table.unpack then
table.unpack=_G.unpack
elseif not unpack then
_G.unpack=table.unpack
end
if not package.loaders then
package.loaders=package.searchers
end
local print,select,tostring=print,select,tostring
local inspectors={}
function setinspector(inspector)
inspectors[#inspectors+1]=inspector
end
function inspect(...)
for s=1,select("#",...) do
local value=select(s,...)
local done=false
for i=1,#inspectors do
done=inspectors[i](value)
if done then
break
end
end
if not done then
print(tostring(value))
end
end
end
local dummy=function() end
function optionalrequire(...)
local ok,result=xpcall(require,dummy,...)
if ok then
return result
end
end
end -- closure
do -- begin closure to overcome local limits and interference
if not modules then modules={} end modules ['l-lpeg']={
version=1.001,
comment="companion to luat-lib.mkiv",
author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
copyright="PRAGMA ADE / ConTeXt Development Team",
license="see context related readme files"
}
lpeg=require("lpeg")
if not lpeg.print then function lpeg.print(...) print(lpeg.pcode(...)) end end
local type,next,tostring=type,next,tostring
local byte,char,gmatch,format=string.byte,string.char,string.gmatch,string.format
local floor=math.floor
local P,R,S,V,Ct,C,Cs,Cc,Cp,Cmt=lpeg.P,lpeg.R,lpeg.S,lpeg.V,lpeg.Ct,lpeg.C,lpeg.Cs,lpeg.Cc,lpeg.Cp,lpeg.Cmt
local lpegtype,lpegmatch,lpegprint=lpeg.type,lpeg.match,lpeg.print
setinspector(function(v) if lpegtype(v) then lpegprint(v) return true end end)
lpeg.patterns=lpeg.patterns or {}
local patterns=lpeg.patterns
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 sign=S('+-')
local zero=P('0')
local digit=R('09')
local octdigit=R("07")
local lowercase=R("az")
local uppercase=R("AZ")
local underscore=P("_")
local hexdigit=digit+lowercase+uppercase
local cr,lf,crlf=P("\r"),P("\n"),P("\r\n")
local newline=crlf+S("\r\n")
local escaped=P("\\")*anything
local squote=P("'")
local dquote=P('"')
local space=P(" ")
local period=P(".")
local comma=P(",")
local utfbom_32_be=P('\000\000\254\255')
local utfbom_32_le=P('\255\254\000\000')
local utfbom_16_be=P('\254\255')
local utfbom_16_le=P('\255\254')
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*Cc("utf-32-be")+utfbom_32_le*Cc("utf-32-le")+utfbom_16_be*Cc("utf-16-be")+utfbom_16_le*Cc("utf-16-le")+utfbom_8*Cc("utf-8")+alwaysmatched*Cc("utf-8")
local utfstricttype=utfbom_32_be*Cc("utf-32-be")+utfbom_32_le*Cc("utf-32-le")+utfbom_16_be*Cc("utf-16-be")+utfbom_16_le*Cc("utf-16-le")+utfbom_8*Cc("utf-8")
local utfoffset=utfbom_32_be*Cc(4)+utfbom_32_le*Cc(4)+utfbom_16_be*Cc(2)+utfbom_16_le*Cc(2)+utfbom_8*Cc(3)+Cc(0)
local utf8next=R("\128\191")
patterns.utfbom_32_be=utfbom_32_be
patterns.utfbom_32_le=utfbom_32_le
patterns.utfbom_16_be=utfbom_16_be
patterns.utfbom_16_le=utfbom_16_le
patterns.utfbom_8=utfbom_8
patterns.utf_16_be_nl=P("\000\r\000\n")+P("\000\r")+P("\000\n")
patterns.utf_16_le_nl=P("\r\000\n\000")+P("\r\000")+P("\n\000")
patterns.utf8one=R("\000\127")
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
patterns.utfstricttype=utfstricttype
patterns.utfoffset=utfoffset
local utf8char=patterns.utf8one+patterns.utf8two+patterns.utf8three+patterns.utf8four
local validutf8char=utf8char^0*endofstring*Cc(true)+Cc(false)
local utf8character=P(1)*R("\128\191")^0
patterns.utf8=utf8char
patterns.utf8char=utf8char
patterns.utf8character=utf8character
patterns.validutf8=validutf8char
patterns.validutf8char=validutf8char
local eol=S("\n\r")
local spacer=S(" \t\f\v")
local whitespace=eol+spacer
local nonspacer=1-spacer
local nonwhitespace=1-whitespace
patterns.eol=eol
patterns.spacer=spacer
patterns.whitespace=whitespace
patterns.nonspacer=nonspacer
patterns.nonwhitespace=nonwhitespace
local stripper=spacer^0*C((spacer^0*nonspacer^1)^0)
local collapser=Cs(spacer^0/""*nonspacer^0*((spacer^0/" "*nonspacer^1)^0))
patterns.stripper=stripper
patterns.collapser=collapser
patterns.lowercase=lowercase
patterns.uppercase=uppercase
patterns.letter=patterns.lowercase+patterns.uppercase
patterns.space=space
patterns.tab=P("\t")
patterns.spaceortab=patterns.space+patterns.tab
patterns.newline=newline
patterns.emptyline=newline^1
patterns.equal=P("=")
patterns.comma=comma
patterns.commaspacer=comma*spacer^0
patterns.period=period
patterns.colon=P(":")
patterns.semicolon=P(";")
patterns.underscore=underscore
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
patterns.unspacer=((patterns.spacer^1)/"")^0
patterns.singlequoted=squote*patterns.nosquote*squote
patterns.doublequoted=dquote*patterns.nodquote*dquote
patterns.quoted=patterns.doublequoted+patterns.singlequoted
patterns.digit=digit
patterns.octdigit=octdigit
patterns.hexdigit=hexdigit
patterns.sign=sign
patterns.cardinal=digit^1
patterns.integer=sign^-1*digit^1
patterns.unsigned=digit^0*period*digit^1
patterns.float=sign^-1*patterns.unsigned
patterns.cunsigned=digit^0*comma*digit^1
patterns.cfloat=sign^-1*patterns.cunsigned
patterns.number=patterns.float+patterns.integer
patterns.cnumber=patterns.cfloat+patterns.integer
patterns.oct=zero*octdigit^1
patterns.octal=patterns.oct
patterns.HEX=zero*P("X")*(digit+uppercase)^1
patterns.hex=zero*P("x")*(digit+lowercase)^1
patterns.hexadecimal=zero*S("xX")*hexdigit^1
patterns.hexafloat=sign^-1*zero*S("xX")*(hexdigit^0*period*hexdigit^1+hexdigit^1*period*hexdigit^0+hexdigit^1)*(S("pP")*sign^-1*hexdigit^1)^-1
patterns.decafloat=sign^-1*(digit^0*period*digit^1+digit^1*period*digit^0+digit^1)*S("eE")*sign^-1*digit^1
patterns.propername=(uppercase+lowercase+underscore)*(uppercase+lowercase+underscore+digit)^0*endofstring
patterns.somecontent=(anything-newline-space)^1
patterns.beginline=#(1-newline)
patterns.longtostring=Cs(whitespace^0/""*((patterns.quoted+nonwhitespace^1+whitespace^1/""*(P(-1)+Cc(" ")))^0))
local function anywhere(pattern)
return P { P(pattern)+1*V(1) }
end
lpeg.anywhere=anywhere
function lpeg.instringchecker(p)
p=anywhere(p)
return function(str)
return lpegmatch(p,str) and true or false
end
end
function lpeg.splitter(pattern,action)
return (((1-P(pattern))^1)/action+1)^0
end
function lpeg.tsplitter(pattern,action)
return Ct((((1-P(pattern))^1)/action+1)^0)
end
local splitters_s,splitters_m,splitters_t={},{},{}
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 any=anything
splitter=other*(separator*C(any^0)+"")
splitters_s[separator]=splitter
else
splitter=other*(separator*other)^0
splitters_m[separator]=splitter
end
end
return splitter
end
local function tsplitat(separator)
local splitter=splitters_t[separator]
if not splitter then
splitter=Ct(splitat(separator))
splitters_t[separator]=splitter
end
return splitter
end
lpeg.splitat=splitat
lpeg.tsplitat=tsplitat
function string.splitup(str,separator)
if not separator then
separator=","
end
return lpegmatch(splitters_m[separator] or splitat(separator),str)
end
local cache={}
function lpeg.split(separator,str)
local c=cache[separator]
if not c then
c=tsplitat(separator)
cache[separator]=c
end
return lpegmatch(c,str)
end
function string.split(str,separator)
if separator then
local c=cache[separator]
if not c then
c=tsplitat(separator)
cache[separator]=c
end
return lpegmatch(c,str)
else
return { str }
end
end
local spacing=patterns.spacer^0*newline
local empty=spacing*Cc("")
local nonempty=Cs((1-spacing)^1)*spacing^-1
local content=(empty+nonempty)^1
patterns.textline=content
local linesplitter=tsplitat(newline)
patterns.linesplitter=linesplitter
function string.splitlines(str)
return lpegmatch(linesplitter,str)
end
local cache={}
function lpeg.checkedsplit(separator,str)
local c=cache[separator]
if not c then
separator=P(separator)
local other=C((1-separator)^1)
c=Ct(separator^0*other*(separator^1*other)^0)
cache[separator]=c
end
return lpegmatch(c,str)
end
function string.checkedsplit(str,separator)
local c=cache[separator]
if not c then
separator=P(separator)
local other=C((1-separator)^1)
c=Ct(separator^0*other*(separator^1*other)^0)
cache[separator]=c
end
return lpegmatch(c,str)
end
local function f2(s) local c1,c2=byte(s,1,2) return c1*64+c2-12416 end
local function f3(s) local c1,c2,c3=byte(s,1,3) return (c1*64+c2)*64+c3-925824 end
local function f4(s) local c1,c2,c3,c4=byte(s,1,4) return ((c1*64+c2)*64+c3)*64+c4-63447168 end
local utf8byte=patterns.utf8one/byte+patterns.utf8two/f2+patterns.utf8three/f3+patterns.utf8four/f4
patterns.utf8byte=utf8byte
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)
return (P(str)+P(true))*Cs(anything^0)
end
function lpeg.endstripper(str)
return Cs((1-P(str)*endofstring)^0)
end
function lpeg.replacer(one,two,makefunction,isutf)
local pattern
local u=isutf and utf8char or 1
if type(one)=="table" then
local no=#one
local p=P(false)
if no==0 then
for k,v in next,one do
p=p+P(k)/v
end
pattern=Cs((p+u)^0)
elseif no==1 then
local o=one[1]
one,two=P(o[1]),o[2]
pattern=Cs((one/two+u)^0)
else
for i=1,no do
local o=one[i]
p=p+P(o[1])/o[2]
end
pattern=Cs((p+u)^0)
end
else
pattern=Cs((P(one)/(two or "")+u)^0)
end
if makefunction then
return function(str)
return lpegmatch(pattern,str)
end
else
return pattern
end
end
function lpeg.finder(lst,makefunction)
local pattern
if type(lst)=="table" then
pattern=P(false)
if #lst==0 then
for k,v in next,lst do
pattern=pattern+P(k)
end
else
for i=1,#lst do
pattern=pattern+P(lst[i])
end
end
else
pattern=P(lst)
end
pattern=(1-pattern)^0*pattern
if makefunction then
return function(str)
return lpegmatch(pattern,str)
end
else
return pattern
end
end
local splitters_f,splitters_s={},{}
function lpeg.firstofsplit(separator)
local splitter=splitters_f[separator]
if not splitter then
local pattern=P(separator)
splitter=C((1-pattern)^0)
splitters_f[separator]=splitter
end
return splitter
end
function lpeg.secondofsplit(separator)
local splitter=splitters_s[separator]
if not splitter then
local pattern=P(separator)
splitter=(1-pattern)^0*pattern*C(anything^0)
splitters_s[separator]=splitter
end
return splitter
end
local splitters_s,splitters_p={},{}
function lpeg.beforesuffix(separator)
local splitter=splitters_s[separator]
if not splitter then
local pattern=P(separator)
splitter=C((1-pattern)^0)*pattern*endofstring
splitters_s[separator]=splitter
end
return splitter
end
function lpeg.afterprefix(separator)
local splitter=splitters_p[separator]
if not splitter then
local pattern=P(separator)
splitter=pattern*C(anything^0)
splitters_p[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
local nany=utf8char/""
function lpeg.counter(pattern)
pattern=Cs((P(pattern)/" "+nany)^0)
return function(str)
return #lpegmatch(pattern,str)
end
end
utf=utf or (unicode and unicode.utf8) or {}
local utfcharacters=utf and utf.characters or string.utfcharacters
local utfgmatch=utf and utf.gmatch
local utfchar=utf and utf.char
lpeg.UP=lpeg.P
if utfcharacters then
function lpeg.US(str)
local p=P(false)
for uc in utfcharacters(str) do
p=p+P(uc)
end
return p
end
elseif utfgmatch then
function lpeg.US(str)
local p=P(false)
for uc in utfgmatch(str,".") do
p=p+P(uc)
end
return p
end
else
function lpeg.US(str)
local p=P(false)
local f=function(uc)
p=p+P(uc)
end
lpegmatch((utf8char/f)^0,str)
return p
end
end
local range=utf8byte*utf8byte+Cc(false)
function lpeg.UR(str,more)
local first,last
if type(str)=="number" then
first=str
last=more or first
else
first,last=lpegmatch(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
local p=P(false)
for i=first,last do
p=p+P(utfchar(i))
end
return p
else
local f=function(b)
return b>=first and b<=last
end
return utf8byte/f
end
end
function lpeg.is_lpeg(p)
return p and lpegtype(p)=="pattern"
end
function lpeg.oneof(list,...)
if type(list)~="table" then
list={ list,... }
end
local p=P(list[1])
for l=2,#list do
p=p+P(list[l])
end
return p
end
local sort=table.sort
local function copyindexed(old)
local new={}
for i=1,#old do
new[i]=old
end
return new
end
local function sortedkeys(tab)
local keys,s={},0
for key,_ in next,tab do
s=s+1
keys[s]=key
end
sort(keys)
return keys
end
function lpeg.append(list,pp,delayed,checked)
local p=pp
if #list>0 then
local keys=copyindexed(list)
sort(keys)
for i=#keys,1,-1 do
local k=keys[i]
if p then
p=P(k)+p
else
p=P(k)
end
end
elseif delayed then
local keys=sortedkeys(list)
if p then
for i=1,#keys,1 do
local k=keys[i]
local v=list[k]
p=P(k)/list+p
end
else
for i=1,#keys do
local k=keys[i]
local v=list[k]
if p then
p=P(k)+p
else
p=P(k)
end
end
if p then
p=p/list
end
end
elseif checked then
local keys=sortedkeys(list)
for i=1,#keys do
local k=keys[i]
local v=list[k]
if p then
if k==v then
p=P(k)+p
else
p=P(k)/v+p
end
else
if k==v then
p=P(k)
else
p=P(k)/v
end
end
end
else
local keys=sortedkeys(list)
for i=1,#keys do
local k=keys[i]
local v=list[k]
if p then
p=P(k)/v+p
else
p=P(k)/v
end
end
end
return p
end
local function make(t)
local p
local keys=sortedkeys(t)
for i=1,#keys do
local k=keys[i]
local v=t[k]
if not p then
if next(v) then
p=P(k)*make(v)
else
p=P(k)
end
else
if next(v) then
p=p+P(k)*make(v)
else
p=p+P(k)
end
end
end
return p
end
function lpeg.utfchartabletopattern(list)
local tree={}
for i=1,#list do
local t=tree
for c in gmatch(list[i],".") do
if not t[c] then
t[c]={}
end
t=t[c]
end
end
return make(tree)
end
patterns.containseol=lpeg.finder(eol)
local function nextstep(n,step,result)
local m=n%step
local d=floor(n/step)
if d>0 then
local v=V(tostring(step))
local s=result.start
for i=1,d do
if s then
s=v*s
else
s=v
end
end
result.start=s
end
if step>1 and result.start then
local v=V(tostring(step/2))
result[tostring(step)]=v*v
end
if step>0 then
return nextstep(m,step/2,result)
else
return result
end
end
function lpeg.times(pattern,n)
return P(nextstep(n,2^16,{ "start",["1"]=pattern }))
end
local trailingzeros=zero^0*-digit
local case_1=period*trailingzeros/""
local case_2=period*(digit-trailingzeros)^1*(trailingzeros/"")
local number=digit^1*(case_1+case_2)
local stripper=Cs((number+1)^0)
lpeg.patterns.stripzeros=stripper
end -- closure
do -- begin closure to overcome local limits and interference
if not modules then modules={} end modules ['l-functions']={
version=1.001,
comment="companion to luat-lib.mkiv",
author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
copyright="PRAGMA ADE / ConTeXt Development Team",
license="see context related readme files"
}
functions=functions or {}
function functions.dummy() end
end -- closure
do -- begin closure to overcome local limits and interference
if not modules then modules={} end modules ['l-string']={
version=1.001,
comment="companion to luat-lib.mkiv",
author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
copyright="PRAGMA ADE / ConTeXt Development Team",
license="see context related readme files"
}
local string=string
local sub,gmatch,format,char,byte,rep,lower=string.sub,string.gmatch,string.format,string.char,string.byte,string.rep,string.lower
local lpegmatch,patterns=lpeg.match,lpeg.patterns
local P,S,C,Ct,Cc,Cs=lpeg.P,lpeg.S,lpeg.C,lpeg.Ct,lpeg.Cc,lpeg.Cs
local unquoted=patterns.squote*C(patterns.nosquote)*patterns.squote+patterns.dquote*C(patterns.nodquote)*patterns.dquote
function string.unquoted(str)
return lpegmatch(unquoted,str) or str
end
function string.quoted(str)
return format("%q",str)
end
function string.count(str,pattern)
local n=0
for _ in gmatch(str,pattern) do
n=n+1
end
return n
end
function string.limit(str,n,sentinel)
if #str>n then
sentinel=sentinel or "..."
return sub(str,1,(n-#sentinel))..sentinel
else
return str
end
end
local stripper=patterns.stripper
local collapser=patterns.collapser
local longtostring=patterns.longtostring
function string.strip(str)
return lpegmatch(stripper,str) or ""
end
function string.collapsespaces(str)
return lpegmatch(collapser,str) or ""
end
function string.longtostring(str)
return lpegmatch(longtostring,str) or ""
end
local pattern=P(" ")^0*P(-1)
function string.is_empty(str)
if str=="" then
return true
else
return lpegmatch(pattern,str) and true or false
end
end
local anything=patterns.anything
local allescapes=Cc("%")*S(".-+%?()[]*")
local someescapes=Cc("%")*S(".-+%()[]")
local matchescapes=Cc(".")*S("*?")
local pattern_a=Cs ((allescapes+anything )^0 )
local pattern_b=Cs ((someescapes+matchescapes+anything )^0 )
local pattern_c=Cs (Cc("^")*(someescapes+matchescapes+anything )^0*Cc("$") )
function string.escapedpattern(str,simple)
return lpegmatch(simple and pattern_b or pattern_a,str)
end
function string.topattern(str,lowercase,strict)
if str=="" or type(str)~="string" then
return ".*"
elseif strict then
str=lpegmatch(pattern_c,str)
else
str=lpegmatch(pattern_b,str)
end
if lowercase then
return lower(str)
else
return str
end
end
function string.valid(str,default)
return (type(str)=="string" and str~="" and str) or default or nil
end
string.itself=function(s) return s end
local pattern=Ct(C(1)^0)
function string.totable(str)
return lpegmatch(pattern,str)
end
local replacer=lpeg.replacer("@","%%")
function string.tformat(fmt,...)
return format(lpegmatch(replacer,fmt),...)
end
string.quote=string.quoted
string.unquote=string.unquoted
end -- closure
do -- begin closure to overcome local limits and interference
if not modules then modules={} end modules ['l-table']={
version=1.001,
comment="companion to luat-lib.mkiv",
author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
copyright="PRAGMA ADE / ConTeXt Development Team",
license="see context related readme files"
}
local type,next,tostring,tonumber,ipairs,select=type,next,tostring,tonumber,ipairs,select
local table,string=table,string
local concat,sort,insert,remove=table.concat,table.sort,table.insert,table.remove
local format,lower,dump=string.format,string.lower,string.dump
local getmetatable,setmetatable=getmetatable,setmetatable
local getinfo=debug.getinfo
local lpegmatch,patterns=lpeg.match,lpeg.patterns
local floor=math.floor
local stripper=patterns.stripper
function table.strip(tab)
local lst,l={},0
for i=1,#tab do
local s=lpegmatch(stripper,tab[i]) or ""
if s=="" then
else
l=l+1
lst[l]=s
end
end
return lst
end
function table.keys(t)
if t then
local keys,k={},0
for key,_ in next,t do
k=k+1
keys[k]=key
end
return keys
else
return {}
end
end
local function compare(a,b)
local ta,tb=type(a),type(b)
if ta==tb then
return a<b
else
return tostring(a)<tostring(b)
end
end
local function sortedkeys(tab)
if tab then
local srt,category,s={},0,0
for key,_ in next,tab do
s=s+1
srt[s]=key
if category==3 then
else
local tkey=type(key)
if tkey=="string" then
category=(category==2 and 3) or 1
elseif tkey=="number" then
category=(category==1 and 3) or 2
else
category=3
end
end
end
if category==0 or category==3 then
sort(srt,compare)
else
sort(srt)
end
return srt
else
return {}
end
end
local function sortedhashkeys(tab,cmp)
if tab then
local srt,s={},0
for key,_ in next,tab do
if key then
s=s+1
srt[s]=key
end
end
sort(srt,cmp)
return srt
else
return {}
end
end
function table.allkeys(t)
local keys={}
for k,v in next,t do
for k,v in next,v do
keys[k]=true
end
end
return sortedkeys(keys)
end
table.sortedkeys=sortedkeys
table.sortedhashkeys=sortedhashkeys
local function nothing() end
local function sortedhash(t,cmp)
if t then
local s
if cmp then
s=sortedhashkeys(t,function(a,b) return cmp(t,a,b) end)
else
s=sortedkeys(t)
end
local n=0
local function kv(s)
n=n+1
local k=s[n]
return k,t[k]
end
return kv,s
else
return nothing
end
end
table.sortedhash=sortedhash
table.sortedpairs=sortedhash
function table.append(t,list)
local n=#t
for i=1,#list do
n=n+1
t[n]=list[i]
end
return t
end
function table.prepend(t,list)
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,...)
t=t or {}
for i=1,select("#",...) do
for k,v in next,(select(i,...)) do
t[k]=v
end
end
return t
end
function table.merged(...)
local t={}
for i=1,select("#",...) do
for k,v in next,(select(i,...)) do
t[k]=v
end
end
return t
end
function table.imerge(t,...)
local nt=#t
for i=1,select("#",...) do
local nst=select(i,...)
for j=1,#nst do
nt=nt+1
t[nt]=nst[j]
end
end
return t
end
function table.imerged(...)
local tmp,ntmp={},0
for i=1,select("#",...) do
local nst=select(i,...)
for j=1,#nst do
ntmp=ntmp+1
tmp[ntmp]=nst[j]
end
end
return tmp
end
local function fastcopy(old,metatabletoo)
if old then
local new={}
for k,v in next,old do
if type(v)=="table" then
new[k]=fastcopy(v,metatabletoo)
else
new[k]=v
end
end
if metatabletoo then
local mt=getmetatable(old)
if mt then
setmetatable(new,mt)
end
end
return new
else
return {}
end
end
local function copy(t,tables)
tables=tables or {}
local tcopy={}
if not tables[t] then
tables[t]=tcopy
end
for i,v in next,t do
if type(i)=="table" then
if tables[i] then
i=tables[i]
else
i=copy(i,tables)
end
end
if type(v)~="table" then
tcopy[i]=v
elseif tables[v] then
tcopy[i]=tables[v]
else
tcopy[i]=copy(v,tables)
end
end
local mt=getmetatable(t)
if mt then
setmetatable(tcopy,mt)
end
return tcopy
end
table.fastcopy=fastcopy
table.copy=copy
function table.derive(parent)
local child={}
if parent then
setmetatable(child,{ __index=parent })
end
return child
end
function table.tohash(t,value)
local h={}
if t then
if value==nil then value=true end
for _,v in next,t do
h[v]=value
end
end
return h
end
function table.fromhash(t)
local hsh,h={},0
for k,v in next,t do
if v then
h=h+1
hsh[h]=k
end
end
return hsh
end
local noquotes,hexify,handle,reduce,compact,inline,functions
local reserved=table.tohash {
'and','break','do','else','elseif','end','false','for','function','if',
'in','local','nil','not','or','repeat','return','then','true','until','while',
'NaN','goto',
}
local function simple_table(t)
if #t>0 then
local n=0
for _,v in next,t do
n=n+1
end
if n==#t then
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[nt]=format("0x%04X",v)
else
tt[nt]=tostring(v)
end
elseif tv=="string" then
nt=nt+1
tt[nt]=format("%q",v)
elseif tv=="boolean" then
nt=nt+1
tt[nt]=v and "true" or "false"
else
tt=nil
break
end
end
return tt
end
end
return nil
end
local propername=patterns.propername
local function dummy() end
local function do_serialize(root,name,depth,level,indexed)
if level>0 then
depth=depth.." "
if indexed then
handle(format("%s{",depth))
else
local tn=type(name)
if tn=="number" then
if hexify then
handle(format("%s[0x%04X]={",depth,name))
else
handle(format("%s[%s]={",depth,name))
end
elseif tn=="string" then
if noquotes and not reserved[name] and lpegmatch(propername,name) then
handle(format("%s%s={",depth,name))
else
handle(format("%s[%q]={",depth,name))
end
elseif tn=="boolean" then
handle(format("%s[%s]={",depth,name and "true" or "false"))
else
handle(format("%s{",depth))
end
end
end
if root and next(root) then
local first,last=nil,0
if compact then
last=#root
for k=1,last do
if root[k]==nil then
last=k-1
break
end
end
if last>0 then
first=1
end
end
local sk=sortedkeys(root)
for i=1,#sk do
local k=sk[i]
local v=root[k]
local tv,tk=type(v),type(k)
if compact and first and tk=="number" and k>=first and k<=last then
if tv=="number" then
if hexify then
handle(format("%s 0x%04X,",depth,v))
else
handle(format("%s %s,",depth,v))
end
elseif tv=="string" then
if reduce and tonumber(v) then
handle(format("%s %s,",depth,v))
else
handle(format("%s %q,",depth,v))
end
elseif tv=="table" then
if not next(v) then
handle(format("%s {},",depth))
elseif inline then
local st=simple_table(v)
if st then
handle(format("%s { %s },",depth,concat(st,", ")))
else
do_serialize(v,k,depth,level+1,true)
end
else
do_serialize(v,k,depth,level+1,true)
end
elseif tv=="boolean" then
handle(format("%s %s,",depth,v and "true" or "false"))
elseif tv=="function" then
if functions then
handle(format('%s load(%q),',depth,dump(v)))
else
handle(format('%s "function",',depth))
end
else
handle(format("%s %q,",depth,tostring(v)))
end
elseif k=="__p__" then
if false then
handle(format("%s __p__=nil,",depth))
end
elseif tv=="number" then
if tk=="number" then
if hexify then
handle(format("%s [0x%04X]=0x%04X,",depth,k,v))
else
handle(format("%s [%s]=%s,",depth,k,v))
end
elseif tk=="boolean" then
if hexify then
handle(format("%s [%s]=0x%04X,",depth,k and "true" or "false",v))
else
handle(format("%s [%s]=%s,",depth,k and "true" or "false",v))
end
elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
if hexify then
handle(format("%s %s=0x%04X,",depth,k,v))
else
handle(format("%s %s=%s,",depth,k,v))
end
else
if hexify then
handle(format("%s [%q]=0x%04X,",depth,k,v))
else
handle(format("%s [%q]=%s,",depth,k,v))
end
end
elseif tv=="string" then
if reduce and tonumber(v) then
if tk=="number" 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,k and "true" or "false",v))
elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
handle(format("%s %s=%s,",depth,k,v))
else
handle(format("%s [%q]=%s,",depth,k,v))
end
else
if tk=="number" 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,k and "true" or "false",v))
elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
handle(format("%s %s=%q,",depth,k,v))
else
handle(format("%s [%q]=%q,",depth,k,v))
end
end
elseif tv=="table" then
if not next(v) then
if tk=="number" 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,k and "true" or "false"))
elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
handle(format("%s %s={},",depth,k))
else
handle(format("%s [%q]={},",depth,k))
end
elseif inline then
local st=simple_table(v)
if st then
if tk=="number" 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
handle(format("%s [%s]={ %s },",depth,k and "true" or "false",concat(st,", ")))
elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
handle(format("%s %s={ %s },",depth,k,concat(st,", ")))
else
handle(format("%s [%q]={ %s },",depth,k,concat(st,", ")))
end
else
do_serialize(v,k,depth,level+1)
end
else
do_serialize(v,k,depth,level+1)
end
elseif tv=="boolean" then
if tk=="number" then
if hexify then
handle(format("%s [0x%04X]=%s,",depth,k,v and "true" or "false"))
else
handle(format("%s [%s]=%s,",depth,k,v and "true" or "false"))
end
elseif tk=="boolean" then
handle(format("%s [%s]=%s,",depth,tostring(k),v and "true" or "false"))
elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
handle(format("%s %s=%s,",depth,k,v and "true" or "false"))
else
handle(format("%s [%q]=%s,",depth,k,v and "true" or "false"))
end
elseif tv=="function" then
if functions then
local f=getinfo(v).what=="C" and dump(dummy) or dump(v)
if tk=="number" then
if hexify then
handle(format("%s [0x%04X]=load(%q),",depth,k,f))
else
handle(format("%s [%s]=load(%q),",depth,k,f))
end
elseif tk=="boolean" then
handle(format("%s [%s]=load(%q),",depth,k and "true" or "false",f))
elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
handle(format("%s %s=load(%q),",depth,k,f))
else
handle(format("%s [%q]=load(%q),",depth,k,f))
end
end
else
if tk=="number" 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
handle(format("%s [%s]=%q,",depth,k and "true" or "false",tostring(v)))
elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
handle(format("%s %s=%q,",depth,k,tostring(v)))
else
handle(format("%s [%q]=%q,",depth,k,tostring(v)))
end
end
end
end
if level>0 then
handle(format("%s},",depth))
end
end
local function serialize(_handle,root,name,specification)
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 {")
else
handle(name.."={")
end
elseif tname=="number" then
if hexify then
handle(format("[0x%04X]={",name))
else
handle("["..name.."]={")
end
elseif tname=="boolean" then
if name then
handle("return {")
else
handle("{")
end
else
handle("t={")
end
if root then
if getmetatable(root) then
local dummy=root._w_h_a_t_e_v_e_r_
root._w_h_a_t_e_v_e_r_=nil
end
if next(root) then
do_serialize(root,name,"",0)
end
end
handle("}")
end
function table.serialize(root,name,specification)
local t,n={},0
local function flush(s)
n=n+1
t[n]=s
end
serialize(flush,root,name,specification)
return concat(t,"\n")
end
table.tohandle=serialize
local maxtab=2*1024
function table.tofile(filename,root,name,specification)
local f=io.open(filename,'w')
if f then
if maxtab>1 then
local t,n={},0
local function flush(s)
n=n+1
t[n]=s
if n>maxtab then
f:write(concat(t,"\n"),"\n")
t,n={},0
end
end
serialize(flush,root,name,specification)
f:write(concat(t,"\n"),"\n")
else
local function flush(s)
f:write(s,"\n")
end
serialize(flush,root,name,specification)
end
f:close()
io.flush()
end
end
local function flattened(t,f,depth)
if f==nil then
f={}
depth=0xFFFF
elseif tonumber(f) then
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
end
end
end
for k=1,#t do
local v=t[k]
if depth>0 and type(v)=="table" then
flattened(v,f,depth-1)
else
f[#f+1]=v
end
end
return f
end
table.flattened=flattened
local function unnest(t,f)
if not f then
f={}
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
end
return f
end
function table.unnest(t)
return unnest(t)
end
local function are_equal(a,b,n,m)
if a and b and #a==#b then
n=n or 1
m=m or #a
for i=n,m do
local ai,bi=a[i],b[i]
if ai==bi then
elseif type(ai)=="table" and type(bi)=="table" then
if not are_equal(ai,bi) then
return false
end
else
return false
end
end
return true
else
return false
end
end
local function identical(a,b)
for ka,va in next,a do
local vb=b[ka]
if va==vb then
elseif type(va)=="table" and type(vb)=="table" then
if not identical(va,vb) then
return false
end
else
return false
end
end
return true
end
table.identical=identical
table.are_equal=are_equal
function table.compact(t)
if t then
for k,v in next,t do
if not next(v) then
t[k]=nil
end
end
end
end
function table.contains(t,v)
if t then
for i=1,#t do
if t[i]==v then
return i
end
end
end
return false
end
function table.count(t)
local n=0
for k,v in next,t do
n=n+1
end
return n
end
function table.swapped(t,s)
local n={}
if s then
for k,v in next,s do
n[k]=v
end
end
for k,v in next,t do
n[v]=k
end
return n
end
function table.mirrored(t)
local n={}
for k,v in next,t do
n[v]=k
n[k]=v
end
return n
end
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
end
function table.reverse(t)
if t then
local n=#t
for i=1,floor(n/2) do
local j=n-i+1
t[i],t[j]=t[j],t[i]
end
return t
end
end
function table.sequenced(t,sep,simple)
if not t then
return ""
end
local n=#t
local s={}
if n>0 then
for i=1,n do
s[i]=tostring(t[i])
end
else
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
end
return concat(s,sep or " | ")
end
function table.print(t,...)
if type(t)~="table" then
print(tostring(t))
else
serialize(print,t,...)
end
end
setinspector(function(v) if type(v)=="table" then serialize(print,v,"table") return true end end)
function table.sub(t,i,j)
return { unpack(t,i,j) }
end
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
function table.loweredkeys(t)
local l={}
for k,v in next,t do
l[lower(k)]=v
end
return l
end
function table.unique(old)
local hash={}
local new={}
local n=0
for i=1,#old do
local oi=old[i]
if not hash[oi] then
n=n+1
new[n]=oi
hash[oi]=true
end
end
return new
end
function table.sorted(t,...)
sort(t,...)
return t
end
end -- closure
do -- begin closure to overcome local limits and interference
if not modules then modules={} end modules ['l-io']={
version=1.001,
comment="companion to luat-lib.mkiv",
author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
copyright="PRAGMA ADE / ConTeXt Development Team",
license="see context related readme files"
}
local io=io
local byte,find,gsub,format=string.byte,string.find,string.gsub,string.format
local concat=table.concat
local floor=math.floor
local type=type
if string.find(os.getenv("PATH"),";") then
io.fileseparator,io.pathseparator="\\",";"
else
io.fileseparator,io.pathseparator="/",":"
end
local function readall(f)
return f:read("*all")
end
local function readall(f)
local size=f:seek("end")
if size==0 then
return ""
elseif size<1024*1024 then
f:seek("set",0)
return f:read('*all')
else
local done=f:seek("set",0)
local step
if size<1024*1024 then
step=1024*1024
elseif size>16*1024*1024 then
step=16*1024*1024
else
step=floor(size/(1024*1024))*1024*1024/8
end
local data={}
while true do
local r=f:read(step)
if not r then
return concat(data)
else
data[#data+1]=r
end
end
end
end
io.readall=readall
function io.loaddata(filename,textmode)
local f=io.open(filename,(textmode and 'r') or 'rb')
if f then
local data=readall(f)
f:close()
if #data>0 then
return data
end
end
end
function io.savedata(filename,data,joiner)
local f=io.open(filename,"wb")
if f then
if type(data)=="table" then
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
end
end
function io.loadlines(filename,n)
local f=io.open(filename,'r')
if not f then
elseif n then
local lines={}
for i=1,n do
local line=f:read("*lines")
if line then
lines[#lines+1]=line
else
break
end
end
f:close()
lines=concat(lines,"\n")
if #lines>0 then
return lines
end
else
local line=f:read("*line") or ""
f:close()
if #line>0 then
return line
end
end
end
function io.loadchunk(filename,n)
local f=io.open(filename,'rb')
if f then
local data=f:read(n or 1024)
f:close()
if #data>0 then
return data
end
end
end
function io.exists(filename)
local f=io.open(filename)
if f==nil then
return false
else
f:close()
return true
end
end
function io.size(filename)
local f=io.open(filename)
if f==nil then
return 0
else
local s=f:seek("end")
f:close()
return s
end
end
function io.noflines(f)
if type(f)=="string" then
local f=io.open(filename)
if f then
local n=f and io.noflines(f) or 0
f:close()
return n
else
return 0
end
else
local n=0
for _ in f:lines() do
n=n+1
end
f:seek('set',0)
return n
end
end
local nextchar={
[ 4]=function(f)
return f:read(1,1,1,1)
end,
[ 2]=function(f)
return f:read(1,1)
end,
[ 1]=function(f)
return f:read(1)
end,
[-2]=function(f)
local a,b=f:read(1,1)
return b,a
end,
[-4]=function(f)
local a,b,c,d=f:read(1,1,1,1)
return d,c,b,a
end
}
function io.characters(f,n)
if f then
return nextchar[n or 1],f
end
end
local nextbyte={
[4]=function(f)
local a,b,c,d=f:read(1,1,1,1)
if d then
return byte(a),byte(b),byte(c),byte(d)
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)
end
end,
[1]=function (f)
local a=f:read(1)
if a then
return byte(a)
end
end,
[-2]=function (f)
local a,b=f:read(1,1)
if b then
return byte(b),byte(a)
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)
end
end
}
function io.bytes(f,n)
if f then
return nextbyte[n or 1],f
else
return nil,nil
end
end
function io.ask(question,default,options)
while true do
io.write(question)
if options then
io.write(format(" [%s]",concat(options,"|")))
end
if default then
io.write(format(" [%s]",default))
end
io.write(format(" "))
io.flush()
local answer=io.read()
answer=gsub(answer,"^%s*(.*)%s*$","%1")
if answer=="" and default then
return default
elseif not options then
return answer
else
for k=1,#options do
if options[k]==answer then
return answer
end
end
local pattern="^"..answer
for k=1,#options do
local v=options[k]
if find(v,pattern) then
return v
end
end
end
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),"\000","")
return str
end
if not io.i_limiter then function io.i_limiter() end end
if not io.o_limiter then function io.o_limiter() end end
end -- closure
do -- begin closure to overcome local limits and interference
if not modules then modules={} end modules ['l-file']={
version=1.001,
comment="companion to luat-lib.mkiv",
author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
copyright="PRAGMA ADE / ConTeXt Development Team",
license="see context related readme files"
}
file=file or {}
local file=file
if not lfs then
lfs=optionalrequire("lfs")
end
if not lfs then
lfs={
getcurrentdir=function()
return "."
end,
attributes=function()
return nil
end,
isfile=function(name)
local f=io.open(name,'rb')
if f then
f:close()
return true
end
end,
isdir=function(name)
print("you need to load lfs")
return false
end
}
elseif not lfs.isfile then
local attributes=lfs.attributes
function lfs.isdir(name)
return attributes(name,"mode")=="directory"
end
function lfs.isfile(name)
return attributes(name,"mode")=="file"
end
end
local insert,concat=table.insert,table.concat
local match,find,gmatch=string.match,string.find,string.gmatch
local lpegmatch=lpeg.match
local getcurrentdir,attributes=lfs.currentdir,lfs.attributes
local checkedsplit=string.checkedsplit
local P,R,S,C,Cs,Cp,Cc,Ct=lpeg.P,lpeg.R,lpeg.S,lpeg.C,lpeg.Cs,lpeg.Cp,lpeg.Cc,lpeg.Ct
local colon=P(":")
local period=P(".")
local periods=P("..")
local fwslash=P("/")
local bwslash=P("\\")
local slashes=S("\\/")
local noperiod=1-period
local noslashes=1-slashes
local name=noperiod^1
local suffix=period/""*(1-period-slashes)^1*-1
local pattern=C((1-(slashes^1*noslashes^1*-1))^1)*P(1)
local function pathpart(name,default)
return name and lpegmatch(pattern,name) or default or ""
end
local pattern=(noslashes^0*slashes)^1*C(noslashes^1)*-1
local function basename(name)
return name and lpegmatch(pattern,name) or name
end
local pattern=(noslashes^0*slashes^1)^0*Cs((1-suffix)^1)*suffix^0
local function nameonly(name)
return name and lpegmatch(pattern,name) or name
end
local pattern=(noslashes^0*slashes)^0*(noperiod^1*period)^1*C(noperiod^1)*-1
local function suffixonly(name)
return name and lpegmatch(pattern,name) or ""
end
local pattern=(noslashes^0*slashes)^0*noperiod^1*((period*C(noperiod^1))^1)*-1+Cc("")
local function suffixesonly(name)
if name then
return lpegmatch(pattern,name)
else
return ""
end
end
file.pathpart=pathpart
file.basename=basename
file.nameonly=nameonly
file.suffixonly=suffixonly
file.suffix=suffixonly
file.suffixesonly=suffixesonly
file.suffixes=suffixesonly
file.dirname=pathpart
file.extname=suffixonly
local drive=C(R("az","AZ"))*colon
local path=C((noslashes^0*slashes)^0)
local suffix=period*C(P(1-period)^0*P(-1))
local base=C((1-suffix)^0)
local rest=C(P(1)^0)
drive=drive+Cc("")
path=path+Cc("")
base=base+Cc("")
suffix=suffix+Cc("")
local pattern_a=drive*path*base*suffix
local pattern_b=path*base*suffix
local pattern_c=C(drive*path)*C(base*suffix)
local pattern_d=path*rest
function file.splitname(str,splitdrive)
if not str then
elseif splitdrive then
return lpegmatch(pattern_a,str)
else
return lpegmatch(pattern_b,str)
end
end
function file.splitbase(str)
if str then
return lpegmatch(pattern_d,str)
else
return "",str
end
end
function file.nametotable(str,splitdrive)
if str then
local path,drive,subpath,name,base,suffix=lpegmatch(pattern_c,str)
if splitdrive then
return {
path=path,
drive=drive,
subpath=subpath,
name=name,
base=base,
suffix=suffix,
}
else
return {
path=path,
name=name,
base=base,
suffix=suffix,
}
end
end
end
local pattern=Cs(((period*(1-period-slashes)^1*-1)/""+1)^1)
function file.removesuffix(name)
return name and lpegmatch(pattern,name)
end
local suffix=period/""*(1-period-slashes)^1*-1
local pattern=Cs((noslashes^0*slashes^1)^0*((1-suffix)^1))*Cs(suffix)
function file.addsuffix(filename,suffix,criterium)
if not filename or not suffix or suffix=="" then
return filename
elseif criterium==true then
return filename.."."..suffix
elseif not criterium then
local n,s=lpegmatch(pattern,filename)
if not s or s=="" then
return filename.."."..suffix
else
return filename
end
else
local n,s=lpegmatch(pattern,filename)
if s and s~="" then
local t=type(criterium)
if t=="table" then
for i=1,#criterium do
if s==criterium[i] then
return filename
end
end
elseif t=="string" then
if s==criterium then
return filename
end
end
end
return (n or filename).."."..suffix
end
end
local suffix=period*(1-period-slashes)^1*-1
local pattern=Cs((1-suffix)^0)
function file.replacesuffix(name,suffix)
if name and suffix and suffix~="" then
return lpegmatch(pattern,name).."."..suffix
else
return name
end
end
local reslasher=lpeg.replacer(P("\\"),"/")
function file.reslash(str)
return str and lpegmatch(reslasher,str)
end
function file.is_writable(name)
if not name then
elseif lfs.isdir(name) then
name=name.."/m_t_x_t_e_s_t.tmp"
local f=io.open(name,"wb")
if f then
f:close()
os.remove(name)
return true
end
elseif lfs.isfile(name) then
local f=io.open(name,"ab")
if f then
f:close()
return true
end
else
local f=io.open(name,"ab")
if f then
f:close()
os.remove(name)
return true
end
end
return false
end
local readable=P("r")*Cc(true)
function file.is_readable(name)
if name then
local a=attributes(name)
return a and lpegmatch(readable,a.permissions) or false
else
return false
end
end
file.isreadable=file.is_readable
file.iswritable=file.is_writable
function file.size(name)
if name then
local a=attributes(name)
return a and a.size or 0
else
return 0
end
end
function file.splitpath(str,separator)
return str and checkedsplit(lpegmatch(reslasher,str),separator or io.pathseparator)
end
function file.joinpath(tab,separator)
return tab and concat(tab,separator or io.pathseparator)
end
local someslash=S("\\/")
local stripper=Cs(P(fwslash)^0/""*reslasher)
local isnetwork=someslash*someslash*(1-someslash)+(1-fwslash-colon)^1*colon
local isroot=fwslash^1*-1
local hasroot=fwslash^1
local reslasher=lpeg.replacer(S("\\/"),"/")
local deslasher=lpeg.replacer(S("\\/")^1,"/")
function file.join(...)
local lst={... }
local one=lst[1]
if lpegmatch(isnetwork,one) then
local one=lpegmatch(reslasher,one)
local two=lpegmatch(deslasher,concat(lst,"/",2))
if lpegmatch(hasroot,two) then
return one..two
else
return one.."/"..two
end
elseif lpegmatch(isroot,one) then
local two=lpegmatch(deslasher,concat(lst,"/",2))
if lpegmatch(hasroot,two) then
return two
else
return "/"..two
end
elseif one=="" then
return lpegmatch(stripper,concat(lst,"/",2))
else
return lpegmatch(deslasher,concat(lst,"/"))
end
end
local drivespec=R("az","AZ")^1*colon
local anchors=fwslash+drivespec
local untouched=periods+(1-period)^1*P(-1)
local mswindrive=Cs(drivespec*(bwslash/"/"+fwslash)^0)
local mswinuncpath=(bwslash+fwslash)*(bwslash+fwslash)*Cc("//")
local splitstarter=(mswindrive+mswinuncpath+Cc(false))*Ct(lpeg.splitat(S("/\\")^1))
local absolute=fwslash
function file.collapsepath(str,anchor)
if not str then
return
end
if anchor==true and not lpegmatch(anchors,str) then
str=getcurrentdir().."/"..str
end
if str=="" or str=="." then
return "."
elseif lpegmatch(untouched,str) then
return lpegmatch(reslasher,str)
end
local starter,oldelements=lpegmatch(splitstarter,str)
local newelements={}
local i=#oldelements
while i>0 do
local element=oldelements[i]
if element=='.' then
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 lpegmatch(absolute,str) then
return "/"..concat(newelements,'/')
else
newelements=concat(newelements,'/')
if anchor=="." and find(str,"^%./") then
return "./"..newelements
else
return newelements
end
end
end
local validchars=R("az","09","AZ","--","..")
local pattern_a=lpeg.replacer(1-validchars)
local pattern_a=Cs((validchars+P(1)/"-")^1)
local whatever=P("-")^0/""
local pattern_b=Cs(whatever*(1-whatever*-1)^1)
function file.robustname(str,strict)
if str then
str=lpegmatch(pattern_a,str) or str
if strict then
return lpegmatch(pattern_b,str) or str
else
return str
end
end
end
file.readdata=io.loaddata
file.savedata=io.savedata
function file.copy(oldname,newname)
if oldname and newname then
local data=io.loaddata(oldname)
if data and data~="" then
file.savedata(newname,data)
end
end
end
local letter=R("az","AZ")+S("_-+")
local separator=P("://")
local qualified=period^0*fwslash+letter*colon+letter^1*separator+letter^1*fwslash
local rootbased=fwslash+letter*colon
lpeg.patterns.qualified=qualified
lpeg.patterns.rootbased=rootbased
function file.is_qualified_path(filename)
return filename and lpegmatch(qualified,filename)~=nil
end
function file.is_rootbased_path(filename)
return filename and lpegmatch(rootbased,filename)~=nil
end
function file.strip(name,dir)
if name then
local b,a=match(name,"^(.-)"..dir.."(.*)$")
return a~="" and a or name
end
end
function lfs.mkdirs(path)
local full=""
for sub in gmatch(path,"(/*[^\\/]+)") do
full=full..sub
lfs.mkdir(full)
end
end
end -- closure
do -- begin closure to overcome local limits and interference
if not modules then modules={} end modules ['l-boolean']={
version=1.001,
comment="companion to luat-lib.mkiv",
author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
copyright="PRAGMA ADE / ConTeXt Development Team",
license="see context related readme files"
}
local type,tonumber=type,tonumber
boolean=boolean or {}
local boolean=boolean
function boolean.tonumber(b)
if b then return 1 else return 0 end
end
function toboolean(str,tolerant)
if str==nil then
return false
elseif str==false then
return false
elseif str==true then
return true
elseif str=="true" then
return true
elseif str=="false" then
return false
elseif not tolerant then
return false
elseif str==0 then
return false
elseif (tonumber(str) or 0)>0 then
return true
else
return str=="yes" or str=="on" or str=="t"
end
end
string.toboolean=toboolean
function string.booleanstring(str)
if str=="0" then
return false
elseif str=="1" then
return true
elseif str=="" then
return false
elseif str=="false" then
return false
elseif str=="true" then
return true
elseif (tonumber(str) or 0)>0 then
return true
else
return str=="yes" or str=="on" or str=="t"
end
end
function string.is_boolean(str,default)
if type(str)=="string" then
if str=="true" or str=="yes" or str=="on" or str=="t" or str=="1" then
return true
elseif str=="false" or str=="no" or str=="off" or str=="f" or str=="0" then
return false
end
end
return default
end
end -- closure
do -- begin closure to overcome local limits and interference
if not modules then modules={} end modules ['l-math']={
version=1.001,
comment="companion to luat-lib.mkiv",
author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
copyright="PRAGMA ADE / ConTeXt Development Team",
license="see context related readme files"
}
local 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
end
if not math.div then
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
end
local pipi=2*math.pi/360
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
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
do -- begin closure to overcome local limits and interference
if not modules then modules={} end modules ['util-str']={
version=1.001,
comment="companion to luat-lib.mkiv",
author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
copyright="PRAGMA ADE / ConTeXt Development Team",
license="see context related readme files"
}
utilities=utilities or {}
utilities.strings=utilities.strings or {}
local strings=utilities.strings
local format,gsub,rep,sub=string.format,string.gsub,string.rep,string.sub
local load,dump=load,string.dump
local tonumber,type,tostring=tonumber,type,tostring
local unpack,concat=table.unpack,table.concat
local P,V,C,S,R,Ct,Cs,Cp,Carg,Cc=lpeg.P,lpeg.V,lpeg.C,lpeg.S,lpeg.R,lpeg.Ct,lpeg.Cs,lpeg.Cp,lpeg.Carg,lpeg.Cc
local patterns,lpegmatch=lpeg.patterns,lpeg.match
local utfchar,utfbyte=utf.char,utf.byte
local loadstripped=_LUAVERSION<5.2 and load or function(str)
return load(dump(load(str),true))
end
if not number then number={} end
local stripper=patterns.stripzeros
local function points(n)
return (not n or n==0) and "0pt" or lpegmatch(stripper,format("%.5fpt",n/65536))
end
local function basepoints(n)
return (not n or n==0) and "0bp" or lpegmatch(stripper,format("%.5fbp",n*(7200/7227)/65536))
end
number.points=points
number.basepoints=basepoints
local rubish=patterns.spaceortab^0*patterns.newline
local anyrubish=patterns.spaceortab+patterns.newline
local anything=patterns.anything
local stripped=(patterns.spaceortab^1/"")*patterns.newline
local leading=rubish^0/""
local trailing=(anyrubish^1*patterns.endofstring)/""
local redundant=rubish^3/"\n"
local pattern=Cs(leading*(trailing+redundant+stripped+anything)^0)
function strings.collapsecrlf(str)
return lpegmatch(pattern,str)
end
local repeaters={}
function strings.newrepeater(str,offset)
offset=offset or 0
local s=repeaters[str]
if not s then
s={}
repeaters[str]=s
end
local t=s[offset]
if t then
return t
end
t={}
setmetatable(t,{ __index=function(t,k)
if not k then
return ""
end
local n=k+offset
local s=n>0 and rep(str,n) or ""
t[k]=s
return s
end })
s[offset]=t
return t
end
local extra,tab,start=0,0,4,0
local nspaces=strings.newrepeater(" ")
string.nspaces=nspaces
local pattern=Carg(1)/function(t)
extra,tab,start=0,t or 7,1
end*Cs((
Cp()*patterns.tab/function(position)
local current=(position-start+1)+extra
local spaces=tab-(current-1)%tab
if spaces>0 then
extra=extra+spaces-1
return nspaces[spaces]
else
return ""
end
end+patterns.newline*Cp()/function(position)
extra,start=0,position
end+patterns.anything
)^1)
function strings.tabtospace(str,tab)
return lpegmatch(pattern,str,1,tab or 7)
end
function strings.striplong(str)
str=gsub(str,"^%s*","")
str=gsub(str,"[\n\r]+ *","\n")
return str
end
function strings.nice(str)
str=gsub(str,"[:%-+_]+"," ")
return str
end
local n=0
local sequenced=table.sequenced
function string.autodouble(s,sep)
if s==nil then
return '""'
end
local t=type(s)
if t=="number" then
return tostring(s)
end
if t=="table" then
return ('"'..sequenced(s,sep or ",")..'"')
end
return ('"'..tostring(s)..'"')
end
function string.autosingle(s,sep)
if s==nil then
return "''"
end
local t=type(s)
if t=="number" then
return tostring(s)
end
if t=="table" then
return ("'"..sequenced(s,sep or ",").."'")
end
return ("'"..tostring(s).."'")
end
local tracedchars={}
string.tracedchars=tracedchars
strings.tracers=tracedchars
function string.tracedchar(b)
if type(b)=="number" then
return tracedchars[b] or (utfchar(b).." (U+"..format('%05X',b)..")")
else
local c=utfbyte(b)
return tracedchars[c] or (b.." (U+"..format('%05X',c)..")")
end
end
function number.signed(i)
if i>0 then
return "+",i
else
return "-",-i
end
end
local zero=P("0")^1/""
local plus=P("+")/""
local minus=P("-")
local separator=S(".")
local digit=R("09")
local trailing=zero^1*#S("eE")
local exponent=(S("eE")*(plus+Cs((minus*zero^0*P(-1))/"")+minus)*zero^0*(P(-1)*Cc("0")+P(1)^1))
local pattern_a=Cs(minus^0*digit^1*(separator/""*trailing+separator*(trailing+digit)^0)*exponent)
local pattern_b=Cs((exponent+P(1))^0)
function number.sparseexponent(f,n)
if not n then
n=f
f="%e"
end
local tn=type(n)
if tn=="string" then
local m=tonumber(n)
if m then
return lpegmatch((f=="%e" or f=="%E") and pattern_a or pattern_b,format(f,m))
end
elseif tn=="number" then
return lpegmatch((f=="%e" or f=="%E") and pattern_a or pattern_b,format(f,n))
end
return tostring(n)
end
local preamble=[[
local type = type
local tostring = tostring
local tonumber = tonumber
local format = string.format
local concat = table.concat
local signed = number.signed
local points = number.points
local basepoints = number.basepoints
local utfchar = utf.char
local utfbyte = utf.byte
local lpegmatch = lpeg.match
local nspaces = string.nspaces
local tracedchar = string.tracedchar
local autosingle = string.autosingle
local autodouble = string.autodouble
local sequenced = table.sequenced
local formattednumber = number.formatted
local sparseexponent = number.sparseexponent
]]
local template=[[
%s
%s
return function(%s) return %s end
]]
local arguments={ "a1" }
setmetatable(arguments,{ __index=function(t,k)
local v=t[k-1]..",a"..k
t[k]=v
return v
end
})
local prefix_any=C((S("+- .")+R("09"))^0)
local prefix_tab=P("{")*C((1-P("}"))^0)*P("}")+C((1-R("az","AZ","09","%%"))^0)
local format_s=function(f)
n=n+1
if f and f~="" then
return format("format('%%%ss',a%s)",f,n)
else
return format("(a%s or '')",n)
end
end
local format_S=function(f)
n=n+1
if f and f~="" then
return format("format('%%%ss',tostring(a%s))",f,n)
else
return format("tostring(a%s)",n)
end
end
local format_q=function()
n=n+1
return format("(a%s and format('%%q',a%s) or '')",n,n)
end
local format_Q=function()
n=n+1
return format("format('%%q',tostring(a%s))",n)
end
local format_i=function(f)
n=n+1
if f and f~="" then
return format("format('%%%si',a%s)",f,n)
else
return format("format('%%i',a%s)",n)
end
end
local format_d=format_i
local format_I=function(f)
n=n+1
return format("format('%%s%%%si',signed(a%s))",f,n)
end
local format_f=function(f)
n=n+1
return format("format('%%%sf',a%s)",f,n)
end
local format_g=function(f)
n=n+1
return format("format('%%%sg',a%s)",f,n)
end
local format_G=function(f)
n=n+1
return format("format('%%%sG',a%s)",f,n)
end
local format_e=function(f)
n=n+1
return format("format('%%%se',a%s)",f,n)
end
local format_E=function(f)
n=n+1
return format("format('%%%sE',a%s)",f,n)
end
local format_j=function(f)
n=n+1
return format("sparseexponent('%%%se',a%s)",f,n)
end
local format_J=function(f)
n=n+1
return format("sparseexponent('%%%sE',a%s)",f,n)
end
local format_x=function(f)
n=n+1
return format("format('%%%sx',a%s)",f,n)
end
local format_X=function(f)
n=n+1
return format("format('%%%sX',a%s)",f,n)
end
local format_o=function(f)
n=n+1
return format("format('%%%so',a%s)",f,n)
end
local format_c=function()
n=n+1
return format("utfchar(a%s)",n)
end
local format_C=function()
n=n+1
return format("tracedchar(a%s)",n)
end
local format_r=function(f)
n=n+1
return format("format('%%%s.0f',a%s)",f,n)
end
local format_h=function(f)
n=n+1
if f=="-" then
f=sub(f,2)
return format("format('%%%sx',type(a%s) == 'number' and a%s or utfbyte(a%s))",f=="" and "05" or f,n,n,n)
else
return format("format('0x%%%sx',type(a%s) == 'number' and a%s or utfbyte(a%s))",f=="" and "05" or f,n,n,n)
end
end
local format_H=function(f)
n=n+1
if f=="-" then
f=sub(f,2)
return format("format('%%%sX',type(a%s) == 'number' and a%s or utfbyte(a%s))",f=="" and "05" or f,n,n,n)
else
return format("format('0x%%%sX',type(a%s) == 'number' and a%s or utfbyte(a%s))",f=="" and "05" or f,n,n,n)
end
end
local format_u=function(f)
n=n+1
if f=="-" then
f=sub(f,2)
return format("format('%%%sx',type(a%s) == 'number' and a%s or utfbyte(a%s))",f=="" and "05" or f,n,n,n)
else
return format("format('u+%%%sx',type(a%s) == 'number' and a%s or utfbyte(a%s))",f=="" and "05" or f,n,n,n)
end
end
local format_U=function(f)
n=n+1
if f=="-" then
f=sub(f,2)
return format("format('%%%sX',type(a%s) == 'number' and a%s or utfbyte(a%s))",f=="" and "05" or f,n,n,n)
else
return format("format('U+%%%sX',type(a%s) == 'number' and a%s or utfbyte(a%s))",f=="" and "05" or f,n,n,n)
end
end
local format_p=function()
n=n+1
return format("points(a%s)",n)
end
local format_b=function()
n=n+1
return format("basepoints(a%s)",n)
end
local format_t=function(f)
n=n+1
if f and f~="" then
return format("concat(a%s,%q)",n,f)
else
return format("concat(a%s)",n)
end
end
local format_T=function(f)
n=n+1
if f and f~="" then
return format("sequenced(a%s,%q)",n,f)
else
return format("sequenced(a%s)",n)
end
end
local format_l=function()
n=n+1
return format("(a%s and 'true' or 'false')",n)
end
local format_L=function()
n=n+1
return format("(a%s and 'TRUE' or 'FALSE')",n)
end
local format_N=function()
n=n+1
return format("tostring(tonumber(a%s) or a%s)",n,n)
end
local format_a=function(f)
n=n+1
if f and f~="" then
return format("autosingle(a%s,%q)",n,f)
else
return format("autosingle(a%s)",n)
end
end
local format_A=function(f)
n=n+1
if f and f~="" then
return format("autodouble(a%s,%q)",n,f)
else
return format("autodouble(a%s)",n)
end
end
local format_w=function(f)
n=n+1
f=tonumber(f)
if f then
return format("nspaces[%s+a%s]",f,n)
else
return format("nspaces[a%s]",n)
end
end
local format_W=function(f)
return format("nspaces[%s]",tonumber(f) or 0)
end
local digit=patterns.digit
local period=patterns.period
local three=digit*digit*digit
local splitter=Cs (
(((1-(three^1*period))^1+C(three))*(Carg(1)*three)^1+C((1-period)^1))*(P(1)/""*Carg(2))*C(2)
)
patterns.formattednumber=splitter
function number.formatted(n,sep1,sep2)
local s=type(s)=="string" and n or format("%0.2f",n)
if sep1==true then
return lpegmatch(splitter,s,1,".",",")
elseif sep1=="." then
return lpegmatch(splitter,s,1,sep1,sep2 or ",")
elseif sep1=="," then
return lpegmatch(splitter,s,1,sep1,sep2 or ".")
else
return lpegmatch(splitter,s,1,sep1 or ",",sep2 or ".")
end
end
local format_m=function(f)
n=n+1
if not f or f=="" then
f=","
end
return format([[formattednumber(a%s,%q,".")]],n,f)
end
local format_M=function(f)
n=n+1
if not f or f=="" then
f="."
end
return format([[formattednumber(a%s,%q,",")]],n,f)
end
local format_z=function(f)
n=n+(tonumber(f) or 1)
return "''"
end
local format_rest=function(s)
return format("%q",s)
end
local format_extension=function(extensions,f,name)
local extension=extensions[name] or "tostring(%s)"
local f=tonumber(f) or 1
if f==0 then
return extension
elseif f==1 then
n=n+1
local a="a"..n
return format(extension,a,a)
elseif f<0 then
local a="a"..(n+f+1)
return format(extension,a,a)
else
local t={}
for i=1,f do
n=n+1
t[#t+1]="a"..n
end
return format(extension,unpack(t))
end
end
local builder=Cs { "start",
start=(
(
P("%")/""*(
V("!")
+V("s")+V("q")+V("i")+V("d")+V("f")+V("g")+V("G")+V("e")+V("E")+V("x")+V("X")+V("o")
+V("c")+V("C")+V("S")
+V("Q")
+V("N")
+V("r")+V("h")+V("H")+V("u")+V("U")+V("p")+V("b")+V("t")+V("T")+V("l")+V("L")+V("I")+V("w")
+V("W")
+V("a")
+V("A")
+V("j")+V("J")
+V("m")+V("M")
+V("z")
+V("*")
)+V("*")
)*(P(-1)+Carg(1))
)^0,
["s"]=(prefix_any*P("s"))/format_s,
["q"]=(prefix_any*P("q"))/format_q,
["i"]=(prefix_any*P("i"))/format_i,
["d"]=(prefix_any*P("d"))/format_d,
["f"]=(prefix_any*P("f"))/format_f,
["g"]=(prefix_any*P("g"))/format_g,
["G"]=(prefix_any*P("G"))/format_G,
["e"]=(prefix_any*P("e"))/format_e,
["E"]=(prefix_any*P("E"))/format_E,
["x"]=(prefix_any*P("x"))/format_x,
["X"]=(prefix_any*P("X"))/format_X,
["o"]=(prefix_any*P("o"))/format_o,
["S"]=(prefix_any*P("S"))/format_S,
["Q"]=(prefix_any*P("Q"))/format_S,
["N"]=(prefix_any*P("N"))/format_N,
["c"]=(prefix_any*P("c"))/format_c,
["C"]=(prefix_any*P("C"))/format_C,
["r"]=(prefix_any*P("r"))/format_r,
["h"]=(prefix_any*P("h"))/format_h,
["H"]=(prefix_any*P("H"))/format_H,
["u"]=(prefix_any*P("u"))/format_u,
["U"]=(prefix_any*P("U"))/format_U,
["p"]=(prefix_any*P("p"))/format_p,
["b"]=(prefix_any*P("b"))/format_b,
["t"]=(prefix_tab*P("t"))/format_t,
["T"]=(prefix_tab*P("T"))/format_T,
["l"]=(prefix_any*P("l"))/format_l,
["L"]=(prefix_any*P("L"))/format_L,
["I"]=(prefix_any*P("I"))/format_I,
["w"]=(prefix_any*P("w"))/format_w,
["W"]=(prefix_any*P("W"))/format_W,
["j"]=(prefix_any*P("j"))/format_j,
["J"]=(prefix_any*P("J"))/format_J,
["m"]=(prefix_tab*P("m"))/format_m,
["M"]=(prefix_tab*P("M"))/format_M,
["z"]=(prefix_any*P("z"))/format_z,
["a"]=(prefix_any*P("a"))/format_a,
["A"]=(prefix_any*P("A"))/format_A,
["*"]=Cs(((1-P("%"))^1+P("%%")/"%%")^1)/format_rest,
["!"]=Carg(2)*prefix_any*P("!")*C((1-P("!"))^1)*P("!")/format_extension,
}
local direct=Cs (
P("%")/""*Cc([[local format = string.format return function(str) return format("%]])*(S("+- .")+R("09"))^0*S("sqidfgGeExXo")*Cc([[",str) end]])*P(-1)
)
local function make(t,str)
local f
local p
local p=lpegmatch(direct,str)
if p then
f=loadstripped(p)()
else
n=0
p=lpegmatch(builder,str,1,"..",t._extensions_)
if n>0 then
p=format(template,preamble,t._preamble_,arguments[n],p)
f=loadstripped(p)()
else
f=function() return str end
end
end
t[str]=f
return f
end
local function use(t,fmt,...)
return t[fmt](...)
end
strings.formatters={}
function strings.formatters.new()
local t={ _extensions_={},_preamble_="",_type_="formatter" }
setmetatable(t,{ __index=make,__call=use })
return t
end
local formatters=strings.formatters.new()
string.formatters=formatters
string.formatter=function(str,...) return formatters[str](...) end
local function add(t,name,template,preamble)
if type(t)=="table" and t._type_=="formatter" then
t._extensions_[name]=template or "%s"
if preamble then
t._preamble_=preamble.."\n"..t._preamble_
end
end
end
strings.formatters.add=add
patterns.xmlescape=Cs((P("<")/"<"+P(">")/">"+P("&")/"&"+P('"')/"""+P(1))^0)
patterns.texescape=Cs((C(S("#$%\\{}"))/"\\%1"+P(1))^0)
patterns.luaescape=Cs(((1-S('"\n'))^1+P('"')/'\\"'+P('\n')/'\\n"')^0)
patterns.luaquoted=Cs(Cc('"')*((1-S('"\n'))^1+P('"')/'\\"'+P('\n')/'\\n"')^0*Cc('"'))
add(formatters,"xml",[[lpegmatch(xmlescape,%s)]],[[local xmlescape = lpeg.patterns.xmlescape]])
add(formatters,"tex",[[lpegmatch(texescape,%s)]],[[local texescape = lpeg.patterns.texescape]])
add(formatters,"lua",[[lpegmatch(luaescape,%s)]],[[local luaescape = lpeg.patterns.luaescape]])
end -- closure
do -- begin closure to overcome local limits and interference
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.reporter or texio.write_nl)(c.." : "..string.formatters(...))
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={
register=dummyfunction,
shared={},
}
logs={
new=dummyreporter,
reporter=dummyreporter,
messenger=dummyreporter,
report=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={}
}
texconfig.kpse_init=true
resolvers=resolvers or {}
local remapper={
otf="opentype fonts",
ttf="truetype fonts",
ttc="truetype fonts",
dfont="truetype fonts",
cid="cid maps",
cidmap="cid maps",
fea="font feature files",
pfa="type1 fonts",
pfb="type1 fonts",
afm="afm",
}
function resolvers.findfile(name,fileformat)
name=string.gsub(name,"\\","/")
if not fileformat or fileformat=="" then
fileformat=file.suffix(name)
if fileformat=="" then
fileformat="tex"
end
end
fileformat=string.lower(fileformat)
fileformat=remapper[fileformat] or fileformat
local found=kpse.find_file(name,fileformat)
if not found or found=="" then
found=kpse.find_file(name,"other text files")
end
return found
end
resolvers.findbinfile=resolvers.findfile
function resolvers.loadbinfile(filename,filetype)
local data=io.loaddata(filename)
return true,data,#data
end
function resolvers.resolve(s)
return s
end
function resolvers.unresolve(s)
return s
end
caches={}
local writable=nil
local readables={}
local usingjit=jit
if not caches.namespace or caches.namespace=="" or caches.namespace=="context" then
caches.namespace='generic'
end
do
local cachepaths=kpse.expand_var('$TEXMFCACHE') or ""
if cachepaths=="" or cachepaths=="$TEXMFCACHE" then
cachepaths=kpse.expand_var('$TEXMFVAR') or ""
end
if cachepaths=="" or cachepaths=="$TEXMFVAR" then
cachepaths=kpse.expand_var('$VARTEXMF') or ""
end
if cachepaths=="" then
local fallbacks={ "TMPDIR","TEMPDIR","TMP","TEMP","HOME","HOMEPATH" }
for i=1,#fallbacks do
cachepaths=os.getenv(fallbacks[i]) or ""
if cachepath~="" and lfs.isdir(cachepath) then
break
end
end
end
if cachepaths=="" then
cachepaths="."
end
cachepaths=string.split(cachepaths,os.type=="windows" and ";" or ":")
for i=1,#cachepaths do
local cachepath=cachepaths[i]
if not lfs.isdir(cachepath) then
lfs.mkdirs(cachepath)
if lfs.isdir(cachepath) then
texio.write(string.format("(created cache path: %s)",cachepath))
end
end
if file.is_writable(cachepath) then
writable=file.join(cachepath,"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 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
return file.addsuffix(file.join(path,name),"lua"),file.addsuffix(file.join(path,name),usingjit and "lub" or "luc")
end
end
function caches.is_writable(path,name)
local fullname=makefullname(path,name)
return fullname and file.is_writable(fullname)
end
function caches.loaddata(paths,name)
for i=1,#paths do
local data=false
local luaname,lucname=makefullname(paths[i],name)
if lucname and lfs.isfile(lucname) then
texio.write(string.format("(load luc: %s)",lucname))
data=loadfile(lucname)
if data then
data=data()
end
if data then
return data
else
texio.write(string.format("(loading failed: %s)",lucname))
end
end
if luaname and lfs.isfile(luaname) then
texio.write(string.format("(load lua: %s)",luaname))
data=loadfile(luaname)
if data then
data=data()
end
if data then
return data
end
end
end
end
function caches.savedata(path,name,data)
local luaname,lucname=makefullname(path,name)
if luaname then
texio.write(string.format("(save: %s)",luaname))
table.tofile(luaname,data,true)
if lucname and type(caches.compile)=="function" then
os.remove(lucname)
texio.write(string.format("(save: %s)",lucname))
caches.compile(data,luaname,lucname)
end
end
end
function caches.compile(data,luaname,lucname)
local d=io.loaddata(luaname)
if not d or d=="" then
d=table.serialize(data,true)
end
if d and d~="" then
local f=io.open(lucname,'wb')
if f then
local s=loadstring(d)
if s then
f:write(string.dump(s,true))
end
f:close()
end
end
end
function 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.100,
comment="companion to luat-lib.mkiv",
author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
copyright="PRAGMA ADE / ConTeXt Development Team",
license="see context related readme files"
}
local format,lower,gsub=string.format,string.lower,string.gsub
local trace_cache=false trackers.register("resolvers.cache",function(v) trace_cache=v end)
local trace_containers=false trackers.register("resolvers.containers",function(v) trace_containers=v end)
local trace_storage=false trackers.register("resolvers.storage",function(v) trace_storage=v end)
containers=containers or {}
local containers=containers
containers.usecache=true
local report_containers=logs.reporter("resolvers","containers")
local allocated={}
local mt={
__index=function(t,k)
if k=="writable" then
local writable=caches.getwritablepath(t.category,t.subcategory) or { "." }
t.writable=writable
return writable
elseif k=="readables" then
local readables=caches.getreadablepaths(t.category,t.subcategory) or { "." }
t.readables=readables
return readables
end
end,
__storage__=true
}
function containers.define(category,subcategory,version,enabled)
if category and subcategory then
local c=allocated[category]
if not c then
c={}
allocated[category]=c
end
local s=c[subcategory]
if not s then
s={
category=category,
subcategory=subcategory,
storage={},
enabled=enabled,
version=version or math.pi,
trace=false,
}
setmetatable(s,mt)
c[subcategory]=s
end
return s
end
end
function containers.is_usable(container,name)
return container.enabled and caches and caches.is_writable(container.writable,name)
end
function containers.is_valid(container,name)
if name and name~="" then
local storage=container.storage[name]
return storage and storage.cache_version==container.version
else
return false
end
end
function containers.read(container,name)
local storage=container.storage
local stored=storage[name]
if not stored and container.enabled and caches and containers.usecache then
stored=caches.loaddata(container.readables,name)
if stored and stored.cache_version==container.version then
if trace_cache or trace_containers then
report_containers("action %a, category %a, name %a","load",container.subcategory,name)
end
else
stored=nil
end
storage[name]=stored
elseif stored then
if trace_cache or trace_containers then
report_containers("action %a, category %a, name %a","reuse",container.subcategory,name)
end
end
return stored
end
function containers.write(container,name,data)
if data then
data.cache_version=container.version
if container.enabled and caches then
local unique,shared=data.unique,data.shared
data.unique,data.shared=nil,nil
caches.savedata(container.writable,name,data)
if trace_cache or trace_containers then
report_containers("action %a, category %a, name %a","save",container.subcategory,name)
end
data.unique,data.shared=unique,shared
end
if trace_cache or trace_containers then
report_containers("action %a, category %a, name %a","store",container.subcategory,name)
end
container.storage[name]=data
end
return data
end
function containers.content(container,name)
return container.storage[name]
end
function containers.cleanname(name)
return (gsub(lower(name),"[^%w\128-\255]+","-"))
end
end -- closure
do -- begin closure to overcome local limits and interference
if not modules then modules={} end modules ['luatex-fonts-nod']={
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
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","! purposes so setting them at the TeX end might break the font handler.")
texio.write_nl("log","!")
tex.attribute[0]=0
end
attributes=attributes or {}
attributes.unsetvalue=-0x7FFFFFFF
local numbers,last={},127
attributes.private=attributes.private or function(name)
local number=numbers[name]
if not number then
if last<255 then
last=last+1
end
number=last
numbers[name]=number
end
return number
end
nodes={}
nodes.pool={}
nodes.handlers={}
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 disccodes={ [0]="discretionary","explicit","automatic","regular","first","second" }
nodes.nodecodes=nodecodes
nodes.whatcodes=whatcodes
nodes.whatsitcodes=whatcodes
nodes.glyphcodes=glyphcodes
nodes.disccodes=disccodes
local free_node=node.free
local remove_node=node.remove
local new_node=node.new
local traverse_id=node.traverse_id
local math_code=nodecodes.math
nodes.handlers.protectglyphs=node.protect_glyphs
nodes.handlers.unprotectglyphs=node.unprotect_glyphs
function nodes.remove(head,current,free_too)
local t=current
head,current=remove_node(head,current)
if t then
if free_too then
free_node(t)
t=nil
else
t.next,t.prev=nil,nil
end
end
return head,current,t
end
function nodes.delete(head,current)
return nodes.remove(head,current,true)
end
function nodes.pool.kern(k)
local n=new_node("kern",1)
n.kern=k
return n
end
local getfield=node.getfield or function(n,tag) return n[tag] end
local setfield=node.setfield or function(n,tag,value) n[tag]=value end
nodes.getfield=getfield
nodes.setfield=setfield
nodes.getattr=getfield
nodes.setattr=setfield
if node.getid then nodes.getid=node.getid else function nodes.getid (n) return getfield(n,"id") end end
if node.getsubtype then nodes.getsubtype=node.getsubtype else function nodes.getsubtype(n) return getfield(n,"subtype") end end
if node.getnext then nodes.getnext=node.getnext else function nodes.getnext (n) return getfield(n,"next") end end
if node.getprev then nodes.getprev=node.getprev else function nodes.getprev (n) return getfield(n,"prev") end end
if node.getchar then nodes.getchar=node.getchar else function nodes.getchar (n) return getfield(n,"char") end end
if node.getfont then nodes.getfont=node.getfont else function nodes.getfont (n) return getfield(n,"font") end end
if node.getlist then nodes.getlist=node.getlist else function nodes.getlist (n) return getfield(n,"list") end end
function nodes.tonut (n) return n end
function nodes.tonode(n) return n end
nodes.tostring=node.tostring or tostring
nodes.copy=node.copy
nodes.copy_list=node.copy_list
nodes.delete=node.delete
nodes.dimensions=node.dimensions
nodes.end_of_math=node.end_of_math
nodes.flush_list=node.flush_list
nodes.flush_node=node.flush_node
nodes.free=node.free
nodes.insert_after=node.insert_after
nodes.insert_before=node.insert_before
nodes.hpack=node.hpack
nodes.new=node.new
nodes.tail=node.tail
nodes.traverse=node.traverse
nodes.traverse_id=node.traverse_id
nodes.slide=node.slide
nodes.vpack=node.vpack
nodes.first_glyph=node.first_glyph
nodes.first_character=node.first_character
nodes.has_glyph=node.has_glyph or node.first_glyph
nodes.current_attr=node.current_attr
nodes.do_ligature_n=node.do_ligature_n
nodes.has_field=node.has_field
nodes.last_node=node.last_node
nodes.usedlist=node.usedlist
nodes.protrusion_skippable=node.protrusion_skippable
nodes.write=node.write
nodes.has_attribute=node.has_attribute
nodes.set_attribute=node.set_attribute
nodes.unset_attribute=node.unset_attribute
nodes.protect_glyphs=node.protect_glyphs
nodes.unprotect_glyphs=node.unprotect_glyphs
nodes.kerning=node.kerning
nodes.ligaturing=node.ligaturing
nodes.mlist_to_hlist=node.mlist_to_hlist
nodes.nuts=nodes
end -- closure
do -- begin closure to overcome local limits and interference
if not modules then modules={} end modules ['font-ini']={
version=1.001,
comment="companion to font-ini.mkiv",
author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
copyright="PRAGMA ADE / ConTeXt Development Team",
license="see context related readme files"
}
local allocate=utilities.storage.allocate
local report_defining=logs.reporter("fonts","defining")
fonts=fonts or {}
local fonts=fonts
fonts.hashes={ identifiers=allocate() }
fonts.tables=fonts.tables or {}
fonts.helpers=fonts.helpers or {}
fonts.tracers=fonts.tracers or {}
fonts.specifiers=fonts.specifiers or {}
fonts.analyzers={}
fonts.readers={}
fonts.definers={ methods={} }
fonts.loggers={ register=function() end }
fontloader.totable=fontloader.to_table
end -- closure
do -- begin closure to overcome local limits and interference
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",
copyright="PRAGMA ADE / ConTeXt Development Team",
license="see context related readme files"
}
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 derivetable=table.derive
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)
local report_defining=logs.reporter("fonts","defining")
local fonts=fonts
local constructors=fonts.constructors or {}
fonts.constructors=constructors
local handlers=fonts.handlers or {}
fonts.handlers=handlers
local allocate=utilities.storage.allocate
local setmetatableindex=table.setmetatableindex
constructors.dontembed=allocate()
constructors.autocleanup=true
constructors.namemode="fullpath"
constructors.version=1.01
constructors.cache=containers.define("fonts","constructors",constructors.version,false)
constructors.privateoffset=0xF0000
constructors.keys={
properties={
encodingbytes="number",
embedding="number",
cidinfo={},
format="string",
fontname="string",
fullname="string",
filename="filename",
psname="string",
name="string",
virtualized="boolean",
hasitalics="boolean",
autoitalicamount="basepoints",
nostackmath="boolean",
noglyphnames="boolean",
mode="string",
hasmath="boolean",
mathitalics="boolean",
textitalics="boolean",
finalized="boolean",
},
parameters={
mathsize="number",
scriptpercentage="float",
scriptscriptpercentage="float",
units="cardinal",
designsize="scaledpoints",
expansion={
stretch="integerscale",
shrink="integerscale",
step="integerscale",
auto="boolean",
},
protrusion={
auto="boolean",
},
slantfactor="float",
extendfactor="float",
factor="float",
hfactor="float",
vfactor="float",
size="scaledpoints",
units="scaledpoints",
scaledpoints="scaledpoints",
slantperpoint="scaledpoints",
spacing={
width="scaledpoints",
stretch="scaledpoints",
shrink="scaledpoints",
extra="scaledpoints",
},
xheight="scaledpoints",
quad="scaledpoints",
ascender="scaledpoints",
descender="scaledpoints",
synonyms={
space="spacing.width",
spacestretch="spacing.stretch",
spaceshrink="spacing.shrink",
extraspace="spacing.extra",
x_height="xheight",
space_stretch="spacing.stretch",
space_shrink="spacing.shrink",
extra_space="spacing.extra",
em="quad",
ex="xheight",
slant="slantperpoint",
},
},
description={
width="basepoints",
height="basepoints",
depth="basepoints",
boundingbox={},
},
character={
width="scaledpoints",
height="scaledpoints",
depth="scaledpoints",
italic="scaledpoints",
},
}
local designsizes=allocate()
constructors.designsizes=designsizes
local loadedfonts=allocate()
constructors.loadedfonts=loadedfonts
local factors={
pt=65536.0,
bp=65781.8,
}
function constructors.setfactor(f)
constructors.factor=factors[f or 'pt'] or factors.pt
end
constructors.setfactor()
function constructors.scaled(scaledpoints,designsize)
if scaledpoints<0 then
if designsize then
local factor=constructors.factor
if designsize>factor then
return (- scaledpoints/1000)*designsize
else
return (- scaledpoints/1000)*designsize*factor
end
else
return (- scaledpoints/1000)*10*factor
end
else
return scaledpoints
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
end
end
end
function constructors.calculatescale(tfmdata,scaledpoints)
local parameters=tfmdata.parameters
if scaledpoints<0 then
scaledpoints=(- scaledpoints/1000)*(tfmdata.designsize or parameters.designsize)
end
return scaledpoints,scaledpoints/(parameters.units or 1000)
end
local unscaled={
ScriptPercentScaleDown=true,
ScriptScriptPercentScaleDown=true,
RadicalDegreeBottomRaisePercent=true
}
function constructors.assignmathparameters(target,original)
local mathparameters=original.mathparameters
if mathparameters and next(mathparameters) then
local targetparameters=target.parameters
local targetproperties=target.properties
local targetmathparameters={}
local factor=targetproperties.math_is_scaled and 1 or targetparameters.factor
for name,value in next,mathparameters do
if unscaled[name] then
targetmathparameters[name]=value
else
targetmathparameters[name]=value*factor
end
end
if not targetmathparameters.FractionDelimiterSize then
targetmathparameters.FractionDelimiterSize=1.01*targetparameters.size
end
if not mathparameters.FractionDelimiterDisplayStyleSize then
targetmathparameters.FractionDelimiterDisplayStyleSize=2.40*targetparameters.size
end
target.mathparameters=targetmathparameters
end
end
function constructors.beforecopyingcharacters(target,original)
end
function constructors.aftercopyingcharacters(target,original)
end
function constructors.enhanceparameters(parameters)
local xheight=parameters.x_height
local quad=parameters.quad
local space=parameters.space
local stretch=parameters.space_stretch
local shrink=parameters.space_shrink
local extra=parameters.extra_space
local slant=parameters.slant
parameters.xheight=xheight
parameters.spacestretch=stretch
parameters.spaceshrink=shrink
parameters.extraspace=extra
parameters.em=quad
parameters.ex=xheight
parameters.slantperpoint=slant
parameters.spacing={
width=space,
stretch=stretch,
shrink=shrink,
extra=extra,
}
end
function constructors.scale(tfmdata,specification)
local target={}
if tonumber(specification) then
specification={ size=specification }
end
target.specification=specification
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 {}
local characters=tfmdata.characters or {}
local changed=tfmdata.changed or {}
local shared=tfmdata.shared or {}
local parameters=tfmdata.parameters or {}
local mathparameters=tfmdata.mathparameters or {}
local targetcharacters={}
local targetdescriptions=derivetable(descriptions)
local targetparameters=derivetable(parameters)
local targetproperties=derivetable(properties)
local targetgoodies=goodies
target.characters=targetcharacters
target.descriptions=targetdescriptions
target.parameters=targetparameters
target.properties=targetproperties
target.goodies=targetgoodies
target.shared=shared
target.resources=resources
target.unscaled=tfmdata
local mathsize=tonumber(specification.mathsize) or 0
local textsize=tonumber(specification.textsize) or scaledpoints
local forcedsize=tonumber(parameters.mathsize ) or 0
local extrafactor=tonumber(specification.factor ) or 1
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
scaledpoints=forcedsize
end
targetparameters.mathsize=mathsize
targetparameters.textsize=textsize
targetparameters.forcedsize=forcedsize
targetparameters.extrafactor=extrafactor
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)
end
targetproperties.language=properties.language or "dflt"
targetproperties.script=properties.script or "dflt"
targetproperties.mode=properties.mode or "base"
local askedscaledpoints=scaledpoints
local scaledpoints,delta=constructors.calculatescale(tfmdata,scaledpoints,nil,specification)
local hdelta=delta
local vdelta=delta
target.designsize=parameters.designsize
target.units_per_em=units
local direction=properties.direction or tfmdata.direction or 0
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
local fullname=properties.fullname or tfmdata.fullname
local filename=properties.filename or tfmdata.filename
local psname=properties.psname or tfmdata.psname
local name=properties.name or tfmdata.name
if not psname or psname=="" then
psname=fontname or (fullname and fonts.names.cleanname(fullname))
end
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
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
local protrusion=parameters.protrusion
if protrusion then
target.auto_protrude=protrusion.auto
end
local extendfactor=parameters.extendfactor or 0
if extendfactor~=0 and extendfactor~=1 then
hdelta=hdelta*extendfactor
target.extend=extendfactor*1000
else
target.extend=1000
end
local slantfactor=parameters.slantfactor or 0
if slantfactor~=0 then
target.slant=slantfactor*1000
else
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 hasitalics=properties.hasitalics
local autoitalicamount=properties.autoitalicamount
local stackmath=not properties.nostackmath
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"
target.postprocessors=tfmdata.postprocessors
local targetslant=(parameters.slant or parameters[1] or 0)*factors.pt
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
local ascender=parameters.ascender
if ascender then
targetparameters.ascender=delta*ascender
end
local descender=parameters.descender
if descender then
targetparameters.descender=delta*descender
end
constructors.enhanceparameters(targetparameters)
local protrusionfactor=(targetquad~=0 and 1000/targetquad) or 0
local scaledwidth=defaultwidth*hdelta
local scaledheight=defaultheight*vdelta
local scaleddepth=defaultdepth*vdelta
local hasmath=(properties.hasmath or next(mathparameters)) and true
if hasmath then
constructors.assignmathparameters(target,tfmdata)
properties.hasmath=true
target.nomath=false
target.MathConstants=target.mathparameters
else
properties.hasmath=false
target.nomath=true
target.mathparameters=nil
end
local italickey="italic"
local useitalics=true
if hasmath then
autoitalicamount=false
elseif properties.textitalics then
italickey="italic_correction"
useitalics=false
if properties.delaytextitalics then
autoitalicamount=false
end
end
if trace_defining then
report_defining("defining tfm, name %a, fullname %a, filename %a, hscale %a, vscale %a, math %a, italics %a",
name,fullname,filename,hdelta,vdelta,
hasmath and "enabled" or "disabled",useitalics and "enabled" or "disabled")
end
constructors.beforecopyingcharacters(target,tfmdata)
local sharedkerns={}
for unicode,character in next,characters do
local chr,description,index,touni
if changed then
local c=changed[unicode]
if c then
description=descriptions[c] or descriptions[unicode] or character
character=characters[c] or character
index=description.index or c
if tounicode then
touni=tounicode[index]
if not touni then
local d=descriptions[unicode] or characters[unicode]
local i=d.index or unicode
touni=tounicode[i]
end
end
else
description=descriptions[unicode] or character
index=description.index or unicode
if tounicode then
touni=tounicode[index]
end
end
else
description=descriptions[unicode] or character
index=description.index or unicode
if tounicode then
touni=tounicode[index]
end
end
local width=description.width
local height=description.height
local depth=description.depth
if width then width=hdelta*width else width=scaledwidth end
if height then height=vdelta*height else height=scaledheight end
if depth and depth~=0 then
depth=delta*depth
if nonames then
chr={
index=index,
height=height,
depth=depth,
width=width,
}
else
chr={
name=description.name,
index=index,
height=height,
depth=depth,
width=width,
}
end
else
if nonames then
chr={
index=index,
height=height,
width=width,
}
else
chr={
name=description.name,
index=index,
height=height,
width=width,
}
end
end
if touni then
chr.tounicode=touni
end
if hasquality then
local ve=character.expansion_factor
if ve then
chr.expansion_factor=ve*1000
end
local vl=character.left_protruding
if vl then
chr.left_protruding=protrusionfactor*width*vl
end
local vr=character.right_protruding
if vr then
chr.right_protruding=protrusionfactor*width*vr
end
end
if autoitalicamount then
local vi=description.italic
if not vi then
local vi=description.boundingbox[3]-description.width+autoitalicamount
if vi>0 then
chr[italickey]=vi*hdelta
end
elseif vi~=0 then
chr[italickey]=vi*hdelta
end
elseif hasitalics then
local vi=description.italic
if vi and vi~=0 then
chr[italickey]=vi*hdelta
end
end
if hasmath then
local vn=character.next
if vn then
chr.next=vn
else
local vv=character.vert_variants
if vv then
local t={}
for i=1,#vv do
local vvi=vv[i]
t[i]={
["start"]=(vvi["start"] or 0)*vdelta,
["end"]=(vvi["end"] or 0)*vdelta,
["advance"]=(vvi["advance"] or 0)*vdelta,
["extender"]=vvi["extender"],
["glyph"]=vvi["glyph"],
}
end
chr.vert_variants=t
else
local hv=character.horiz_variants
if hv then
local t={}
for i=1,#hv do
local hvi=hv[i]
t[i]={
["start"]=(hvi["start"] or 0)*hdelta,
["end"]=(hvi["end"] or 0)*hdelta,
["advance"]=(hvi["advance"] or 0)*hdelta,
["extender"]=hvi["extender"],
["glyph"]=hvi["glyph"],
}
end
chr.horiz_variants=t
end
end
end
local va=character.top_accent
if va then
chr.top_accent=vdelta*va
end
if stackmath then
local mk=character.mathkerns
if mk then
local kerns={}
local v=mk.top_right if v then local k={} for i=1,#v do local vi=v[i]
k[i]={ height=vdelta*vi.height,kern=vdelta*vi.kern }
end kerns.top_right=k end
local v=mk.top_left if v then local k={} for i=1,#v do local vi=v[i]
k[i]={ height=vdelta*vi.height,kern=vdelta*vi.kern }
end kerns.top_left=k end
local v=mk.bottom_left if v then local k={} for i=1,#v do local vi=v[i]
k[i]={ height=vdelta*vi.height,kern=vdelta*vi.kern }
end kerns.bottom_left=k end
local v=mk.bottom_right if v then local k={} for i=1,#v do local vi=v[i]
k[i]={ height=vdelta*vi.height,kern=vdelta*vi.kern }
end kerns.bottom_right=k end
chr.mathkern=kerns
end
end
end
if not nodemode then
local vk=character.kerns
if vk then
local s=sharedkerns[vk]
if not s then
s={}
for k,v in next,vk do s[k]=v*hdelta end
sharedkerns[vk]=s
end
chr.kerns=s
end
local vl=character.ligatures
if vl then
if true then
chr.ligatures=vl
else
local tt={}
for i,l in next,vl do
tt[i]=l
end
chr.ligatures=tt
end
end
end
if isvirtual then
local vc=character.commands
if vc then
local ok=false
for i=1,#vc do
local key=vc[i][1]
if key=="right" or key=="down" then
ok=true
break
end
end
if ok then
local tt={}
for i=1,#vc do
local ivc=vc[i]
local key=ivc[1]
if key=="right" then
tt[i]={ key,ivc[2]*hdelta }
elseif key=="down" then
tt[i]={ key,ivc[2]*vdelta }
elseif key=="rule" then
tt[i]={ key,ivc[2]*vdelta,ivc[3]*hdelta }
else
tt[i]=ivc
end
end
chr.commands=tt
else
chr.commands=vc
end
chr.index=nil
end
end
targetcharacters[unicode]=chr
end
constructors.aftercopyingcharacters(target,tfmdata)
return target
end
function constructors.finalize(tfmdata)
if tfmdata.properties and tfmdata.properties.finalized then
return
end
if not tfmdata.characters then
return nil
end
if not tfmdata.goodies then
tfmdata.goodies={}
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.extendfactor then
parameters.extendfactor=tfmdata.extend or 0
end
if not parameters.slantfactor then
parameters.slantfactor=tfmdata.slant or 0
end
if not parameters.designsize then
parameters.designsize=tfmdata.designsize or (factors.pt*10)
end
if not parameters.units then
parameters.units=tfmdata.units_per_em or 1000
end
if not tfmdata.descriptions then
local descriptions={}
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
if not properties.hasmath then
properties.hasmath=not tfmdata.nomath
end
tfmdata.MathConstants=nil
tfmdata.postprocessors=nil
tfmdata.fontname=nil
tfmdata.filename=nil
tfmdata.fullname=nil
tfmdata.name=nil
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
local hashmethods={}
constructors.hashmethods=hashmethods
function constructors.hashfeatures(specification)
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
if tn>0 then
return concat(t," & ")
end
end
return "unknown"
end
hashmethods.normal=function(list)
local s={}
local n=0
for k,v in next,list do
if not k then
elseif k=="number" or k=="features" then
else
n=n+1
s[n]=k
end
end
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
end
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
if fallbacks then
return hash..' @ '..tostring(size)..' @ '..fallbacks
else
return hash..' @ '..tostring(size)
end
end
function constructors.setname(tfmdata,specification)
if constructors.namemode=="specification" then
local specname=specification.specification
if specname then
tfmdata.properties.name=specname
if trace_defining then
report_otf("overloaded fontname %a",specname)
end
end
end
end
function constructors.checkedfilename(data)
local foundfilename=data.foundfilename
if not foundfilename then
local askedfilename=data.filename or ""
if askedfilename~="" then
askedfilename=resolvers.resolve(askedfilename)
foundfilename=resolvers.findbinfile(askedfilename,"") or ""
if foundfilename=="" then
report_defining("source file %a is not found",askedfilename)
foundfilename=resolvers.findbinfile(file.basename(askedfilename),"") or ""
if foundfilename~="" then
report_defining("using source file %a due to 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.suffix(l))
end)
local locations={}
local function setindeed(mode,target,group,name,action,position)
local t=target[mode]
if not t then
report_defining("fatal error in setting feature %a, group %a, mode %a",name,group,mode)
os.exit()
elseif position then
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
end
insert(t,{ name=name,action=action })
end
end
local function set(group,name,target,source)
target=target[group]
if not target then
report_defining("fatal target error in setting feature %a, group %a",name,group)
os.exit()
end
local source=source[group]
if not source then
report_defining("fatal source error in setting feature %a, group %a",name,group)
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
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
constructors.registerfeature=register
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
function constructors.newhandler(what)
local handler=handlers[what]
if not handler then
handler={}
handlers[what]=handler
end
return handler
end
function constructors.newfeatures(what)
local handler=handlers[what]
local features=handler.features
if not features then
local tables=handler.tables
local statistics=handler.statistics
features=allocate {
defaults={},
descriptions=tables and tables.features or {},
used=statistics and statistics.usedfeatures or {},
initializers={ base={},node={} },
processors={ base={},node={} },
manipulators={ base={},node={} },
}
features.register=function(specification) return register(features,specification) end
handler.features=features
end
return features
end
function constructors.checkedfeatures(what,features)
local defaults=handlers[what].features.defaults
if features and next(features) then
features=fastcopy(features)
for key,value in next,defaults do
if features[key]==nil then
features[key]=value
end
end
return features
else
return fastcopy(defaults)
end
end
function constructors.initializefeatures(what,tfmdata,features,trace,report)
if features and next(features) then
local properties=tfmdata.properties or {}
local whathandler=handlers[what]
local whatfeatures=whathandler.features
local whatinitializers=whatfeatures.initializers
local whatmodechecker=whatfeatures.modechecker
local mode=properties.mode or (whatmodechecker and whatmodechecker(tfmdata,features,features.mode)) or features.mode or "base"
properties.mode=mode
features.mode=mode
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
local value=features[feature]
if not value then
elseif done[feature] then
else
local action=step.action
if trace then
report("initializing feature %a to %a for mode %a for font %a",feature,
value,mode,tfmdata.properties.fullname)
end
action(tfmdata,value,features)
if mode~=properties.mode or mode~=features.mode then
if whatmodechecker then
properties.mode=whatmodechecker(tfmdata,features,properties.mode)
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
if not redo then
break
end
else
break
end
end
properties.mode=mode
return true
else
return false
end
end
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 mode=properties.mode
local processors=whatprocessors[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 %a for mode %a for font %a",feature,mode,tfmdata.properties.fullname)
end
if action then
nofprocesses=nofprocesses+1
processes[nofprocesses]=action
end
end
end
elseif trace then
report("no feature processors for mode %a for font %a",mode,properties.fullname)
end
end
return processes
end
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 mode=properties.mode
local manipulators=whatmanipulators[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 %a for mode %a for font %a",feature,mode,properties.fullname)
end
if action then
action(tfmdata,feature,value)
end
end
end
end
end
end
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-age.lua"))
fonts.encodings.agl={ unicodes=unicodes }
return unicodes
else
return nil
end
end })
end -- closure
do -- begin closure to overcome local limits and interference
if not modules then modules={} end modules ['font-cid']={
version=1.001,
comment="companion to font-otf.lua (cidmaps)",
author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
copyright="PRAGMA ADE / ConTeXt Development Team",
license="see context related readme files"
}
local format,match,lower=string.format,string.match,string.lower
local tonumber=tonumber
local P,S,R,C,V,lpegmatch=lpeg.P,lpeg.S,lpeg.R,lpeg.C,lpeg.V,lpeg.match
local fonts,logs,trackers=fonts,logs,trackers
local trace_loading=false trackers.register("otf.loading",function(v) trace_loading=v end)
local report_otf=logs.reporter("fonts","otf loading")
local cid={}
fonts.cid=cid
local cidmap={}
local cidmax=10
local number=C(R("09","af","AF")^1)
local space=S(" \n\r\t")
local spaces=space^0
local period=P(".")
local periods=period*period
local name=P("/")*C((1-space)^1)
local unicodes,names={},{}
local function do_one(a,b)
unicodes[tonumber(a)]=tonumber(b,16)
end
local function do_range(a,b,c)
c=tonumber(c,16)
for i=tonumber(a),tonumber(b) do
unicodes[i]=c
c=c+1
end
end
local function do_name(a,b)
names[tonumber(a)]=b
end
local grammar=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
}
local function loadcidfile(filename)
local data=io.loaddata(filename)
if data then
unicodes,names={},{}
lpegmatch(grammar,data)
local supplement,registry,ordering=match(filename,"^(.-)%-(.-)%-()%.(.-)$")
return {
supplement=supplement,
registry=registry,
ordering=ordering,
filename=filename,
unicodes=unicodes,
names=names
}
end
end
cid.loadfile=loadcidfile
local template="%s-%s-%s.cidmap"
local function locate(registry,ordering,supplement)
local filename=format(template,registry,ordering,supplement)
local hashname=lower(filename)
local found=cidmap[hashname]
if not found then
if trace_loading then
report_otf("checking cidmap, registry %a, ordering %a, supplement %a, filename %a",registry,ordering,supplement,filename)
end
local fullname=resolvers.findfile(filename,'cid') or ""
if fullname~="" then
found=loadcidfile(fullname)
if found then
if trace_loading then
report_otf("using cidmap file %a",filename)
end
cidmap[hashname]=found
found.usedname=file.basename(filename)
end
end
end
return found
end
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
local filename=format(registry,ordering,supplement)
local found=cidmap[lower(filename)]
if found then
return found
end
if trace_loading then
report_otf("cidmap needed, registry %a, ordering %a, supplement %a",registry,ordering,supplement)
end
found=locate(registry,ordering,supplement)
if not found then
local supnum=tonumber(supplement)
local cidnum=nil
if supnum<cidmax then
for s=supnum+1,cidmax do
local c=locate(registry,ordering,s)
if c then
found,cidnum=c,s
break
end
end
end
if not found and supnum>0 then
for s=supnum-1,0,-1 do
local c=locate(registry,ordering,s)
if c then
found,cidnum=c,s
break
end
end
end
registry=lower(registry)
ordering=lower(ordering)
if found and cidnum>0 then
for s=0,cidnum-1 do
local filename=format(template,registry,ordering,s)
if not cidmap[filename] then
cidmap[filename]=found
end
end
end
end
return found
end
end -- closure
do -- begin closure to overcome local limits and interference
if not modules then modules={} end modules ['font-map']={
version=1.001,
comment="companion to font-ini.mkiv",
author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
copyright="PRAGMA ADE / ConTeXt Development Team",
license="see context related readme files"
}
local tonumber=tonumber
local match,format,find,concat,gsub,lower=string.match,string.format,string.find,table.concat,string.gsub,string.lower
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 floor=math.floor
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 report_fonts=logs.reporter("fonts","loading")
local fonts=fonts or {}
local mappings=fonts.mappings or {}
fonts.mappings=mappings
local function loadlumtable(filename)
local lumname=file.replacesuffix(file.basename(filename),"lum")
local lumfile=resolvers.findfile(lumname,"map") or ""
if lumfile~="" and lfs.isfile(lumfile) then
if trace_loading or trace_mapping then
report_fonts("loading map table %a",lumfile)
end
lumunic=dofile(lumfile)
return lumunic,lumfile
end
end
local hex=R("AF","09")
local hexfour=(hex*hex*hex*hex)/function(s) return tonumber(s,16) end
local hexsix=(hex*hex*hex*hex*hex*hex)/function(s) return tonumber(s,16) end
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 makenameparser(str)
if not str or str=="" then
return parser
else
local p=parsers[str]
if not p then
p=P(str)*period*dec*Cc(false)
parsers[str]=p
end
return p
end
end
local function tounicode16(unicode,name)
if unicode<0x10000 then
return format("%04X",unicode)
elseif unicode<0x1FFFFFFFFF then
return format("%04X%04X",floor(unicode/1024),unicode%1024+0xDC00)
else
report_fonts("can't convert %a in %a into tounicode",unicode,name)
end
end
local function tounicode16sequence(unicodes,name)
local t={}
for l=1,#unicodes do
local unicode=unicodes[l]
if unicode<0x10000 then
t[l]=format("%04X",unicode)
elseif unicode<0x1FFFFFFFFF then
t[l]=format("%04X%04X",floor(unicode/1024),unicode%1024+0xDC00)
else
report_fonts ("can't convert %a in %a into tounicode",unicode,name)
end
end
return concat(t)
end
local function fromunicode16(str)
if #str==4 then
return tonumber(str,16)
else
local l,r=match(str,"(....)(....)")
return (tonumber(l,16))*0x400+tonumber(r,16)-0xDC00
end
end
mappings.loadlumtable=loadlumtable
mappings.makenameparser=makenameparser
mappings.tounicode16=tounicode16
mappings.tounicode16sequence=tounicode16sequence
mappings.fromunicode16=fromunicode16
local ligseparator=P("_")
local varseparator=P(".")
local namesplitter=Ct(C((1-ligseparator-varseparator)^1)*(ligseparator*C((1-ligseparator-varseparator)^1))^0)
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
unicodes['space']=unicodes['space'] or 32
unicodes['hyphen']=unicodes['hyphen'] or 45
unicodes['zwj']=unicodes['zwj'] or 0x200D
unicodes['zwnj']=unicodes['zwnj'] or 0x200C
local private=fonts.constructors.privateoffset
local unknown=format("%04X",utfbyte("?"))
local unicodevector=fonts.encodings.agl.unicodes
local tounicode={}
local originals={}
resources.tounicode=tounicode
resources.originals=originals
local lumunic,uparser,oparser
local cidinfo,cidnames,cidcodes,usedmap
if false then
lumunic=loadlumtable(filename)
lumunic=lumunic and lumunic.tounicode
end
cidinfo=properties.cidinfo
usedmap=cidinfo and fonts.cid.getmap(cidinfo)
if usedmap then
oparser=usedmap and makenameparser(cidinfo.ordering)
cidnames=usedmap.names
cidcodes=usedmap.unicodes
end
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 unicodevector[name]
if unicode then
originals[index]=unicode
tounicode[index]=tounicode16(unicode,name)
ns=ns+1
end
if (not unicode) and usedmap then
local foundindex=lpegmatch(oparser,name)
if foundindex then
unicode=cidcodes[foundindex]
if unicode then
originals[index]=unicode
tounicode[index]=tounicode16(unicode,name)
ns=ns+1
else
local reference=cidnames[foundindex]
if reference then
local foundindex=lpegmatch(oparser,reference)
if foundindex then
unicode=cidcodes[foundindex]
if unicode then
originals[index]=unicode
tounicode[index]=tounicode16(unicode,name)
ns=ns+1
end
end
if not unicode or unicode=="" then
local foundcodes,multiple=lpegmatch(uparser,reference)
if foundcodes then
originals[index]=foundcodes
if multiple then
tounicode[index]=tounicode16sequence(foundcodes)
nl=nl+1
unicode=true
else
tounicode[index]=tounicode16(foundcodes,name)
ns=ns+1
unicode=foundcodes
end
end
end
end
end
end
end
if not unicode or unicode=="" then
local split=lpegmatch(namesplitter,name)
local nsplit=split and #split or 0
local t,n={},0
unicode=true
for l=1,nsplit do
local base=split[l]
local u=unicodes[base] or unicodevector[base]
if not u then
break
elseif type(u)=="table" then
if u[1]>=private then
unicode=false
break
end
n=n+1
t[n]=u[1]
else
if u>=private then
unicode=false
break
end
n=n+1
t[n]=u
end
end
if n==0 then
elseif n==1 then
originals[index]=t[1]
tounicode[index]=tounicode16(t[1],name)
else
originals[index]=t
tounicode[index]=tounicode16sequence(t)
end
nl=nl+1
end
if not unicode or unicode=="" then
local foundcodes,multiple=lpegmatch(uparser,name)
if foundcodes then
if multiple then
originals[index]=foundcodes
tounicode[index]=tounicode16sequence(foundcodes,name)
nl=nl+1
unicode=true
else
originals[index]=foundcodes
tounicode[index]=tounicode16(foundcodes,name)
ns=ns+1
unicode=foundcodes
end
end
end
end
end
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
report_fonts("internal slot %U, name %a, unicode %U, tounicode %a",index,name,unic,toun)
else
report_fonts("internal slot %U, name %a, unicode %U",index,name,unic)
end
end
end
if trace_loading and (ns>0 or nl>0) then
report_fonts("%s tounicode entries added, ligatures %s",nl+ns,ns)
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
local fonts=fonts
fonts.names=fonts.names or {}
fonts.names.version=1.001
fonts.names.basename="luatex-fonts-names"
fonts.names.new_to_old={}
fonts.names.old_to_new={}
fonts.names.cache=containers.define("fonts","data",fonts.names.version,true)
local data,loaded=nil,false
local fileformats={ "lua","tex","other text files" }
function fonts.names.reportmissingbase()
texio.write("<missing font database, run: mtxrun --script fonts --reload --simple>")
fonts.names.reportmissingbase=nil
end
function fonts.names.reportmissingname()
texio.write("<unknown font in database, run: mtxrun --script fonts --reload --simple>")
fonts.names.reportmissingname=nil
end
function fonts.names.resolve(name,sub)
if not loaded then
local basename=fonts.names.basename
if basename and basename~="" then
data=containers.read(fonts.names.cache,basename)
if not data then
basename=file.addsuffix(basename,"lua")
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
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
elseif fonts.names.reportmissingname then
fonts.names.reportmissingname()
return name,false
end
elseif fonts.names.reportmissingbase then
fonts.names.reportmissingbase()
end
end
fonts.names.resolvespec=fonts.names.resolve
function fonts.names.getfilename(askedname,suffix)
return ""
end
function fonts.names.ignoredfile(filename)
return false
end
end -- closure
do -- begin closure to overcome local limits and interference
if not modules then modules={} end modules ['font-tfm']={
version=1.001,
comment="companion to font-ini.mkiv",
author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
copyright="PRAGMA ADE / ConTeXt Development Team",
license="see context related readme files"
}
local next=next
local match=string.match
local trace_defining=false trackers.register("fonts.defining",function(v) trace_defining=v end)
local trace_features=false trackers.register("tfm.features",function(v) trace_features=v end)
local report_defining=logs.reporter("fonts","defining")
local report_tfm=logs.reporter("fonts","tfm loading")
local findbinfile=resolvers.findbinfile
local fonts=fonts
local handlers=fonts.handlers
local readers=fonts.readers
local constructors=fonts.constructors
local encodings=fonts.encodings
local tfm=constructors.newhandler("tfm")
local tfmfeatures=constructors.newfeatures("tfm")
local registertfmfeature=tfmfeatures.register
constructors.resolvevirtualtoo=false
fonts.formats.tfm="type1"
function tfm.setfeatures(tfmdata,features)
local okay=constructors.initializefeatures("tfm",tfmdata,features,trace_features,report_tfm)
if okay then
return constructors.collectprocessors("tfm",tfmdata,features,trace_features,report_tfm)
else
return {}
end
end
local function read_from_tfm(specification)
local filename=specification.filename
local size=specification.size
if trace_defining then
report_defining("loading tfm file %a at size %s",filename,size)
end
local tfmdata=font.read_tfm(filename,size)
if tfmdata then
local features=specification.features and specification.features.normal or {}
local resources=tfmdata.resources or {}
local properties=tfmdata.properties or {}
local parameters=tfmdata.parameters or {}
local shared=tfmdata.shared or {}
properties.name=tfmdata.name
properties.fontname=tfmdata.fontname
properties.psname=tfmdata.psname
properties.filename=specification.filename
parameters.size=size
shared.rawdata={}
shared.features=features
shared.processes=next(features) and tfm.setfeatures(tfmdata,features) or nil
tfmdata.properties=properties
tfmdata.resources=resources
tfmdata.parameters=parameters
tfmdata.shared=shared
parameters.slant=parameters.slant or parameters[1] or 0
parameters.space=parameters.space or parameters[2] or 0
parameters.space_stretch=parameters.space_stretch or parameters[3] or 0
parameters.space_shrink=parameters.space_shrink or parameters[4] or 0
parameters.x_height=parameters.x_height or parameters[5] or 0
parameters.quad=parameters.quad or parameters[6] or 0
parameters.extra_space=parameters.extra_space or parameters[7] or 0
constructors.enhanceparameters(parameters)
if constructors.resolvevirtualtoo then
fonts.loggers.register(tfmdata,file.suffix(filename),specification)
local vfname=findbinfile(specification.name,'ovf')
if vfname and vfname~="" then
local vfdata=font.read_vf(vfname,size)
if vfdata then
local chars=tfmdata.characters
for k,v in next,vfdata.characters do
chars[k].commands=v.commands
end
properties.virtualized=true
tfmdata.fonts=vfdata.fonts
end
end
end
local allfeatures=tfmdata.shared.features or specification.features.normal
constructors.applymanipulators("tfm",tfmdata,allfeatures.normal,trace_features,report_tfm)
if not features.encoding then
local encoding,filename=match(properties.filename,"^(.-)%-(.*)$")
if filename and encoding and encodings.known and encodings.known[encoding] then
features.encoding=encoding
end
end
return tfmdata
end
end
local function check_tfm(specification,fullname)
local foundname=findbinfile(fullname,'tfm') or ""
if foundname=="" then
foundname=findbinfile(fullname,'ofm') or ""
end
if foundname=="" then
foundname=fonts.names.getfilename(fullname,"tfm") or ""
end
if foundname~="" then
specification.filename=foundname
specification.format="ofm"
return read_from_tfm(specification)
elseif trace_defining then
report_defining("loading tfm with name %a fails",specification.name)
end
end
readers.check_tfm=check_tfm
function 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
return check_tfm(specification,fullname)
end
end -- closure
do -- begin closure to overcome local limits and interference
if not modules then modules={} end modules ['font-afm']={
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 fonts,logs,trackers,containers,resolvers=fonts,logs,trackers,containers,resolvers
local next,type,tonumber=next,type,tonumber
local format,match,gmatch,lower,gsub,strip=string.format,string.match,string.gmatch,string.lower,string.gsub,string.strip
local abs=math.abs
local P,S,C,R,lpegmatch,patterns=lpeg.P,lpeg.S,lpeg.C,lpeg.R,lpeg.match,lpeg.patterns
local derivetable=table.derive
local trace_features=false trackers.register("afm.features",function(v) trace_features=v end)
local trace_indexing=false trackers.register("afm.indexing",function(v) trace_indexing=v end)
local trace_loading=false trackers.register("afm.loading",function(v) trace_loading=v end)
local trace_defining=false trackers.register("fonts.defining",function(v) trace_defining=v end)
local report_afm=logs.reporter("fonts","afm loading")
local findbinfile=resolvers.findbinfile
local definers=fonts.definers
local readers=fonts.readers
local constructors=fonts.constructors
local afm=constructors.newhandler("afm")
local pfb=constructors.newhandler("pfb")
local afmfeatures=constructors.newfeatures("afm")
local registerafmfeature=afmfeatures.register
afm.version=1.410
afm.cache=containers.define("fonts","afm",afm.version,true)
afm.autoprefixed=true
afm.helpdata={}
afm.syncspace=true
afm.addligatures=true
afm.addtexligatures=true
afm.addkerns=true
local applyruntimefixes=fonts.treatments and fonts.treatments.applyfixes
local function setmode(tfmdata,value)
if value then
tfmdata.properties.mode=lower(value)
end
end
registerafmfeature {
name="mode",
description="mode",
initializers={
base=setmode,
node=setmode,
}
}
local comment=P("Comment")
local spacing=patterns.spacer
local lineend=patterns.newline
local words=C((1-lineend)^1)
local number=C((R("09")+S("."))^1)/tonumber*spacing^0
local data=lpeg.Carg(1)
local pattern=(
comment*spacing*(
data*(
("CODINGSCHEME"*spacing*words )/function(fd,a) end+("DESIGNSIZE"*spacing*number*words )/function(fd,a) fd[ 1]=a end+("CHECKSUM"*spacing*number*words )/function(fd,a) fd[ 2]=a end+("SPACE"*spacing*number*"plus"*number*"minus"*number)/function(fd,a,b,c) fd[ 3],fd[ 4],fd[ 5]=a,b,c end+("QUAD"*spacing*number )/function(fd,a) fd[ 6]=a end+("EXTRASPACE"*spacing*number )/function(fd,a) fd[ 7]=a end+("NUM"*spacing*number*number*number )/function(fd,a,b,c) fd[ 8],fd[ 9],fd[10]=a,b,c end+("DENOM"*spacing*number*number )/function(fd,a,b ) fd[11],fd[12]=a,b end+("SUP"*spacing*number*number*number )/function(fd,a,b,c) fd[13],fd[14],fd[15]=a,b,c end+("SUB"*spacing*number*number )/function(fd,a,b) fd[16],fd[17]=a,b end+("SUPDROP"*spacing*number )/function(fd,a) fd[18]=a end+("SUBDROP"*spacing*number )/function(fd,a) fd[19]=a end+("DELIM"*spacing*number*number )/function(fd,a,b) fd[20],fd[21]=a,b end+("AXISHEIGHT"*spacing*number )/function(fd,a) fd[22]=a end
)+(1-lineend)^0
)+(1-comment)^1
)^0
local function scan_comment(str)
local fd={}
lpegmatch(pattern,str,1,fd)
return fd
end
local keys={}
function keys.FontName (data,line) data.metadata.fontname=strip (line)
data.metadata.fullname=strip (line) end
function keys.ItalicAngle (data,line) data.metadata.italicangle=tonumber (line) end
function keys.IsFixedPitch(data,line) data.metadata.isfixedpitch=toboolean(line,true) end
function keys.CharWidth (data,line) data.metadata.charwidth=tonumber (line) end
function keys.XHeight (data,line) data.metadata.xheight=tonumber (line) end
function keys.Descender (data,line) data.metadata.descender=tonumber (line) end
function keys.Ascender (data,line) data.metadata.ascender=tonumber (line) end
function keys.Comment (data,line)
line=lower(line)
local designsize=match(line,"designsize[^%d]*(%d+)")
if designsize then data.metadata.designsize=tonumber(designsize) end
end
local function get_charmetrics(data,charmetrics,vector)
local characters=data.characters
local chr,ind={},0
for k,v in gmatch(charmetrics,"([%a]+) +(.-) *;") do
if k=='C' then
v=tonumber(v)
if v<0 then
ind=ind+1
else
ind=v
end
chr={
index=ind
}
elseif k=='WX' then
chr.width=tonumber(v)
elseif k=='N' then
characters[v]=chr
elseif k=='B' then
local llx,lly,urx,ury=match(v,"^ *(.-) +(.-) +(.-) +(.-)$")
chr.boundingbox={ tonumber(llx),tonumber(lly),tonumber(urx),tonumber(ury) }
elseif k=='L' then
local plus,becomes=match(v,"^(.-) +(.-)$")
local ligatures=chr.ligatures
if ligatures then
ligatures[plus]=becomes
else
chr.ligatures={ [plus]=becomes }
end
end
end
end
local function get_kernpairs(data,kernpairs)
local characters=data.characters
for one,two,value in gmatch(kernpairs,"KPX +(.-) +(.-) +(.-)\n") do
local chr=characters[one]
if chr then
local kerns=chr.kerns
if kerns then
kerns[two]=tonumber(value)
else
chr.kerns={ [two]=tonumber(value) }
end
end
end
end
local function get_variables(data,fontmetrics)
for key,rest in gmatch(fontmetrics,"(%a+) *(.-)[\n\r]") do
local keyhandler=keys[key]
if keyhandler then
keyhandler(data,rest)
end
end
end
local function get_indexes(data,pfbname)
data.resources.filename=resolvers.unresolve(pfbname)
local pfbblob=fontloader.open(pfbname)
if pfbblob then
local characters=data.characters
local pfbdata=fontloader.to_table(pfbblob)
if pfbdata then
local glyphs=pfbdata.glyphs
if glyphs then
if trace_loading then
report_afm("getting index data from %a",pfbname)
end
for index,glyph in next,glyphs do
local name=glyph.name
if name then
local char=characters[name]
if char then
if trace_indexing then
report_afm("glyph %a has index %a",name,index)
end
char.index=index
end
end
end
elseif trace_loading then
report_afm("no glyph data in pfb file %a",pfbname)
end
elseif trace_loading then
report_afm("no data in pfb file %a",pfbname)
end
fontloader.close(pfbblob)
elseif trace_loading then
report_afm("invalid pfb file %a",pfbname)
end
end
local function readafm(filename)
local ok,afmblob,size=resolvers.loadbinfile(filename)
if ok and afmblob then
local data={
resources={
filename=resolvers.unresolve(filename),
version=afm.version,
creator="context mkiv",
},
properties={
hasitalics=false,
},
goodies={},
metadata={
filename=file.removesuffix(file.basename(filename))
},
characters={
},
descriptions={
},
}
afmblob=gsub(afmblob,"StartCharMetrics(.-)EndCharMetrics",function(charmetrics)
if trace_loading then
report_afm("loading char metrics")
end
get_charmetrics(data,charmetrics,vector)
return ""
end)
afmblob=gsub(afmblob,"StartKernPairs(.-)EndKernPairs",function(kernpairs)
if trace_loading then
report_afm("loading kern pairs")
end
get_kernpairs(data,kernpairs)
return ""
end)
afmblob=gsub(afmblob,"StartFontMetrics%s+([%d%.]+)(.-)EndFontMetrics",function(version,fontmetrics)
if trace_loading then
report_afm("loading variables")
end
data.afmversion=version
get_variables(data,fontmetrics)
data.fontdimens=scan_comment(fontmetrics)
return ""
end)
return data
else
if trace_loading then
report_afm("no valid afm file %a",filename)
end
return nil
end
end
local addkerns,addligatures,addtexligatures,unify,normalize
function afm.load(filename)
filename=resolvers.findfile(filename,'afm') or ""
if filename~="" and not fonts.names.ignoredfile(filename) then
local name=file.removesuffix(file.basename(filename))
local data=containers.read(afm.cache,name)
local attr=lfs.attributes(filename)
local size,time=attr.size or 0,attr.modification or 0
local pfbfile=file.replacesuffix(name,"pfb")
local pfbname=resolvers.findfile(pfbfile,"pfb") or ""
if pfbname=="" then
pfbname=resolvers.findfile(file.basename(pfbfile),"pfb") or ""
end
local pfbsize,pfbtime=0,0
if pfbname~="" then
local attr=lfs.attributes(pfbname)
pfbsize=attr.size or 0
pfbtime=attr.modification or 0
end
if not data or data.size~=size or data.time~=time or data.pfbsize~=pfbsize or data.pfbtime~=pfbtime then
report_afm("reading %a",filename)
data=readafm(filename)
if data then
if pfbname~="" then
get_indexes(data,pfbname)
elseif trace_loading then
report_afm("no pfb file for %a",filename)
end
report_afm("unifying %a",filename)
unify(data,filename)
if afm.addligatures then
report_afm("add ligatures")
addligatures(data)
end
if afm.addtexligatures then
report_afm("add tex ligatures")
addtexligatures(data)
end
if afm.addkerns then
report_afm("add extra kerns")
addkerns(data)
end
normalize(data)
report_afm("add tounicode data")
fonts.mappings.addtounicode(data,filename)
data.size=size
data.time=time
data.pfbsize=pfbsize
data.pfbtime=pfbtime
report_afm("saving %a in cache",name)
data=containers.write(afm.cache,name,data)
data=containers.read(afm.cache,name)
end
if applyruntimefixes and data then
applyruntimefixes(filename,data)
end
end
return data
else
return nil
end
end
local uparser=fonts.mappings.makenameparser()
unify=function(data,filename)
local unicodevector=fonts.encodings.agl.unicodes
local unicodes,names={},{}
local private=constructors.privateoffset
local descriptions=data.descriptions
for name,blob in next,data.characters do
local code=unicodevector[name]
if not code then
code=lpegmatch(uparser,name)
if not code then
code=private
private=private+1
report_afm("assigning private slot %U for unknown glyph name %a",code,name)
end
end
local index=blob.index
unicodes[name]=code
names[name]=index
blob.name=name
descriptions[code]={
boundingbox=blob.boundingbox,
width=blob.width,
kerns=blob.kerns,
index=index,
name=name,
}
end
for unicode,description in next,descriptions do
local kerns=description.kerns
if kerns then
local krn={}
for name,kern in next,kerns do
local unicode=unicodes[name]
if unicode then
krn[unicode]=kern
else
print(unicode,name)
end
end
description.kerns=krn
end
end
data.characters=nil
local resources=data.resources
local filename=resources.filename or file.removesuffix(file.basename(filename))
resources.filename=resolvers.unresolve(filename)
resources.unicodes=unicodes
resources.marks={}
resources.names=names
resources.private=private
end
normalize=function(data)
end
local addthem=function(rawdata,ligatures)
if ligatures then
local descriptions=rawdata.descriptions
local resources=rawdata.resources
local unicodes=resources.unicodes
local names=resources.names
for ligname,ligdata in next,ligatures do
local one=descriptions[unicodes[ligname]]
if one then
for _,pair in next,ligdata do
local two,three=unicodes[pair[1]],unicodes[pair[2]]
if two and three then
local ol=one.ligatures
if ol then
if not ol[two] then
ol[two]=three
end
else
one.ligatures={ [two]=three }
end
end
end
end
end
end
end
addligatures=function(rawdata) addthem(rawdata,afm.helpdata.ligatures ) end
addtexligatures=function(rawdata) addthem(rawdata,afm.helpdata.texligatures) end
addkerns=function(rawdata)
local descriptions=rawdata.descriptions
local resources=rawdata.resources
local unicodes=resources.unicodes
local function do_it_left(what)
if what then
for unicode,description in next,descriptions do
local kerns=description.kerns
if kerns then
local extrakerns
for complex,simple in next,what do
complex=unicodes[complex]
simple=unicodes[simple]
if complex and simple then
local ks=kerns[simple]
if ks and not kerns[complex] then
if extrakerns then
extrakerns[complex]=ks
else
extrakerns={ [complex]=ks }
end
end
end
end
if extrakerns then
description.extrakerns=extrakerns
end
end
end
end
end
local function do_it_copy(what)
if what then
for complex,simple in next,what do
complex=unicodes[complex]
simple=unicodes[simple]
if complex and simple then
local complexdescription=descriptions[complex]
if complexdescription then
local simpledescription=descriptions[complex]
if simpledescription then
local extrakerns
local kerns=simpledescription.kerns
if kerns then
for unicode,kern in next,kerns do
if extrakerns then
extrakerns[unicode]=kern
else
extrakerns={ [unicode]=kern }
end
end
end
local extrakerns=simpledescription.extrakerns
if extrakerns then
for unicode,kern in next,extrakerns do
if extrakerns then
extrakerns[unicode]=kern
else
extrakerns={ [unicode]=kern }
end
end
end
if extrakerns then
complexdescription.extrakerns=extrakerns
end
end
end
end
end
end
end
do_it_left(afm.helpdata.leftkerned)
do_it_left(afm.helpdata.bothkerned)
do_it_copy(afm.helpdata.bothkerned)
do_it_copy(afm.helpdata.rightkerned)
end
local function adddimensions(data)
if data then
for unicode,description in next,data.descriptions do
local bb=description.boundingbox
if bb then
local ht,dp=bb[4],-bb[2]
if ht==0 or ht<0 then
else
description.height=ht
end
if dp==0 or dp<0 then
else
description.depth=dp
end
end
end
end
end
local function copytotfm(data)
if data and data.descriptions then
local metadata=data.metadata
local resources=data.resources
local properties=derivetable(data.properties)
local descriptions=derivetable(data.descriptions)
local goodies=derivetable(data.goodies)
local characters={}
local parameters={}
local unicodes=resources.unicodes
for unicode,description in next,data.descriptions do
characters[unicode]={}
end
local filename=constructors.checkedfilename(resources)
local fontname=metadata.fontname or metadata.fullname
local fullname=metadata.fullname or metadata.fontname
local endash=unicodes['space']
local emdash=unicodes['emdash']
local spacer="space"
local spaceunits=500
local monospaced=metadata.isfixedpitch
local charwidth=metadata.charwidth
local italicangle=metadata.italicangle
local charxheight=metadata.xheight and metadata.xheight>0 and metadata.xheight
properties.monospaced=monospaced
parameters.italicangle=italicangle
parameters.charwidth=charwidth
parameters.charxheight=charxheight
if properties.monospaced then
if descriptions[endash] then
spaceunits,spacer=descriptions[endash].width,"space"
end
if not spaceunits and descriptions[emdash] then
spaceunits,spacer=descriptions[emdash].width,"emdash"
end
if not spaceunits and charwidth then
spaceunits,spacer=charwidth,"charwidth"
end
else
if descriptions[endash] then
spaceunits,spacer=descriptions[endash].width,"space"
end
if not spaceunits and charwidth then
spaceunits,spacer=charwidth,"charwidth"
end
end
spaceunits=tonumber(spaceunits)
if spaceunits<200 then
end
parameters.slant=0
parameters.space=spaceunits
parameters.space_stretch=500
parameters.space_shrink=333
parameters.x_height=400
parameters.quad=1000
if italicangle and italicangle~=0 then
parameters.italicangle=italicangle
parameters.italicfactor=math.cos(math.rad(90+italicangle))
parameters.slant=- math.tan(italicangle*math.pi/180)
end
if monospaced then
parameters.space_stretch=0
parameters.space_shrink=0
elseif afm.syncspace then
parameters.space_stretch=spaceunits/2
parameters.space_shrink=spaceunits/3
end
parameters.extra_space=parameters.space_shrink
if charxheight then
parameters.x_height=charxheight
else
local x=unicodes['x']
if x then
local x=descriptions[x]
if x then
parameters.x_height=x.height
end
end
end
local fd=data.fontdimens
if fd and fd[8] and fd[9] and fd[10] then
for k,v in next,fd do
parameters[k]=v
end
end
parameters.designsize=(metadata.designsize or 10)*65536
parameters.ascender=abs(metadata.ascender or 0)
parameters.descender=abs(metadata.descender or 0)
parameters.units=1000
properties.spacer=spacer
properties.encodingbytes=2
properties.format=fonts.formats[filename] or "type1"
properties.filename=filename
properties.fontname=fontname
properties.fullname=fullname
properties.psname=fullname
properties.name=filename or fullname or fontname
if next(characters) then
return {
characters=characters,
descriptions=descriptions,
parameters=parameters,
resources=resources,
properties=properties,
goodies=goodies,
}
end
end
return nil
end
function afm.setfeatures(tfmdata,features)
local okay=constructors.initializefeatures("afm",tfmdata,features,trace_features,report_afm)
if okay then
return constructors.collectprocessors("afm",tfmdata,features,trace_features,report_afm)
else
return {}
end
end
local function checkfeatures(specification)
end
local function afmtotfm(specification)
local afmname=specification.filename or specification.name
if specification.forced=="afm" or specification.format=="afm" then
if trace_loading then
report_afm("forcing afm format for %a",afmname)
end
else
local tfmname=findbinfile(afmname,"ofm") or ""
if tfmname~="" then
if trace_loading then
report_afm("fallback from afm to tfm for %a",afmname)
end
return
end
end
if afmname~="" then
local features=constructors.checkedfeatures("afm",specification.features.normal)
specification.features.normal=features
constructors.hashinstance(specification,true)
specification=definers.resolve(specification)
local cache_id=specification.hash
local tfmdata=containers.read(constructors.cache,cache_id)
if not tfmdata then
local rawdata=afm.load(afmname)
if rawdata and next(rawdata) then
adddimensions(rawdata)
tfmdata=copytotfm(rawdata)
if tfmdata and next(tfmdata) then
local shared=tfmdata.shared
if not shared then
shared={}
tfmdata.shared=shared
end
shared.rawdata=rawdata
shared.features=features
shared.processes=afm.setfeatures(tfmdata,features)
end
elseif trace_loading then
report_afm("no (valid) afm file found with name %a",afmname)
end
tfmdata=containers.write(constructors.cache,cache_id,tfmdata)
end
return tfmdata
end
end
local function read_from_afm(specification)
local tfmdata=afmtotfm(specification)
if tfmdata then
tfmdata.properties.name=specification.name
tfmdata=constructors.scale(tfmdata,specification)
local allfeatures=tfmdata.shared.features or specification.features.normal
constructors.applymanipulators("afm",tfmdata,allfeatures,trace_features,report_afm)
fonts.loggers.register(tfmdata,'afm',specification)
end
return tfmdata
end
local function prepareligatures(tfmdata,ligatures,value)
if value then
local descriptions=tfmdata.descriptions
for unicode,character in next,tfmdata.characters do
local description=descriptions[unicode]
local dligatures=description.ligatures
if dligatures then
local cligatures=character.ligatures
if not cligatures then
cligatures={}
character.ligatures=cligatures
end
for unicode,ligature in next,dligatures do
cligatures[unicode]={
char=ligature,
type=0
}
end
end
end
end
end
local function preparekerns(tfmdata,kerns,value)
if value then
local rawdata=tfmdata.shared.rawdata
local resources=rawdata.resources
local unicodes=resources.unicodes
local descriptions=tfmdata.descriptions
for u,chr in next,tfmdata.characters do
local d=descriptions[u]
local newkerns=d[kerns]
if newkerns then
local kerns=chr.kerns
if not kerns then
kerns={}
chr.kerns=kerns
end
for k,v in next,newkerns do
local uk=unicodes[k]
if uk then
kerns[uk]=v
end
end
end
end
end
end
local list={
[0x0027]=0x2019,
}
local function texreplacements(tfmdata,value)
local descriptions=tfmdata.descriptions
local characters=tfmdata.characters
for k,v in next,list do
characters [k]=characters [v]
descriptions[k]=descriptions[v]
end
end
local function ligatures (tfmdata,value) prepareligatures(tfmdata,'ligatures',value) end
local function texligatures(tfmdata,value) prepareligatures(tfmdata,'texligatures',value) end
local function kerns (tfmdata,value) preparekerns (tfmdata,'kerns',value) end
local function extrakerns (tfmdata,value) preparekerns (tfmdata,'extrakerns',value) end
registerafmfeature {
name="liga",
description="traditional ligatures",
initializers={
base=ligatures,
node=ligatures,
}
}
registerafmfeature {
name="kern",
description="intercharacter kerning",
initializers={
base=kerns,
node=kerns,
}
}
registerafmfeature {
name="extrakerns",
description="additional intercharacter kerning",
initializers={
base=extrakerns,
node=extrakerns,
}
}
registerafmfeature {
name='tlig',
description='tex ligatures',
initializers={
base=texligatures,
node=texligatures,
}
}
registerafmfeature {
name='trep',
description='tex replacements',
initializers={
base=texreplacements,
node=texreplacements,
}
}
local check_tfm=readers.check_tfm
fonts.formats.afm="type1"
fonts.formats.pfb="type1"
local function check_afm(specification,fullname)
local foundname=findbinfile(fullname,'afm') or ""
if foundname=="" then
foundname=fonts.names.getfilename(fullname,"afm") or ""
end
if foundname=="" and afm.autoprefixed then
local encoding,shortname=match(fullname,"^(.-)%-(.*)$")
if encoding and shortname and fonts.encodings.known[encoding] then
shortname=findbinfile(shortname,'afm') or ""
if shortname~="" then
foundname=shortname
if trace_defining then
report_afm("stripping encoding prefix from filename %a",afmname)
end
end
end
end
if foundname~="" then
specification.filename=foundname
specification.format="afm"
return read_from_afm(specification)
end
end
function readers.afm(specification,method)
local fullname,tfmdata=specification.filename or "",nil
if fullname=="" then
local forced=specification.forced or ""
if forced~="" then
tfmdata=check_afm(specification,specification.name.."."..forced)
end
if not tfmdata then
method=method or definers.method or "afm or tfm"
if method=="tfm" then
tfmdata=check_tfm(specification,specification.name)
elseif method=="afm" then
tfmdata=check_afm(specification,specification.name)
elseif method=="tfm or afm" then
tfmdata=check_tfm(specification,specification.name) or check_afm(specification,specification.name)
else
tfmdata=check_afm(specification,specification.name) or check_tfm(specification,specification.name)
end
end
else
tfmdata=check_afm(specification,fullname)
end
return tfmdata
end
function readers.pfb(specification,method)
local original=specification.specification
if trace_defining then
report_afm("using afm reader for %a",original)
end
specification.specification=gsub(original,"%.pfb",".afm")
specification.forced="afm"
return readers.afm(specification,method)
end
end -- closure
do -- begin closure to overcome local limits and interference
if not modules then modules={} end modules ['font-afk']={
version=1.001,
comment="companion to font-afm.lua",
author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
copyright="PRAGMA ADE / ConTeXt Development Team",
license="see context related readme files",
dataonly=true,
}
local allocate=utilities.storage.allocate
fonts.handlers.afm.helpdata={
ligatures=allocate {
['f']={
{ 'f','ff' },
{ 'i','fi' },
{ 'l','fl' },
},
['ff']={
{ 'i','ffi' }
},
['fi']={
{ 'i','fii' }
},
['fl']={
{ 'i','fli' }
},
['s']={
{ 't','st' }
},
['i']={
{ 'j','ij' }
},
},
texligatures=allocate {
['quoteleft']={
{ 'quoteleft','quotedblleft' }
},
['quoteright']={
{ 'quoteright','quotedblright' }
},
['hyphen']={
{ 'hyphen','endash' }
},
['endash']={
{ 'hyphen','emdash' }
}
},
leftkerned=allocate {
AEligature="A",aeligature="a",
OEligature="O",oeligature="o",
IJligature="I",ijligature="i",
AE="A",ae="a",
OE="O",oe="o",
IJ="I",ij="i",
Ssharp="S",ssharp="s",
},
rightkerned=allocate {
AEligature="E",aeligature="e",
OEligature="E",oeligature="e",
IJligature="J",ijligature="j",
AE="E",ae="e",
OE="E",oe="e",
IJ="J",ij="j",
Ssharp="S",ssharp="s",
},
bothkerned=allocate {
Acircumflex="A",acircumflex="a",
Ccircumflex="C",ccircumflex="c",
Ecircumflex="E",ecircumflex="e",
Gcircumflex="G",gcircumflex="g",
Hcircumflex="H",hcircumflex="h",
Icircumflex="I",icircumflex="i",
Jcircumflex="J",jcircumflex="j",
Ocircumflex="O",ocircumflex="o",
Scircumflex="S",scircumflex="s",
Ucircumflex="U",ucircumflex="u",
Wcircumflex="W",wcircumflex="w",
Ycircumflex="Y",ycircumflex="y",
Agrave="A",agrave="a",
Egrave="E",egrave="e",
Igrave="I",igrave="i",
Ograve="O",ograve="o",
Ugrave="U",ugrave="u",
Ygrave="Y",ygrave="y",
Atilde="A",atilde="a",
Itilde="I",itilde="i",
Otilde="O",otilde="o",
Utilde="U",utilde="u",
Ntilde="N",ntilde="n",
Adiaeresis="A",adiaeresis="a",Adieresis="A",adieresis="a",
Ediaeresis="E",ediaeresis="e",Edieresis="E",edieresis="e",
Idiaeresis="I",idiaeresis="i",Idieresis="I",idieresis="i",
Odiaeresis="O",odiaeresis="o",Odieresis="O",odieresis="o",
Udiaeresis="U",udiaeresis="u",Udieresis="U",udieresis="u",
Ydiaeresis="Y",ydiaeresis="y",Ydieresis="Y",ydieresis="y",
Aacute="A",aacute="a",
Cacute="C",cacute="c",
Eacute="E",eacute="e",
Iacute="I",iacute="i",
Lacute="L",lacute="l",
Nacute="N",nacute="n",
Oacute="O",oacute="o",
Racute="R",racute="r",
Sacute="S",sacute="s",
Uacute="U",uacute="u",
Yacute="Y",yacute="y",
Zacute="Z",zacute="z",
Dstroke="D",dstroke="d",
Hstroke="H",hstroke="h",
Tstroke="T",tstroke="t",
Cdotaccent="C",cdotaccent="c",
Edotaccent="E",edotaccent="e",
Gdotaccent="G",gdotaccent="g",
Idotaccent="I",idotaccent="i",
Zdotaccent="Z",zdotaccent="z",
Amacron="A",amacron="a",
Emacron="E",emacron="e",
Imacron="I",imacron="i",
Omacron="O",omacron="o",
Umacron="U",umacron="u",
Ccedilla="C",ccedilla="c",
Kcedilla="K",kcedilla="k",
Lcedilla="L",lcedilla="l",
Ncedilla="N",ncedilla="n",
Rcedilla="R",rcedilla="r",
Scedilla="S",scedilla="s",
Tcedilla="T",tcedilla="t",
Ohungarumlaut="O",ohungarumlaut="o",
Uhungarumlaut="U",uhungarumlaut="u",
Aogonek="A",aogonek="a",
Eogonek="E",eogonek="e",
Iogonek="I",iogonek="i",
Uogonek="U",uogonek="u",
Aring="A",aring="a",
Uring="U",uring="u",
Abreve="A",abreve="a",
Ebreve="E",ebreve="e",
Gbreve="G",gbreve="g",
Ibreve="I",ibreve="i",
Obreve="O",obreve="o",
Ubreve="U",ubreve="u",
Ccaron="C",ccaron="c",
Dcaron="D",dcaron="d",
Ecaron="E",ecaron="e",
Lcaron="L",lcaron="l",
Ncaron="N",ncaron="n",
Rcaron="R",rcaron="r",
Scaron="S",scaron="s",
Tcaron="T",tcaron="t",
Zcaron="Z",zcaron="z",
dotlessI="I",dotlessi="i",
dotlessJ="J",dotlessj="j",
AEligature="AE",aeligature="ae",AE="AE",ae="ae",
OEligature="OE",oeligature="oe",OE="OE",oe="oe",
IJligature="IJ",ijligature="ij",IJ="IJ",ij="ij",
Lstroke="L",lstroke="l",Lslash="L",lslash="l",
Ostroke="O",ostroke="o",Oslash="O",oslash="o",
Ssharp="SS",ssharp="ss",
Aumlaut="A",aumlaut="a",
Eumlaut="E",eumlaut="e",
Iumlaut="I",iumlaut="i",
Oumlaut="O",oumlaut="o",
Uumlaut="U",uumlaut="u",
}
}
end -- closure
do -- begin closure to overcome local limits and interference
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"
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",
copyright="PRAGMA ADE / ConTeXt Development Team",
license="see context related readme files"
}
local lower=string.lower
local fonts=fonts
local constructors=fonts.constructors
local otf=constructors.newhandler("otf")
local otffeatures=constructors.newfeatures("otf")
local otftables=otf.tables
local registerotffeature=otffeatures.register
local allocate=utilities.storage.allocate
registerotffeature {
name="features",
description="initialization of feature handler",
default=true,
}
local function setmode(tfmdata,value)
if value then
tfmdata.properties.mode=lower(value)
end
end
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 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
registerotffeature {
name="mode",
description="mode",
initializers={
base=setmode,
node=setmode,
}
}
registerotffeature {
name="language",
description="language",
initializers={
base=setlanguage,
node=setlanguage,
}
}
registerotffeature {
name="script",
description="script",
initializers={
base=setscript,
node=setscript,
}
}
end -- closure
do -- begin closure to overcome local limits and interference
if not modules then modules={} end modules ['font-otf']={
version=1.001,
comment="companion to font-ini.mkiv",
author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
copyright="PRAGMA ADE / ConTeXt Development Team",
license="see context related readme files"
}
local 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,derivetable=table.fastcopy,table.tohash,table.derive
local formatters=string.formatters
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.747
otf.cache=containers.define("fonts","otf",otf.version,true)
local fontdata=fonts.hashes.identifiers
local chardata=characters and characters.data
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
local usemetatables=false
local packdata=true
local syncspace=true
local forcenotdef=false
local includesubfonts=false
local overloadkerns=false
local applyruntimefixes=fonts.treatments and fonts.treatments.applyfixes
local wildcard="*"
local default="dflt"
local fontloaderfields=fontloader.fields
local mainfields=nil
local glyphfields=nil
local formats=fonts.formats
formats.otf="opentype"
formats.ttf="truetype"
formats.ttc="truetype"
formats.dfont="truetype"
registerdirective("fonts.otf.loader.cleanup",function(v) cleanup=tonumber(v) or (v and 1) or 0 end)
registerdirective("fonts.otf.loader.force",function(v) forceload=v end)
registerdirective("fonts.otf.loader.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)
registerdirective("fonts.otf.loader.overloadkerns",function(v) overloadkerns=v end)
local function otf_format(filename)
return formats[lower(file.suffix(filename))]
end
local function load_featurefile(raw,featurefile)
if featurefile and featurefile~="" then
if trace_loading then
report_otf("using featurefile %a",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 %a has %s sequences",filename,#sequences)
report_otf(" ")
end
for nos=1,#sequences do
local sequence=sequences[nos]
local typ=sequence.type or "no-type"
local name=sequence.name or "no-name"
local subtables=sequence.subtables or { "no-subtables" }
local features=sequence.features
if trace_loading then
report_otf("%3i %-15s %-20s [% t]",nos,name,typ,subtables)
end
if features then
for feature,scripts in next,features do
local tt={}
if type(scripts)=="table" then
for script,languages in next,scripts do
local ttt={}
for language,_ in next,languages do
ttt[#ttt+1]=language
end
tt[#tt+1]=formatters["[%s: % t]"](script,ttt)
end
if trace_loading then
report_otf(" %s: % t",feature,tt)
end
else
if trace_loading then
report_otf(" %s: %S",feature,scripts)
end
end
end
end
end
if trace_loading then
report_otf("\n")
end
elseif trace_loading then
report_otf("font %a has no sequences",filename)
end
end
local valid_fields=table.tohash {
"ascent",
"cidinfo",
"copyright",
"descent",
"design_range_bottom",
"design_range_top",
"design_size",
"encodingchanged",
"extrema_bound",
"familyname",
"fontname",
"fontname",
"fontstyle_id",
"fontstyle_name",
"fullname",
"hasvmetrics",
"horiz_base",
"issans",
"isserif",
"italicangle",
"macstyle",
"onlybitmaps",
"origname",
"os2_version",
"pfminfo",
"serifcheck",
"sfd_version",
"strokedfont",
"strokewidth",
"table_version",
"ttf_tables",
"uni_interp",
"uniqueid",
"units_per_em",
"upos",
"use_typo_metrics",
"uwidth",
"version",
"vert_base",
"weight",
"weight_width_slope_only",
}
local ordered_enhancers={
"prepare tables",
"prepare glyphs",
"prepare lookups",
"analyze glyphs",
"analyze math",
"prepare tounicode",
"reorganize lookups",
"reorganize mark classes",
"reorganize anchor classes",
"reorganize glyph kerns",
"reorganize glyph lookups",
"reorganize glyph anchors",
"merge kern classes",
"reorganize features",
"reorganize subtables",
"check glyphs",
"check metadata",
"check extra features",
"check encoding",
"add duplicates",
"cleanup tables",
}
local actions=allocate()
local before=allocate()
local after=allocate()
patches.before=before
patches.after=after
local function enhance(name,data,filename,raw)
local enhancer=actions[name]
if enhancer then
if trace_loading then
report_otf("apply enhancement %a to file %a",name,filename)
ioflush()
end
enhancer(data,filename,raw)
else
end
end
function enhancers.apply(data,filename,raw)
local basename=file.basename(lower(filename))
if trace_loading then
report_otf("%s enhancing file %a","start",filename)
end
ioflush()
for e=1,#ordered_enhancers do
local enhancer=ordered_enhancers[e]
local b=before[enhancer]
if b then
for pattern,action in next,b do
if find(basename,pattern) then
action(data,filename,raw)
end
end
end
enhance(enhancer,data,filename,raw)
local a=after[enhancer]
if a then
for pattern,action in next,a do
if find(basename,pattern) then
action(data,filename,raw)
end
end
end
ioflush()
end
if trace_loading then
report_otf("%s enhancing file %a","stop",filename)
end
ioflush()
end
function patches.register(what,where,pattern,action)
local pw=patches[what]
if pw then
local ww=pw[where]
if ww then
ww[pattern]=action
else
pw[where]={ [pattern]=action}
end
end
end
function patches.report(fmt,...)
if trace_loading then
report_otf("patching: %s",formatters[fmt](...))
end
end
function enhancers.register(what,action)
actions[what]=action
end
function otf.load(filename,sub,featurefile)
local base=file.basename(file.removesuffix(filename))
local name=file.removesuffix(base)
local attr=lfs.attributes(filename)
local size=attr and attr.size or 0
local time=attr and attr.modification or 0
if featurefile then
name=name.."@"..file.removesuffix(file.basename(featurefile))
end
if sub=="" then
sub=false
end
local hash=name
if sub then
hash=hash.."-"..sub
end
hash=containers.cleanname(hash)
local featurefiles
if featurefile then
featurefiles={}
for s in gmatch(featurefile,"[^,]+") do
local name=resolvers.findfile(file.addsuffix(s,'fea'),'fea') or ""
if name=="" then
report_otf("loading error, no featurefile %a",s)
else
local attr=lfs.attributes(name)
featurefiles[#featurefiles+1]={
name=name,
size=attr and attr.size or 0,
time=attr and attr.modification or 0,
}
end
end
if #featurefiles==0 then
featurefiles=nil
end
end
local data=containers.read(otf.cache,hash)
local reload=not data or data.size~=size or data.time~=time
if forceload then
report_otf("forced reload of %a due to hard coded flag",filename)
reload=true
end
if not reload then
local featuredata=data.featuredata
if featurefiles then
if not featuredata or #featuredata~=#featurefiles then
reload=true
else
for i=1,#featurefiles do
local fi,fd=featurefiles[i],featuredata[i]
if fi.name~=fd.name or fi.size~=fd.size or fi.time~=fd.time then
reload=true
break
end
end
end
elseif featuredata then
reload=true
end
if reload then
report_otf("loading: forced reload due to changed featurefile specification %a",featurefile)
end
end
if reload then
report_otf("loading %a, hash %a",filename,hash)
local fontdata,messages
if sub then
fontdata,messages=fontloader.open(filename,sub)
else
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
report_otf("warning: %s",messages)
else
for m=1,#messages do
report_otf("warning: %S",messages[m])
end
end
else
report_otf("loading done")
end
if fontdata then
if featurefiles then
for i=1,#featurefiles do
load_featurefile(fontdata,featurefiles[i].name)
end
end
local unicodes={
}
local splitter=lpeg.splitter(" ",unicodes)
data={
size=size,
time=time,
format=otf_format(filename),
featuredata=featurefiles,
resources={
filename=resolvers.unresolve(filename),
version=otf.version,
creator="context mkiv",
unicodes=unicodes,
indices={
},
duplicates={
},
variants={
},
lookuptypes={},
},
metadata={
},
properties={
},
descriptions={},
goodies={},
helpers={
tounicodelist=splitter,
tounicodetable=lpeg.Ct(splitter),
},
}
starttiming(data)
report_otf("file size: %s",size)
enhancers.apply(data,filename,fontdata)
local packtime={}
if packdata then
if cleanup>0 then
collectgarbage("collect")
end
starttiming(packtime)
enhance("pack",data,filename,nil)
stoptiming(packtime)
end
report_otf("saving %a in cache",filename)
data=containers.write(otf.cache,hash,data)
if cleanup>1 then
collectgarbage("collect")
end
stoptiming(data)
if elapsedtime then
report_otf("preprocessing and caching time %s, packtime %s",
elapsedtime(data),packdata and elapsedtime(packtime) or 0)
end
fontloader.close(fontdata)
if cleanup>3 then
collectgarbage("collect")
end
data=containers.read(otf.cache,hash)
if cleanup>2 then
collectgarbage("collect")
end
else
data=nil
report_otf("loading failed due to read error")
end
end
if data then
if trace_defining then
report_otf("loading from cache using hash %a",hash)
end
enhance("unpack",data,filename,nil,false)
if applyruntimefixes then
applyruntimefixes(filename,data)
end
enhance("add dimensions",data,filename,nil,false)
if trace_sequences then
showfeatureorder(data,filename)
end
end
return data
end
local mt={
__index=function(t,k)
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
return forcenotdef and ".notdef"
end
end
}
actions["prepare tables"]=function(data,filename,raw)
data.properties.hasitalics=false
end
actions["add dimensions"]=function(data,filename)
if data then
local descriptions=data.descriptions
local resources=data.resources
local defaultwidth=resources.defaultwidth or 0
local defaultheight=resources.defaultheight or 0
local defaultdepth=resources.defaultdepth or 0
local basename=trace_markwidth and file.basename(filename)
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 %a with width %b found in %a",d.name or "<noname>",wd,basename)
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 %a with width %b found in %a",d.name or "<noname>",wd,basename)
end
if bb then
local ht,dp=bb[4],-bb[2]
if ht==0 or ht<0 then
else
d.height=ht
end
if dp==0 or dp<0 then
else
d.depth=dp
end
end
end
end
end
end
local function somecopy(old)
if old then
local new={}
if type(old)=="table" then
for k,v in next,old do
if k=="glyphs" then
elseif type(v)=="table" then
new[k]=somecopy(v)
else
new[k]=v
end
end
else
for i=1,#mainfields do
local k=mainfields[i]
local v=old[k]
if k=="glyphs" then
elseif type(v)=="table" then
new[k]=somecopy(v)
else
new[k]=v
end
end
end
return new
else
return {}
end
end
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
local indices=resources.indices
local duplicates=resources.duplicates
local variants=resources.variants
if rawsubfonts then
metadata.subfonts=includesubfonts and {}
properties.cidinfo=rawcidinfo
if rawcidinfo.registry then
local cidmap=fonts.cid.getmap(rawcidinfo)
if cidmap then
rawcidinfo.usedname=cidmap.usedname
local nofnames,nofunicodes=0,0
local cidunicodes,cidnames=cidmap.unicodes,cidmap.names
for cidindex=1,#rawsubfonts do
local subfont=rawsubfonts[cidindex]
local cidglyphs=subfont.glyphs
if includesubfonts then
metadata.subfonts[cidindex]=somecopy(subfont)
end
for index=0,subfont.glyphcnt-1 do
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 unicode and descriptions[unicode] then
report_otf("preventing glyph %a at index %H to overload unicode %U",name or "noname",index,unicode)
unicode=-1
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("glyph %a at index %H is moved to private unicode slot %U",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
local description={
boundingbox=glyph.boundingbox,
name=glyph.name or name or "unknown",
cidindex=cidindex,
index=index,
glyph=glyph,
}
descriptions[unicode]=description
else
end
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 %a",filename)
end
elseif trace_loading then
report_otf("font %a has no glyphs",filename)
end
else
for index=0,raw.glyphcnt-1 do
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("glyph %a at index %H is moved to private unicode slot %U",name,index,private)
end
private=private+1
else
unicodes[name]=unicode
end
indices[index]=unicode
if not name then
name=format("u%06X",unicode)
end
descriptions[unicode]={
boundingbox=glyph.boundingbox,
name=name,
index=index,
glyph=glyph,
}
local altuni=glyph.altuni
if altuni then
local d
for i=1,#altuni do
local a=altuni[i]
local u=a.unicode
local v=a.variant
if v then
local vv=variants[v]
if vv then
vv[u]=unicode
else
vv={ [u]=unicode }
variants[v]=vv
end
elseif d then
d[#d+1]=u
else
d={ u }
end
end
if d then
duplicates[unicode]=d
end
end
else
report_otf("potential problem: glyph %U is used but empty",index)
end
end
end
resources.private=private
end
actions["check encoding"]=function(data,filename,raw)
local descriptions=data.descriptions
local resources=data.resources
local properties=data.properties
local unicodes=resources.unicodes
local indices=resources.indices
local duplicates=resources.duplicates
local mapdata=raw.map or {}
local unicodetoindex=mapdata and mapdata.map or {}
local encname=lower(data.enc_name or mapdata.enc_name or "")
local criterium=0xFFFF
if find(encname,"unicode") then
if trace_loading then
report_otf("checking embedded unicode map %a",encname)
end
for unicode,index in next,unicodetoindex do
if unicode<=criterium and not descriptions[unicode] then
local parent=indices[index]
if not parent then
report_otf("weird, unicode %U points to nowhere with index %H",unicode,index)
else
local parentdescription=descriptions[parent]
if parentdescription then
local altuni=parentdescription.altuni
if not altuni then
altuni={ { unicode=parent } }
parentdescription.altuni=altuni
duplicates[parent]={ unicode }
else
local done=false
for i=1,#altuni do
if altuni[i].unicode==parent then
done=true
break
end
end
if not done then
altuni[#altuni+1]={ unicode=parent }
table.insert(duplicates[parent],unicode)
end
end
if trace_loading then
report_otf("weird, unicode %U points to nowhere with index %H",unicode,index)
end
else
report_otf("weird, unicode %U points to %U with index %H",unicode,index)
end
end
end
end
elseif properties.cidinfo then
report_otf("warning: no unicode map, used cidmap %a",properties.cidinfo.usedname)
else
report_otf("warning: non unicode map %a, only using glyph unicode data",encname or "whatever")
end
if mapdata then
mapdata.map={}
end
end
actions["add duplicates"]=function(data,filename,raw)
local descriptions=data.descriptions
local resources=data.resources
local properties=data.properties
local unicodes=resources.unicodes
local indices=resources.indices
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)
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
end
if trace_loading then
report_otf("duplicating %U to %U with index %H (%s kerns)",unicode,u,description.index,n)
end
end
end
end
end
actions["analyze glyphs"]=function(data,filename,raw)
local descriptions=data.descriptions
local resources=data.resources
local metadata=data.metadata
local properties=data.properties
local hasitalics=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
elseif italic==0 then
else
description.italic=italic
hasitalics=true
end
local width=glyph.width
widths[width]=(widths[width] or 0)+1
local class=glyph.class
if class then
if class=="mark" then
marks[unicode]=true
end
description.class=class
end
end
properties.hasitalics=hasitalics
resources.marks=marks
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
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
else
description.width=description.glyph.width
end
end
resources.defaultwidth=wd
else
for unicode,description in next,descriptions do
description.width=description.glyph.width
end
end
end
actions["reorganize mark classes"]=function(data,filename,raw)
local mark_classes=raw.mark_classes
if mark_classes then
local resources=data.resources
local unicodes=resources.unicodes
local markclasses={}
resources.markclasses=markclasses
for name,class in next,mark_classes do
local t={}
for s in gmatch(class,"[^ ]+") do
t[unicodes[s]]=true
end
markclasses[name]=t
end
end
end
actions["reorganize features"]=function(data,filename,raw)
local features={}
data.resources.features=features
for k,what in next,otf.glists do
local dw=raw[what]
if dw then
local f={}
features[what]=f
for i=1,#dw do
local d=dw[i]
local dfeatures=d.features
if dfeatures then
for i=1,#dfeatures do
local df=dfeatures[i]
local tag=strip(lower(df.tag))
local ft=f[tag]
if not ft then
ft={}
f[tag]=ft
end
local dscripts=df.scripts
for i=1,#dscripts do
local d=dscripts[i]
local languages=d.langs
local script=strip(lower(d.script))
local fts=ft[script] if not fts then fts={} ft[script]=fts end
for i=1,#languages do
fts[strip(lower(languages[i]))]=true
end
end
end
end
end
end
end
end
actions["reorganize anchor classes"]=function(data,filename,raw)
local resources=data.resources
local anchor_to_lookup={}
local lookup_to_anchor={}
resources.anchor_to_lookup=anchor_to_lookup
resources.lookup_to_anchor=lookup_to_anchor
local classes=raw.anchor_classes
if classes then
for c=1,#classes do
local class=classes[c]
local anchor=class.name
local lookups=class.lookup
if type(lookups)~="table" then
lookups={ lookups }
end
local a=anchor_to_lookup[anchor]
if not a then
a={}
anchor_to_lookup[anchor]=a
end
for l=1,#lookups do
local lookup=lookups[l]
local l=lookup_to_anchor[lookup]
if l then
l[anchor]=true
else
l={ [anchor]=true }
lookup_to_anchor[lookup]=l
end
a[lookup]=true
end
end
end
end
actions["prepare tounicode"]=function(data,filename,raw)
fonts.mappings.addtounicode(data,filename)
end
local g_directions={
gsub_contextchain=1,
gpos_contextchain=1,
gsub_reversecontextchain=-1,
gpos_reversecontextchain=-1,
}
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 features=gk.features
local typ=gk.type
local chain=g_directions[typ] or 0
local subtables=gk.subtables
if subtables then
local t={}
for s=1,#subtables do
t[s]=subtables[s].name
end
subtables=t
end
local flags,markclass=gk.flags,nil
if flags then
local t={
(flags.ignorecombiningmarks and "mark") or false,
(flags.ignoreligatures and "ligature") or false,
(flags.ignorebaseglyphs and "base") or false,
flags.r2l or false,
}
markclass=flags.mark_class
if markclass then
markclass=resources.markclasses[markclass]
end
flags=t
end
local name=gk.name
if not name then
report_otf("skipping weird lookup number %s",k)
elseif features then
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
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
actions["prepare lookups"]=function(data,filename,raw)
local lookups=raw.lookups
if lookups then
data.lookups=lookups
end
end
local function t_uncover(splitter,cache,covers)
local result={}
for n=1,#covers do
local cover=covers[n]
local uncovered=cache[cover]
if not uncovered then
uncovered=lpegmatch(splitter,cover)
cache[cover]=uncovered
end
result[n]=uncovered
end
return result
end
local function s_uncover(splitter,cache,cover)
if cover=="" then
return nil
else
local uncovered=cache[cover]
if not uncovered then
uncovered=lpegmatch(splitter,cover)
cache[cover]=uncovered
end
return { uncovered }
end
end
local function t_hashed(t,cache)
if t then
local ht={}
for i=1,#t do
local ti=t[i]
local tih=cache[ti]
if not tih then
tih={}
for i=1,#ti do
tih[ti[i]]=true
end
cache[ti]=tih
end
ht[i]=tih
end
return ht
else
return nil
end
end
local function s_hashed(t,cache)
if t then
local ht={}
local tf=t[1]
for i=1,#tf do
ht[i]={ [tf[i]]=true }
end
return ht
else
return nil
end
end
local function r_uncover(splitter,cache,cover,replacements)
if cover=="" then
return nil
else
local uncovered=cover[1]
local replaced=cache[replacements]
if not replaced then
replaced=lpegmatch(splitter,replacements)
cache[replacements]=replaced
end
local nu,nr=#uncovered,#replaced
local r={}
if nu==nr then
for i=1,nu do
r[uncovered[i]]=replaced[i]
end
end
return r
end
end
actions["reorganize lookups"]=function(data,filename,raw)
if data.lookups then
local splitter=data.helpers.tounicodetable
local t_u_cache={}
local s_u_cache=t_u_cache
local t_h_cache={}
local s_h_cache=t_h_cache
local r_u_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,t_u_cache,reversed(before_class))
end
local current_class=lookup.current_class
if current_class then
current_class=t_uncover(splitter,t_u_cache,current_class)
end
local after_class=lookup.after_class
if after_class then
after_class=t_uncover(splitter,t_u_cache,after_class)
end
for i=1,#rules do
local rule=rules[i]
local class=rule.class
local before=class.before
if before then
for i=1,#before do
before[i]=before_class[before[i]] or {}
end
rule.before=t_hashed(before,t_h_cache)
end
local current=class.current
local lookups=rule.lookups
if current then
for i=1,#current do
current[i]=current_class[current[i]] or {}
if lookups and not lookups[i] then
lookups[i]=""
end
end
rule.current=t_hashed(current,t_h_cache)
end
local after=class.after
if after then
for i=1,#after do
after[i]=after_class[after[i]] or {}
end
rule.after=t_hashed(after,t_h_cache)
end
rule.class=nil
end
lookup.before_class=nil
lookup.current_class=nil
lookup.after_class=nil
lookup.format="coverage"
elseif format=="coverage" then
for i=1,#rules do
local rule=rules[i]
local coverage=rule.coverage
if coverage then
local before=coverage.before
if before then
before=t_uncover(splitter,t_u_cache,reversed(before))
rule.before=t_hashed(before,t_h_cache)
end
local current=coverage.current
if current then
current=t_uncover(splitter,t_u_cache,current)
local lookups=rule.lookups
if lookups then
for i=1,#current do
if not lookups[i] then
lookups[i]=""
end
end
end
rule.current=t_hashed(current,t_h_cache)
end
local after=coverage.after
if after then
after=t_uncover(splitter,t_u_cache,after)
rule.after=t_hashed(after,t_h_cache)
end
rule.coverage=nil
end
end
elseif format=="reversecoverage" then
for i=1,#rules do
local rule=rules[i]
local reversecoverage=rule.reversecoverage
if reversecoverage then
local before=reversecoverage.before
if before then
before=t_uncover(splitter,t_u_cache,reversed(before))
rule.before=t_hashed(before,t_h_cache)
end
local current=reversecoverage.current
if current then
current=t_uncover(splitter,t_u_cache,current)
rule.current=t_hashed(current,t_h_cache)
end
local after=reversecoverage.after
if after then
after=t_uncover(splitter,t_u_cache,after)
rule.after=t_hashed(after,t_h_cache)
end
local replacements=reversecoverage.replacements
if replacements then
rule.replacements=r_uncover(splitter,r_u_cache,current,replacements)
end
rule.reversecoverage=nil
end
end
elseif format=="glyphs" then
for i=1,#rules do
local rule=rules[i]
local glyphs=rule.glyphs
if glyphs then
local fore=glyphs.fore
if fore and fore~="" then
fore=s_uncover(splitter,s_u_cache,fore)
rule.before=s_hashed(fore,s_h_cache)
end
local back=glyphs.back
if back then
back=s_uncover(splitter,s_u_cache,back)
rule.after=s_hashed(back,s_h_cache)
end
local names=glyphs.names
if names then
names=s_uncover(splitter,s_u_cache,names)
rule.current=s_hashed(names,s_h_cache)
end
rule.glyphs=nil
end
end
end
end
end
end
end
local function check_variants(unicode,the_variants,splitter,unicodes)
local variants=the_variants.variants
if variants then
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 in math variant %U",g,unicode)
else
if n==0 then
n=1
variants={ g }
else
n=n+1
variants[n]=g
end
done[g]=true
end
end
if n==0 then
variants=nil
end
end
local parts=the_variants.parts
if parts then
local p=#parts
if p>0 then
for i=1,p do
local pi=parts[i]
pi.glyph=unicodes[pi.component] or 0
pi.component=nil
end
else
parts=nil
end
end
local italic_correction=the_variants.italic_correction
if italic_correction and italic_correction==0 then
italic_correction=nil
end
return variants,parts,italic_correction
end
actions["analyze math"]=function(data,filename,raw)
if raw.math then
data.metadata.math=raw.math
local unicodes=data.resources.unicodes
local splitter=data.helpers.tounicodetable
for unicode,description in next,data.descriptions do
local glyph=description.glyph
local mathkerns=glyph.mathkern
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={}
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
mathkerns[k]=nil
else
for k,v in next,v do
if v==0 then
k[v]=nil
end
end
end
end
math.kerns=mathkerns
end
if horiz_variants then
math.horiz_variants,math.horiz_parts,math.horiz_italic_correction=check_variants(unicode,horiz_variants,splitter,unicodes)
end
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
actions["reorganize glyph kerns"]=function(data,filename,raw)
local descriptions=data.descriptions
local resources=data.resources
local unicodes=resources.unicodes
for unicode,description in next,descriptions do
local kerns=description.glyph.kerns
if kerns then
local newkerns={}
for k,kern in next,kerns do
local name=kern.char
local offset=kern.off
local lookup=kern.lookup
if name and offset and lookup then
local unicode=unicodes[name]
if unicode then
if type(lookup)=="table" then
for l=1,#lookup do
local lookup=lookup[l]
local lookupkerns=newkerns[lookup]
if lookupkerns then
lookupkerns[unicode]=offset
else
newkerns[lookup]={ [unicode]=offset }
end
end
else
local lookupkerns=newkerns[lookup]
if lookupkerns then
lookupkerns[unicode]=offset
else
newkerns[lookup]={ [unicode]=offset }
end
end
elseif trace_loading then
report_otf("problems with unicode %a of kern %a of glyph %U",name,k,unicode)
end
end
end
description.kerns=newkerns
end
end
end
actions["merge kern classes"]=function(data,filename,raw)
local gposlist=raw.gpos
if gposlist then
local descriptions=data.descriptions
local resources=data.resources
local unicodes=resources.unicodes
local splitter=data.helpers.tounicodetable
local ignored=0
local blocked=0
for gp=1,#gposlist do
local gpos=gposlist[gp]
local subtables=gpos.subtables
if subtables then
local first_done={}
local split={}
for s=1,#subtables do
local subtable=subtables[s]
local kernclass=subtable.kernclass
local lookup=subtable.lookup or subtable.name
if kernclass then
if #kernclass>0 then
kernclass=kernclass[1]
lookup=type(kernclass.lookup)=="string" and kernclass.lookup or lookup
report_otf("fixing kernclass table of lookup %a",lookup)
end
local firsts=kernclass.firsts
local seconds=kernclass.seconds
local offsets=kernclass.offsets
for n,s in next,firsts do
split[s]=split[s] or lpegmatch(splitter,s)
end
local maxseconds=0
for n,s in next,seconds do
if n>maxseconds then
maxseconds=n
end
split[s]=split[s] or lpegmatch(splitter,s)
end
for fk=1,#firsts do
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 splt=split[sv]
if splt then
local offset=offsets[baseoffset+sk]
if offset then
for i=1,#splt do
extrakerns[splt[i]]=offset
end
end
end
end
for i=1,#splt do
local first_unicode=splt[i]
if first_done[first_unicode] then
report_otf("lookup %a: ignoring further kerns of %C",lookup,first_unicode)
blocked=blocked+1
else
first_done[first_unicode]=true
local description=descriptions[first_unicode]
if description then
local kerns=description.kerns
if not kerns then
kerns={}
description.kerns=kerns
end
local lookupkerns=kerns[lookup]
if not lookupkerns then
lookupkerns={}
kerns[lookup]=lookupkerns
end
if overloadkerns then
for second_unicode,kern in next,extrakerns do
lookupkerns[second_unicode]=kern
end
else
for second_unicode,kern in next,extrakerns do
local k=lookupkerns[second_unicode]
if not k then
lookupkerns[second_unicode]=kern
elseif k~=kern then
if trace_loading then
report_otf("lookup %a: ignoring overload of kern between %C and %C, rejecting %a, keeping %a",lookup,first_unicode,second_unicode,k,kern)
end
ignored=ignored+1
end
end
end
elseif trace_loading then
report_otf("no glyph data for %U",first_unicode)
end
end
end
end
end
subtable.kernclass={}
end
end
end
end
if ignored>0 then
report_otf("%s kern overloads ignored",ignored)
end
if blocked>0 then
report_otf("%s succesive kerns blocked",blocked)
end
end
end
actions["check glyphs"]=function(data,filename,raw)
for unicode,description in next,data.descriptions do
description.glyph=nil
end
end
actions["check metadata"]=function(data,filename,raw)
local metadata=data.metadata
for _,k in next,mainfields do
if valid_fields[k] then
local v=raw[k]
if not metadata[k] then
metadata[k]=v
end
end
end
local ttftables=metadata.ttf_tables
if ttftables then
for i=1,#ttftables do
ttftables[i].data="deleted"
end
end
end
actions["cleanup tables"]=function(data,filename,raw)
data.resources.indices=nil
data.helpers=nil
end
actions["reorganize glyph lookups"]=function(data,filename,raw)
local resources=data.resources
local unicodes=resources.unicodes
local descriptions=data.descriptions
local splitter=data.helpers.tounicodelist
local lookuptypes=resources.lookuptypes
for unicode,description in next,descriptions do
local lookups=description.glyph.lookups
if lookups then
for tag,lookuplist in next,lookups do
for l=1,#lookuplist do
local lookup=lookuplist[l]
local specification=lookup.specification
local lookuptype=lookup.type
local lt=lookuptypes[tag]
if not lt then
lookuptypes[tag]=lookuptype
elseif lt~=lookuptype then
report_otf("conflicting lookuptypes, %a points to %a and %a",tag,lt,lookuptype)
end
if lookuptype=="ligature" then
lookuplist[l]={ lpegmatch(splitter,specification.components) }
elseif lookuptype=="alternate" then
lookuplist[l]={ lpegmatch(splitter,specification.components) }
elseif lookuptype=="substitution" then
lookuplist[l]=unicodes[specification.variant]
elseif lookuptype=="multiple" then
lookuplist[l]={ lpegmatch(splitter,specification.components) }
elseif lookuptype=="position" then
lookuplist[l]={
specification.x or 0,
specification.y or 0,
specification.h or 0,
specification.v or 0
}
elseif lookuptype=="pair" then
local one=specification.offsets[1]
local two=specification.offsets[2]
local paired=unicodes[specification.paired]
if one then
if two then
lookuplist[l]={ paired,{ one.x or 0,one.y or 0,one.h or 0,one.v or 0 },{ two.x or 0,two.y or 0,two.h or 0,two.v or 0 } }
else
lookuplist[l]={ paired,{ one.x or 0,one.y or 0,one.h or 0,one.v or 0 } }
end
else
if two then
lookuplist[l]={ paired,{},{ two.x or 0,two.y or 0,two.h or 0,two.v or 0} }
else
lookuplist[l]={ paired }
end
end
end
end
end
local slookups,mlookups
for tag,lookuplist in next,lookups do
if #lookuplist==1 then
if slookups then
slookups[tag]=lookuplist[1]
else
slookups={ [tag]=lookuplist[1] }
end
else
if mlookups then
mlookups[tag]=lookuplist
else
mlookups={ [tag]=lookuplist }
end
end
end
if slookups then
description.slookups=slookups
end
if mlookups then
description.mlookups=mlookups
end
end
end
end
actions["reorganize glyph anchors"]=function(data,filename,raw)
local descriptions=data.descriptions
for unicode,description in next,descriptions do
local anchors=description.glyph.anchors
if anchors then
for class,data in next,anchors do
if class=="baselig" then
for tag,specification in next,data do
for i=1,#specification do
local si=specification[i]
specification[i]={ si.x or 0,si.y or 0 }
end
end
else
for tag,specification in next,data do
data[tag]={ specification.x or 0,specification.y or 0 }
end
end
end
description.anchors=anchors
end
end
end
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 {}
end
end
local function copytotfm(data,cache_id)
if data then
local metadata=data.metadata
local resources=data.resources
local properties=derivetable(data.properties)
local descriptions=derivetable(data.descriptions)
local goodies=derivetable(data.goodies)
local characters={}
local parameters={}
local mathparameters={}
local pfminfo=metadata.pfminfo or {}
local resources=data.resources
local unicodes=resources.unicodes
local 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
if mathspecs then
for name,value in next,mathspecs do
mathparameters[name]=value
end
end
for unicode,_ in next,data.descriptions do
characters[unicode]={}
end
if mathspecs then
for unicode,character in next,characters do
local d=descriptions[unicode]
local m=d.math
if m then
local variants=m.horiz_variants
local parts=m.horiz_parts
if variants then
local c=character
for i=1,#variants do
local un=variants[i]
c.next=un
c=characters[un]
end
c.horiz_variants=parts
elseif parts then
character.horiz_variants=parts
end
local variants=m.vert_variants
local parts=m.vert_parts
if variants then
local c=character
for i=1,#variants do
local un=variants[i]
c.next=un
c=characters[un]
end
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
end
local top_accent=m.top_accent
if top_accent then
character.top_accent=top_accent
end
local kerns=m.kerns
if kerns then
character.mathkerns=kerns
end
end
end
end
local filename=constructors.checkedfilename(resources)
local fontname=metadata.fontname
local fullname=metadata.fullname or fontname
local units=metadata.units_per_em or 1000
if units==0 then
units=1000
metadata.units_per_em=1000
report_otf("changing %a units to %a",0,units)
end
local monospaced=metadata.isfixedpitch or (pfminfo.panose and pfminfo.panose.proportion=="Monospaced")
local charwidth=pfminfo.avgwidth
local charxheight=pfminfo.os2_xheight and pfminfo.os2_xheight>0 and pfminfo.os2_xheight
local italicangle=metadata.italicangle
properties.monospaced=monospaced
parameters.italicangle=italicangle
parameters.charwidth=charwidth
parameters.charxheight=charxheight
local space=0x0020
local emdash=0x2014
if monospaced then
if descriptions[space] then
spaceunits,spacer=descriptions[space].width,"space"
end
if not spaceunits and descriptions[emdash] then
spaceunits,spacer=descriptions[emdash].width,"emdash"
end
if not spaceunits and charwidth then
spaceunits,spacer=charwidth,"charwidth"
end
else
if descriptions[space] then
spaceunits,spacer=descriptions[space].width,"space"
end
if not spaceunits and descriptions[emdash] then
spaceunits,spacer=descriptions[emdash].width/2,"emdash/2"
end
if not spaceunits and charwidth then
spaceunits,spacer=charwidth,"charwidth"
end
end
spaceunits=tonumber(spaceunits) or 500
parameters.slant=0
parameters.space=spaceunits
parameters.space_stretch=units/2
parameters.space_shrink=1*units/3
parameters.x_height=2*units/5
parameters.quad=units
if spaceunits<2*units/5 then
end
if italicangle and italicangle~=0 then
parameters.italicangle=italicangle
parameters.italicfactor=math.cos(math.rad(90+italicangle))
parameters.slant=- math.tan(italicangle*math.pi/180)
end
if monospaced then
parameters.space_stretch=0
parameters.space_shrink=0
elseif syncspace then
parameters.space_stretch=spaceunits/2
parameters.space_shrink=spaceunits/3
end
parameters.extra_space=parameters.space_shrink
if charxheight then
parameters.x_height=charxheight
else
local x=0x78
if x then
local x=descriptions[x]
if x then
parameters.x_height=x.height
end
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 otf_format(filename) or formats.otf
properties.noglyphnames=true
properties.filename=filename
properties.fontname=fontname
properties.fullname=fullname
properties.psname=fontname or fullname
properties.name=filename or fullname
return {
characters=characters,
descriptions=descriptions,
parameters=parameters,
mathparameters=mathparameters,
resources=resources,
properties=properties,
goodies=goodies,
}
end
end
local function otftotfm(specification)
local cache_id=specification.hash
local tfmdata=containers.read(constructors.cache,cache_id)
if not tfmdata then
local name=specification.name
local sub=specification.sub
local filename=specification.filename
local features=specification.features.normal
local rawdata=otf.load(filename,sub,features and features.featurefile)
if rawdata and next(rawdata) then
rawdata.lookuphash={}
tfmdata=copytotfm(rawdata,cache_id)
if tfmdata and next(tfmdata) then
local features=constructors.checkedfeatures("otf",features)
local shared=tfmdata.shared
if not shared then
shared={}
tfmdata.shared=shared
end
shared.rawdata=rawdata
shared.dynamics={}
tfmdata.changed={}
shared.features=features
shared.processes=otf.setfeatures(tfmdata,features)
end
end
containers.write(constructors.cache,cache_id,tfmdata)
end
return tfmdata
end
local function read_from_otf(specification)
local tfmdata=otftotfm(specification)
if tfmdata then
tfmdata.properties.name=specification.name
tfmdata.properties.sub=specification.sub
tfmdata=constructors.scale(tfmdata,specification)
local allfeatures=tfmdata.shared.features or specification.features.normal
constructors.applymanipulators("otf",tfmdata,allfeatures,trace_features,report_otf)
constructors.setname(tfmdata,specification)
fonts.loggers.register(tfmdata,file.suffix(specification.filename),specification)
end
return tfmdata
end
local function checkmathsize(tfmdata,mathsize)
local mathdata=tfmdata.shared.rawdata.metadata.math
local mathsize=tonumber(mathsize)
if mathdata then
local parameters=tfmdata.parameters
parameters.scriptpercentage=mathdata.ScriptPercentScaleDown
parameters.scriptscriptpercentage=mathdata.ScriptScriptPercentScaleDown
parameters.mathsize=mathsize
end
end
registerotffeature {
name="mathsize",
description="apply mathsize specified in the font",
initializers={
base=checkmathsize,
node=checkmathsize,
}
}
function otf.collectlookups(rawdata,kind,script,language)
local sequences=rawdata.resources.sequences
if sequences then
local featuremap,featurelist={},{}
for s=1,#sequences do
local sequence=sequences[s]
local features=sequence.features
features=features and features[kind]
features=features and (features[script] or features[default] or features[wildcard])
features=features and (features[language] or features[default] or features[wildcard])
if features then
local subtables=sequence.subtables
if subtables then
for s=1,#subtables do
local ss=subtables[s]
if not featuremap[s] then
featuremap[ss]=true
featurelist[#featurelist+1]=ss
end
end
end
end
end
if #featurelist>0 then
return featuremap,featurelist
end
end
return nil,nil
end
local function check_otf(forced,specification,suffix)
local name=specification.name
if forced then
name=specification.forcedname
end
local fullname=findbinfile(name,suffix) or ""
if fullname=="" then
fullname=fonts.names.getfilename(name,suffix) or ""
end
if fullname~="" and not fonts.names.ignoredfile(fullname) then
specification.filename=fullname
return read_from_otf(specification)
end
end
local function opentypereader(specification,suffix)
local forced=specification.forced or ""
if formats[forced] then
return check_otf(true,specification,forced)
else
return check_otf(false,specification,suffix)
end
end
readers.opentype=opentypereader
function readers.otf (specification) return opentypereader(specification,"otf") end
function readers.ttf (specification) return opentypereader(specification,"ttf") end
function readers.ttc (specification) return opentypereader(specification,"ttf") end
function readers.dfont(specification) return opentypereader(specification,"ttf") end
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-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_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_ligatures_detail=false trackers.register("otf.ligatures.detail",function(v) trace_ligatures_detail=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 report_prepare=logs.reporter("fonts","otf prepare")
local fonts=fonts
local otf=fonts.handlers.otf
local otffeatures=otf.features
local registerotffeature=otffeatures.register
otf.defaultbasealternate="none"
local wildcard="*"
local default="dflt"
local formatters=string.formatters
local f_unicode=formatters["%U"]
local f_uniname=formatters["%U (%s)"]
local f_unilist=formatters["% t (% t)"]
local function gref(descriptions,n)
if type(n)=="number" then
local name=descriptions[n].name
if name then
return f_uniname(n,name)
else
return f_unicode(n)
end
elseif n then
local num,nam={},{}
for i=2,#n do
local ni=n[i]
if tonumber(ni) then
local di=descriptions[ni]
num[i]=f_unicode(ni)
nam[i]=di and di.name or "-"
end
end
return f_unilist(num,nam)
else
return "<error in base mode tracing>"
end
end
local function cref(feature,lookupname)
if lookupname then
return formatters["feature %a, lookup %a"](feature,lookupname)
else
return formatters["feature %a"](feature)
end
end
local function report_alternate(feature,lookupname,descriptions,unicode,replacement,value,comment)
report_prepare("%s: base alternate %s => %s (%S => %S)",
cref(feature,lookupname),
gref(descriptions,unicode),
replacement and gref(descriptions,replacement),
value,
comment)
end
local function report_substitution(feature,lookupname,descriptions,unicode,substitution)
report_prepare("%s: base substitution %s => %S",
cref(feature,lookupname),
gref(descriptions,unicode),
gref(descriptions,substitution))
end
local function report_ligature(feature,lookupname,descriptions,unicode,ligature)
report_prepare("%s: base ligature %s => %S",
cref(feature,lookupname),
gref(descriptions,ligature),
gref(descriptions,unicode))
end
local function report_kern(feature,lookupname,descriptions,unicode,otherunicode,value)
report_prepare("%s: base kern %s + %s => %S",
cref(feature,lookupname),
gref(descriptions,unicode),
gref(descriptions,otherunicode),
value)
end
local basemethods={}
local basemethod="<unset>"
local function applybasemethod(what,...)
local m=basemethods[basemethod][what]
if m then
return m(...)
end
end
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
applied={}
end
local function registerbasefeature(feature,value)
applied[#applied+1]=feature.."="..tostring(value)
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
trace_ligatures_detail("building % a into %a",lookupdata,unicode)
end
local size=#lookupdata
local firstcode=lookupdata[1]
local firstdata=characters[firstcode]
local okay=false
if firstdata then
local firstname="ctx_"..firstcode
for i=1,size-1 do
local firstdata=characters[firstcode]
if not firstdata then
firstcode=private
if trace then
trace_ligatures_detail("defining %a as %a",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
end
okay=true
else
target=unicodes[secondname]
if not target then
break
end
end
if trace then
trace_ligatures_detail("codes (%a,%a) + (%a,%a) -> %a",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
end
tfmdata.resources.private=private
end
end
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
local ligatures={}
local alternate=tonumber(value)
local defaultalt=otf.defaultbasealternate
local trace_singles=trace_baseinit and trace_singles
local trace_alternatives=trace_baseinit and trace_alternatives
local trace_ligatures=trace_baseinit and trace_ligatures
local actions={
substitution=function(lookupdata,lookupname,description,unicode)
if trace_singles then
report_substitution(feature,lookupname,descriptions,unicode,lookupdata)
end
changed[unicode]=lookupdata
end,
alternate=function(lookupdata,lookupname,description,unicode)
local replacement=lookupdata[alternate]
if replacement then
changed[unicode]=replacement
if trace_alternatives then
report_alternate(feature,lookupname,descriptions,unicode,replacement,value,"normal")
end
elseif defaultalt=="first" then
replacement=lookupdata[1]
changed[unicode]=replacement
if trace_alternatives then
report_alternate(feature,lookupname,descriptions,unicode,replacement,value,defaultalt)
end
elseif defaultalt=="last" then
replacement=lookupdata[#data]
if trace_alternatives then
report_alternate(feature,lookupname,descriptions,unicode,replacement,value,defaultalt)
end
else
if trace_alternatives then
report_alternate(feature,lookupname,descriptions,unicode,replacement,value,"unknown")
end
end
end,
ligature=function(lookupdata,lookupname,description,unicode)
if trace_ligatures then
report_ligature(feature,lookupname,descriptions,unicode,lookupdata)
end
ligatures[#ligatures+1]={ unicode,lookupdata }
end,
}
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
finalize_ligatures(tfmdata,ligatures)
end
local function preparepositionings(tfmdata,feature,value,validlookups,lookuplist)
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
if rawkerns then
local s=sharedkerns[rawkerns]
if s==false then
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
elseif not newkerns then
newkerns={ [otherunicode]=value }
done=true
if traceindeed then
report_kern(feature,lookup,descriptions,unicode,otherunicode,value)
end
elseif not newkerns[otherunicode] then
newkerns[otherunicode]=value
done=true
if traceindeed then
report_kern(feature,lookup,descriptions,unicode,otherunicode,value)
end
end
end
end
end
if done then
sharedkerns[rawkerns]=newkerns
character.kerns=newkerns
else
sharedkerns[rawkerns]=false
end
end
end
end
end
basemethods.independent={
preparesubstitutions=preparesubstitutions,
preparepositionings=preparepositionings,
}
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
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 %a, current %C, preceding %C",lookupname,v,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
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={}
local alternate=tonumber(value)
local defaultalt=otf.defaultbasealternate
local trace_singles=trace_baseinit and trace_singles
local trace_alternatives=trace_baseinit and trace_alternatives
local trace_ligatures=trace_baseinit and trace_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_singles then
report_substitution(feature,lookupname,descriptions,unicode,data)
end
changed[unicode]=data
elseif lookuptype=="alternate" then
local replacement=data[alternate]
if replacement then
changed[unicode]=replacement
if trace_alternatives then
report_alternate(feature,lookupname,descriptions,unicode,replacement,value,"normal")
end
elseif defaultalt=="first" then
replacement=data[1]
changed[unicode]=replacement
if trace_alternatives then
report_alternate(feature,lookupname,descriptions,unicode,replacement,value,defaultalt)
end
elseif defaultalt=="last" then
replacement=data[#data]
if trace_alternatives then
report_alternate(feature,lookupname,descriptions,unicode,replacement,value,defaultalt)
end
else
if trace_alternatives then
report_alternate(feature,lookupname,descriptions,unicode,replacement,value,"unknown")
end
end
elseif lookuptype=="ligature" then
ligatures[#ligatures+1]={ unicode,data,lookupname }
if trace_ligatures then
report_ligature(feature,lookupname,descriptions,unicode,data)
end
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
end
end
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
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_kern(feature,lookup,descriptions,unicode,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
local function initializehashes(tfmdata)
nodeinitializers.features(tfmdata)
end
basemethods.shared={
initializehashes=initializehashes,
preparesubstitutions=preparesubstitutions,
preparepositionings=preparepositionings,
}
basemethod="independent"
local function featuresinitializer(tfmdata,value)
if true 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 basepositionings then
for feature,data in next,basepositionings 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 %a",os.clock()-t,tfmdata.properties.fullname)
end
end
end
registerotffeature {
name="features",
description="features",
default=true,
initializers={
base=featuresinitializer,
}
}
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 ['node-inj']={
version=1.001,
comment="companion to node-ini.mkiv",
author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
copyright="PRAGMA ADE / ConTeXt Development Team",
license="see context related readme files",
}
local next=next
local utfchar=utf.char
local trace_injections=false trackers.register("nodes.injections",function(v) trace_injections=v end)
local report_injections=logs.reporter("nodes","injections")
local attributes,nodes,node=attributes,nodes,node
fonts=fonts
local fontdata=fonts.hashes.identifiers
nodes.injections=nodes.injections or {}
local injections=nodes.injections
local nodecodes=nodes.nodecodes
local glyph_code=nodecodes.glyph
local kern_code=nodecodes.kern
local nuts=nodes.nuts
local nodepool=nuts.pool
local newkern=nodepool.kern
local tonode=nuts.tonode
local tonut=nuts.tonut
local getfield=nuts.getfield
local getnext=nuts.getnext
local getprev=nuts.getprev
local getid=nuts.getid
local getattr=nuts.getattr
local getfont=nuts.getfont
local getsubtype=nuts.getsubtype
local getchar=nuts.getchar
local setfield=nuts.setfield
local setattr=nuts.setattr
local traverse_id=nuts.traverse_id
local insert_node_before=nuts.insert_before
local insert_node_after=nuts.insert_after
local a_kernpair=attributes.private('kernpair')
local a_ligacomp=attributes.private('ligacomp')
local a_markbase=attributes.private('markbase')
local a_markmark=attributes.private('markmark')
local a_markdone=attributes.private('markdone')
local a_cursbase=attributes.private('cursbase')
local a_curscurs=attributes.private('curscurs')
local a_cursdone=attributes.private('cursdone')
function injections.installnewkern(nk)
newkern=nk or newkern
end
local cursives={}
local marks={}
local kerns={}
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
setattr(start,a_cursbase,bound)
setattr(nxt,a_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]
if x~=0 or w~=0 or y~=0 or h~=0 then
local bound=getattr(current,a_kernpair)
if bound then
local kb=kerns[bound]
kb[2],kb[3],kb[4],kb[5]=(kb[2] or 0)+x,(kb[3] or 0)+y,(kb[4] or 0)+w,(kb[5] or 0)+h
else
bound=#kerns+1
setattr(current,a_kernpair,bound)
kerns[bound]={ rlmode,x,y,w,h,r2lflag,tfmchr.width }
end
return x,y,w,h,bound
end
return x,y,w,h
end
function injections.setkern(current,factor,rlmode,x,tfmchr)
local dx=factor*x
if dx~=0 then
local bound=#kerns+1
setattr(current,a_kernpair,bound)
kerns[bound]={ rlmode,dx }
return dx,bound
else
return 0,0
end
end
function injections.setmark(start,base,factor,rlmode,ba,ma,index,baseismark)
local dx,dy=factor*(ba[1]-ma[1]),factor*(ba[2]-ma[2])
local bound=getattr(base,a_markbase)
local index=1
if bound then
local mb=marks[bound]
if mb then
index=#mb+1
mb[index]={ dx,dy,rlmode }
setattr(start,a_markmark,bound)
setattr(start,a_markdone,index)
return dx,dy,bound
else
report_injections("possible problem, %U is base mark without data (id %a)",getchar(base),bound)
end
end
index=index or 1
bound=#marks+1
setattr(base,a_markbase,bound)
setattr(start,a_markmark,bound)
setattr(start,a_markdone,index)
marks[bound]={ [index]={ dx,dy,rlmode,baseismark } }
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 getsubtype(n)<256 then
local kp=getattr(n,a_kernpair)
local mb=getattr(n,a_markbase)
local mm=getattr(n,a_markmark)
local md=getattr(n,a_markdone)
local cb=getattr(n,a_cursbase)
local cc=getattr(n,a_curscurs)
local char=getchar(n)
report_injections("font %s, char %U, glyph %c",getfont(n),char,char)
if kp then
local k=kerns[kp]
if k[3] then
report_injections(" pairkern: dir %a, x %p, y %p, w %p, h %p",dir(k[1]),k[2],k[3],k[4],k[5])
else
report_injections(" kern: dir %a, dx %p",dir(k[1]),k[2])
end
end
if mb then
report_injections(" markbase: bound %a",mb)
end
if mm then
local m=marks[mm]
if mb then
local m=m[mb]
if m then
report_injections(" markmark: bound %a, index %a, dx %p, dy %p",mm,md,m[1],m[2])
else
report_injections(" markmark: bound %a, missing index",mm)
end
else
m=m[1]
report_injections(" markmark: bound %a, dx %p, dy %p",mm,m and m[1],m and m[2])
end
end
if cb then
report_injections(" cursbase: bound %a",cb)
end
if cc then
local c=cursives[cc]
report_injections(" curscurs: bound %a, dir %a, dx %p, dy %p",cc,dir(c[1]),c[2],c[3])
end
end
end
report_injections("end run")
end
local function show_result(head)
local current=head
local skipping=false
while current do
local id=getid(current)
if id==glyph_code then
report_injections("char: %C, width %p, xoffset %p, yoffset %p",
getchar(current),getfield(current,"width"),getfield(current,"xoffset"),getfield(current,"yoffset"))
skipping=false
elseif id==kern_code then
report_injections("kern: %p",getfield(current,"kern"))
skipping=false
elseif not skipping then
report_injections()
skipping=true
end
current=getnext(current)
end
end
function injections.handler(head,where,keep)
head=tonut(head)
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
local done,ky,rl,valid,cx,wx,mk,nofvalid=false,{},{},{},{},{},{},0
if has_kerns then
local nf,tm=nil,nil
for n in traverse_id(glyph_code,head) do
if getsubtype(n)<256 then
nofvalid=nofvalid+1
valid[nofvalid]=n
local f=getfont(n)
if f~=nf then
nf=f
tm=fontdata[nf].resources.marks
end
if tm then
mk[n]=tm[getchar(n)]
end
local k=getattr(n,a_kernpair)
if k then
local kk=kerns[k]
if kk then
local x,y,w,h=kk[2] or 0,kk[3] or 0,kk[4] or 0,kk[5] or 0
local dy=y-h
if dy~=0 then
ky[n]=dy
end
if w~=0 or x~=0 then
wx[n]=kk
end
rl[n]=kk[1]
end
end
end
end
else
local nf,tm=nil,nil
for n in traverse_id(glyph_code,head) do
if getsubtype(n)<256 then
nofvalid=nofvalid+1
valid[nofvalid]=n
local f=getfont(n)
if f~=nf then
nf=f
tm=fontdata[nf].resources.marks
end
if tm then
mk[n]=tm[getchar(n)]
end
end
end
end
if nofvalid>0 then
local cx={}
if has_kerns and next(ky) then
for n,k in next,ky do
setfield(n,"yoffset",k)
end
end
if has_cursives then
local p_cursbase,p=nil,nil
local t,d,maxt={},{},0
for i=1,nofvalid do
local n=valid[i]
if not mk[n] then
local n_cursbase=getattr(n,a_cursbase)
if p_cursbase then
local n_curscurs=getattr(n,a_curscurs)
if p_cursbase==n_curscurs then
local c=cursives[n_curscurs]
if c then
local rlmode,dx,dy,ws,wn=c[1],c[2],c[3],c[4],c[5]
if rlmode>=0 then
dx=dx-ws
else
dx=dx+wn
end
if dx~=0 then
cx[n]=dx
rl[n]=rlmode
end
dy=-dy
maxt=maxt+1
t[maxt]=p
d[maxt]=dy
else
maxt=0
end
end
elseif maxt>0 then
local ny=getfield(n,"yoffset")
for i=maxt,1,-1 do
ny=ny+d[i]
local ti=t[i]
setfield(ti,"yoffset",getfield(ti,"yoffset")+ny)
end
maxt=0
end
if not n_cursbase and maxt>0 then
local ny=getfield(n,"yoffset")
for i=maxt,1,-1 do
ny=ny+d[i]
local ti=t[i]
setfield(ti,"yoffset",ny)
end
maxt=0
end
p_cursbase,p=n_cursbase,n
end
end
if maxt>0 then
local ny=getfield(n,"yoffset")
for i=maxt,1,-1 do
ny=ny+d[i]
local ti=t[i]
setfield(ti,"yoffset",ny)
end
maxt=0
end
if not keep then
cursives={}
end
end
if has_marks then
for i=1,nofvalid do
local p=valid[i]
local p_markbase=getattr(p,a_markbase)
if p_markbase then
local mrks=marks[p_markbase]
local nofmarks=#mrks
for n in traverse_id(glyph_code,getnext(p)) do
local n_markmark=getattr(n,a_markmark)
if p_markbase==n_markmark then
local index=getattr(n,a_markdone) or 1
local d=mrks[index]
if d then
local rlmode=d[3]
local k=wx[p]
local px=getfield(p,"xoffset")
local ox=0
if k then
local x=k[2]
local w=k[4]
if w then
if rlmode and rlmode>=0 then
ox=px-getfield(p,"width")+d[1]-(w-x)
else
ox=px-d[1]-x
end
else
if rlmode and rlmode>=0 then
ox=px-getfield(p,"width")+d[1]
else
ox=px-d[1]-x
end
end
else
local wp=getfield(p,"width")
local wn=getfield(n,"width")
if rlmode and rlmode>=0 then
ox=px-wp+d[1]
else
ox=px-d[1]
end
if wn~=0 then
insert_node_before(head,n,newkern(-wn/2))
insert_node_after(head,n,newkern(-wn/2))
end
end
setfield(n,"xoffset",ox)
local py=getfield(p,"yoffset")
local oy=0
if mk[p] then
oy=py+d[2]
else
oy=getfield(n,"yoffset")+py+d[2]
end
setfield(n,"yoffset",oy)
if nofmarks==1 then
break
else
nofmarks=nofmarks-1
end
end
elseif not n_markmark then
break
else
end
end
end
end
if not keep then
marks={}
end
end
if next(wx) then
for n,k in next,wx do
local x=k[2]
local w=k[4]
if w then
local rl=k[1]
local wx=w-x
if rl<0 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
elseif x~=0 then
insert_node_before(head,n,newkern(x))
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
head=tonode(head)
return head,true
elseif not keep then
kerns,cursives,marks={},{},{}
end
elseif has_kerns then
if trace_injections then
trace(head)
end
for n in traverse_id(glyph_code,head) do
if getsubtype(n)<256 then
local k=getattr(n,a_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
setfield(n,"yoffset",y)
end
if w then
local wx=w-x
if rl<0 then
if wx~=0 then
insert_node_before(head,n,newkern(wx))
end
if x~=0 then
insert_node_after (head,n,newkern(x))
end
else
if x~=0 then
insert_node_before(head,n,newkern(x))
end
if wx~=0 then
insert_node_after(head,n,newkern(wx))
end
end
else
if x~=0 then
insert_node_before(head,n,newkern(x))
end
end
end
end
end
end
if not keep then
kerns={}
end
return tonode(head),true
else
end
return tonode(head),false
end
end -- closure
do -- begin closure to overcome local limits and interference
if not modules then modules={} end modules ['font-ota']={
version=1.001,
comment="companion to font-otf.lua (analysing)",
author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
copyright="PRAGMA ADE / ConTeXt Development Team",
license="see context related readme files"
}
local type=type
if not trackers then trackers={ register=function() end } end
local fonts,nodes,node=fonts,nodes,node
local allocate=utilities.storage.allocate
local otf=fonts.handlers.otf
local analyzers=fonts.analyzers
local initializers=allocate()
local methods=allocate()
analyzers.initializers=initializers
analyzers.methods=methods
analyzers.useunicodemarks=false
local a_state=attributes.private('state')
local nodecodes=nodes.nodecodes
local glyph_code=nodecodes.glyph
local disc_code=nodecodes.disc
local math_code=nodecodes.math
local traverse_id=node.traverse_id
local traverse_node_list=node.traverse
local end_of_math=node.end_of_math
local fontdata=fonts.hashes.identifiers
local categories=characters and characters.categories or {}
local otffeatures=fonts.constructors.newfeatures("otf")
local registerotffeature=otffeatures.register
local s_init=1 local s_rphf=7
local s_medi=2 local s_half=8
local s_fina=3 local s_pref=9
local s_isol=4 local s_blwf=10
local s_mark=5 local s_pstf=11
local s_rest=6
local states={
init=s_init,
medi=s_medi,
fina=s_fina,
isol=s_isol,
mark=s_mark,
rest=s_rest,
rphf=s_rphf,
half=s_half,
pref=s_pref,
blwf=s_blwf,
pstf=s_pstf,
}
local features={
init=s_init,
medi=s_medi,
fina=s_fina,
isol=s_isol,
rphf=s_rphf,
half=s_half,
pref=s_pref,
blwf=s_blwf,
pstf=s_pstf,
}
analyzers.states=states
analyzers.features=features
function analyzers.setstate(head,font)
local useunicodemarks=analyzers.useunicodemarks
local tfmdata=fontdata[font]
local descriptions=tfmdata.descriptions
local first,last,current,n,done=nil,nil,head,0,false
while current do
local id=current.id
if id==glyph_code and current.font==font then
done=true
local char=current.char
local d=descriptions[char]
if d then
if d.class=="mark" or (useunicodemarks and categories[char]=="mn") then
done=true
current[a_state]=s_mark
elseif n==0 then
first,last,n=current,current,1
current[a_state]=s_init
else
last,n=current,n+1
current[a_state]=s_medi
end
else
if first and first==last then
last[a_state]=s_isol
elseif last then
last[a_state]=s_fina
end
first,last,n=nil,nil,0
end
elseif id==disc_code then
current[a_state]=s_medi
last=current
else
if first and first==last then
last[a_state]=s_isol
elseif last then
last[a_state]=s_fina
end
first,last,n=nil,nil,0
if id==math_code then
current=end_of_math(current)
end
end
current=current.next
end
if first and first==last then
last[a_state]=s_isol
elseif last then
last[a_state]=s_fina
end
return head,done
end
local function analyzeinitializer(tfmdata,value)
local script,language=otf.scriptandlanguage(tfmdata)
local action=initializers[script]
if not action then
elseif type(action)=="function" then
return action(tfmdata,value)
else
local action=action[language]
if action then
return action(tfmdata,value)
end
end
end
local function analyzeprocessor(head,font,attr)
local tfmdata=fontdata[font]
local script,language=otf.scriptandlanguage(tfmdata,attr)
local action=methods[script]
if not action then
elseif type(action)=="function" then
return action(head,font,attr)
else
action=action[language]
if action then
return action(head,font,attr)
end
end
return head,false
end
registerotffeature {
name="analyze",
description="analysis of character classes",
default=true,
initializers={
node=analyzeinitializer,
},
processors={
position=1,
node=analyzeprocessor,
}
}
methods.latn=analyzers.setstate
local tatweel=0x0640
local zwnj=0x200C
local zwj=0x200D
local isolated={
[0x0600]=true,[0x0601]=true,[0x0602]=true,[0x0603]=true,
[0x0604]=true,
[0x0608]=true,[0x060B]=true,[0x0621]=true,[0x0674]=true,
[0x06DD]=true,
[0x0856]=true,[0x0858]=true,[0x0857]=true,
[0x07FA]=true,
[zwnj]=true,
}
local final={
[0x0622]=true,[0x0623]=true,[0x0624]=true,[0x0625]=true,
[0x0627]=true,[0x0629]=true,[0x062F]=true,[0x0630]=true,
[0x0631]=true,[0x0632]=true,[0x0648]=true,[0x0671]=true,
[0x0672]=true,[0x0673]=true,[0x0675]=true,[0x0676]=true,
[0x0677]=true,[0x0688]=true,[0x0689]=true,[0x068A]=true,
[0x068B]=true,[0x068C]=true,[0x068D]=true,[0x068E]=true,
[0x068F]=true,[0x0690]=true,[0x0691]=true,[0x0692]=true,
[0x0693]=true,[0x0694]=true,[0x0695]=true,[0x0696]=true,
[0x0697]=true,[0x0698]=true,[0x0699]=true,[0x06C0]=true,
[0x06C3]=true,[0x06C4]=true,[0x06C5]=true,[0x06C6]=true,
[0x06C7]=true,[0x06C8]=true,[0x06C9]=true,[0x06CA]=true,
[0x06CB]=true,[0x06CD]=true,[0x06CF]=true,[0x06D2]=true,
[0x06D3]=true,[0x06D5]=true,[0x06EE]=true,[0x06EF]=true,
[0x0759]=true,[0x075A]=true,[0x075B]=true,[0x076B]=true,
[0x076C]=true,[0x0771]=true,[0x0773]=true,[0x0774]=true,
[0x0778]=true,[0x0779]=true,
[0x08AA]=true,[0x08AB]=true,[0x08AC]=true,
[0xFEF5]=true,[0xFEF7]=true,[0xFEF9]=true,[0xFEFB]=true,
[0x0710]=true,[0x0715]=true,[0x0716]=true,[0x0717]=true,
[0x0718]=true,[0x0719]=true,[0x0728]=true,[0x072A]=true,
[0x072C]=true,[0x071E]=true,
[0x072F]=true,[0x074D]=true,
[0x0840]=true,[0x0849]=true,[0x0854]=true,[0x0846]=true,
[0x084F]=true
}
local medial={
[0x0626]=true,[0x0628]=true,[0x062A]=true,[0x062B]=true,
[0x062C]=true,[0x062D]=true,[0x062E]=true,[0x0633]=true,
[0x0634]=true,[0x0635]=true,[0x0636]=true,[0x0637]=true,
[0x0638]=true,[0x0639]=true,[0x063A]=true,[0x063B]=true,
[0x063C]=true,[0x063D]=true,[0x063E]=true,[0x063F]=true,
[0x0641]=true,[0x0642]=true,[0x0643]=true,
[0x0644]=true,[0x0645]=true,[0x0646]=true,[0x0647]=true,
[0x0649]=true,[0x064A]=true,[0x066E]=true,[0x066F]=true,
[0x0678]=true,[0x0679]=true,[0x067A]=true,[0x067B]=true,
[0x067C]=true,[0x067D]=true,[0x067E]=true,[0x067F]=true,
[0x0680]=true,[0x0681]=true,[0x0682]=true,[0x0683]=true,
[0x0684]=true,[0x0685]=true,[0x0686]=true,[0x0687]=true,
[0x069A]=true,[0x069B]=true,[0x069C]=true,[0x069D]=true,
[0x069E]=true,[0x069F]=true,[0x06A0]=true,[0x06A1]=true,
[0x06A2]=true,[0x06A3]=true,[0x06A4]=true,[0x06A5]=true,
[0x06A6]=true,[0x06A7]=true,[0x06A8]=true,[0x06A9]=true,
[0x06AA]=true,[0x06AB]=true,[0x06AC]=true,[0x06AD]=true,
[0x06AE]=true,[0x06AF]=true,[0x06B0]=true,[0x06B1]=true,
[0x06B2]=true,[0x06B3]=true,[0x06B4]=true,[0x06B5]=true,
[0x06B6]=true,[0x06B7]=true,[0x06B8]=true,[0x06B9]=true,
[0x06BA]=true,[0x06BB]=true,[0x06BC]=true,[0x06BD]=true,
[0x06BE]=true,[0x06BF]=true,[0x06C1]=true,[0x06C2]=true,
[0x06CC]=true,[0x06CE]=true,[0x06D0]=true,[0x06D1]=true,
[0x06FA]=true,[0x06FB]=true,[0x06FC]=true,[0x06FF]=true,
[0x0750]=true,[0x0751]=true,[0x0752]=true,[0x0753]=true,
[0x0754]=true,[0x0755]=true,[0x0756]=true,[0x0757]=true,
[0x0758]=true,[0x075C]=true,[0x075D]=true,[0x075E]=true,
[0x075F]=true,[0x0760]=true,[0x0761]=true,[0x0762]=true,
[0x0763]=true,[0x0764]=true,[0x0765]=true,[0x0766]=true,
[0x0767]=true,[0x0768]=true,[0x0769]=true,[0x076A]=true,
[0x076D]=true,[0x076E]=true,[0x076F]=true,[0x0770]=true,
[0x0772]=true,[0x0775]=true,[0x0776]=true,[0x0777]=true,
[0x077A]=true,[0x077B]=true,[0x077C]=true,[0x077D]=true,
[0x077E]=true,[0x077F]=true,
[0x08A0]=true,[0x08A2]=true,[0x08A4]=true,[0x08A5]=true,
[0x08A6]=true,[0x0620]=true,[0x08A8]=true,[0x08A9]=true,
[0x08A7]=true,[0x08A3]=true,
[0x0712]=true,[0x0713]=true,[0x0714]=true,[0x071A]=true,
[0x071B]=true,[0x071C]=true,[0x071D]=true,[0x071F]=true,
[0x0720]=true,[0x0721]=true,[0x0722]=true,[0x0723]=true,
[0x0724]=true,[0x0725]=true,[0x0726]=true,[0x0727]=true,
[0x0729]=true,[0x072B]=true,[0x072D]=true,[0x072E]=true,
[0x074E]=true,[0x074F]=true,
[0x0841]=true,[0x0842]=true,[0x0843]=true,[0x0844]=true,
[0x0845]=true,[0x0847]=true,[0x0848]=true,[0x0855]=true,
[0x0851]=true,[0x084E]=true,[0x084D]=true,[0x084A]=true,
[0x084B]=true,[0x084C]=true,[0x0850]=true,[0x0852]=true,
[0x0853]=true,
[0x07D7]=true,[0x07E8]=true,[0x07D9]=true,[0x07EA]=true,
[0x07CA]=true,[0x07DB]=true,[0x07CC]=true,[0x07DD]=true,
[0x07CE]=true,[0x07DF]=true,[0x07D4]=true,[0x07E5]=true,
[0x07E9]=true,[0x07E7]=true,[0x07E3]=true,[0x07E2]=true,
[0x07E0]=true,[0x07E1]=true,[0x07DE]=true,[0x07DC]=true,
[0x07D1]=true,[0x07DA]=true,[0x07D8]=true,[0x07D6]=true,
[0x07D2]=true,[0x07D0]=true,[0x07CF]=true,[0x07CD]=true,
[0x07CB]=true,[0x07D3]=true,[0x07E4]=true,[0x07D5]=true,
[0x07E6]=true,
[tatweel]=true,
[zwj]=true,
}
local arab_warned={}
local function warning(current,what)
local char=current.char
if not arab_warned[char] then
log.report("analyze","arab: character %C has no %a class",char,what)
arab_warned[char]=true
end
end
local function finish(first,last)
if last then
if first==last then
local fc=first.char
if medial[fc] or final[fc] then
first[a_state]=s_isol
else
warning(first,"isol")
first[a_state]=s_error
end
else
local lc=last.char
if medial[lc] or final[lc] then
last[a_state]=s_fina
else
warning(last,"fina")
last[a_state]=s_error
end
end
first,last=nil,nil
elseif first then
local fc=first.char
if medial[fc] or final[fc] then
first[a_state]=s_isol
else
warning(first,"isol")
first[a_state]=s_error
end
first=nil
end
return first,last
end
function methods.arab(head,font,attr)
local useunicodemarks=analyzers.useunicodemarks
local tfmdata=fontdata[font]
local marks=tfmdata.resources.marks
local first,last,current,done=nil,nil,head,false
while current do
local id=current.id
if id==glyph_code and current.font==font and current.subtype<256 and not current[a_state] then
done=true
local char=current.char
if marks[char] or (useunicodemarks and categories[char]=="mn") then
current[a_state]=s_mark
elseif isolated[char] then
first,last=finish(first,last)
current[a_state]=s_isol
first,last=nil,nil
elseif not first then
if medial[char] then
current[a_state]=s_init
first,last=first or current,current
elseif final[char] then
current[a_state]=s_isol
first,last=nil,nil
else
first,last=finish(first,last)
end
elseif medial[char] then
first,last=first or current,current
current[a_state]=s_medi
elseif final[char] then
if not last[a_state]==s_init then
last[a_state]=s_medi
end
current[a_state]=s_fina
first,last=nil,nil
elseif char>=0x0600 and char<=0x06FF then
current[a_state]=s_rest
first,last=finish(first,last)
else
first,last=finish(first,last)
end
else
if first or last then
first,last=finish(first,last)
end
if id==math_code then
current=end_of_math(current)
end
end
current=current.next
end
if first or last then
finish(first,last)
end
return head,done
end
methods.syrc=methods.arab
methods.mand=methods.arab
methods.nko=methods.arab
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-otn']={
version=1.001,
comment="companion to font-ini.mkiv",
author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
copyright="PRAGMA ADE / ConTeXt Development Team",
license="see context related readme files",
}
local concat,insert,remove=table.concat,table.insert,table.remove
local gmatch,gsub,find,match,lower,strip=string.gmatch,string.gsub,string.find,string.match,string.lower,string.strip
local type,next,tonumber,tostring=type,next,tonumber,tostring
local lpegmatch=lpeg.match
local random=math.random
local formatters=string.formatters
local logs,trackers,nodes,attributes=logs,trackers,nodes,attributes
local registertracker=trackers.register
local fonts=fonts
local otf=fonts.handlers.otf
local trace_lookups=false registertracker("otf.lookups",function(v) trace_lookups=v end)
local trace_singles=false registertracker("otf.singles",function(v) trace_singles=v end)
local trace_multiples=false registertracker("otf.multiples",function(v) trace_multiples=v end)
local trace_alternatives=false registertracker("otf.alternatives",function(v) trace_alternatives=v end)
local trace_ligatures=false registertracker("otf.ligatures",function(v) trace_ligatures=v end)
local trace_contexts=false registertracker("otf.contexts",function(v) trace_contexts=v end)
local trace_marks=false registertracker("otf.marks",function(v) trace_marks=v end)
local trace_kerns=false registertracker("otf.kerns",function(v) trace_kerns=v end)
local trace_cursive=false registertracker("otf.cursive",function(v) trace_cursive=v end)
local trace_preparing=false registertracker("otf.preparing",function(v) trace_preparing=v end)
local trace_bugs=false registertracker("otf.bugs",function(v) trace_bugs=v end)
local trace_details=false registertracker("otf.details",function(v) trace_details=v end)
local trace_applied=false registertracker("otf.applied",function(v) trace_applied=v end)
local trace_steps=false registertracker("otf.steps",function(v) trace_steps=v end)
local trace_skips=false registertracker("otf.skips",function(v) trace_skips=v end)
local trace_directions=false registertracker("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")
local report_warning=logs.reporter("fonts","otf warning")
registertracker("otf.verbose_chain",function(v) otf.setcontextchain(v and "verbose") end)
registertracker("otf.normal_chain",function(v) otf.setcontextchain(v and "normal") end)
registertracker("otf.replacements","otf.singles,otf.multiples,otf.alternatives,otf.ligatures")
registertracker("otf.positions","otf.marks,otf.kerns,otf.cursive")
registertracker("otf.actions","otf.replacements,otf.positions")
registertracker("otf.injections","nodes.injections")
registertracker("*otf.sample","otf.steps,otf.actions,otf.analyzing")
local nuts=nodes.nuts
local tonode=nuts.tonode
local tonut=nuts.tonut
local getfield=nuts.getfield
local getnext=nuts.getnext
local getprev=nuts.getprev
local getid=nuts.getid
local getattr=nuts.getattr
local getfont=nuts.getfont
local getsubtype=nuts.getsubtype
local getchar=nuts.getchar
local setfield=nuts.setfield
local setattr=nuts.setattr
local insert_node_after=nuts.insert_after
local delete_node=nuts.delete
local copy_node=nuts.copy
local find_node_tail=nuts.tail
local flush_node_list=nuts.flush_list
local end_of_math=nuts.end_of_math
local setmetatableindex=table.setmetatableindex
local zwnj=0x200C
local zwj=0x200D
local wildcard="*"
local default="dflt"
local nodecodes=nodes.nodecodes
local whatcodes=nodes.whatcodes
local glyphcodes=nodes.glyphcodes
local disccodes=nodes.disccodes
local glyph_code=nodecodes.glyph
local glue_code=nodecodes.glue
local disc_code=nodecodes.disc
local whatsit_code=nodecodes.whatsit
local math_code=nodecodes.math
local dir_code=whatcodes.dir
local localpar_code=whatcodes.localpar
local discretionary_code=disccodes.discretionary
local ligature_code=glyphcodes.ligature
local privateattribute=attributes.private
local a_state=privateattribute('state')
local a_markbase=privateattribute('markbase')
local a_markmark=privateattribute('markmark')
local a_markdone=privateattribute('markdone')
local a_cursbase=privateattribute('cursbase')
local a_curscurs=privateattribute('curscurs')
local a_cursdone=privateattribute('cursdone')
local a_kernpair=privateattribute('kernpair')
local a_ligacomp=privateattribute('ligacomp')
local injections=nodes.injections
local setmark=injections.setmark
local setcursive=injections.setcursive
local setkern=injections.setkern
local setpair=injections.setpair
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 or function() end
otf.defaultnodealternate="none"
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
local checkstep=(nodes and nodes.tracers and nodes.tracers.steppers.check) or function() end
local registerstep=(nodes and nodes.tracers and nodes.tracers.steppers.register) or function() end
local registermessage=(nodes and nodes.tracers and nodes.tracers.steppers.message) or function() end
local function logprocess(...)
if trace_steps then
registermessage(...)
end
report_direct(...)
end
local function logwarning(...)
report_direct(...)
end
local f_unicode=formatters["%U"]
local f_uniname=formatters["%U (%s)"]
local f_unilist=formatters["% t (% t)"]
local function gref(n)
if type(n)=="number" then
local description=descriptions[n]
local name=description and description.name
if name then
return f_uniname(n,name)
else
return f_unicode(n)
end
elseif n then
local num,nam={},{}
for i=1,#n do
local ni=n[i]
if tonumber(ni) then
local di=descriptions[ni]
num[i]=f_unicode(ni)
nam[i]=di and di.name or "-"
end
end
return f_unilist(num,nam)
else
return "<error in node mode tracing>"
end
end
local function cref(kind,chainname,chainlookupname,lookupname,index)
if index then
return formatters["feature %a, chain %a, sub %a, lookup %a, index %a"](kind,chainname,chainlookupname,lookupname,index)
elseif lookupname then
return formatters["feature %a, chain %a, sub %a, lookup %a"](kind,chainname,chainlookupname,lookupname)
elseif chainlookupname then
return formatters["feature %a, chain %a, sub %a"](kind,chainname,chainlookupname)
elseif chainname then
return formatters["feature %a, chain %a"](kind,chainname)
else
return formatters["feature %a"](kind)
end
end
local function pref(kind,lookupname)
return formatters["feature %a, lookup %a"](kind,lookupname)
end
local function copy_glyph(g)
local components=getfield(g,"components")
if components then
setfield(g,"components",nil)
local n=copy_node(g)
setfield(g,"components",components)
return n
else
return copy_node(g)
end
end
local function markstoligature(kind,lookupname,head,start,stop,char)
if start==stop and getchar(start)==char then
return head,start
else
local prev=getprev(start)
local next=getnext(stop)
setfield(start,"prev",nil)
setfield(stop,"next",nil)
local base=copy_glyph(start)
if head==start then
head=base
end
setfield(base,"char",char)
setfield(base,"subtype",ligature_code)
setfield(base,"components",start)
if prev then
setfield(prev,"next",base)
end
if next then
setfield(next,"prev",base)
end
setfield(base,"next",next)
setfield(base,"prev",prev)
return head,base
end
end
local function getcomponentindex(start)
if getid(start)~=glyph_code then
return 0
elseif getsubtype(start)==ligature_code then
local i=0
local components=getfield(start,"components")
while components do
i=i+getcomponentindex(components)
components=getnext(components)
end
return i
elseif not marks[getchar(start)] then
return 1
else
return 0
end
end
local function toligature(kind,lookupname,head,start,stop,char,markflag,discfound)
if start==stop and getchar(start)==char then
setfield(start,"char",char)
return head,start
end
local prev=getprev(start)
local next=getnext(stop)
setfield(start,"prev",nil)
setfield(stop,"next",nil)
local base=copy_glyph(start)
if start==head then
head=base
end
setfield(base,"char",char)
setfield(base,"subtype",ligature_code)
setfield(base,"components",start)
if prev then
setfield(prev,"next",base)
end
if next then
setfield(next,"prev",base)
end
setfield(base,"next",next)
setfield(base,"prev",prev)
if not discfound then
local deletemarks=markflag~="mark"
local components=start
local baseindex=0
local componentindex=0
local head=base
local current=base
while start do
local char=getchar(start)
if not marks[char] then
baseindex=baseindex+componentindex
componentindex=getcomponentindex(start)
elseif not deletemarks then
setattr(start,a_ligacomp,baseindex+(getattr(start,a_ligacomp) or componentindex))
if trace_marks then
logwarning("%s: keep mark %s, gets index %s",pref(kind,lookupname),gref(char),getattr(start,a_ligacomp))
end
head,current=insert_node_after(head,current,copy_node(start))
elseif trace_marks then
logwarning("%s: delete mark %s",pref(kind,lookupname),gref(char))
end
start=getnext(start)
end
local start=getnext(current)
while start and getid(start)==glyph_code do
local char=getchar(start)
if marks[char] then
setattr(start,a_ligacomp,baseindex+(getattr(start,a_ligacomp) or componentindex))
if trace_marks then
logwarning("%s: set mark %s, gets index %s",pref(kind,lookupname),gref(char),getattr(start,a_ligacomp))
end
else
break
end
start=getnext(start)
end
end
return head,base
end
function handlers.gsub_single(head,start,kind,lookupname,replacement)
if trace_singles then
logprocess("%s: replacing %s by single %s",pref(kind,lookupname),gref(getchar(start)),gref(replacement))
end
setfield(start,"char",replacement)
return head,start,true
end
local function get_alternative_glyph(start,alternatives,value,trace_alternatives)
local n=#alternatives
if value=="random" then
local r=random(1,n)
return alternatives[r],trace_alternatives and formatters["value %a, taking %a"](value,r)
elseif value=="first" then
return alternatives[1],trace_alternatives and formatters["value %a, taking %a"](value,1)
elseif value=="last" then
return alternatives[n],trace_alternatives and formatters["value %a, taking %a"](value,n)
else
value=tonumber(value)
if type(value)~="number" then
return alternatives[1],trace_alternatives and formatters["invalid value %s, taking %a"](value,1)
elseif value>n then
local defaultalt=otf.defaultnodealternate
if defaultalt=="first" then
return alternatives[n],trace_alternatives and formatters["invalid value %s, taking %a"](value,1)
elseif defaultalt=="last" then
return alternatives[1],trace_alternatives and formatters["invalid value %s, taking %a"](value,n)
else
return false,trace_alternatives and formatters["invalid value %a, %s"](value,"out of range")
end
elseif value==0 then
return getchar(start),trace_alternatives and formatters["invalid value %a, %s"](value,"no change")
elseif value<1 then
return alternatives[1],trace_alternatives and formatters["invalid value %a, taking %a"](value,1)
else
return alternatives[value],trace_alternatives and formatters["value %a, taking %a"](value,value)
end
end
end
local function multiple_glyphs(head,start,multiple,ignoremarks)
local nofmultiples=#multiple
if nofmultiples>0 then
setfield(start,"char",multiple[1])
if nofmultiples>1 then
local sn=getnext(start)
for k=2,nofmultiples do
local n=copy_node(start)
setfield(n,"char",multiple[k])
setfield(n,"next",sn)
setfield(n,"prev",start)
if sn then
setfield(sn,"prev",n)
end
setfield(start,"next",n)
start=n
end
end
return head,start,true
else
if trace_multiples then
logprocess("no multiple for %s",gref(getchar(start)))
end
return head,start,false
end
end
function handlers.gsub_alternate(head,start,kind,lookupname,alternative,sequence)
local value=featurevalue==true and tfmdata.shared.features[kind] or featurevalue
local choice,comment=get_alternative_glyph(start,alternative,value,trace_alternatives)
if choice then
if trace_alternatives then
logprocess("%s: replacing %s by alternative %a to %s, %s",pref(kind,lookupname),gref(getchar(start)),choice,gref(choice),comment)
end
setfield(start,"char",choice)
else
if trace_alternatives then
logwarning("%s: no variant %a for %s, %s",pref(kind,lookupname),value,gref(getchar(start)),comment)
end
end
return head,start,true
end
function handlers.gsub_multiple(head,start,kind,lookupname,multiple,sequence)
if trace_multiples then
logprocess("%s: replacing %s by multiple %s",pref(kind,lookupname),gref(getchar(start)),gref(multiple))
end
return multiple_glyphs(head,start,multiple,sequence.flags[1])
end
function handlers.gsub_ligature(head,start,kind,lookupname,ligature,sequence)
local s,stop,discfound=getnext(start),nil,false
local startchar=getchar(start)
if marks[startchar] then
while s do
local id=getid(s)
if id==glyph_code and getfont(s)==currentfont and getsubtype(s)<256 then
local lg=ligature[getchar(s)]
if lg then
stop=s
ligature=lg
s=getnext(s)
else
break
end
else
break
end
end
if stop then
local lig=ligature.ligature
if lig then
if trace_ligatures then
local stopchar=getchar(stop)
head,start=markstoligature(kind,lookupname,head,start,stop,lig)
logprocess("%s: replacing %s upto %s by ligature %s case 1",pref(kind,lookupname),gref(startchar),gref(stopchar),gref(getchar(start)))
else
head,start=markstoligature(kind,lookupname,head,start,stop,lig)
end
return head,start,true
else
end
end
else
local skipmark=sequence.flags[1]
while s do
local id=getid(s)
if id==glyph_code and getsubtype(s)<256 then
if getfont(s)==currentfont then
local char=getchar(s)
if skipmark and marks[char] then
s=getnext(s)
else
local lg=ligature[char]
if lg then
stop=s
ligature=lg
s=getnext(s)
else
break
end
end
else
break
end
elseif id==disc_code then
discfound=true
s=getnext(s)
else
break
end
end
if stop then
local lig=ligature.ligature
if lig then
if trace_ligatures then
local stopchar=getchar(stop)
head,start=toligature(kind,lookupname,head,start,stop,lig,skipmark,discfound)
logprocess("%s: replacing %s upto %s by ligature %s case 2",pref(kind,lookupname),gref(startchar),gref(stopchar),gref(getchar(start)))
else
head,start=toligature(kind,lookupname,head,start,stop,lig,skipmark,discfound)
end
return head,start,true
else
end
end
end
return head,start,false
end
function handlers.gpos_mark2base(head,start,kind,lookupname,markanchors,sequence)
local markchar=getchar(start)
if marks[markchar] then
local base=getprev(start)
if base and getid(base)==glyph_code and getfont(base)==currentfont and getsubtype(base)<256 then
local basechar=getchar(base)
if marks[basechar] then
while true do
base=getprev(base)
if base and getid(base)==glyph_code and getfont(base)==currentfont and getsubtype(base)<256 then
basechar=getchar(base)
if not marks[basechar] then
break
end
else
if trace_bugs then
logwarning("%s: no base for mark %s",pref(kind,lookupname),gref(markchar))
end
return head,start,false
end
end
end
local baseanchors=descriptions[basechar]
if baseanchors then
baseanchors=baseanchors.anchors
end
if baseanchors then
local baseanchors=baseanchors['basechar']
if baseanchors then
local al=anchorlookups[lookupname]
for anchor,ba in next,baseanchors do
if al[anchor] then
local ma=markanchors[anchor]
if ma then
local dx,dy,bound=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 => (%p,%p)",
pref(kind,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy)
end
return head,start,true
end
end
end
if trace_bugs then
logwarning("%s, no matching anchors for mark %s and base %s",pref(kind,lookupname),gref(markchar),gref(basechar))
end
end
elseif trace_bugs then
onetimemessage(currentfont,basechar,"no base anchors",report_fonts)
end
elseif trace_bugs then
logwarning("%s: prev node is no char",pref(kind,lookupname))
end
elseif trace_bugs then
logwarning("%s: mark %s is no mark",pref(kind,lookupname),gref(markchar))
end
return head,start,false
end
function handlers.gpos_mark2ligature(head,start,kind,lookupname,markanchors,sequence)
local markchar=getchar(start)
if marks[markchar] then
local base=getprev(start)
if base and getid(base)==glyph_code and getfont(base)==currentfont and getsubtype(base)<256 then
local basechar=getchar(base)
if marks[basechar] then
while true do
base=getprev(base)
if base and getid(base)==glyph_code and getfont(base)==currentfont and getsubtype(base)<256 then
basechar=getchar(base)
if not marks[basechar] then
break
end
else
if trace_bugs then
logwarning("%s: no base for mark %s",pref(kind,lookupname),gref(markchar))
end
return head,start,false
end
end
end
local index=getattr(start,a_ligacomp)
local baseanchors=descriptions[basechar]
if baseanchors then
baseanchors=baseanchors.anchors
if baseanchors then
local baseanchors=baseanchors['baselig']
if baseanchors then
local al=anchorlookups[lookupname]
for anchor,ba in next,baseanchors do
if al[anchor] then
local ma=markanchors[anchor]
if ma then
ba=ba[index]
if ba then
local dx,dy,bound=setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma)
if trace_marks then
logprocess("%s, anchor %s, index %s, bound %s: anchoring mark %s to baselig %s at index %s => (%p,%p)",
pref(kind,lookupname),anchor,index,bound,gref(markchar),gref(basechar),index,dx,dy)
end
return head,start,true
else
if trace_bugs then
logwarning("%s: no matching anchors for mark %s and baselig %s with index %a",pref(kind,lookupname),gref(markchar),gref(basechar),index)
end
end
end
end
end
if trace_bugs then
logwarning("%s: no matching anchors for mark %s and baselig %s",pref(kind,lookupname),gref(markchar),gref(basechar))
end
end
end
elseif trace_bugs then
onetimemessage(currentfont,basechar,"no base anchors",report_fonts)
end
elseif trace_bugs then
logwarning("%s: prev node is no char",pref(kind,lookupname))
end
elseif trace_bugs then
logwarning("%s: mark %s is no mark",pref(kind,lookupname),gref(markchar))
end
return head,start,false
end
function handlers.gpos_mark2mark(head,start,kind,lookupname,markanchors,sequence)
local markchar=getchar(start)
if marks[markchar] then
local base=getprev(start)
local slc=getattr(start,a_ligacomp)
if slc then
while base do
local blc=getattr(base,a_ligacomp)
if blc and blc~=slc then
base=getprev(base)
else
break
end
end
end
if base and getid(base)==glyph_code and getfont(base)==currentfont and getsubtype(base)<256 then
local basechar=getchar(base)
local baseanchors=descriptions[basechar]
if baseanchors then
baseanchors=baseanchors.anchors
if baseanchors then
baseanchors=baseanchors['basemark']
if baseanchors then
local al=anchorlookups[lookupname]
for anchor,ba in next,baseanchors do
if al[anchor] then
local ma=markanchors[anchor]
if ma then
local dx,dy,bound=setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma,true)
if trace_marks then
logprocess("%s, anchor %s, bound %s: anchoring mark %s to basemark %s => (%p,%p)",
pref(kind,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy)
end
return head,start,true
end
end
end
if trace_bugs then
logwarning("%s: no matching anchors for mark %s and basemark %s",pref(kind,lookupname),gref(markchar),gref(basechar))
end
end
end
elseif trace_bugs then
onetimemessage(currentfont,basechar,"no base anchors",report_fonts)
end
elseif trace_bugs then
logwarning("%s: prev node is no mark",pref(kind,lookupname))
end
elseif trace_bugs then
logwarning("%s: mark %s is no mark",pref(kind,lookupname),gref(markchar))
end
return head,start,false
end
function handlers.gpos_cursive(head,start,kind,lookupname,exitanchors,sequence)
local alreadydone=cursonce and getattr(start,a_cursbase)
if not alreadydone then
local done=false
local startchar=getchar(start)
if marks[startchar] then
if trace_cursive then
logprocess("%s: ignoring cursive for mark %s",pref(kind,lookupname),gref(startchar))
end
else
local nxt=getnext(start)
while not done and nxt and getid(nxt)==glyph_code and getfont(nxt)==currentfont and getsubtype(nxt)<256 do
local nextchar=getchar(nxt)
if marks[nextchar] then
nxt=getnext(nxt)
else
local entryanchors=descriptions[nextchar]
if entryanchors then
entryanchors=entryanchors.anchors
if entryanchors then
entryanchors=entryanchors['centry']
if entryanchors then
local al=anchorlookups[lookupname]
for anchor,entry in next,entryanchors do
if al[anchor] then
local exit=exitanchors[anchor]
if exit then
local dx,dy,bound=setcursive(start,nxt,tfmdata.parameters.factor,rlmode,exit,entry,characters[startchar],characters[nextchar])
if trace_cursive then
logprocess("%s: moving %s to %s cursive (%p,%p) using anchor %s and bound %s in rlmode %s",pref(kind,lookupname),gref(startchar),gref(nextchar),dx,dy,anchor,bound,rlmode)
end
done=true
break
end
end
end
end
end
elseif trace_bugs then
onetimemessage(currentfont,startchar,"no entry anchors",report_fonts)
end
break
end
end
end
return head,start,done
else
if trace_cursive and trace_details then
logprocess("%s, cursive %s is already done",pref(kind,lookupname),gref(getchar(start)),alreadydone)
end
return head,start,false
end
end
function handlers.gpos_single(head,start,kind,lookupname,kerns,sequence)
local startchar=getchar(start)
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 (%p,%p) and correction (%p,%p)",pref(kind,lookupname),gref(startchar),dx,dy,w,h)
end
return head,start,false
end
function handlers.gpos_pair(head,start,kind,lookupname,kerns,sequence)
local snext=getnext(start)
if not snext then
return head,start,false
else
local prev,done=start,false
local factor=tfmdata.parameters.factor
local lookuptype=lookuptypes[lookupname]
while snext and getid(snext)==glyph_code and getfont(snext)==currentfont and getsubtype(snext)<256 do
local nextchar=getchar(snext)
local krn=kerns[nextchar]
if not krn and marks[nextchar] then
prev=snext
snext=getnext(snext)
else
if not krn then
elseif type(krn)=="table" then
if lookuptype=="pair" then
local a,b=krn[2],krn[3]
if a and #a>0 then
local startchar=getchar(start)
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 (%p,%p) and correction (%p,%p)",pref(kind,lookupname),gref(startchar),gref(nextchar),x,y,w,h)
end
end
if b and #b>0 then
local startchar=getchar(start)
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 (%p,%p) and correction (%p,%p)",pref(kind,lookupname),gref(startchar),gref(nextchar),x,y,w,h)
end
end
else
report_process("%s: check this out (old kern stuff)",pref(kind,lookupname))
end
done=true
elseif krn~=0 then
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(getchar(prev)),gref(nextchar))
end
done=true
end
break
end
end
return head,start,done
end
end
local chainmores={}
local chainprocs={}
local function logprocess(...)
if trace_steps then
registermessage(...)
end
report_subchain(...)
end
local logwarning=report_subchain
local function logprocess(...)
if trace_steps then
registermessage(...)
end
report_chain(...)
end
local logwarning=report_chain
function chainprocs.chainsub(head,start,stop,kind,chainname,currentcontext,lookuphash,lookuplist,chainlookupname)
logwarning("%s: a direct call to chainsub cannot happen",cref(kind,chainname,chainlookupname))
return head,start,false
end
function chainmores.chainsub(head,start,stop,kind,chainname,currentcontext,lookuphash,lookuplist,chainlookupname,n)
logprocess("%s: a direct call to chainsub cannot happen",cref(kind,chainname,chainlookupname))
return head,start,false
end
function chainprocs.reversesub(head,start,stop,kind,chainname,currentcontext,lookuphash,replacements)
local char=getchar(start)
local replacement=replacements[char]
if replacement then
if trace_singles then
logprocess("%s: single reverse replacement of %s by %s",cref(kind,chainname),gref(char),gref(replacement))
end
setfield(start,"char",replacement)
return head,start,true
else
return head,start,false
end
end
function chainprocs.gsub_single(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex)
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 getid(current)==glyph_code then
local currentchar=getchar(current)
local lookupname=subtables[1]
local replacement=lookuphash[lookupname]
if not replacement then
if trace_bugs then
logwarning("%s: no single hits",cref(kind,chainname,chainlookupname,lookupname,chainindex))
end
else
replacement=replacement[currentchar]
if not replacement or replacement=="" then
if trace_bugs then
logwarning("%s: no single for %s",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(currentchar))
end
else
if trace_singles then
logprocess("%s: replacing single %s by %s",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(currentchar),gref(replacement))
end
setfield(current,"char",replacement)
end
end
return head,start,true
elseif current==stop then
break
else
current=getnext(current)
end
end
return head,start,false
end
chainmores.gsub_single=chainprocs.gsub_single
function chainprocs.gsub_multiple(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname)
local startchar=getchar(start)
local subtables=currentlookup.subtables
local lookupname=subtables[1]
local replacements=lookuphash[lookupname]
if not replacements then
if trace_bugs then
logwarning("%s: no multiple hits",cref(kind,chainname,chainlookupname,lookupname))
end
else
replacements=replacements[startchar]
if not replacements or replacement=="" then
if trace_bugs then
logwarning("%s: no multiple for %s",cref(kind,chainname,chainlookupname,lookupname),gref(startchar))
end
else
if trace_multiples then
logprocess("%s: replacing %s by multiple characters %s",cref(kind,chainname,chainlookupname,lookupname),gref(startchar),gref(replacements))
end
return multiple_glyphs(head,start,replacements,currentlookup.flags[1])
end
end
return head,start,false
end
chainmores.gsub_multiple=chainprocs.gsub_multiple
function chainprocs.gsub_alternate(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname)
local current=start
local subtables=currentlookup.subtables
local value=featurevalue==true and tfmdata.shared.features[kind] or featurevalue
while current do
if getid(current)==glyph_code then
local currentchar=getchar(current)
local lookupname=subtables[1]
local alternatives=lookuphash[lookupname]
if not alternatives then
if trace_bugs then
logwarning("%s: no alternative hit",cref(kind,chainname,chainlookupname,lookupname))
end
else
alternatives=alternatives[currentchar]
if alternatives then
local choice,comment=get_alternative_glyph(current,alternatives,value,trace_alternatives)
if choice then
if trace_alternatives then
logprocess("%s: replacing %s by alternative %a to %s, %s",cref(kind,chainname,chainlookupname,lookupname),gref(char),choice,gref(choice),comment)
end
setfield(start,"char",choice)
else
if trace_alternatives then
logwarning("%s: no variant %a for %s, %s",cref(kind,chainname,chainlookupname,lookupname),value,gref(char),comment)
end
end
elseif trace_bugs then
logwarning("%s: no alternative for %s, %s",cref(kind,chainname,chainlookupname,lookupname),gref(currentchar),comment)
end
end
return head,start,true
elseif current==stop then
break
else
current=getnext(current)
end
end
return head,start,false
end
chainmores.gsub_alternate=chainprocs.gsub_alternate
function chainprocs.gsub_ligature(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex)
local startchar=getchar(start)
local subtables=currentlookup.subtables
local lookupname=subtables[1]
local ligatures=lookuphash[lookupname]
if not ligatures then
if trace_bugs then
logwarning("%s: no ligature hits",cref(kind,chainname,chainlookupname,lookupname,chainindex))
end
else
ligatures=ligatures[startchar]
if not ligatures then
if trace_bugs then
logwarning("%s: no ligatures starting with %s",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar))
end
else
local s=getnext(start)
local discfound=false
local last=stop
local nofreplacements=0
local skipmark=currentlookup.flags[1]
while s do
local id=getid(s)
if id==disc_code then
s=getnext(s)
discfound=true
else
local schar=getchar(s)
if skipmark and marks[schar] then
s=getnext(s)
else
local lg=ligatures[schar]
if lg then
ligatures,last,nofreplacements=lg,s,nofreplacements+1
if s==stop then
break
else
s=getnext(s)
end
else
break
end
end
end
end
local l2=ligatures.ligature
if l2 then
if chainindex then
stop=last
end
if trace_ligatures then
if start==stop then
logprocess("%s: replacing character %s by ligature %s case 3",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar),gref(l2))
else
logprocess("%s: replacing character %s upto %s by ligature %s case 4",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar),gref(getchar(stop)),gref(l2))
end
end
head,start=toligature(kind,lookupname,head,start,stop,l2,currentlookup.flags[1],discfound)
return head,start,true,nofreplacements
elseif trace_bugs then
if start==stop then
logwarning("%s: replacing character %s by ligature fails",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar))
else
logwarning("%s: replacing character %s upto %s by ligature fails",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar),gref(getchar(stop)))
end
end
end
end
return head,start,false,0
end
chainmores.gsub_ligature=chainprocs.gsub_ligature
function chainprocs.gpos_mark2base(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname)
local markchar=getchar(start)
if marks[markchar] then
local subtables=currentlookup.subtables
local lookupname=subtables[1]
local markanchors=lookuphash[lookupname]
if markanchors then
markanchors=markanchors[markchar]
end
if markanchors then
local base=getprev(start)
if base and getid(base)==glyph_code and getfont(base)==currentfont and getsubtype(base)<256 then
local basechar=getchar(base)
if marks[basechar] then
while true do
base=getprev(base)
if base and getid(base)==glyph_code and getfont(base)==currentfont and getsubtype(base)<256 then
basechar=getchar(base)
if not marks[basechar] then
break
end
else
if trace_bugs then
logwarning("%s: no base for mark %s",pref(kind,lookupname),gref(markchar))
end
return head,start,false
end
end
end
local baseanchors=descriptions[basechar].anchors
if baseanchors then
local baseanchors=baseanchors['basechar']
if baseanchors then
local al=anchorlookups[lookupname]
for anchor,ba in next,baseanchors do
if al[anchor] then
local ma=markanchors[anchor]
if ma then
local dx,dy,bound=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 => (%p,%p)",
cref(kind,chainname,chainlookupname,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy)
end
return head,start,true
end
end
end
if trace_bugs then
logwarning("%s, no matching anchors for mark %s and base %s",cref(kind,chainname,chainlookupname,lookupname),gref(markchar),gref(basechar))
end
end
end
elseif trace_bugs then
logwarning("%s: prev node is no char",cref(kind,chainname,chainlookupname,lookupname))
end
elseif trace_bugs then
logwarning("%s: mark %s has no anchors",cref(kind,chainname,chainlookupname,lookupname),gref(markchar))
end
elseif trace_bugs then
logwarning("%s: mark %s is no mark",cref(kind,chainname,chainlookupname),gref(markchar))
end
return head,start,false
end
function chainprocs.gpos_mark2ligature(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname)
local markchar=getchar(start)
if marks[markchar] then
local subtables=currentlookup.subtables
local lookupname=subtables[1]
local markanchors=lookuphash[lookupname]
if markanchors then
markanchors=markanchors[markchar]
end
if markanchors then
local base=getprev(start)
if base and getid(base)==glyph_code and getfont(base)==currentfont and getsubtype(base)<256 then
local basechar=getchar(base)
if marks[basechar] then
while true do
base=getprev(base)
if base and getid(base)==glyph_code and getfont(base)==currentfont and getsubtype(base)<256 then
basechar=getchar(base)
if not marks[basechar] then
break
end
else
if trace_bugs then
logwarning("%s: no base for mark %s",cref(kind,chainname,chainlookupname,lookupname),markchar)
end
return head,start,false
end
end
end
local index=getattr(start,a_ligacomp)
local baseanchors=descriptions[basechar].anchors
if baseanchors then
local baseanchors=baseanchors['baselig']
if baseanchors then
local al=anchorlookups[lookupname]
for anchor,ba in next,baseanchors do
if al[anchor] then
local ma=markanchors[anchor]
if ma then
ba=ba[index]
if ba then
local dx,dy,bound=setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma)
if trace_marks then
logprocess("%s, anchor %s, bound %s: anchoring mark %s to baselig %s at index %s => (%p,%p)",
cref(kind,chainname,chainlookupname,lookupname),anchor,a or bound,gref(markchar),gref(basechar),index,dx,dy)
end
return head,start,true
end
end
end
end
if trace_bugs then
logwarning("%s: no matching anchors for mark %s and baselig %s",cref(kind,chainname,chainlookupname,lookupname),gref(markchar),gref(basechar))
end
end
end
elseif trace_bugs then
logwarning("feature %s, lookup %s: prev node is no char",kind,lookupname)
end
elseif trace_bugs then
logwarning("%s: mark %s has no anchors",cref(kind,chainname,chainlookupname,lookupname),gref(markchar))
end
elseif trace_bugs then
logwarning("%s: mark %s is no mark",cref(kind,chainname,chainlookupname),gref(markchar))
end
return head,start,false
end
function chainprocs.gpos_mark2mark(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname)
local markchar=getchar(start)
if marks[markchar] then
local subtables=currentlookup.subtables
local lookupname=subtables[1]
local markanchors=lookuphash[lookupname]
if markanchors then
markanchors=markanchors[markchar]
end
if markanchors then
local base=getprev(start)
local slc=getattr(start,a_ligacomp)
if slc then
while base do
local blc=getattr(base,a_ligacomp)
if blc and blc~=slc then
base=getprev(base)
else
break
end
end
end
if base and getid(base)==glyph_code and getfont(base)==currentfont and getsubtype(base)<256 then
local basechar=getchar(base)
local baseanchors=descriptions[basechar].anchors
if baseanchors then
baseanchors=baseanchors['basemark']
if baseanchors then
local al=anchorlookups[lookupname]
for anchor,ba in next,baseanchors do
if al[anchor] then
local ma=markanchors[anchor]
if ma then
local dx,dy,bound=setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma,true)
if trace_marks then
logprocess("%s, anchor %s, bound %s: anchoring mark %s to basemark %s => (%p,%p)",
cref(kind,chainname,chainlookupname,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy)
end
return head,start,true
end
end
end
if trace_bugs then
logwarning("%s: no matching anchors for mark %s and basemark %s",gref(kind,chainname,chainlookupname,lookupname),gref(markchar),gref(basechar))
end
end
end
elseif trace_bugs then
logwarning("%s: prev node is no mark",cref(kind,chainname,chainlookupname,lookupname))
end
elseif trace_bugs then
logwarning("%s: mark %s has no anchors",cref(kind,chainname,chainlookupname,lookupname),gref(markchar))
end
elseif trace_bugs then
logwarning("%s: mark %s is no mark",cref(kind,chainname,chainlookupname),gref(markchar))
end
return head,start,false
end
function chainprocs.gpos_cursive(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname)
local alreadydone=cursonce and getattr(start,a_cursbase)
if not alreadydone then
local startchar=getchar(start)
local subtables=currentlookup.subtables
local lookupname=subtables[1]
local exitanchors=lookuphash[lookupname]
if exitanchors then
exitanchors=exitanchors[startchar]
end
if exitanchors then
local done=false
if marks[startchar] then
if trace_cursive then
logprocess("%s: ignoring cursive for mark %s",pref(kind,lookupname),gref(startchar))
end
else
local nxt=getnext(start)
while not done and nxt and getid(nxt)==glyph_code and getfont(nxt)==currentfont and getsubtype(nxt)<256 do
local nextchar=getchar(nxt)
if marks[nextchar] then
nxt=getnext(nxt)
else
local entryanchors=descriptions[nextchar]
if entryanchors then
entryanchors=entryanchors.anchors
if entryanchors then
entryanchors=entryanchors['centry']
if entryanchors then
local al=anchorlookups[lookupname]
for anchor,entry in next,entryanchors do
if al[anchor] then
local exit=exitanchors[anchor]
if exit then
local dx,dy,bound=setcursive(start,nxt,tfmdata.parameters.factor,rlmode,exit,entry,characters[startchar],characters[nextchar])
if trace_cursive then
logprocess("%s: moving %s to %s cursive (%p,%p) using anchor %s and bound %s in rlmode %s",pref(kind,lookupname),gref(startchar),gref(nextchar),dx,dy,anchor,bound,rlmode)
end
done=true
break
end
end
end
end
end
elseif trace_bugs then
onetimemessage(currentfont,startchar,"no entry anchors",report_fonts)
end
break
end
end
end
return head,start,done
else
if trace_cursive and trace_details then
logprocess("%s, cursive %s is already done",pref(kind,lookupname),gref(getchar(start)),alreadydone)
end
return head,start,false
end
end
return head,start,false
end
function chainprocs.gpos_single(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex,sequence)
local startchar=getchar(start)
local subtables=currentlookup.subtables
local lookupname=subtables[1]
local kerns=lookuphash[lookupname]
if kerns then
kerns=kerns[startchar]
if kerns then
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 (%p,%p) and correction (%p,%p)",cref(kind,chainname,chainlookupname),gref(startchar),dx,dy,w,h)
end
end
end
return head,start,false
end
chainmores.gpos_single=chainprocs.gpos_single
function chainprocs.gpos_pair(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex,sequence)
local snext=getnext(start)
if snext then
local startchar=getchar(start)
local subtables=currentlookup.subtables
local lookupname=subtables[1]
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.parameters.factor
while snext and getid(snext)==glyph_code and getfont(snext)==currentfont and getsubtype(snext)<256 do
local nextchar=getchar(snext)
local krn=kerns[nextchar]
if not krn and marks[nextchar] then
prev=snext
snext=getnext(snext)
else
if not krn then
elseif type(krn)=="table" then
if lookuptype=="pair" then
local a,b=krn[2],krn[3]
if a and #a>0 then
local startchar=getchar(start)
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 (%p,%p) and correction (%p,%p)",cref(kind,chainname,chainlookupname),gref(startchar),gref(nextchar),x,y,w,h)
end
end
if b and #b>0 then
local startchar=getchar(start)
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 (%p,%p) and correction (%p,%p)",cref(kind,chainname,chainlookupname),gref(startchar),gref(nextchar),x,y,w,h)
end
end
else
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=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(getchar(prev)),gref(nextchar))
end
end
if b and b~=0 then
logwarning("%s: ignoring second kern xoff %s",cref(kind,chainname,chainlookupname),b*factor)
end
end
done=true
elseif krn~=0 then
local k=setkern(snext,factor,rlmode,krn)
if trace_kerns then
logprocess("%s: inserting kern %s between %s and %s",cref(kind,chainname,chainlookupname),k,gref(getchar(prev)),gref(nextchar))
end
done=true
end
break
end
end
return head,start,done
end
end
end
return head,start,false
end
chainmores.gpos_pair=chainprocs.gpos_pair
local function show_skip(kind,chainname,char,ck,class)
if ck[9] then
logwarning("%s: skipping char %s, class %a, rule %a, lookuptype %a, %a => %a",cref(kind,chainname),gref(char),class,ck[1],ck[2],ck[9],ck[10])
else
logwarning("%s: skipping char %s, class %a, rule %a, lookuptype %a",cref(kind,chainname),gref(char),class,ck[1],ck[2])
end
end
local quit_on_no_replacement=true
directives.register("otf.chain.quitonnoreplacement",function(value)
quit_on_no_replacement=value
end)
local function normal_handle_contextchain(head,start,kind,chainname,contexts,sequence,lookuphash)
local flags=sequence.flags
local done=false
local skipmark=flags[1]
local skipligature=flags[2]
local skipbase=flags[3]
local someskip=skipmark or skipligature or skipbase
local markclass=sequence.markclass
local skipped=false
for k=1,#contexts do
local match=true
local current=start
local last=start
local ck=contexts[k]
local seq=ck[3]
local s=#seq
if s==1 then
match=getid(current)==glyph_code and getfont(current)==currentfont and getsubtype(current)<256 and seq[1][getchar(current)]
else
local f,l=ck[4],ck[5]
if f==1 and f==l then
else
if f==l then
else
local n=f+1
last=getnext(last)
while n<=l do
if last then
local id=getid(last)
if id==glyph_code then
if getfont(last)==currentfont and getsubtype(last)<256 then
local char=getchar(last)
local ccd=descriptions[char]
if ccd then
local class=ccd.class
if class==skipmark or class==skipligature or class==skipbase or (markclass and class=="mark" and not markclass[char]) then
skipped=true
if trace_skips then
show_skip(kind,chainname,char,ck,class)
end
last=getnext(last)
elseif seq[n][char] then
if n<l then
last=getnext(last)
end
n=n+1
else
match=false
break
end
else
match=false
break
end
else
match=false
break
end
elseif id==disc_code then
last=getnext(last)
else
match=false
break
end
else
match=false
break
end
end
end
end
if match and f>1 then
local prev=getprev(start)
if prev then
local n=f-1
while n>=1 do
if prev then
local id=getid(prev)
if id==glyph_code then
if getfont(prev)==currentfont and getsubtype(prev)<256 then
local char=getchar(prev)
local ccd=descriptions[char]
if ccd then
local class=ccd.class
if class==skipmark or class==skipligature or class==skipbase or (markclass and class=="mark" and not markclass[char]) then
skipped=true
if trace_skips then
show_skip(kind,chainname,char,ck,class)
end
elseif seq[n][char] then
n=n -1
else
match=false
break
end
else
match=false
break
end
else
match=false
break
end
elseif id==disc_code then
elseif seq[n][32] then
n=n -1
else
match=false
break
end
prev=getprev(prev)
elseif seq[n][32] then
n=n -1
else
match=false
break
end
end
elseif f==2 then
match=seq[1][32]
else
for n=f-1,1 do
if not seq[n][32] then
match=false
break
end
end
end
end
if match and s>l then
local current=last and getnext(last)
if current then
local n=l+1
while n<=s do
if current then
local id=getid(current)
if id==glyph_code then
if getfont(current)==currentfont and getsubtype(current)<256 then
local char=getchar(current)
local ccd=descriptions[char]
if ccd then
local class=ccd.class
if class==skipmark or class==skipligature or class==skipbase or (markclass and class=="mark" and not markclass[char]) then
skipped=true
if trace_skips then
show_skip(kind,chainname,char,ck,class)
end
elseif seq[n][char] then
n=n+1
else
match=false
break
end
else
match=false
break
end
else
match=false
break
end
elseif id==disc_code then
elseif seq[n][32] then
n=n+1
else
match=false
break
end
current=getnext(current)
elseif seq[n][32] then
n=n+1
else
match=false
break
end
end
elseif s-l==1 then
match=seq[s][32]
else
for n=l+1,s do
if not seq[n][32] then
match=false
break
end
end
end
end
end
if match then
if trace_contexts then
local rule,lookuptype,f,l=ck[1],ck[2],ck[4],ck[5]
local char=getchar(start)
if ck[9] then
logwarning("%s: rule %s matches at char %s for (%s,%s,%s) chars, lookuptype %a, %a => %a",
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 %a",
cref(kind,chainname),rule,gref(char),f-1,l-f+1,s-l,lookuptype)
end
end
local chainlookups=ck[6]
if chainlookups then
local nofchainlookups=#chainlookups
if nofchainlookups==1 then
local chainlookupname=chainlookups[1]
local chainlookup=lookuptable[chainlookupname]
if chainlookup then
local cp=chainprocs[chainlookup.type]
if cp then
local ok
head,start,ok=cp(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence)
if ok then
done=true
end
else
logprocess("%s: %s is not yet supported",cref(kind,chainname,chainlookupname),chainlookup.type)
end
else
logprocess("%s is not yet supported",cref(kind,chainname,chainlookupname))
end
else
local i=1
repeat
if skipped then
while true do
local char=getchar(start)
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=getnext(start)
else
break
end
else
break
end
end
end
local chainlookupname=chainlookups[i]
local chainlookup=lookuptable[chainlookupname]
if not chainlookup then
i=i+1
else
local cp=chainmores[chainlookup.type]
if not cp then
logprocess("%s: %s is not yet supported",cref(kind,chainname,chainlookupname),chainlookup.type)
i=i+1
else
local ok,n
head,start,ok,n=cp(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,i,sequence)
if ok then
done=true
i=i+(n or 1)
else
i=i+1
end
end
end
if start then
start=getnext(start)
else
end
until i>nofchainlookups
end
else
local replacements=ck[7]
if replacements then
head,start,done=chainprocs.reversesub(head,start,last,kind,chainname,ck,lookuphash,replacements)
else
done=quit_on_no_replacement
if trace_contexts then
logprocess("%s: skipping match",cref(kind,chainname))
end
end
end
end
end
return head,start,done
end
local verbose_handle_contextchain=function(font,...)
logwarning("no verbose handler installed, reverting to 'normal'")
otf.setcontextchain()
return normal_handle_contextchain(...)
end
otf.chainhandlers={
normal=normal_handle_contextchain,
verbose=verbose_handle_contextchain,
}
function otf.setcontextchain(method)
if not method or method=="normal" or not otf.chainhandlers[method] then
if handlers.contextchain then
logwarning("installing normal contextchain handler")
end
handlers.contextchain=normal_handle_contextchain
else
logwarning("installing contextchain handler %a",method)
local handler=otf.chainhandlers[method]
handlers.contextchain=function(...)
return handler(currentfont,...)
end
end
handlers.gsub_context=handlers.contextchain
handlers.gsub_contextchain=handlers.contextchain
handlers.gsub_reversecontextchain=handlers.contextchain
handlers.gpos_contextchain=handlers.contextchain
handlers.gpos_context=handlers.contextchain
end
otf.setcontextchain()
local missing={}
local function logprocess(...)
if trace_steps then
registermessage(...)
end
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 %a, type %a, font %a, name %a",lookup,typ,currentfont,tfmdata.properties.fullname)
end
end
local resolved={}
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)
local autofeatures=fonts.analyzers.features
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,autofeatures[kind] or false,sequence.chain or 0,kind,sequence }
end
end
end
end
return false
end
function otf.dataset(tfmdata,font)
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
local sequences=tfmdata.resources.sequences
for s=1,#sequences do
local v=enabled and initialize(sequences[s],script,language,enabled)
if v then
rl[#rl+1]=v
end
end
end
return rl
end
local function featuresprocessor(head,font,attr)
local lookuphash=lookuphashes[font]
if not lookuphash then
return head,false
end
head=tonut(head)
if trace_steps then
checkstep(head)
end
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,font,attr)
local dirstack={}
for s=1,#datasets do
local dataset=datasets[s]
featurevalue=dataset[1]
local sequence=dataset[5]
local rlparmode=0
local topstack=0
local success=false
local attribute=dataset[2]
local chain=dataset[3]
local typ=sequence.type
local subtables=sequence.subtables
if chain<0 then
local handler=handlers[typ]
local start=find_node_tail(head)
while start do
local id=getid(start)
if id==glyph_code then
if getfont(start)==font and getsubtype(start)<256 then
local a=getattr(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[getchar(start)]
if lookupmatch then
head,start,success=handler(head,start,dataset[4],lookupname,lookupmatch,sequence,lookuphash,i)
if success then
break
end
end
else
report_missing_cache(typ,lookupname)
end
end
if start then start=getprev(start) end
else
start=getprev(start)
end
else
start=getprev(start)
end
else
start=getprev(start)
end
end
else
local handler=handlers[typ]
local ns=#subtables
local start=head
rlmode=0
if ns==1 then
local lookupname=subtables[1]
local lookupcache=lookuphash[lookupname]
if not lookupcache then
report_missing_cache(typ,lookupname)
else
local function subrun(start)
local head=start
local done=false
while start do
local id=getid(start)
if id==glyph_code and getfont(start)==font and getsubtype(start)<256 then
local a=getattr(start,0)
if a then
a=(a==attr) and (not attribute or getattr(start,a_state)==attribute)
else
a=not attribute or getattr(start,a_state)==attribute
end
if a then
local lookupmatch=lookupcache[getchar(start)]
if lookupmatch then
local ok
head,start,ok=handler(head,start,dataset[4],lookupname,lookupmatch,sequence,lookuphash,1)
if ok then
done=true
end
end
if start then start=getnext(start) end
else
start=getnext(start)
end
else
start=getnext(start)
end
end
if done then
success=true
return head
end
end
while start do
local id=getid(start)
if id==glyph_code then
if getfont(start)==font and getsubtype(start)<256 then
local a=getattr(start,0)
if a then
a=(a==attr) and (not attribute or getattr(start,a_state)==attribute)
else
a=not attribute or getattr(start,a_state)==attribute
end
if a then
local lookupmatch=lookupcache[getchar(start)]
if lookupmatch then
local ok
head,start,ok=handler(head,start,dataset[4],lookupname,lookupmatch,sequence,lookuphash,1)
if ok then
success=true
end
end
if start then start=getnext(start) end
else
start=getnext(start)
end
else
start=getnext(start)
end
elseif id==disc_code then
if getsubtype(start)==discretionary_code then
local pre=getfield(start,"pre")
if pre then
local new=subrun(pre)
if new then setfield(start,"pre",new) end
end
local post=getfield(start,"post")
if post then
local new=subrun(post)
if new then setfield(start,"post",new) end
end
local replace=getfield(start,"replace")
if replace then
local new=subrun(replace)
if new then setfield(start,"replace",new) end
end
end
start=getnext(start)
elseif id==whatsit_code then
local subtype=getsubtype(start)
if subtype==dir_code then
local dir=getfield(start,"dir")
if dir=="+TRT" or dir=="+TLT" then
topstack=topstack+1
dirstack[topstack]=dir
elseif dir=="-TRT" or dir=="-TLT" then
topstack=topstack-1
end
local newdir=dirstack[topstack]
if newdir=="+TRT" then
rlmode=-1
elseif newdir=="+TLT" then
rlmode=1
else
rlmode=rlparmode
end
if trace_directions then
report_process("directions after txtdir %a: parmode %a, txtmode %a, # stack %a, new dir %a",dir,rlparmode,rlmode,topstack,newdir)
end
elseif subtype==localpar_code then
local dir=getfield(start,"dir")
if dir=="TRT" then
rlparmode=-1
elseif dir=="TLT" then
rlparmode=1
else
rlparmode=0
end
rlmode=rlparmode
if trace_directions then
report_process("directions after pardir %a: parmode %a, txtmode %a",dir,rlparmode,rlmode)
end
end
start=getnext(start)
elseif id==math_code then
start=getnext(end_of_math(start))
else
start=getnext(start)
end
end
end
else
local function subrun(start)
local head=start
local done=false
while start do
local id=getid(start)
if id==glyph_code and getfont(start)==font and getsubtype(start)<256 then
local a=getattr(start,0)
if a then
a=(a==attr) and (not attribute or getattr(start,a_state)==attribute)
else
a=not attribute or getattr(start,a_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[getchar(start)]
if lookupmatch then
local ok
head,start,ok=handler(head,start,dataset[4],lookupname,lookupmatch,sequence,lookuphash,i)
if ok then
done=true
break
elseif not start then
break
end
end
else
report_missing_cache(typ,lookupname)
end
end
if start then start=getnext(start) end
else
start=getnext(start)
end
else
start=getnext(start)
end
end
if done then
success=true
return head
end
end
while start do
local id=getid(start)
if id==glyph_code then
if getfont(start)==font and getsubtype(start)<256 then
local a=getattr(start,0)
if a then
a=(a==attr) and (not attribute or getattr(start,a_state)==attribute)
else
a=not attribute or getattr(start,a_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[getchar(start)]
if lookupmatch then
local ok
head,start,ok=handler(head,start,dataset[4],lookupname,lookupmatch,sequence,lookuphash,i)
if ok then
success=true
break
elseif not start then
break
end
end
else
report_missing_cache(typ,lookupname)
end
end
if start then start=getnext(start) end
else
start=getnext(start)
end
else
start=getnext(start)
end
elseif id==disc_code then
if getsubtype(start)==discretionary_code then
local pre=getfield(start,"pre")
if pre then
local new=subrun(pre)
if new then setfield(start,"pre",new) end
end
local post=getfield(start,"post")
if post then
local new=subrun(post)
if new then setfield(start,"post",new) end
end
local replace=getfield(start,"replace")
if replace then
local new=subrun(replace)
if new then setfield(start,"replace",new) end
end
end
start=getnext(start)
elseif id==whatsit_code then
local subtype=getsubtype(start)
if subtype==dir_code then
local dir=getfield(start,"dir")
if dir=="+TRT" or dir=="+TLT" then
topstack=topstack+1
dirstack[topstack]=dir
elseif dir=="-TRT" or dir=="-TLT" then
topstack=topstack-1
end
local newdir=dirstack[topstack]
if newdir=="+TRT" then
rlmode=-1
elseif newdir=="+TLT" then
rlmode=1
else
rlmode=rlparmode
end
if trace_directions then
report_process("directions after txtdir %a: parmode %a, txtmode %a, # stack %a, new dir %a",dir,rlparmode,rlmode,topstack,newdir)
end
elseif subtype==localpar_code then
local dir=getfield(start,"dir")
if dir=="TRT" then
rlparmode=-1
elseif dir=="TLT" then
rlparmode=1
else
rlparmode=0
end
rlmode=rlparmode
if trace_directions then
report_process("directions after pardir %a: parmode %a, txtmode %a",dir,rlparmode,rlmode)
end
end
start=getnext(start)
elseif id==math_code then
start=getnext(end_of_math(start))
else
start=getnext(start)
end
end
end
end
if success then
done=true
end
if trace_steps then
registerstep(head)
end
end
head=tonode(head)
return head,done
end
local function generic(lookupdata,lookupname,unicode,lookuphash)
local target=lookuphash[lookupname]
if target then
target[unicode]=lookupdata
else
lookuphash[lookupname]={ [unicode]=lookupdata }
end
end
local action={
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
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
target.ligature=unicode
end,
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
others={ [paired]=lookupdata }
target[unicode]=others
end
end,
}
local function prepare_lookups(tfmdata)
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
for unicode,character in next,characters do
local description=descriptions[unicode]
if description then
local lookups=description.slookups
if lookups then
for lookupname,lookupdata in next,lookups do
action[lookuptypes[lookupname]](lookupdata,lookupname,unicode,lookuphash)
end
end
local lookups=description.mlookups
if lookups then
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=description.kerns
if list then
for lookup,krn in next,list do
local target=lookuphash[lookup]
if target then
target[unicode]=krn
else
lookuphash[lookup]={ [unicode]=krn }
end
end
end
local list=description.anchors
if list then
for typ,anchors in next,list do
if typ=="mark" or typ=="cexit" then
for name,anchor in next,anchors do
local lookups=anchor_to_lookup[name]
if lookups then
for lookup,_ in next,lookups do
local target=lookuphash[lookup]
if target then
target[unicode]=anchors
else
lookuphash[lookup]={ [unicode]=anchors }
end
end
end
end
end
end
end
end
end
end
local function split(replacement,original)
local result={}
for i=1,#replacement do
result[original[i]]=replacement[i]
end
return result
end
local valid={
coverage={ chainsub=true,chainpos=true,contextsub=true },
reversecoverage={ reversesub=true },
glyphs={ chainsub=true,chainpos=true },
}
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 lookuptype then
local rules=lookupdata.rules
if rules then
local format=lookupdata.format
local validformat=valid[format]
if not validformat then
report_prepare("unsupported format %a",format)
elseif not validformat[lookuptype] then
report_prepare("unsupported format %a, lookuptype %a, lookupname %a",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
if before then
for n=1,#before do
nofsequences=nofsequences+1
sequence[nofsequences]=before[n]
end
end
local start=nofsequences+1
for n=1,#current do
nofsequences=nofsequences+1
sequence[nofsequences]=current[n]
end
local stop=nofsequences
if after then
for n=1,#after do
nofsequences=nofsequences+1
sequence[nofsequences]=after[n]
end
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
else
end
else
report_prepare("missing lookuptype for lookupname %a",lookupname)
end
end
end
end
local function featuresinitializer(tfmdata,value)
if true then
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)
properties.initialized=true
if trace_preparing then
report_prepare("preparation time is %0.3f seconds for %a",os.clock()-starttime,tfmdata.properties.fullname)
end
end
end
end
registerotffeature {
name="features",
description="features",
default=true,
initializers={
position=1,
node=featuresinitializer,
},
processors={
node=featuresprocessor,
}
}
otf.handlers=handlers
end -- closure
do -- begin closure to overcome local limits and interference
if not modules then modules={} end modules ['font-otp']={
version=1.001,
comment="companion to font-otf.lua (packing)",
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 sort,concat=table.sort,table.concat
local sortedhash=table.sortedhash
local trace_packing=false trackers.register("otf.packing",function(v) trace_packing=v end)
local trace_loading=false trackers.register("otf.loading",function(v) trace_loading=v end)
local report_otf=logs.reporter("fonts","otf loading")
fonts=fonts or {}
local handlers=fonts.handlers or {}
fonts.handlers=handlers
local otf=handlers.otf or {}
handlers.otf=otf
local enhancers=otf.enhancers or {}
otf.enhancers=enhancers
local glists=otf.glists or { "gsub","gpos" }
otf.glists=glists
local criterium=1
local threshold=0
local function tabstr_normal(t)
local s={}
local n=0
for k,v in next,t do
n=n+1
if type(v)=="table" then
s[n]=k..">"..tabstr_normal(v)
elseif v==true then
s[n]=k.."+"
elseif v then
s[n]=k.."="..v
else
s[n]=k.."-"
end
end
if n==0 then
return ""
elseif n==1 then
return s[1]
else
sort(s)
return concat(s,",")
end
end
local function tabstr_flat(t)
local s={}
local n=0
for k,v in next,t do
n=n+1
s[n]=k.."="..v
end
if n==0 then
return ""
elseif n==1 then
return s[1]
else
sort(s)
return concat(s,",")
end
end
local function tabstr_mixed(t)
local s={}
local n=#t
if n==0 then
return ""
elseif n==1 then
local k=t[1]
if k==true then
return "++"
elseif k==false then
return "--"
else
return tostring(k)
end
else
for i=1,n do
local k=t[i]
if k==true then
s[i]="++"
elseif k==false then
s[i]="--"
else
s[i]=k
end
end
return concat(s,",")
end
end
local function tabstr_boolean(t)
local s={}
local n=0
for k,v in next,t do
n=n+1
if v then
s[n]=k.."+"
else
s[n]=k.."-"
end
end
if n==0 then
return ""
elseif n==1 then
return s[1]
else
sort(s)
return concat(s,",")
end
end
local function packdata(data)
if data then
local h,t,c={},{},{}
local hh,tt,cc={},{},{}
local nt,ntt=0,0
local function pack_normal(v)
local tag=tabstr_normal(v)
local ht=h[tag]
if ht then
c[ht]=c[ht]+1
return ht
else
nt=nt+1
t[nt]=v
h[tag]=nt
c[nt]=1
return nt
end
end
local function pack_flat(v)
local tag=tabstr_flat(v)
local ht=h[tag]
if ht then
c[ht]=c[ht]+1
return ht
else
nt=nt+1
t[nt]=v
h[tag]=nt
c[nt]=1
return nt
end
end
local function pack_boolean(v)
local tag=tabstr_boolean(v)
local ht=h[tag]
if ht then
c[ht]=c[ht]+1
return ht
else
nt=nt+1
t[nt]=v
h[tag]=nt
c[nt]=1
return nt
end
end
local function pack_indexed(v)
local tag=concat(v," ")
local ht=h[tag]
if ht then
c[ht]=c[ht]+1
return ht
else
nt=nt+1
t[nt]=v
h[tag]=nt
c[nt]=1
return nt
end
end
local function pack_mixed(v)
local tag=tabstr_mixed(v)
local ht=h[tag]
if ht then
c[ht]=c[ht]+1
return ht
else
nt=nt+1
t[nt]=v
h[tag]=nt
c[nt]=1
return nt
end
end
local function pack_final(v)
if c[v]<=criterium then
return t[v]
else
local hv=hh[v]
if hv then
return hv
else
ntt=ntt+1
tt[ntt]=t[v]
hh[v]=ntt
cc[ntt]=c[v]
return ntt
end
end
end
local function success(stage,pass)
if nt==0 then
if trace_loading or trace_packing then
report_otf("pack quality: nothing to pack")
end
return false
elseif nt>=threshold then
local one,two,rest=0,0,0
if pass==1 then
for k,v in next,c do
if v==1 then
one=one+1
elseif v==2 then
two=two+1
else
rest=rest+1
end
end
else
for k,v in next,cc do
if v>20 then
rest=rest+1
elseif v>10 then
two=two+1
else
one=one+1
end
end
data.tables=tt
end
if trace_loading or trace_packing then
report_otf("pack quality: stage %s, pass %s, %s packed, 1-10:%s, 11-20:%s, rest:%s (criterium: %s)",stage,pass,one+two+rest,one,two,rest,criterium)
end
return true
else
if trace_loading or trace_packing then
report_otf("pack quality: stage %s, pass %s, %s packed, aborting pack (threshold: %s)",stage,pass,nt,threshold)
end
return false
end
end
local function packers(pass)
if pass==1 then
return pack_normal,pack_indexed,pack_flat,pack_boolean,pack_mixed
else
return pack_final,pack_final,pack_final,pack_final,pack_final
end
end
local resources=data.resources
local lookuptypes=resources.lookuptypes
for pass=1,2 do
if trace_packing then
report_otf("start packing: stage 1, pass %s",pass)
end
local pack_normal,pack_indexed,pack_flat,pack_boolean,pack_mixed=packers(pass)
for unicode,description in next,data.descriptions do
local boundingbox=description.boundingbox
if boundingbox then
description.boundingbox=pack_indexed(boundingbox)
end
local slookups=description.slookups
if slookups then
for tag,slookup in next,slookups do
local what=lookuptypes[tag]
if what=="pair" then
local t=slookup[2] if t then slookup[2]=pack_indexed(t) end
local t=slookup[3] if t then slookup[3]=pack_indexed(t) end
elseif what~="substitution" then
slookups[tag]=pack_indexed(slookup)
end
end
end
local mlookups=description.mlookups
if mlookups then
for tag,mlookup in next,mlookups do
local what=lookuptypes[tag]
if what=="pair" then
for i=1,#mlookup do
local lookup=mlookup[i]
local t=lookup[2] if t then lookup[2]=pack_indexed(t) end
local t=lookup[3] if t then lookup[3]=pack_indexed(t) end
end
elseif what~="substitution" then
for i=1,#mlookup do
mlookup[i]=pack_indexed(mlookup[i])
end
end
end
end
local kerns=description.kerns
if kerns then
for tag,kern in next,kerns do
kerns[tag]=pack_flat(kern)
end
end
local math=description.math
if math then
local kerns=math.kerns
if kerns then
for tag,kern in next,kerns do
kerns[tag]=pack_normal(kern)
end
end
end
local anchors=description.anchors
if anchors then
for what,anchor in next,anchors do
if what=="baselig" then
for _,a in next,anchor do
for k=1,#a do
a[k]=pack_indexed(a[k])
end
end
else
for k,v in next,anchor do
anchor[k]=pack_indexed(v)
end
end
end
end
local altuni=description.altuni
if altuni then
for i=1,#altuni do
altuni[i]=pack_flat(altuni[i])
end
end
end
local lookups=data.lookups
if lookups then
for _,lookup in next,lookups do
local rules=lookup.rules
if rules then
for i=1,#rules do
local rule=rules[i]
local r=rule.before if r then for i=1,#r do r[i]=pack_boolean(r[i]) end end
local r=rule.after if r then for i=1,#r do r[i]=pack_boolean(r[i]) end end
local r=rule.current if r then for i=1,#r do r[i]=pack_boolean(r[i]) end end
local r=rule.replacements if r then rule.replacements=pack_flat (r) end
local r=rule.lookups if r then rule.lookups=pack_indexed(r) end
end
end
end
end
local anchor_to_lookup=resources.anchor_to_lookup
if anchor_to_lookup then
for anchor,lookup in next,anchor_to_lookup do
anchor_to_lookup[anchor]=pack_normal(lookup)
end
end
local lookup_to_anchor=resources.lookup_to_anchor
if lookup_to_anchor then
for lookup,anchor in next,lookup_to_anchor do
lookup_to_anchor[lookup]=pack_normal(anchor)
end
end
local sequences=resources.sequences
if sequences then
for feature,sequence in next,sequences do
local flags=sequence.flags
if flags then
sequence.flags=pack_normal(flags)
end
local subtables=sequence.subtables
if subtables then
sequence.subtables=pack_normal(subtables)
end
local features=sequence.features
if features then
for script,feature in next,features do
features[script]=pack_normal(feature)
end
end
end
end
local lookups=resources.lookups
if lookups then
for name,lookup in next,lookups do
local flags=lookup.flags
if flags then
lookup.flags=pack_normal(flags)
end
local subtables=lookup.subtables
if subtables then
lookup.subtables=pack_normal(subtables)
end
end
end
local features=resources.features
if features then
for _,what in next,glists do
local list=features[what]
if list then
for feature,spec in next,list do
list[feature]=pack_normal(spec)
end
end
end
end
if not success(1,pass) then
return
end
end
if nt>0 then
for pass=1,2 do
if trace_packing then
report_otf("start packing: stage 2, pass %s",pass)
end
local pack_normal,pack_indexed,pack_flat,pack_boolean,pack_mixed=packers(pass)
for unicode,description in next,data.descriptions do
local kerns=description.kerns
if kerns then
description.kerns=pack_normal(kerns)
end
local math=description.math
if math then
local kerns=math.kerns
if kerns then
math.kerns=pack_normal(kerns)
end
end
local anchors=description.anchors
if anchors then
description.anchors=pack_normal(anchors)
end
local mlookups=description.mlookups
if mlookups then
for tag,mlookup in next,mlookups do
mlookups[tag]=pack_normal(mlookup)
end
end
local altuni=description.altuni
if altuni then
description.altuni=pack_normal(altuni)
end
end
local lookups=data.lookups
if lookups then
for _,lookup in next,lookups do
local rules=lookup.rules
if rules then
for i=1,#rules do
local rule=rules[i]
local r=rule.before if r then rule.before=pack_normal(r) end
local r=rule.after if r then rule.after=pack_normal(r) end
local r=rule.current if r then rule.current=pack_normal(r) end
end
end
end
end
local sequences=resources.sequences
if sequences then
for feature,sequence in next,sequences do
sequence.features=pack_normal(sequence.features)
end
end
if not success(2,pass) then
end
end
for pass=1,2 do
local pack_normal,pack_indexed,pack_flat,pack_boolean,pack_mixed=packers(pass)
for unicode,description in next,data.descriptions do
local slookups=description.slookups
if slookups then
description.slookups=pack_normal(slookups)
end
local mlookups=description.mlookups
if mlookups then
description.mlookups=pack_normal(mlookups)
end
end
end
end
end
end
local unpacked_mt={
__index=function(t,k)
t[k]=false
return k
end
}
local function unpackdata(data)
if data then
local tables=data.tables
if tables then
local resources=data.resources
local lookuptypes=resources.lookuptypes
local unpacked={}
setmetatable(unpacked,unpacked_mt)
for unicode,description in next,data.descriptions do
local tv=tables[description.boundingbox]
if tv then
description.boundingbox=tv
end
local slookups=description.slookups
if slookups then
local tv=tables[slookups]
if tv then
description.slookups=tv
slookups=unpacked[tv]
end
if slookups then
for tag,lookup in next,slookups do
local what=lookuptypes[tag]
if what=="pair" then
local tv=tables[lookup[2]]
if tv then
lookup[2]=tv
end
local tv=tables[lookup[3]]
if tv then
lookup[3]=tv
end
elseif what~="substitution" then
local tv=tables[lookup]
if tv then
slookups[tag]=tv
end
end
end
end
end
local mlookups=description.mlookups
if mlookups then
local tv=tables[mlookups]
if tv then
description.mlookups=tv
mlookups=unpacked[tv]
end
if mlookups then
for tag,list in next,mlookups do
local tv=tables[list]
if tv then
mlookups[tag]=tv
list=unpacked[tv]
end
if list then
local what=lookuptypes[tag]
if what=="pair" then
for i=1,#list do
local lookup=list[i]
local tv=tables[lookup[2]]
if tv then
lookup[2]=tv
end
local tv=tables[lookup[3]]
if tv then
lookup[3]=tv
end
end
elseif what~="substitution" then
for i=1,#list do
local tv=tables[list[i]]
if tv then
list[i]=tv
end
end
end
end
end
end
end
local kerns=description.kerns
if kerns then
local tm=tables[kerns]
if tm then
description.kerns=tm
kerns=unpacked[tm]
end
if kerns then
for k,kern in next,kerns do
local tv=tables[kern]
if tv then
kerns[k]=tv
end
end
end
end
local math=description.math
if math then
local kerns=math.kerns
if kerns then
local tm=tables[kerns]
if tm then
math.kerns=tm
kerns=unpacked[tm]
end
if kerns then
for k,kern in next,kerns do
local tv=tables[kern]
if tv then
kerns[k]=tv
end
end
end
end
end
local anchors=description.anchors
if anchors then
local ta=tables[anchors]
if ta then
description.anchors=ta
anchors=unpacked[ta]
end
if anchors then
for tag,anchor in next,anchors do
if tag=="baselig" then
for _,list in next,anchor do
for i=1,#list do
local tv=tables[list[i]]
if tv then
list[i]=tv
end
end
end
else
for a,data in next,anchor do
local tv=tables[data]
if tv then
anchor[a]=tv
end
end
end
end
end
end
local altuni=description.altuni
if altuni then
local altuni=tables[altuni]
if altuni then
description.altuni=altuni
for i=1,#altuni do
local tv=tables[altuni[i]]
if tv then
altuni[i]=tv
end
end
end
end
end
local lookups=data.lookups
if lookups then
for _,lookup in next,lookups do
local rules=lookup.rules
if rules then
for i=1,#rules do
local rule=rules[i]
local before=rule.before
if before then
local tv=tables[before]
if tv then
rule.before=tv
before=unpacked[tv]
end
if before then
for i=1,#before do
local tv=tables[before[i]]
if tv then
before[i]=tv
end
end
end
end
local after=rule.after
if after then
local tv=tables[after]
if tv then
rule.after=tv
after=unpacked[tv]
end
if after then
for i=1,#after do
local tv=tables[after[i]]
if tv then
after[i]=tv
end
end
end
end
local current=rule.current
if current then
local tv=tables[current]
if tv then
rule.current=tv
current=unpacked[tv]
end
if current then
for i=1,#current do
local tv=tables[current[i]]
if tv then
current[i]=tv
end
end
end
end
local replacements=rule.replacements
if replacements then
local tv=tables[replacements]
if tv then
rule.replacements=tv
end
end
local fore=rule.fore
if fore then
local tv=tables[fore]
if tv then
rule.fore=tv
end
end
local back=rule.back
if back then
local tv=tables[back]
if tv then
rule.back=tv
end
end
local names=rule.names
if names then
local tv=tables[names]
if tv then
rule.names=tv
end
end
local lookups=rule.lookups
if lookups then
local tv=tables[lookups]
if tv then
rule.lookups=tv
end
end
end
end
end
end
local anchor_to_lookup=resources.anchor_to_lookup
if anchor_to_lookup then
for anchor,lookup in next,anchor_to_lookup do
local tv=tables[lookup]
if tv then
anchor_to_lookup[anchor]=tv
end
end
end
local lookup_to_anchor=resources.lookup_to_anchor
if lookup_to_anchor then
for lookup,anchor in next,lookup_to_anchor do
local tv=tables[anchor]
if tv then
lookup_to_anchor[lookup]=tv
end
end
end
local ls=resources.sequences
if ls then
for _,feature in next,ls do
local flags=feature.flags
if flags then
local tv=tables[flags]
if tv then
feature.flags=tv
end
end
local subtables=feature.subtables
if subtables then
local tv=tables[subtables]
if tv then
feature.subtables=tv
end
end
local features=feature.features
if features then
local tv=tables[features]
if tv then
feature.features=tv
features=unpacked[tv]
end
if features then
for script,data in next,features do
local tv=tables[data]
if tv then
features[script]=tv
end
end
end
end
end
end
local lookups=resources.lookups
if lookups then
for _,lookup in next,lookups do
local flags=lookup.flags
if flags then
local tv=tables[flags]
if tv then
lookup.flags=tv
end
end
local subtables=lookup.subtables
if subtables then
local tv=tables[subtables]
if tv then
lookup.subtables=tv
end
end
end
end
local features=resources.features
if features then
for _,what in next,glists do
local feature=features[what]
if feature then
for tag,spec in next,feature do
local tv=tables[spec]
if tv then
feature[tag]=tv
end
end
end
end
end
data.tables=nil
end
end
end
if otf.enhancers.register then
otf.enhancers.register("pack",packdata)
otf.enhancers.register("unpack",unpackdata)
end
otf.enhancers.unpack=unpackdata
end -- closure
do -- begin closure to overcome local limits and interference
if not modules then modules={} end modules ['luatex-fonts-lua']={
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.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 fullname=resolvers.findfile(fullname) or ""
if fullname~="" then
local loader=loadfile(fullname)
loader=loader and loader()
return loader and loader(specification)
end
end
end -- closure
do -- begin closure to overcome local limits and interference
if not modules then modules={} end modules ['font-def']={
version=1.001,
comment="companion to font-ini.mkiv",
author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
copyright="PRAGMA ADE / ConTeXt Development Team",
license="see context related readme files"
}
local format,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 suffixonly,removesuffix=file.suffix,file.removesuffix
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")
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 fontgoodies=fonts.goodies
readers.sequence=allocate { 'otf','ttf','afm','tfm','lua' }
local variants=allocate()
specifiers.variants=variants
definers.methods=definers.methods or {}
local internalized=allocate()
local lastdefined=nil
local loadedfonts=constructors.loadedfonts
local designsizes=constructors.designsizes
local resolvefile=fontgoodies and fontgoodies.filenames and fontgoodies.filenames.resolve or function(s) return s end
local splitter,splitspecifiers=nil,""
local P,C,S,Cc=lpeg.P,lpeg.C,lpeg.S,lpeg.Cc
local left=P("(")
local right=P(")")
local colon=P(":")
local space=P(" ")
definers.defaultlookup="file"
local prefixpattern=P(false)
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)
local name=C((1-sub-specification)^1)
splitter=P((lookup+Cc(""))*name*(sub+Cc(""))*(specification+Cc("")))
end
local function addlookup(str,default)
prefixpattern=prefixpattern+P(str)
end
definers.addlookup=addlookup
addlookup("file")
addlookup("name")
addlookup("spec")
local function getspecification(str)
return lpegmatch(splitter,str or "")
end
definers.getspecification=getspecification
function definers.registersplit(symbol,action,verbosename)
addspecifier(symbol)
variants[symbol]=action
if verbosename then
variants[verbosename]=action
end
end
local function makespecification(specification,lookup,name,sub,method,detail,size)
size=size or 655360
if not lookup or lookup=="" then
lookup=definers.defaultlookup
end
if trace_defining then
report_defining("specification %a, lookup %a, name %a, sub %a, method %a, detail %a",
specification,lookup,name,sub,method,detail)
end
local t={
lookup=lookup,
specification=specification,
size=size,
name=name,
sub=sub,
method=method,
detail=detail,
resolved="",
forced="",
features={},
}
return t
end
definers.makespecification=makespecification
function definers.analyze(specification,size)
local lookup,name,sub,method,detail=getspecification(specification or "")
return makespecification(specification,lookup,name,sub,method,detail,size)
end
definers.resolvers=definers.resolvers or {}
local resolvers=definers.resolvers
function resolvers.file(specification)
local name=resolvefile(specification.name)
local suffix=lower(suffixonly(name))
if fonts.formats[suffix] then
specification.forced=suffix
specification.forcedname=name
specification.name=removesuffix(name)
else
specification.name=name
end
end
function resolvers.name(specification)
local resolve=fonts.names.resolve
if resolve then
local resolved,sub=resolve(specification.name,specification.sub,specification)
if resolved then
specification.resolved=resolved
specification.sub=sub
local suffix=lower(suffixonly(resolved))
if fonts.formats[suffix] then
specification.forced=suffix
specification.forcedname=resolved
specification.name=removesuffix(resolved)
else
specification.name=resolved
end
end
else
resolvers.file(specification)
end
end
function resolvers.spec(specification)
local resolvespec=fonts.names.resolvespec
if resolvespec then
local resolved,sub=resolvespec(specification.name,specification.sub,specification)
if resolved then
specification.resolved=resolved
specification.sub=sub
specification.forced=lower(suffixonly(resolved))
specification.forcedname=resolved
specification.name=removesuffix(resolved)
end
else
resolvers.name(specification)
end
end
function definers.resolve(specification)
if not specification.resolved or specification.resolved=="" then
local r=resolvers[specification.lookup]
if r then
r(specification)
end
end
if specification.forced=="" then
specification.forced=nil
specification.forcedname=nil
end
specification.hash=lower(specification.name..' @ '..constructors.hashfeatures(specification))
if specification.sub and specification.sub~="" then
specification.hash=specification.sub..' @ '..specification.hash
end
return specification
end
function definers.applypostprocessors(tfmdata)
local postprocessors=tfmdata.postprocessors
if postprocessors then
local properties=tfmdata.properties
for i=1,#postprocessors do
local extrahash=postprocessors[i](tfmdata)
if type(extrahash)=="string" and extrahash~="" then
extrahash=gsub(lower(extrahash),"[^a-z]","-")
properties.fullname=format("%s-%s",properties.fullname,extrahash)
end
end
end
return tfmdata
end
local function checkembedding(tfmdata)
local properties=tfmdata.properties
local embedding
if directive_embedall then
embedding="full"
elseif properties and properties.filename and constructors.dontembed[properties.filename] then
embedding="no"
else
embedding="subset"
end
if properties then
properties.embedding=embedding
else
tfmdata.properties={ embedding=embedding }
end
tfmdata.embedding=embedding
end
function definers.loadfont(specification)
local hash=constructors.hashinstance(specification)
local tfmdata=loadedfonts[hash]
if not tfmdata then
local forced=specification.forced or ""
if forced~="" then
local reader=readers[lower(forced)]
tfmdata=reader and reader(specification)
if not tfmdata then
report_defining("forced type %a of %a not found",forced,specification.name)
end
else
local sequence=readers.sequence
for s=1,#sequence do
local reader=sequence[s]
if readers[reader] then
if trace_defining then
report_defining("trying (reader sequence driven) type %a for %a with file %a",reader,specification.name,specification.filename)
end
tfmdata=readers[reader](specification)
if tfmdata then
break
else
specification.filename=nil
end
end
end
end
if tfmdata then
tfmdata=definers.applypostprocessors(tfmdata)
checkembedding(tfmdata)
loadedfonts[hash]=tfmdata
designsizes[specification.hash]=tfmdata.parameters.designsize
end
end
if not tfmdata then
report_defining("font with asked name %a is not found using lookup %a",specification.name,specification.lookup)
end
return tfmdata
end
function constructors.checkvirtualids()
end
function constructors.readanddefine(name,size)
local specification=definers.analyze(name,size)
local method=specification.method
if method and variants[method] then
specification=variants[method](specification)
end
specification=definers.resolve(specification)
local hash=constructors.hashinstance(specification)
local id=definers.registered(hash)
if not id then
local tfmdata=definers.loadfont(specification)
if tfmdata then
tfmdata.properties.hash=hash
constructors.checkvirtualids(tfmdata)
id=font.define(tfmdata)
definers.register(tfmdata,id)
else
id=0
end
end
return fontdata[id],id
end
function definers.current()
return lastdefined
end
function definers.registered(hash)
local id=internalized[hash]
return id,id and fontdata[id]
end
function definers.register(tfmdata,id)
if tfmdata and id then
local hash=tfmdata.properties.hash
if not hash then
report_defining("registering font, id %a, name %a, invalid hash",id,tfmdata.properties.filename or "?")
elseif not internalized[hash] then
internalized[hash]=id
if trace_defining then
report_defining("registering font, id %s, hash %a",id,hash)
end
fontdata[id]=tfmdata
end
end
end
function definers.read(specification,size,id)
statistics.starttiming(fonts)
if type(specification)=="string" then
specification=definers.analyze(specification,size)
end
local method=specification.method
if method and variants[method] then
specification=variants[method](specification)
end
specification=definers.resolve(specification)
local hash=constructors.hashinstance(specification)
local tfmdata=definers.registered(hash)
if tfmdata then
if trace_defining then
report_defining("already hashed: %s",hash)
end
else
tfmdata=definers.loadfont(specification)
if tfmdata then
if trace_defining then
report_defining("loaded and hashed: %s",hash)
end
tfmdata.properties.hash=hash
if id then
definers.register(tfmdata,id)
end
else
if trace_defining then
report_defining("not loaded and hashed: %s",hash)
end
end
end
lastdefined=tfmdata or id
if not tfmdata then
report_defining("unknown font %a, 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 %a, name %a, size %a, bytes %a, encoding %a, fullname %a, filename %a",
properties.format,id,properties.name,parameters.size,properties.encodingbytes,
properties.encodingname,properties.fullname,file.basename(properties.filename))
end
statistics.stoptiming(fonts)
return tfmdata
end
function font.getfont(id)
return fontdata[id]
end
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 ['luatex-font-def']={
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.constructors.namemode="specification"
function fonts.definers.getspecification(str)
return "",str,"",":",str
end
local list={}
local function issome () list.lookup='name' 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 iscrap (s) list.crap=string.lower(s) 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 P,S,R,C=lpeg.P,lpeg.S,lpeg.R,lpeg.C
local spaces=P(" ")^0
local namespec=(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=P("(")*(C(P(1-S("()"))^1)/issub)*P(")")
local option=spaces*(keyvalue+falsevalue+truevalue+somevalue)*spaces
local options=P(":")*spaces*(P(";")^0*option)^0
local pattern=(filename_1+filename_2+fontname_1+fontname_2)*subvalue^0*crapspec^0*options^0
local function colonized(specification)
list={}
lpeg.match(pattern,specification.specification)
list.crap=nil
if list.name then
specification.name=list.name
list.name=nil
end
if list.lookup then
specification.lookup=list.lookup
list.lookup=nil
end
if list.sub then
specification.sub=list.sub
list.sub=nil
end
specification.features.normal=fonts.handlers.otf.features.normalize(list)
return specification
end
fonts.definers.registersplit(":",colonized,"cryptic")
fonts.definers.registersplit("",colonized,"more cryptic")
function fonts.definers.applypostprocessors(tfmdata)
local postprocessors=tfmdata.postprocessors
if postprocessors then
for i=1,#postprocessors do
local extrahash=postprocessors[i](tfmdata)
if type(extrahash)=="string" and extrahash~="" then
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 ['luatex-fonts-ext']={
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 otffeatures=fonts.constructors.newfeatures("otf")
local function initializeitlc(tfmdata,value)
if value then
local parameters=tfmdata.parameters
local italicangle=parameters.italicangle
if italicangle and italicangle~=0 then
local properties=tfmdata.properties
local factor=tonumber(value) or 1
properties.hasitalics=true
properties.autoitalicamount=factor*(parameters.uwidth or 40)/2
end
end
end
otffeatures.register {
name="itlc",
description="italic correction",
initializers={
base=initializeitlc,
node=initializeitlc,
}
}
local function initializeslant(tfmdata,value)
value=tonumber(value)
if not value then
value=0
elseif value>1 then
value=1
elseif value<-1 then
value=-1
end
tfmdata.parameters.slantfactor=value
end
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
elseif value>10 then
value=10
elseif value<-10 then
value=-10
end
tfmdata.parameters.extendfactor=value
end
otffeatures.register {
name="extend",
description="scale glyphs horizontally",
initializers={
base=initializeextend,
node=initializeextend,
}
}
fonts.protrusions=fonts.protrusions or {}
fonts.protrusions.setups=fonts.protrusions.setups or {}
local setups=fonts.protrusions.setups
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.parameters.protrusion={
auto=true,
}
for i,chr in next,tfmdata.characters do
local v,pl,pr=setup[i],nil,nil
if v then
pl,pr=v[1],v[2]
end
if pl and pl~=0 then chr.left_protruding=left*pl*factor end
if pr and pr~=0 then chr.right_protruding=right*pr*factor end
end
end
end
end
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
local function initializeexpansion(tfmdata,value)
if value then
local setup=setups[value]
if setup then
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
chr.expansion_factor=v*factor
else
chr.expansion_factor=factor
end
end
end
end
end
otffeatures.register {
name="expansion",
description="apply hz optimization",
initializers={
base=initializeexpansion,
node=initializeexpansion,
}
}
function fonts.loggers.onetimemessage() end
local byte=string.byte
fonts.expansions.setups['default']={
stretch=2,shrink=2,step=.5,factor=1,
[byte('A')]=0.5,[byte('B')]=0.7,[byte('C')]=0.7,[byte('D')]=0.5,[byte('E')]=0.7,
[byte('F')]=0.7,[byte('G')]=0.5,[byte('H')]=0.7,[byte('K')]=0.7,[byte('M')]=0.7,
[byte('N')]=0.7,[byte('O')]=0.5,[byte('P')]=0.7,[byte('Q')]=0.5,[byte('R')]=0.7,
[byte('S')]=0.7,[byte('U')]=0.7,[byte('W')]=0.7,[byte('Z')]=0.7,
[byte('a')]=0.7,[byte('b')]=0.7,[byte('c')]=0.7,[byte('d')]=0.7,[byte('e')]=0.7,
[byte('g')]=0.7,[byte('h')]=0.7,[byte('k')]=0.7,[byte('m')]=0.7,[byte('n')]=0.7,
[byte('o')]=0.7,[byte('p')]=0.7,[byte('q')]=0.7,[byte('s')]=0.7,[byte('u')]=0.7,
[byte('w')]=0.7,[byte('z')]=0.7,
[byte('2')]=0.7,[byte('3')]=0.7,[byte('6')]=0.7,[byte('8')]=0.7,[byte('9')]=0.7,
}
fonts.protrusions.setups['default']={
factor=1,left=1,right=1,
[0x002C]={ 0,1 },
[0x002E]={ 0,1 },
[0x003A]={ 0,1 },
[0x003B]={ 0,1 },
[0x002D]={ 0,1 },
[0x2013]={ 0,0.50 },
[0x2014]={ 0,0.33 },
[0x3001]={ 0,1 },
[0x3002]={ 0,1 },
[0x060C]={ 0,1 },
[0x061B]={ 0,1 },
[0x06D4]={ 0,1 },
}
fonts.handlers.otf.features.normalize=function(t)
if t.rand then
t.rand="random"
end
return t
end
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
fonts.encodings=fonts.encodings or {}
local reencodings={}
fonts.encodings.reencodings=reencodings
local function specialreencode(tfmdata,value)
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
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
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
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)
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