diff options
Diffstat (limited to 'Master/texmf-dist/tex/luatex/luaotfload/fontloader-reference.lua')
-rw-r--r-- | Master/texmf-dist/tex/luatex/luaotfload/fontloader-reference.lua | 4219 |
1 files changed, 3017 insertions, 1202 deletions
diff --git a/Master/texmf-dist/tex/luatex/luaotfload/fontloader-reference.lua b/Master/texmf-dist/tex/luatex/luaotfload/fontloader-reference.lua index 97859888193..3a4a70c6261 100644 --- a/Master/texmf-dist/tex/luatex/luaotfload/fontloader-reference.lua +++ b/Master/texmf-dist/tex/luatex/luaotfload/fontloader-reference.lua @@ -1,6 +1,6 @@ -- merged file : c:/data/develop/context/sources/luatex-fonts-merged.lua -- parent file : c:/data/develop/context/sources/luatex-fonts.lua --- merge date : 06/15/16 20:18:05 +-- merge date : 01/27/17 14:39:39 do -- begin closure to overcome local limits and interference @@ -964,20 +964,20 @@ local fullstripper=patterns.fullstripper local collapser=patterns.collapser local longtostring=patterns.longtostring function string.strip(str) - return lpegmatch(stripper,str) or "" + return str and lpegmatch(stripper,str) or "" end function string.fullstrip(str) - return lpegmatch(fullstripper,str) or "" + return str and lpegmatch(fullstripper,str) or "" end function string.collapsespaces(str) - return lpegmatch(collapser,str) or "" + return str and lpegmatch(collapser,str) or "" end function string.longtostring(str) - return lpegmatch(longtostring,str) or "" + return str and lpegmatch(longtostring,str) or "" end local pattern=P(" ")^0*P(-1) function string.is_empty(str) - if str=="" then + if not str or str=="" then return true else return lpegmatch(pattern,str) and true or false @@ -1352,7 +1352,7 @@ function table.fromhash(t) end return hsh end -local noquotes,hexify,handle,compact,inline,functions +local noquotes,hexify,handle,compact,inline,functions,metacheck 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', @@ -1422,7 +1422,7 @@ local function do_serialize(root,name,depth,level,indexed) if compact then last=#root for k=1,last do - if root[k]==nil then + if rawget(root,k)==nil then last=k-1 break end @@ -1487,6 +1487,7 @@ local function do_serialize(root,name,depth,level,indexed) else handle(format("%s [%s]=%s,",depth,k and "true" or "false",v)) end + elseif tk~="string" then elseif noquotes and not reserved[k] and lpegmatch(propername,k) then if hexify then handle(format("%s %s=0x%X,",depth,k,v)) @@ -1509,6 +1510,7 @@ local function do_serialize(root,name,depth,level,indexed) end elseif tk=="boolean" then handle(format("%s [%s]=%q,",depth,k and "true" or "false",v)) + elseif tk~="string" then elseif noquotes and not reserved[k] and lpegmatch(propername,k) then handle(format("%s %s=%q,",depth,k,v)) else @@ -1524,6 +1526,7 @@ local function do_serialize(root,name,depth,level,indexed) end elseif tk=="boolean" then handle(format("%s [%s]={},",depth,k and "true" or "false")) + elseif tk~="string" then elseif noquotes and not reserved[k] and lpegmatch(propername,k) then handle(format("%s %s={},",depth,k)) else @@ -1540,6 +1543,7 @@ local function do_serialize(root,name,depth,level,indexed) end elseif tk=="boolean" then handle(format("%s [%s]={ %s },",depth,k and "true" or "false",concat(st,", "))) + elseif tk~="string" then elseif noquotes and not reserved[k] and lpegmatch(propername,k) then handle(format("%s %s={ %s },",depth,k,concat(st,", "))) else @@ -1560,6 +1564,7 @@ local function do_serialize(root,name,depth,level,indexed) end elseif tk=="boolean" then handle(format("%s [%s]=%s,",depth,tostring(k),v and "true" or "false")) + elseif tk~="string" then elseif noquotes and not reserved[k] and lpegmatch(propername,k) then handle(format("%s %s=%s,",depth,k,v and "true" or "false")) else @@ -1576,6 +1581,7 @@ local function do_serialize(root,name,depth,level,indexed) end elseif tk=="boolean" then handle(format("%s [%s]=load(%q),",depth,k and "true" or "false",f)) + elseif tk~="string" then elseif noquotes and not reserved[k] and lpegmatch(propername,k) then handle(format("%s %s=load(%q),",depth,k,f)) else @@ -1591,6 +1597,7 @@ local function do_serialize(root,name,depth,level,indexed) end elseif tk=="boolean" then handle(format("%s [%s]=%q,",depth,k and "true" or "false",tostring(v))) + elseif tk~="string" then elseif noquotes and not reserved[k] and lpegmatch(propername,k) then handle(format("%s %s=%q,",depth,k,tostring(v))) else @@ -1612,6 +1619,7 @@ local function serialize(_handle,root,name,specification) functions=specification.functions compact=specification.compact inline=specification.inline and compact + metacheck=specification.metacheck if functions==nil then functions=true end @@ -1621,6 +1629,9 @@ local function serialize(_handle,root,name,specification) if inline==nil then inline=compact end + if metacheck==nil then + metacheck=true + end else noquotes=false hexify=false @@ -1628,6 +1639,7 @@ local function serialize(_handle,root,name,specification) compact=true inline=true functions=true + metacheck=true end if tname=="string" then if name=="return" then @@ -1651,7 +1663,7 @@ local function serialize(_handle,root,name,specification) handle("t={") end if root then - if getmetatable(root) then + if metacheck and 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 @@ -1727,6 +1739,38 @@ local function flattened(t,f,depth) return f end table.flattened=flattened +local function collapsed(t,f,h) + if f==nil then + f={} + h={} + end + for k=1,#t do + local v=t[k] + if type(v)=="table" then + collapsed(v,f,h) + elseif not h[v] then + f[#f+1]=v + h[v]=true + end + end + return f +end +local function collapsedhash(t,h) + if h==nil then + h={} + end + for k=1,#t do + local v=t[k] + if type(v)=="table" then + collapsedhash(v,h) + else + h[v]=true + end + end + return h +end +table.collapsed=collapsed +table.collapsedhash=collapsedhash local function unnest(t,f) if not f then f={} @@ -1833,6 +1877,12 @@ function table.swapped(t,s) end return n end +function table.hashed(t) + for i=1,#t do + t[t[i]]=i + end + return t +end function table.mirrored(t) local n={} for k,v in next,t do @@ -2006,6 +2056,7 @@ if not modules then modules={} end modules ['l-io']={ license="see context related readme files" } local io=io +local open,flush,write,read=io.open,io.flush,io.write,io.read local byte,find,gsub,format=string.byte,string.find,string.gsub,string.format local concat=table.concat local floor=math.floor @@ -2022,15 +2073,13 @@ local function readall(f) local size=f:seek("end") if size==0 then return "" - elseif size<1024*1024 then - f:seek("set",0) + end + f:seek("set",0) + if size<1024*1024 then 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 + if size>16*1024*1024 then step=16*1024*1024 else step=floor(size/(1024*1024))*1024*1024/8 @@ -2048,7 +2097,7 @@ local function readall(f) end io.readall=readall function io.loaddata(filename,textmode) - local f=io.open(filename,(textmode and 'r') or 'rb') + local f=open(filename,(textmode and 'r') or 'rb') if f then local data=readall(f) f:close() @@ -2057,8 +2106,53 @@ function io.loaddata(filename,textmode) end end end +function io.copydata(source,target,action) + local f=open(source,"rb") + if f then + local g=open(target,"wb") + if g then + local size=f:seek("end") + if size==0 then + else + f:seek("set",0) + if size<1024*1024 then + local data=f:read('*all') + if action then + data=action(data) + end + if data then + g:write(data) + end + else + local step + if size>16*1024*1024 then + step=16*1024*1024 + else + step=floor(size/(1024*1024))*1024*1024/8 + end + while true do + local data=f:read(step) + if data then + if action then + data=action(data) + end + if data then + g:write(data) + end + else + break + end + end + end + end + g:close() + end + f:close() + flush() + end +end function io.savedata(filename,data,joiner) - local f=io.open(filename,"wb") + local f=open(filename,"wb") if f then if type(data)=="table" then f:write(concat(data,joiner or "")) @@ -2068,14 +2162,14 @@ function io.savedata(filename,data,joiner) f:write(data or "") end f:close() - io.flush() + flush() return true else return false end end function io.loadlines(filename,n) - local f=io.open(filename,'r') + local f=open(filename,'r') if not f then elseif n then local lines={} @@ -2101,7 +2195,7 @@ function io.loadlines(filename,n) end end function io.loadchunk(filename,n) - local f=io.open(filename,'rb') + local f=open(filename,'rb') if f then local data=f:read(n or 1024) f:close() @@ -2111,7 +2205,7 @@ function io.loadchunk(filename,n) end end function io.exists(filename) - local f=io.open(filename) + local f=open(filename) if f==nil then return false else @@ -2120,7 +2214,7 @@ function io.exists(filename) end end function io.size(filename) - local f=io.open(filename) + local f=open(filename) if f==nil then return 0 else @@ -2129,11 +2223,11 @@ function io.size(filename) return s end end -function io.noflines(f) +local function noflines(f) if type(f)=="string" then - local f=io.open(filename) + local f=open(filename) if f then - local n=f and io.noflines(f) or 0 + local n=f and noflines(f) or 0 f:close() return n else @@ -2148,6 +2242,7 @@ function io.noflines(f) return n end end +io.noflines=noflines local nextchar={ [ 4]=function(f) return f:read(1,1,1,1) @@ -2225,16 +2320,16 @@ function io.bytes(f,n) end function io.ask(question,default,options) while true do - io.write(question) + write(question) if options then - io.write(format(" [%s]",concat(options,"|"))) + write(format(" [%s]",concat(options,"|"))) end if default then - io.write(format(" [%s]",default)) + write(format(" [%s]",default)) end - io.write(format(" ")) - io.flush() - local answer=io.read() + write(format(" ")) + flush() + local answer=read() answer=gsub(answer,"^%s*(.*)%s*$","%1") if answer=="" and default then return default @@ -2643,13 +2738,15 @@ function file.robustname(str,strict) end end end -file.readdata=io.loaddata -file.savedata=io.savedata +local loaddata=io.loaddata +local savedata=io.savedata +file.readdata=loaddata +file.savedata=savedata function file.copy(oldname,newname) if oldname and newname then - local data=io.loaddata(oldname) + local data=loaddata(oldname) if data and data~="" then - file.savedata(newname,data) + savedata(newname,data) end end end @@ -3409,7 +3506,7 @@ if not modules then modules={} end modules ['util-str']={ 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 format,gsub,rep,sub,find=string.format,string.gsub,string.rep,string.sub,string.find local load,dump=load,string.dump local tonumber,type,tostring=tonumber,type,tostring local unpack,concat=table.unpack,table.concat @@ -3592,6 +3689,25 @@ function number.signed(i) return "-",-i end 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 zero=P("0")^1/"" local plus=P("+")/"" local minus=P("-") @@ -3875,25 +3991,6 @@ 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 @@ -3918,9 +4015,16 @@ end local format_extension=function(extensions,f,name) local extension=extensions[name] or "tostring(%s)" local f=tonumber(f) or 1 + local w=find(extension,"%.%.%.") if f==0 then + if w then + extension=gsub(extension,"%.%.%.","") + end return extension elseif f==1 then + if w then + extension=gsub(extension,"%.%.%.","%%s") + end n=n+1 local a="a"..n return format(extension,a,a) @@ -3928,10 +4032,13 @@ local format_extension=function(extensions,f,name) local a="a"..(n+f+1) return format(extension,a,a) else + if w then + extension=gsub(extension,"%.%.%.",rep("%%s,",f-1).."%%s") + end local t={} for i=1,f do n=n+1 - t[#t+1]="a"..n + t[i]="a"..n end return format(extension,unpack(t)) end @@ -4096,7 +4203,9 @@ if not modules then modules={} end modules ['util-fil']={ license="see context related readme files" } local byte=string.byte -local extract=bit32.extract +local char=string.char +local extract=bit32 and bit32.extract +local floor=math.floor utilities=utilities or {} local files={} utilities.files=files @@ -4115,6 +4224,7 @@ end function files.size(f) return f:seek("end") end +files.getsize=files.size function files.setposition(f,n) if zerobased[f] then f:seek("set",n) @@ -4161,7 +4271,7 @@ end function files.readinteger1(f) local n=byte(f:read(1)) if n>=0x80 then - return n-0xFF-1 + return n-0x100 else return n end @@ -4173,11 +4283,24 @@ function files.readcardinal2(f) local a,b=byte(f:read(2),1,2) return 0x100*a+b end +function files.readcardinal2le(f) + local b,a=byte(f:read(2),1,2) + return 0x100*a+b +end function files.readinteger2(f) local a,b=byte(f:read(2),1,2) local n=0x100*a+b if n>=0x8000 then - return n-0xFFFF-1 + return n-0x10000 + else + return n + end +end +function files.readinteger2le(f) + local b,a=byte(f:read(2),1,2) + local n=0x100*a+b + if n>=0x8000 then + return n-0x10000 else return n end @@ -4186,15 +4309,50 @@ function files.readcardinal3(f) local a,b,c=byte(f:read(3),1,3) return 0x10000*a+0x100*b+c end +function files.readcardinal3le(f) + local c,b,a=byte(f:read(3),1,3) + return 0x10000*a+0x100*b+c +end +function files.readinteger3(f) + local a,b,c=byte(f:read(3),1,3) + local n=0x10000*a+0x100*b+c + if n>=0x80000 then + return n-0x1000000 + else + return n + end +end +function files.readinteger3le(f) + local c,b,a=byte(f:read(3),1,3) + local n=0x10000*a+0x100*b+c + if n>=0x80000 then + return n-0x1000000 + else + return n + end +end function files.readcardinal4(f) local a,b,c,d=byte(f:read(4),1,4) return 0x1000000*a+0x10000*b+0x100*c+d end +function files.readcardinal4le(f) + local d,c,b,a=byte(f:read(4),1,4) + return 0x1000000*a+0x10000*b+0x100*c+d +end function files.readinteger4(f) local a,b,c,d=byte(f:read(4),1,4) local n=0x1000000*a+0x10000*b+0x100*c+d if n>=0x8000000 then - return n-0xFFFFFFFF-1 + return n-0x100000000 + else + return n + end +end +function files.readinteger4le(f) + local d,c,b,a=byte(f:read(4),1,4) + local n=0x1000000*a+0x10000*b+0x100*c+d + if n>=0x8000000 then + return n-0x100000000 else return n end @@ -4203,21 +4361,23 @@ function files.readfixed4(f) local a,b,c,d=byte(f:read(4),1,4) local n=0x100*a+b if n>=0x8000 then - return n-0xFFFF-1+(0x100*c+d)/0xFFFF + return n-0x10000+(0x100*c+d)/0xFFFF else return n+(0x100*c+d)/0xFFFF end end -function files.read2dot14(f) - local a,b=byte(f:read(2),1,2) - local n=0x100*a+b - local m=extract(n,0,30) - if n>0x7FFF then - n=extract(n,30,2) - return m/0x4000-4 - else - n=extract(n,30,2) - return n+m/0x4000 +if extract then + function files.read2dot14(f) + local a,b=byte(f:read(2),1,2) + local n=0x100*a+b + local m=extract(n,0,30) + if n>0x7FFF then + n=extract(n,30,2) + return m/0x4000-4 + else + n=extract(n,30,2) + return n+m/0x4000 + end end end function files.skipshort(f,n) @@ -4226,6 +4386,28 @@ end function files.skiplong(f,n) f:read(4*(n or 1)) end +function files.writecardinal2(f,n) + local a=char(n%256) + n=floor(n/256) + local b=char(n%256) + f:write(b,a) +end +function files.writecardinal4(f,n) + local a=char(n%256) + n=floor(n/256) + local b=char(n%256) + n=floor(n/256) + local c=char(n%256) + n=floor(n/256) + local d=char(n%256) + f:write(d,c,b,a) +end +function files.writestring(f,s) + f:write(char(byte(s,1,#s))) +end +function files.writebyte(f,b) + f:write(char(b)) +end end -- closure @@ -4311,6 +4493,7 @@ local remapper={ cidmap="cid maps", pfb="type1 fonts", afm="afm", + enc="enc files", } function resolvers.findfile(name,fileformat) name=string.gsub(name,"\\","/") @@ -4424,44 +4607,42 @@ 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 not lfs.isfile(lucname) and type(caches.compile)=="function" then - texio.write(string.format("(compiling luc: %s)",lucname)) - data=loadfile(luaname) - if data then - data=data() - end - if data then - caches.compile(data,luaname,lucname) - return data - end - end - if lucname and lfs.isfile(lucname) then +function caches.loaddata(readables,name,writable) + for i=1,#readables do + local path=readables[i] + local loader=false + local luaname,lucname=makefullname(path,name) + if lfs.isfile(lucname) then texio.write(string.format("(load luc: %s)",lucname)) - data=loadfile(lucname) - if data then - data=data() + loader=loadfile(lucname) + end + if not loader and lfs.isfile(luaname) then + local luacrap,lucname=makefullname(writable,name) + texio.write(string.format("(compiling luc: %s)",lucname)) + if lfs.isfile(lucname) then + loader=loadfile(lucname) end - if data then - return data + caches.compile(data,luaname,lucname) + if lfs.isfile(lucname) then + texio.write(string.format("(load luc: %s)",lucname)) + loader=loadfile(lucname) 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 + if not loader then + texio.write(string.format("(load lua: %s)",luaname)) + loader=loadfile(luaname) + else + texio.write(string.format("(loading failed: %s)",luaname)) end end + if loader then + loader=loader() + collectgarbage("step") + return loader + end end + return false end function caches.savedata(path,name,data) local luaname,lucname=makefullname(path,name) @@ -4588,7 +4769,7 @@ 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) + stored=caches.loaddata(container.readables,name,container.writable) 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) @@ -4687,7 +4868,7 @@ end nodes.nodecodes=nodecodes nodes.glyphcodes=glyphcodes nodes.disccodes=disccodes -local free_node=node.free +local flush_node=node.flush_node local remove_node=node.remove local new_node=node.new local traverse_id=node.traverse_id @@ -4707,7 +4888,7 @@ function nodes.remove(head,current,free_too) head,current=remove_node(head,current) if t then if free_too then - free_node(t) + flush_node(t) t=nil else t.next,t.prev=nil,nil @@ -4731,12 +4912,14 @@ nodes.getattr=getfield nodes.setattr=setfield nodes.tostring=node.tostring or tostring nodes.copy=node.copy +nodes.copy_node=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.flush=node.flush_node nodes.free=node.free nodes.insert_after=node.insert_after nodes.insert_before=node.insert_before @@ -4750,7 +4933,6 @@ nodes.vpack=node.vpack nodes.first_glyph=node.first_glyph 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 @@ -4837,9 +5019,12 @@ nuts.insert_before=direct.insert_before nuts.insert_after=direct.insert_after nuts.delete=direct.delete nuts.copy=direct.copy +nuts.copy_node=direct.copy nuts.copy_list=direct.copy_list nuts.tail=direct.tail nuts.flush_list=direct.flush_list +nuts.flush_node=direct.flush_node +nuts.flush=direct.flush nuts.free=direct.free nuts.remove=direct.remove nuts.is_node=direct.is_node @@ -5673,9 +5858,10 @@ if not modules then modules={} end modules ['font-con']={ 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 format,match,lower,gsub,find=string.format,string.match,string.lower,string.gsub,string.find local sort,insert,concat,sortedkeys,serialize,fastcopy=table.sort,table.insert,table.concat,table.sortedkeys,table.serialize,table.fastcopy local derivetable=table.derive +local ioflush=io.flush 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") @@ -5693,89 +5879,6 @@ constructors.version=1.01 constructors.cache=containers.define("fonts","constructors",constructors.version,false) constructors.privateoffset=0xF0000 constructors.cacheintex=true -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() @@ -5821,7 +5924,9 @@ end local unscaled={ ScriptPercentScaleDown=true, ScriptScriptPercentScaleDown=true, - RadicalDegreeBottomRaisePercent=true + RadicalDegreeBottomRaisePercent=true, + NoLimitSupFactor=true, + NoLimitSubFactor=true, } function constructors.assignmathparameters(target,original) local mathparameters=original.mathparameters @@ -5913,6 +6018,23 @@ local function mathkerns(v,vdelta) end return k end +local psfake=0 +local function fixedpsname(psname,fallback) + local usedname=psname + if psname and psname~="" then + if find(psname," ") then + usedname=gsub(psname,"[%s]+","-") + else + end + elseif not fallback or fallback=="" then + psfake=psfake+1 + psname="fakename-"..psfake + else + psname=fallback + usedname=gsub(psname,"[^a-zA-Z0-9]+","-") + end + return usedname,psname~=usedname +end function constructors.scale(tfmdata,specification) local target={} if tonumber(specification) then @@ -5986,14 +6108,12 @@ function constructors.scale(tfmdata,specification) target.cidinfo=properties.cidinfo target.format=properties.format target.cache=constructors.cacheintex and "yes" or "renew" - 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 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 + local psname,psfixed=fixedpsname(psname,fontname or fullname or file.nameonly(filename)) target.fontname=fontname target.fullname=fullname target.filename=filename @@ -6043,10 +6163,14 @@ function constructors.scale(tfmdata,specification) local haskerns=properties.haskerns or properties.mode=="base" local hasligatures=properties.hasligatures or properties.mode=="base" local realdimensions=properties.realdimensions + local writingmode=properties.writingmode or "horizontal" + local identity=properties.identity or "horizontal" if changed and not next(changed) then changed=false end target.type=isvirtual and "virtual" or "real" + target.writingmode=writingmode=="vertical" and "vertical" or "horizontal" + target.identity=identity=="vertical" and "vertical" or "horizontal" 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 @@ -6106,8 +6230,9 @@ function constructors.scale(tfmdata,specification) 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",hasitalics and "enabled" or "disabled") + report_defining("defining tfm, name %a, fullname %a, filename %a, %spsname %a, hscale %a, vscale %a, math %a, italics %a", + name,fullname,filename,psfixed and "(fixed) " or "",psname,hdelta,vdelta, + hasmath and "enabled" or "disabled",hasitalics and "enabled" or "disabled") end constructors.beforecopyingcharacters(target,tfmdata) local sharedkerns={} @@ -6431,6 +6556,8 @@ function constructors.finalize(tfmdata) cidinfo=tfmdata.cidinfo or nil, format=tfmdata.format or "type1", direction=tfmdata.direction or 0, + writingmode=tfmdata.writingmode or "horizontal", + identity=tfmdata.identity or "horizontal", } end if not tfmdata.resources then @@ -6568,134 +6695,232 @@ setmetatableindex(formats,function(t,k) 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 +do + local function setindeed(mode,source,target,group,name,position) + local action=source[mode] + if not action then + return + end + 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 - 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 + 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 - if initializers then - set('initializers',name,where,specification) - end - if processors then - set('processors',name,where,specification) + local source=source[group] + if not source then + report_defining("fatal source error in setting feature %a, group %a",name,group) + os.exit() end - if manipulators then - set('manipulators',name,where,specification) - end - if modechecker then - where.modechecker=modechecker + local position=source.position + setindeed("node",source,target,group,name,position) + setindeed("base",source,target,group,name,position) + setindeed("plug",source,target,group,name,position) + 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 -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 + 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 -end -local newhandler={} -constructors.handlers=newhandler -constructors.newhandler=newhandler -local function setnewhandler(what) - local handler=handlers[what] - if not handler then - handler={} - handlers[what]=handler + local newfeatures={} + constructors.newfeatures=newfeatures + constructors.features=newfeatures + local function setnewfeatures(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={},plug={} }, + processors={ base={},node={},plug={} }, + manipulators={ base={},node={},plug={} }, + } + features.register=function(specification) return register(features,specification) end + handler.features=features + end + return features end - return handler + setmetatable(newfeatures,{ + __call=function(t,k) local v=t[k] return v end, + __index=function(t,k) local v=setnewfeatures(k) t[k]=v return v end, + }) end -setmetatable(newhandler,{ - __call=function(t,k) local v=t[k] return v end, - __index=function(t,k) local v=setnewhandler(k) t[k]=v return v end, -}) -local newfeatures={} -constructors.newfeatures=newfeatures -constructors.features=newfeatures -local function setnewfeatures(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 +do + local newhandler={} + constructors.handlers=newhandler + constructors.newhandler=newhandler + local function setnewhandler(what) + local handler=handlers[what] + if not handler then + handler={} + handlers[what]=handler + end + return handler + end + setmetatable(newhandler,{ + __call=function(t,k) local v=t[k] return v end, + __index=function(t,k) local v=setnewhandler(k) t[k]=v return v end, + }) +end +do + local newenhancer={} + constructors.enhancers=newenhancer + constructors.newenhancer=newenhancer + local function setnewenhancer(format) + local handler=handlers[format] + local enhancers=handler.enhancers + if not enhancers then + local actions=allocate() + local before=allocate() + local after=allocate() + local order=allocate() + local patches={ before=before,after=after } + local trace=false + local report=logs.reporter("fonts",format.." enhancing") + trackers.register(format..".loading",function(v) trace=v end) + local function enhance(name,data,filename,raw) + local enhancer=actions[name] + if enhancer then + if trace then + report("apply enhancement %a to file %a",name,filename) + ioflush() + end + enhancer(data,filename,raw) + else + end + end + local function apply(data,filename,raw) + local basename=file.basename(lower(filename)) + if trace then + report("%s enhancing file %a","start",filename) + end + ioflush() + for e=1,#order do + local enhancer=order[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 then + report("%s enhancing file %a","stop",filename) + end + ioflush() + end + local function register(what,action) + if action then + if actions[what] then + else + order[#order+1]=what + end + actions[what]=action + else + report("bad enhancer %a",what) + end + end + local function patch(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 + enhancers={ + register=register, + apply=apply, + patch=patch, + patches={ register=patch }, + } + handler.enhancers=enhancers + end + return enhancers end - return features + setmetatable(newenhancer,{ + __call=function(t,k) local v=t[k] return v end, + __index=function(t,k) local v=setnewenhancer(k) t[k]=v return v end, + }) end -setmetatable(newfeatures,{ - __call=function(t,k) local v=t[k] return v end, - __index=function(t,k) local v=setnewfeatures(k) t[k]=v return v end, -}) function constructors.checkedfeatures(what,features) local defaults=handlers[what].features.defaults if features and next(features) then @@ -6849,19 +7074,61 @@ if context then os.exit() end local fonts=fonts -fonts.encodings={} -fonts.encodings.agl={} -fonts.encodings.known={} -setmetatable(fonts.encodings.agl,{ __index=function(t,k) +local encodings={} +fonts.encodings=encodings +encodings.agl={} +encodings.known={} +setmetatable(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 } + encodings.agl={ unicodes=unicodes } return unicodes else return nil end end }) +encodings.cache=containers.define("fonts","enc",encodings.version,true) +function encodings.load(filename) + local name=file.removesuffix(filename) + local data=containers.read(encodings.cache,name) + if data then + return data + end + local vector,tag,hash,unicodes={},"",{},{} + local foundname=resolvers.findfile(filename,'enc') + if foundname and foundname~="" then + local ok,encoding,size=resolvers.loadbinfile(foundname) + if ok and encoding then + encoding=string.gsub(encoding,"%%(.-)\n","") + local unicoding=encodings.agl.unicodes + local tag,vec=string.match(encoding,"/(%w+)%s*%[(.*)%]%s*def") + local i=0 + for ch in string.gmatch(vec,"/([%a%d%.]+)") do + if ch~=".notdef" then + vector[i]=ch + if not hash[ch] then + hash[ch]=i + else + end + local u=unicoding[ch] + if u then + unicodes[u]=i + end + end + i=i+1 + end + end + end + local data={ + name=name, + tag=tag, + vector=vector, + hash=hash, + unicodes=unicodes + } + return containers.write(encodings.cache,name,data) +end end -- closure @@ -7032,8 +7299,9 @@ local match,format,find,concat,gsub,lower=string.match,string.format,string.find local P,R,S,C,Ct,Cc,lpegmatch=lpeg.P,lpeg.R,lpeg.S,lpeg.C,lpeg.Ct,lpeg.Cc,lpeg.match local floor=math.floor local formatters=string.formatters +local sortedhash,sortedkeys=table.sortedhash,table.sortedkeys 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 trace_mapping=false trackers.register("fonts.mapping",function(v) trace_mapping=v end) local report_fonts=logs.reporter("fonts","loading") local force_ligatures=false directives.register("fonts.mapping.forceligatures",function(v) force_ligatures=v end) local fonts=fonts or {} @@ -7123,36 +7391,44 @@ mappings.fromunicode16=fromunicode16 local ligseparator=P("_") local varseparator=P(".") local namesplitter=Ct(C((1-ligseparator-varseparator)^1)*(ligseparator*C((1-ligseparator-varseparator)^1))^0) -local overloads=allocate { - IJ={ name="I_J",unicode={ 0x49,0x4A },mess=0x0132 }, - ij={ name="i_j",unicode={ 0x69,0x6A },mess=0x0133 }, - ff={ name="f_f",unicode={ 0x66,0x66 },mess=0xFB00 }, - fi={ name="f_i",unicode={ 0x66,0x69 },mess=0xFB01 }, - fl={ name="f_l",unicode={ 0x66,0x6C },mess=0xFB02 }, - ffi={ name="f_f_i",unicode={ 0x66,0x66,0x69 },mess=0xFB03 }, - ffl={ name="f_f_l",unicode={ 0x66,0x66,0x6C },mess=0xFB04 }, - fj={ name="f_j",unicode={ 0x66,0x6A } }, - fk={ name="f_k",unicode={ 0x66,0x6B } }, -} -for k,v in next,overloads do - local name=v.name - local mess=v.mess - if name then - overloads[name]=v - end - if mess then - overloads[mess]=v +do + local overloads=allocate { + IJ={ name="I_J",unicode={ 0x49,0x4A },mess=0x0132 }, + ij={ name="i_j",unicode={ 0x69,0x6A },mess=0x0133 }, + ff={ name="f_f",unicode={ 0x66,0x66 },mess=0xFB00 }, + fi={ name="f_i",unicode={ 0x66,0x69 },mess=0xFB01 }, + fl={ name="f_l",unicode={ 0x66,0x6C },mess=0xFB02 }, + ffi={ name="f_f_i",unicode={ 0x66,0x66,0x69 },mess=0xFB03 }, + ffl={ name="f_f_l",unicode={ 0x66,0x66,0x6C },mess=0xFB04 }, + fj={ name="f_j",unicode={ 0x66,0x6A } }, + fk={ name="f_k",unicode={ 0x66,0x6B } }, + } + local o={} + for k,v in next,overloads do + local name=v.name + local mess=v.mess + if name then + o[name]=v + end + if mess then + o[mess]=v + end + o[k]=v end + mappings.overloads=o end -mappings.overloads=overloads function mappings.addtounicode(data,filename,checklookups) local resources=data.resources local unicodes=resources.unicodes if not unicodes then + if trace_mapping then + report_fonts("no unicode list, quitting tounicode for %a",filename) + end return end local properties=data.properties local descriptions=data.descriptions + local overloads=mappings.overloads unicodes['space']=unicodes['space'] or 32 unicodes['hyphen']=unicodes['hyphen'] or 45 unicodes['zwj']=unicodes['zwj'] or 0x200D @@ -7175,10 +7451,13 @@ function mappings.addtounicode(data,filename,checklookups) end local ns=0 local nl=0 - for du,glyph in next,descriptions do + local dlist=sortedkeys(descriptions) + for i=1,#dlist do + local du=dlist[i] + local glyph=descriptions[du] local name=glyph.name if name then - local overload=overloads[name] + local overload=overloads[name] or overloads[du] if overload then glyph.unicode=overload.unicode else @@ -7297,6 +7576,11 @@ function mappings.addtounicode(data,filename,checklookups) end end end + else + local overload=overloads[du] + if overload then + glyph.unicode=overload.unicode + end end end if type(checklookups)=="function" then @@ -7304,7 +7588,9 @@ function mappings.addtounicode(data,filename,checklookups) end local collected=false local unicoded=0 - for unicode,glyph in next,descriptions do + for i=1,#dlist do + local du=dlist[i] + local glyph=descriptions[du] if glyph.class=="ligature" and (force_ligatures or not glyph.unicode) then if not collected then collected=fonts.handlers.otf.readers.getcomponents(data) @@ -7312,7 +7598,7 @@ function mappings.addtounicode(data,filename,checklookups) break end end - local u=collected[unicode] + local u=collected[du] if u then local n=#u for i=1,n do @@ -7336,9 +7622,11 @@ function mappings.addtounicode(data,filename,checklookups) report_fonts("%n ligature tounicode mappings deduced from gsub ligature features",unicoded) end if trace_mapping then - for unic,glyph in table.sortedhash(descriptions) do - local name=glyph.name - local index=glyph.index + for i=1,#dlist do + local du=dlist[i] + local glyph=descriptions[du] + local name=glyph.name or "-" + local index=glyph.index or 0 local unicode=glyph.unicode if unicode then if type(unicode)=="table" then @@ -7346,12 +7634,12 @@ function mappings.addtounicode(data,filename,checklookups) for i=1,#unicode do unicodes[i]=formatters("%U",unicode[i]) end - report_fonts("internal slot %U, name %a, unicode %U, tounicode % t",index,name,unic,unicodes) + report_fonts("internal slot %U, name %a, unicode %U, tounicode % t",index,name,du,unicodes) else - report_fonts("internal slot %U, name %a, unicode %U, tounicode %U",index,name,unic,unicode) + report_fonts("internal slot %U, name %a, unicode %U, tounicode %U",index,name,du,unicode) end else - report_fonts("internal slot %U, name %a, unicode %U",index,name,unic) + report_fonts("internal slot %U, name %a, unicode %U",index,name,du) end end end @@ -7441,164 +7729,6 @@ 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.handlers.tfm -tfm.version=1.000 -tfm.maxnestingdepth=5 -tfm.maxnestingsize=65536*1024 -local tfmfeatures=constructors.features.tfm -constructors.resolvevirtualtoo=false -fonts.formats.tfm="type1" -fonts.formats.ofm="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 depth={} -local function read_from_tfm(specification) - local filename=specification.filename - local size=specification.size - depth[filename]=(depth[filename] or 0)+1 - 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 - properties.format=fonts.formats.tfm - parameters.size=size - tfmdata.properties=properties - tfmdata.resources=resources - tfmdata.parameters=parameters - tfmdata.shared=shared - shared.rawdata={} - shared.features=features - shared.processes=next(features) and tfm.setfeatures(tfmdata,features) or nil - 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 - tfmdata.type="virtual" - local fontlist=vfdata.fonts - local name=file.nameonly(filename) - for i=1,#fontlist do - local n=fontlist[i].name - local s=fontlist[i].size - local d=depth[filename] - s=constructors.scaled(s,vfdata.designsize) - if d>tfm.maxnestingdepth then - report_defining("too deeply nested virtual font %a with size %a, max nesting depth %s",n,s,tfm.maxnestingdepth) - fontlist[i]={ id=0 } - elseif (d>1) and (s>tfm.maxnestingsize) then - report_defining("virtual font %a exceeds size %s",n,s) - fontlist[i]={ id=0 } - else - local t,id=fonts.constructors.readanddefine(n,s) - fontlist[i]={ id=id } - end - end - 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 - properties.haskerns=true - properties.hasligatures=true - resources.unicodes={} - resources.lookuptags={} - depth[filename]=depth[filename]-1 - return tfmdata - else - depth[filename]=depth[filename]-1 - 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 -readers.ofm=readers.tfm - -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", @@ -7625,6 +7755,7 @@ local function setmode(tfmdata,value) tfmdata.properties.mode=lower(value) end end +otf.modeinitializer=setmode local function setlanguage(tfmdata,value) if value then local cleanvalue=lower(value) @@ -7659,6 +7790,7 @@ registerotffeature { initializers={ base=setmode, node=setmode, + plug=setmode, } } registerotffeature { @@ -7667,6 +7799,7 @@ registerotffeature { initializers={ base=setlanguage, node=setlanguage, + plug=setlanguage, } } registerotffeature { @@ -7675,6 +7808,7 @@ registerotffeature { initializers={ base=setscript, node=setscript, + plug=setscript, } } otftables.featuretypes=allocate { @@ -7770,7 +7904,9 @@ handlers.otf=otf local readers=otf.readers or {} otf.readers=readers local streamreader=utilities.files +local streamwriter=utilities.files readers.streamreader=streamreader +readers.streamwriter=streamwriter local openfile=streamreader.open local closefile=streamreader.close local setposition=streamreader.setposition @@ -8242,11 +8378,43 @@ readers.hhea=function(f,fontdata,specification) reserved_3=readshort(f), reserved_4=readshort(f), metricdataformat=readshort(f), - nofhmetrics=readushort(f), + nofmetrics=readushort(f), } else fontdata.horizontalheader={ - nofhmetrics=0, + nofmetrics=0, + } + end + end +end +readers.vhea=function(f,fontdata,specification) + if specification.details then + local datatable=fontdata.tables.vhea + if datatable then + setposition(f,datatable.offset) + local version=readfixed(f) + fontdata.verticalheader={ + version=version, + ascender=readfword(f), + descender=readfword(f), + linegap=readfword(f), + maxadvanceheight=readufword(f), + mintopsidebearing=readfword(f), + minbottomsidebearing=readfword(f), + maxextent=readfword(f), + caretsloperise=readshort(f), + caretsloperun=readshort(f), + caretoffset=readshort(f), + reserved_1=readshort(f), + reserved_2=readshort(f), + reserved_3=readshort(f), + reserved_4=readshort(f), + metricdataformat=readshort(f), + nofmetrics=readushort(f), + } + else + fontdata.verticalheader={ + nofmetrics=0, } end end @@ -8297,7 +8465,8 @@ readers.hmtx=function(f,fontdata,specification) local datatable=fontdata.tables.hmtx if datatable then setposition(f,datatable.offset) - local nofmetrics=fontdata.horizontalheader.nofhmetrics + local horizontalheader=fontdata.horizontalheader + local nofmetrics=horizontalheader.nofmetrics local glyphs=fontdata.glyphs local nofglyphs=fontdata.nofglyphs local width=0 @@ -8319,6 +8488,43 @@ readers.hmtx=function(f,fontdata,specification) end end end +readers.vmtx=function(f,fontdata,specification) + if specification.glyphs then + local datatable=fontdata.tables.vmtx + if datatable then + setposition(f,datatable.offset) + local verticalheader=fontdata.verticalheader + local nofmetrics=verticalheader.nofmetrics + local glyphs=fontdata.glyphs + local nofglyphs=fontdata.nofglyphs + local vheight=0 + local vdefault=verticalheader.ascender+verticalheader.descender + local topsidebearing=0 + for i=0,nofmetrics-1 do + local glyph=glyphs[i] + vheight=readshort(f) + topsidebearing=readshort(f) + if vheight~=0 and vheight~=vdefault then + glyph.vheight=vheight + end + end + for i=nofmetrics,nofglyphs-1 do + local glyph=glyphs[i] + if vheight~=0 and vheight~=vdefault then + glyph.vheight=vheight + end + end + end + end +end +readers.vorg=function(f,fontdata,specification) + if specification.glyphs then + local datatable=fontdata.tables.vorg + if datatable then + report("todo: %s","vorg") + end + end +end readers.post=function(f,fontdata,specification) local datatable=fontdata.tables.post if datatable then @@ -8876,113 +9082,7 @@ function readers.math(f,fontdata,specification) reportskippedtable("math") end end -local function packoutlines(data,makesequence) - local subfonts=data.subfonts - if subfonts then - for i=1,#subfonts do - packoutlines(subfonts[i],makesequence) - end - return - end - local common=data.segments - if common then - return - end - local glyphs=data.glyphs - if not glyphs then - return - end - if makesequence then - for index=1,#glyphs do - local glyph=glyphs[index] - local segments=glyph.segments - if segments then - local sequence={} - local nofsequence=0 - for i=1,#segments do - local segment=segments[i] - local nofsegment=#segment - nofsequence=nofsequence+1 - sequence[nofsequence]=segment[nofsegment] - for i=1,nofsegment-1 do - nofsequence=nofsequence+1 - sequence[nofsequence]=segment[i] - end - end - glyph.sequence=sequence - glyph.segments=nil - end - end - else - local hash={} - local common={} - local reverse={} - local last=0 - for index=1,#glyphs do - local segments=glyphs[index].segments - if segments then - for i=1,#segments do - local h=concat(segments[i]," ") - hash[h]=(hash[h] or 0)+1 - end - end - end - for index=1,#glyphs do - local segments=glyphs[index].segments - if segments then - for i=1,#segments do - local segment=segments[i] - local h=concat(segment," ") - if hash[h]>1 then - local idx=reverse[h] - if not idx then - last=last+1 - reverse[h]=last - common[last]=segment - idx=last - end - segments[i]=idx - end - end - end - end - if last>0 then - data.segments=common - end - end -end -local function unpackoutlines(data) - local subfonts=data.subfonts - if subfonts then - for i=1,#subfonts do - unpackoutlines(subfonts[i]) - end - return - end - local common=data.segments - if not common then - return - end - local glyphs=data.glyphs - if not glyphs then - return - end - for index=1,#glyphs do - local segments=glyphs[index].segments - if segments then - for i=1,#segments do - local c=common[segments[i]] - if c then - segments[i]=c - end - end - end - end - data.segments=nil -end -otf.packoutlines=packoutlines -otf.unpackoutlines=unpackoutlines -local function getinfo(maindata,sub,platformnames,rawfamilynames) +local function getinfo(maindata,sub,platformnames,rawfamilynames,metricstoo) local fontdata=sub and maindata.subfonts and maindata.subfonts[sub] or maindata local names=fontdata.names local info=nil @@ -8992,8 +9092,8 @@ local function getinfo(maindata,sub,platformnames,rawfamilynames) local fontheader=fontdata.fontheader or {} local cffinfo=fontdata.cffinfo or {} local filename=fontdata.filename - local weight=getname(fontdata,"weight") or cffinfo.weight or metrics.weight - local width=getname(fontdata,"width") or cffinfo.width or metrics.width + local weight=getname(fontdata,"weight") or (cffinfo and cffinfo.weight) or (metrics and metrics.weight) + local width=getname(fontdata,"width") or (cffinfo and cffinfo.width ) or (metrics and metrics.width ) local fontname=getname(fontdata,"postscriptname") local fullname=getname(fontdata,"fullname") local family=getname(fontdata,"family") @@ -9035,6 +9135,27 @@ local function getinfo(maindata,sub,platformnames,rawfamilynames) descender=metrics.typodescender, platformnames=platformnames and fontdata.platformnames or nil, } + if metricstoo then + local keys={ + "version", + "ascender","descender","linegap", + "maxadvancewidth","maxadvanceheight","maxextent", + "minbottomsidebearing","mintopsidebearing", + } + local h=fontdata.horizontalheader or {} + local v=fontdata.verticalheader or {} + if h then + local th={} + local tv={} + for i=1,#keys do + local key=keys[i] + th[key]=h[key] or 0 + tv[key]=v[key] or 0 + end + info.horizontalmetrics=th + info.verticalmetrics=tv + end + end elseif n then info={ filename=fontdata.filename, @@ -9118,7 +9239,10 @@ local function readdata(f,offset,specification) readers["head"](f,fontdata,specification) readers["maxp"](f,fontdata,specification) readers["hhea"](f,fontdata,specification) + readers["vhea"](f,fontdata,specification) readers["hmtx"](f,fontdata,specification) + readers["vmtx"](f,fontdata,specification) + readers["vorg"](f,fontdata,specification) readers["post"](f,fontdata,specification) readers["cff" ](f,fontdata,specification) readers["cmap"](f,fontdata,specification) @@ -9249,6 +9373,13 @@ function readers.loadshapes(filename,n) shapes=true, subfont=n, } + if fontdata then + for k,v in next,fontdata.glyphs do + v.class=nil + v.index=nil + v.math=nil + end + end return fontdata and { filename=filename, format=fontdata.format, @@ -9279,7 +9410,7 @@ function readers.loadfont(filename,n) descriptions=fontdata.descriptions, format=fontdata.format, goodies={}, - metadata=getinfo(fontdata,n), + metadata=getinfo(fontdata,n,false,false,true), properties={ hasitalics=fontdata.hasitalics or false, maxcolorclass=fontdata.maxcolorclass, @@ -9378,56 +9509,6 @@ function readers.extend(fontdata) end end end -if fonts.hashes then - local identifiers=fonts.hashes.identifiers - local loadshapes=readers.loadshapes - readers.version=0.006 - readers.cache=containers.define("fonts","shapes",readers.version,true) - local function load(filename,sub) - local base=file.basename(filename) - local name=file.removesuffix(base) - local kind=file.suffix(filename) - local attr=lfs.attributes(filename) - local size=attr and attr.size or 0 - local time=attr and attr.modification or 0 - local sub=tonumber(sub) - if size>0 and (kind=="otf" or kind=="ttf" or kind=="tcc") then - local hash=containers.cleanname(base) - if sub then - hash=hash.."-"..sub - end - data=containers.read(readers.cache,hash) - if not data or data.time~=time or data.size~=size then - data=loadshapes(filename,sub) - if data then - data.size=size - data.format=data.format or (kind=="otf" and "opentype") or "truetype" - data.time=time - packoutlines(data) - containers.write(readers.cache,hash,data) - data=containers.read(readers.cache,hash) - end - end - unpackoutlines(data) - else - data={ - filename=filename, - size=0, - time=time, - format="unknown", - units=1000, - glyphs={} - } - end - return data - end - fonts.hashes.shapes=table.setmetatableindex(function(t,k) - local d=identifiers[k] - local v=load(d.properties.filename,d.subindex) - t[k]=v - return v - end) -end end -- closure @@ -9446,6 +9527,7 @@ local concat,remove=table.concat,table.remove local floor,abs,round,ceil=math.floor,math.abs,math.round,math.ceil local P,C,R,S,C,Cs,Ct=lpeg.P,lpeg.C,lpeg.R,lpeg.S,lpeg.C,lpeg.Cs,lpeg.Ct local lpegmatch=lpeg.match +local formatters=string.formatters local readers=fonts.handlers.otf.readers local streamreader=readers.streamreader local readbytes=streamreader.readbytes @@ -9846,6 +9928,7 @@ do local ymax=0 local checked=false local keepcurve=false + local version=2 local function showstate(where) report("%w%-10s : [%s] n=%i",depth*2,where,concat(stack," ",1,top),top) end @@ -10268,9 +10351,91 @@ do return floor((stems+7)/8) end end - local function unsupported() + local function unsupported(t) if trace_charstrings then - showstate("unsupported") + showstate("unsupported "..t) + end + top=0 + end + local function unsupportedsub(t) + if trace_charstrings then + showstate("unsupported sub "..t) + end + top=0 + end + local function getstem3() + if trace_charstrings then + showstate("stem3") + end + top=0 + end + local function divide() + if version==1 then + local d=stack[top] + top=top-1 + stack[top]=stack[top]/d + end + end + local function closepath() + if version==1 then + if trace_charstrings then + showstate("closepath") + end + end + top=0 + end + local function hsbw() + if version==1 then + if trace_charstrings then + showstate("dotsection") + end + width=stack[top] + end + top=0 + end + local function seac() + if version==1 then + if trace_charstrings then + showstate("seac") + end + end + top=0 + end + local function sbw() + if version==1 then + if trace_charstrings then + showstate("sbw") + end + width=stack[top-1] + end + top=0 + end + local function callothersubr() + if version==1 then + if trace_charstrings then + showstate("callothersubr (unsupported)") + end + end + top=0 + end + local function pop() + if version==1 then + if trace_charstrings then + showstate("pop (unsupported)") + end + top=top+1 + stack[top]=0 + else + top=0 + end + end + local function setcurrentpoint() + if version==1 then + if trace_charstrings then + showstate("pop (unsupported)") + end + x=x+stack[top-1] + y=y+stack[top] end top=0 end @@ -10287,7 +10452,7 @@ do unsupported, unsupported, unsupported, - unsupported, + hsbw, unsupported, unsupported, unsupported, @@ -10308,6 +10473,15 @@ do hvcurveto, } local subactions={ + [000]=dotsection, + [001]=getstem3, + [002]=getstem3, + [006]=seac, + [007]=sbw, + [012]=divide, + [016]=callothersubr, + [017]=pop, + [033]=setcurrentpoint, [034]=hflex, [035]=flex, [036]=hflex1, @@ -10315,23 +10489,29 @@ do } local p_bytes=Ct((P(1)/byte)^0) local function call(scope,list,bias,process) - local index=stack[top]+bias - top=top-1 - if trace_charstrings then - showvalue(scope,index,true) - end - local str=list[index] - if str then - if type(str)=="string" then - str=lpegmatch(p_bytes,str) - list[index]=str - end - depth=depth+1 - process(str) - depth=depth-1 + depth=depth+1 + if top==0 then + showstate(formatters["unknown %s call"](scope)) + top=0 else - report("unknown %s %i",scope,index) + local index=stack[top]+bias + top=top-1 + if trace_charstrings then + showvalue(scope,index,true) + end + local tab=list[index] + if tab then + if type(tab)=="string" then + tab=lpegmatch(p_bytes,tab) + list[index]=tab + end + process(tab) + else + showstate(formatters["unknown %s call %i"](scope,index)) + top=0 + end end + depth=depth-1 end local function process(tab) local i=1 @@ -10398,7 +10578,7 @@ do local t=tab[i] local a=subactions[t] if a then - a() + a(t) else if trace_charstrings then showvalue("<subaction>",t) @@ -10409,7 +10589,7 @@ do else local a=actions[t] if a then - local s=a() + local s=a(t) if s then i=i+s end @@ -10423,25 +10603,38 @@ do end end end - parsecharstrings=function(data,glyphs,doshapes) + local function setbias(globals,locals) + if version==1 then + return + false, + false + else + local g,l=#globals,#locals + return + ((g<1240 and 107) or (g<33900 and 1131) or 32768)+1, + ((l<1240 and 107) or (l<33900 and 1131) or 32768)+1 + end + end + parsecharstrings=function(data,glyphs,doshapes,tversion) local dictionary=data.dictionaries[1] local charstrings=dictionary.charstrings local charset=dictionary.charset + local private=dictionary.private or { data={} } keepcurve=doshapes + version=tversion stack={} glyphs=glyphs or {} strings=data.strings - locals=dictionary.subroutines - globals=data.routines - globalbias=#globals - localbias=#locals - globalbias=((globalbias<1240 and 107) or (globalbias<33900 and 1131) or 32768)+1 - localbias=((localbias<1240 and 107) or (localbias<33900 and 1131) or 32768)+1 - local nominalwidth=dictionary.private.data.nominalwidthx or 0 - local defaultwidth=dictionary.private.data.defaultwidthx or 0 + globals=data.routines or {} + locals=dictionary.subroutines or {} + globalbias,localbias=setbias(globals,locals) + local nominalwidth=private.data.nominalwidthx or 0 + local defaultwidth=private.data.defaultwidthx or 0 for i=1,#charstrings do - local str=charstrings[i] - local tab=lpegmatch(p_bytes,str) + local tab=charstrings[i] + if type(tab)=="string" then + tab=lpegmatch(p_bytes,tab) + end local index=i-1 x=0 y=0 @@ -10492,19 +10685,19 @@ do end return glyphs end - parsecharstring=function(data,dictionary,charstring,glyphs,index,doshapes) + parsecharstring=function(data,dictionary,tab,glyphs,index,doshapes,tversion) local private=dictionary.private keepcurve=doshapes + version=tversion strings=data.strings locals=dictionary.subroutines or {} globals=data.routines or {} - globalbias=#globals - localbias=#locals - globalbias=((globalbias<1240 and 107) or (globalbias<33900 and 1131) or 32768)+1 - localbias=((localbias<1240 and 107) or (localbias<33900 and 1131) or 32768)+1 + globalbias,localbias=setbias(globals,locals) local nominalwidth=private and private.data.nominalwidthx or 0 local defaultwidth=private and private.data.defaultwidthx or 0 - local tab=lpegmatch(p_bytes,charstring) + if type(tab)=="string" then + tab=lpegmatch(p_bytes,tab) + end x=0 y=0 width=false @@ -10528,7 +10721,7 @@ do else width=nominalwidth+width end -index=index-1 + index=index-1 local glyph=glyphs[index] if not glyph then glyphs[index]={ @@ -10551,7 +10744,6 @@ index=index-1 report("width: %s",tostring(width)) report("boundingbox: % t",boundingbox) end - return charstring end resetcharstrings=function() result={} @@ -10665,7 +10857,7 @@ local function readcidprivates(f,data) end parseprivates(data,dictionaries) end -local function readnoselect(f,data,glyphs,doshapes) +local function readnoselect(f,data,glyphs,doshapes,version) local dictionaries=data.dictionaries local dictionary=dictionaries[1] readglobals(f,data) @@ -10675,10 +10867,11 @@ local function readnoselect(f,data,glyphs,doshapes) readprivates(f,data) parseprivates(data,data.dictionaries) readlocals(f,data,dictionary) - parsecharstrings(data,glyphs,doshapes) + parsecharstrings(data,glyphs,doshapes,version) resetcharstrings() end -local function readfdselect(f,data,glyphs,doshapes) +readers.parsecharstrings=parsecharstrings +local function readfdselect(f,data,glyphs,doshapes,version) local header=data.header local dictionaries=data.dictionaries local dictionary=dictionaries[1] @@ -10736,7 +10929,7 @@ local function readfdselect(f,data,glyphs,doshapes) readlocals(f,data,dictionaries[i]) end for i=1,#charstrings do - parsecharstring(data,dictionaries[fdindex[i]+1],charstrings[i],glyphs,i,doshapes) + parsecharstring(data,dictionaries[fdindex[i]+1],charstrings[i],glyphs,i,doshapes,version) end resetcharstrings() end @@ -11230,6 +11423,7 @@ local bittest=bit32.btest local rshift=bit32.rshift local concat=table.concat local lower=string.lower +local copy=table.copy local sub=string.sub local strip=string.strip local tohash=table.tohash @@ -11242,6 +11436,7 @@ local report=logs.reporter("otf reader") local readers=fonts.handlers.otf.readers local streamreader=readers.streamreader local setposition=streamreader.setposition +local getposition=streamreader.getposition local skipshort=streamreader.skipshort local readushort=streamreader.readcardinal2 local readulong=streamreader.readcardinal4 @@ -11355,10 +11550,15 @@ local function readcoverage(f,offset,simple) end return coverage end -local function readclassdef(f,offset) +local function readclassdef(f,offset,preset) setposition(f,offset) local classdefformat=readushort(f) local classdef={} + if type(preset)=="number" then + for k=0,preset-1 do + classdef[k]=1 + end + end if classdefformat==1 then local index=readushort(f) local nofclassdef=readushort(f) @@ -11380,6 +11580,13 @@ local function readclassdef(f,offset) else report("unknown classdef format %a ",classdefformat) end + if type(preset)=="table" then + for k in next,preset do + if not classdef[k] then + classdef[k]=1 + end + end + end return classdef end local function classtocoverage(defs) @@ -11479,7 +11686,7 @@ local function covered(subset,all) end return used end -local function readlookuparray(f,noflookups) +local function readlookuparray(f,noflookups,nofcurrent) local lookups={} if noflookups>0 then local length=0 @@ -11522,7 +11729,7 @@ local function unchainedcontext(f,fontdata,lookupid,lookupoffset,offset,glyphs,n for i=2,nofcurrent do current[i]={ readushort(f) } end - local lookups=readlookuparray(f,noflookups) + local lookups=readlookuparray(f,noflookups,nofcurrent) rules[#rules+1]={ current=current, lookups=lookups @@ -11544,7 +11751,7 @@ local function unchainedcontext(f,fontdata,lookupid,lookupoffset,offset,glyphs,n local rules={} if subclasssets then coverage=readcoverage(f,tableoffset+coverage) - currentclassdef=readclassdef(f,tableoffset+currentclassdef) + currentclassdef=readclassdef(f,tableoffset+currentclassdef,coverage) local currentclasses=classtocoverage(currentclassdef,fontdata.glyphs) for class=1,#subclasssets do local offset=subclasssets[class] @@ -11563,7 +11770,7 @@ local function unchainedcontext(f,fontdata,lookupid,lookupoffset,offset,glyphs,n for i=2,nofcurrent do current[i]=currentclasses[readushort(f)+1] end - local lookups=readlookuparray(f,noflookups) + local lookups=readlookuparray(f,noflookups,nofcurrent) rules[#rules+1]={ current=current, lookups=lookups @@ -11587,7 +11794,7 @@ local function unchainedcontext(f,fontdata,lookupid,lookupoffset,offset,glyphs,n elseif subtype==3 then local current=readarray(f) local noflookups=readushort(f) - local lookups=readlookuparray(f,noflookups) + local lookups=readlookuparray(f,noflookups,#current) current=readcoveragearray(f,tableoffset,current,true) return { format="coverage", @@ -11642,7 +11849,7 @@ local function chainedcontext(f,fontdata,lookupid,lookupoffset,offset,glyphs,nof end end local noflookups=readushort(f) - local lookups=readlookuparray(f,noflookups) + local lookups=readlookuparray(f,noflookups,nofcurrent) rules[#rules+1]={ before=before, current=current, @@ -11668,9 +11875,9 @@ local function chainedcontext(f,fontdata,lookupid,lookupoffset,offset,glyphs,nof local rules={} if subclasssets then local coverage=readcoverage(f,tableoffset+coverage) - local beforeclassdef=readclassdef(f,tableoffset+beforeclassdef) - local currentclassdef=readclassdef(f,tableoffset+currentclassdef) - local afterclassdef=readclassdef(f,tableoffset+afterclassdef) + local beforeclassdef=readclassdef(f,tableoffset+beforeclassdef,nofglyphs) + local currentclassdef=readclassdef(f,tableoffset+currentclassdef,coverage) + local afterclassdef=readclassdef(f,tableoffset+afterclassdef,nofglyphs) local beforeclasses=classtocoverage(beforeclassdef,fontdata.glyphs) local currentclasses=classtocoverage(currentclassdef,fontdata.glyphs) local afterclasses=classtocoverage(afterclassdef,fontdata.glyphs) @@ -11707,7 +11914,7 @@ local function chainedcontext(f,fontdata,lookupid,lookupoffset,offset,glyphs,nof end end local noflookups=readushort(f) - local lookups=readlookuparray(f,noflookups) + local lookups=readlookuparray(f,noflookups,nofcurrent) rules[#rules+1]={ before=before, current=current, @@ -11735,7 +11942,7 @@ local function chainedcontext(f,fontdata,lookupid,lookupoffset,offset,glyphs,nof local current=readarray(f) local after=readarray(f) local noflookups=readushort(f) - local lookups=readlookuparray(f,noflookups) + local lookups=readlookuparray(f,noflookups,#current) before=readcoveragearray(f,tableoffset,before,true) current=readcoveragearray(f,tableoffset,current,true) after=readcoveragearray(f,tableoffset,after,true) @@ -12061,8 +12268,8 @@ function gposhandlers.pair(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofgly local nofclasses2=readushort(f) local classlist=readpairclasssets(f,nofclasses1,nofclasses2,format1,format2) coverage=readcoverage(f,tableoffset+coverage) - classdef1=readclassdef(f,tableoffset+classdef1) - classdef2=readclassdef(f,tableoffset+classdef2) + classdef1=readclassdef(f,tableoffset+classdef1,coverage) + classdef2=readclassdef(f,tableoffset+classdef2,nofglyphs) local usedcoverage={} for g1,c1 in next,classdef1 do if coverage[g1] then @@ -12285,15 +12492,39 @@ function gposhandlers.extension(f,fontdata,lookupid,lookupoffset,offset,glyphs,n end do local plugins={} - function plugins.size(f,fontdata,tableoffset,parameters) - if not fontdata.designsize then - setposition(f,tableoffset+parameters) - local designsize=readushort(f) - if designsize>0 then + function plugins.size(f,fontdata,tableoffset,feature) + if fontdata.designsize then + else + local function check(offset) + setposition(f,offset) + local designsize=readushort(f) + if designsize>0 then + local fontstyle=readushort(f) + local guimenuid=readushort(f) + local minsize=readushort(f) + local maxsize=readushort(f) + if minsize==0 and maxsize==0 and fontstyleid==0 and guimenuid==0 then + minsize=designsize + maxsize=designsize + end + if designsize>=minsize and designsize<=maxsize then + return minsize,maxsize,designsize + end + end + end + local minsize,maxsize,designsize=check(tableoffset+feature.offset+feature.parameters) + if not designsize then + minsize,maxsize,designsize=check(tableoffset+feature.parameters) + if designsize then + report("bad size feature in %a, falling back to wrong offset",fontdata.filename or "?") + else + report("bad size feature in %a,",fontdata.filename or "?") + end + end + if designsize then + fontdata.minsize=minsize + fontdata.maxsize=maxsize fontdata.designsize=designsize - skipshort(f,2) - fontdata.minsize=readushort(f) - fontdata.maxsize=readushort(f) end end end @@ -12430,7 +12661,7 @@ do feature.parameters=parameters local plugin=plugins[feature.tag] if plugin then - plugin(f,fontdata,offset,parameters) + plugin(f,fontdata,featureoffset,feature) end end end @@ -12651,7 +12882,7 @@ do markclass=d.markclass or nil, flags=d.flags, } - sublookuplist[nofsublookups]=h + sublookuplist[nofsublookups]=copy(h) sublookuphash[lookupid]=nofsublookups sublookupcheck[lookupid]=1 else @@ -12863,7 +13094,7 @@ function readers.gdef(f,fontdata,specification) end end end - if marksetsoffset then + if marksetsoffset and marksetsoffset>tableoffset then setposition(f,marksetsoffset) local format=readushort(f) if format==1 then @@ -13341,7 +13572,11 @@ local f_unicode=formatters["U%05X"] local f_index=formatters["I%05X"] local f_character_y=formatters["%C"] local f_character_n=formatters["[ %C ]"] -local doduplicates=true +local check_duplicates=true +local check_soft_hyphen=false +directives.register("otf.checksofthyphen",function(v) + check_soft_hyphen=v +end) local function replaced(list,index,replacement) if type(list)=="number" then return replacement @@ -13409,7 +13644,7 @@ local function unifyresources(fontdata,indices) end end local done={} - local duplicates=doduplicates and resources.duplicates + local duplicates=check_duplicates and resources.duplicates if duplicates and not next(duplicates) then duplicates=false end @@ -13646,10 +13881,31 @@ local function unifyresources(fontdata,indices) unifythem(resources.sublookups) end local function copyduplicates(fontdata) - if doduplicates then + if check_duplicates then local descriptions=fontdata.descriptions local resources=fontdata.resources local duplicates=resources.duplicates + if check_soft_hyphen then + local ds=descriptions[0xAD] + if not ds or ds.width==0 then + if ds then + descriptions[0xAD]=nil + report("patching soft hyphen") + else + report("adding soft hyphen") + end + if not duplicates then + duplicates={} + resources.duplicates=duplicates + end + local dh=duplicates[0x2D] + if dh then + dh[#dh+1]={ [0xAD]=true } + else + duplicates[0x2D]={ [0xAD]=true } + end + end + end if duplicates then for u,d in next,duplicates do local du=descriptions[u] @@ -14090,6 +14346,7 @@ function readers.getcomponents(fontdata) end end end +readers.unifymissing=unifymissing function readers.rehash(fontdata,hashmethod) if not (fontdata and fontdata.glyphs) then return @@ -15346,7 +15603,6 @@ if not modules then modules={} end modules ['font-otl']={ local gmatch,find,match,lower,strip=string.gmatch,string.find,string.match,string.lower,string.strip local type,next,tonumber,tostring,unpack=type,next,tonumber,tostring,unpack local abs=math.abs -local ioflush=io.flush local derivetable=table.derive local formatters=string.formatters local setmetatableindex=table.setmetatableindex @@ -15363,7 +15619,7 @@ local trace_defining=false registertracker("fonts.defining",function(v) trace_de local report_otf=logs.reporter("fonts","otf loading") local fonts=fonts local otf=fonts.handlers.otf -otf.version=3.023 +otf.version=3.027 otf.cache=containers.define("fonts","otl",otf.version,true) otf.svgcache=containers.define("fonts","svg",otf.version,true) otf.pdfcache=containers.define("fonts","pdf",otf.version,true) @@ -15375,10 +15631,8 @@ local readers=fonts.readers local constructors=fonts.constructors local otffeatures=constructors.features.otf local registerotffeature=otffeatures.register -local enhancers=allocate() -otf.enhancers=enhancers -local patches={} -enhancers.patches=patches +local otfenhancers=constructors.enhancers.otf +local registerotfenhancer=otfenhancers.register local forceload=false local cleanup=0 local syncspace=true @@ -15394,76 +15648,7 @@ registerdirective("fonts.otf.loader.cleanup",function(v) cleanup=tonumber(v) or registerdirective("fonts.otf.loader.force",function(v) forceload=v end) registerdirective("fonts.otf.loader.syncspace",function(v) syncspace=v end) registerdirective("fonts.otf.loader.forcenotdef",function(v) forcenotdef=v end) -local ordered_enhancers={ - "check extra features", -} -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 +registerotfenhancer("check extra features",function() end) function otf.load(filename,sub,featurefile) local featurefile=nil local base=file.basename(file.removesuffix(filename)) @@ -15541,7 +15726,7 @@ function otf.load(filename,sub,featurefile) collectgarbage("collect") end stoptiming(otfreaders) - if elapsedtime then + if elapsedtime then report_otf("loading, optimizing, packing and caching time %s",elapsedtime(otfreaders)) end if cleanup>3 then @@ -15563,7 +15748,7 @@ function otf.load(filename,sub,featurefile) otfreaders.unpack(data) otfreaders.expand(data) otfreaders.addunicodetable(data) - enhancers.apply(data,filename,data) + otfenhancers.apply(data,filename,data) if applyruntimefixes then applyruntimefixes(filename,data) end @@ -15703,13 +15888,13 @@ local function copytotfm(data,cache_id) spaceunits,spacer=charwidth,"charwidth" end end - spaceunits=tonumber(spaceunits) or 500 + spaceunits=tonumber(spaceunits) or units/2 parameters.slant=0 - parameters.space=spaceunits + parameters.space=spaceunits parameters.space_stretch=1*units/2 - parameters.space_shrink=1*units/3 - parameters.x_height=2*units/5 - parameters.quad=units + 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 @@ -15762,10 +15947,38 @@ local function copytotfm(data,cache_id) } end end +local converters={ + woff={ + cachename="webfonts", + action=otf.readers.woff2otf, + } +} +local function checkconversion(specification) + local filename=specification.filename + local converter=converters[lower(file.suffix(filename))] + if converter then + local base=file.basename(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 size>0 then + local cleanname=containers.cleanname(name) + local cachename=caches.setfirstwritablefile(cleanname,converter.cachename) + if not io.exists(cachename) or (time~=lfs.attributes(cachename).modification) then + report_otf("caching font %a in %a",filename,cachename) + converter.action(filename,cachename) + lfs.touch(cachename,time,time) + end + specification.filename=cachename + end + end +end local function otftotfm(specification) local cache_id=specification.hash local tfmdata=containers.read(constructors.cache,cache_id) if not tfmdata then + checkconversion(specification) local name=specification.name local sub=specification.sub local subindex=specification.subindex @@ -15895,7 +16108,6 @@ local function getgsub(tfmdata,k,kind,value) local properties=tfmdata.properties local validlookups,lookuplist=otf.collectlookups(rawdata,kind,properties.script,properties.language) if validlookups then - local choice=tonumber(value) or 1 for i=1,#lookuplist do local lookup=lookuplist[i] local steps=lookup.steps @@ -15973,9 +16185,13 @@ local function opentypereader(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.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.woff(specification) + checkconversion(specification) + opentypereader(specification,"") +end function otf.scriptandlanguage(tfmdata,attr) local properties=tfmdata.properties return properties.script or "dflt",properties.language or "dflt" @@ -16119,7 +16335,7 @@ local function registerbasehash(tfmdata) basehash[hash]=base end properties.basehash=base - properties.fullname=properties.fullname.."-"..base + properties.fullname=(properties.fullname or properties.name).."-"..base applied={} end local function registerbasefeature(feature,value) @@ -16187,6 +16403,10 @@ local function preparesubstitutions(tfmdata,feature,value,validlookups,lookuplis local trace_singles=trace_baseinit and trace_singles local trace_alternatives=trace_baseinit and trace_alternatives local trace_ligatures=trace_baseinit and trace_ligatures + if not changed then + changed={} + tfmdata.changed=changed + end for i=1,#lookuplist do local sequence=lookuplist[i] local steps=sequence.steps @@ -16194,12 +16414,10 @@ local function preparesubstitutions(tfmdata,feature,value,validlookups,lookuplis if kind=="gsub_single" then for i=1,#steps do for unicode,data in next,steps[i].coverage do - if not changed[unicode] then if trace_singles then report_substitution(feature,sequence,descriptions,unicode,data) end changed[unicode]=data - end end end elseif kind=="gsub_alternate" then @@ -16340,7 +16558,8 @@ local function featuresinitializer(tfmdata,value) local properties=tfmdata.properties local script=properties.script local language=properties.language - local rawfeatures=rawdata.resources.features + local rawresources=rawdata.resources + local rawfeatures=rawresources and rawresources.features local basesubstitutions=rawfeatures and rawfeatures.gsub local basepositionings=rawfeatures and rawfeatures.gpos if basesubstitutions or basepositionings then @@ -16411,7 +16630,7 @@ local registertracker=trackers.register local trace_injections=false registertracker("fonts.injections",function(v) trace_injections=v end) local trace_marks=false registertracker("fonts.injections.marks",function(v) trace_marks=v end) local trace_cursive=false registertracker("fonts.injections.cursive",function(v) trace_cursive=v end) -local trace_spaces=false registertracker("otf.spaces",function(v) trace_spaces=v end) +local trace_spaces=false registertracker("fonts.injections.spaces",function(v) trace_spaces=v end) local use_advance=false directives.register("fonts.injections.advance",function(v) use_advance=v end) local report_injections=logs.reporter("fonts","injections") local report_spaces=logs.reporter("fonts","spaces") @@ -16677,7 +16896,7 @@ function injections.setkern(current,factor,rlmode,x,injection) return 0,0 end end -function injections.setmark(start,base,factor,rlmode,ba,ma,tfmbase,mkmk) +function injections.setmark(start,base,factor,rlmode,ba,ma,tfmbase,mkmk,checkmark) local dx,dy=factor*(ba[1]-ma[1]),factor*(ba[2]-ma[2]) nofregisteredmarks=nofregisteredmarks+1 if rlmode>=0 then @@ -16695,6 +16914,7 @@ function injections.setmark(start,base,factor,rlmode,ba,ma,tfmbase,mkmk) i.markbase=nofregisteredmarks i.markbasenode=base i.markmark=mkmk + i.checkmark=checkmark end else p.injections={ @@ -16704,6 +16924,7 @@ function injections.setmark(start,base,factor,rlmode,ba,ma,tfmbase,mkmk) markbase=nofregisteredmarks, markbasenode=base, markmark=mkmk, + checkmark=checkmark, } end else @@ -16715,6 +16936,7 @@ function injections.setmark(start,base,factor,rlmode,ba,ma,tfmbase,mkmk) markbase=nofregisteredmarks, markbasenode=base, markmark=mkmk, + checkmark=checkmark, }, } end @@ -17266,10 +17488,16 @@ local function inject_everything(head,where) end else ox=px-pn.markx - local wn=getfield(n,"width") - if wn~=0 then - pn.leftkern=-wn/2 - pn.rightkern=-wn/2 + if pn.checkmark then + local wn=getfield(n,"width") + if wn~=0 then + wn=wn/2 + if trace_injections then + report_injections("correcting non zero width mark %C",getchar(n)) + end + insert_node_before(n,n,newkern(-wn)) + insert_node_after(n,n,newkern(-wn)) + end end end local oy=getfield(n,"yoffset")+getfield(p,"yoffset")+pn.marky @@ -17293,6 +17521,10 @@ local function inject_everything(head,where) nofmarks=nofmarks+1 marks[nofmarks]=current else + local yoffset=i.yoffset + if yoffset and yoffset~=0 then + setfield(current,"yoffset",yoffset) + end if hascursives then local cursivex=i.cursivex if cursivex then @@ -17344,10 +17576,6 @@ local function inject_everything(head,where) cursiveanchor=nil end end - local yoffset=i.yoffset - if yoffset and yoffset~=0 then - setfield(current,"yoffset",yoffset) - end local leftkern=i.leftkern if leftkern and leftkern~=0 then insert_node_before(head,current,newkern(leftkern)) @@ -17594,6 +17822,37 @@ function nodes.injections.setspacekerns(font,sequence) triggers={ [font]=sequence } end end +local getthreshold +if context then + local threshold=1 + local parameters=fonts.hashes.parameters + directives.register("otf.threshold",function(v) threshold=tonumber(v) or 1 end) + getthreshold=function(font) + local p=parameters[font] + local f=p.factor + local s=p.spacing + local t=threshold*(s and s.width or p.space or 0)-2 + return t>0 and t or 0,f + end +else + injections.threshold=0 + getthreshold=function(font) + local p=fontdata[font].parameters + local f=p.factor + local s=p.spacing + local t=injections.threshold*(s and s.width or p.space or 0)-2 + return t>0 and t or 0,f + end +end +injections.getthreshold=getthreshold +function injections.isspace(n,threshold) + if getid(n)==glue_code then + local w=getfield(n,"width") + if threshold and w>threshold then + return 32 + end + end +end local function injectspaces(head) if not triggers then return head,false @@ -17609,10 +17868,9 @@ local function injectspaces(head) local function updatefont(font,trig) leftkerns=trig.left rightkerns=trig.right - local par=fontdata[font].parameters - factor=par.factor - threshold=par.spacing.width-1 lastfont=font + threshold, + factor=getthreshold(font) end for n in traverse_id(glue_code,tonut(head)) do local prev,next=getboth(n) @@ -17631,7 +17889,7 @@ local function injectspaces(head) end end if prevchar then - local font=getfont(next) + local font=getfont(prev) local trig=triggers[font] if trig then if lastfont~=font then @@ -17644,7 +17902,7 @@ local function injectspaces(head) end if leftkern then local old=getfield(n,"width") - if old>=threshold then + if old>threshold then if rightkern then local new=old+(leftkern+rightkern)*factor if trace_spaces then @@ -17663,7 +17921,7 @@ local function injectspaces(head) leftkern=false elseif rightkern then local old=getfield(n,"width") - if old>=threshold then + if old>threshold then local new=old+rightkern*factor if trace_spaces then report_spaces("[%p -> %p] %C",nextchar,old,new) @@ -17907,33 +18165,40 @@ local mappers={ } local classifiers=characters.classifiers if not classifiers then - local first_arabic,last_arabic=characters.blockrange("arabic") - local first_syriac,last_syriac=characters.blockrange("syriac") - local first_mandiac,last_mandiac=characters.blockrange("mandiac") - local first_nko,last_nko=characters.blockrange("nko") + local f_arabic,l_arabic=characters.blockrange("arabic") + local f_syriac,l_syriac=characters.blockrange("syriac") + local f_mandiac,l_mandiac=characters.blockrange("mandiac") + local f_nko,l_nko=characters.blockrange("nko") + local f_ext_a,l_ext_a=characters.blockrange("arabicextendeda") classifiers=table.setmetatableindex(function(t,k) - local c=chardata[k] - local v=false - if c then - local arabic=c.arabic - if arabic then - v=mappers[arabic] - if not v then - log.report("analyze","error in mapping arabic %C",k) - v=false - end - elseif k>=first_arabic and k<=last_arabic or k>=first_syriac and k<=last_syriac or - k>=first_mandiac and k<=last_mandiac or k>=first_nko and k<=last_nko then - if categories[k]=="mn" then - v=s_mark - else - v=s_rest + if type(k)=="number" then + local c=chardata[k] + local v=false + if c then + local arabic=c.arabic + if arabic then + v=mappers[arabic] + if not v then + log.report("analyze","error in mapping arabic %C",k) + v=false + end + elseif (k>=f_arabic and k<=l_arabic) or + (k>=f_syriac and k<=l_syriac) or + (k>=f_mandiac and k<=l_mandiac) or + (k>=f_nko and k<=l_nko) or + (k>=f_ext_a and k<=l_ext_a) then + if categories[k]=="mn" then + v=s_mark + else + v=s_rest + end end end + t[k]=v + return v end - t[k]=v - return v end) + characters.classifiers=classifiers end function methods.arab(head,font,attr) local first,last=nil,nil @@ -18110,15 +18375,13 @@ local trace_details=false registertracker("otf.details",function(v) trace_detail 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 trace_plugins=false registertracker("otf.plugins",function(v) trace_plugins=v end) local trace_kernruns=false registertracker("otf.kernruns",function(v) trace_kernruns=v end) local trace_discruns=false registertracker("otf.discruns",function(v) trace_discruns=v end) local trace_compruns=false registertracker("otf.compruns",function(v) trace_compruns=v end) local trace_testruns=false registertracker("otf.testruns",function(v) trace_testruns=v end) -local quit_on_no_replacement=true -local zwnjruns=true local optimizekerns=true -registerdirective("otf.zwnjruns",function(v) zwnjruns=v end) -registerdirective("otf.chain.quitonnoreplacement",function(value) quit_on_no_replacement=value end) +local alwaysdisc=true registerdirective("otf.alwaysdisc",function(v) alwaysdisc=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") @@ -18160,7 +18423,7 @@ local copy_node=nuts.copy local copy_node_list=nuts.copy_list local find_node_tail=nuts.tail local flush_node_list=nuts.flush_list -local free_node=nuts.free +local flush_node=nuts.flush_node local end_of_math=nuts.end_of_math local traverse_nodes=nuts.traverse local traverse_id=nuts.traverse_id @@ -18195,9 +18458,11 @@ local getligaindex=injections.getligaindex local cursonce=true local fonthashes=fonts.hashes local fontdata=fonthashes.identifiers +local fontfeatures=fonthashes.features local otffeatures=fonts.constructors.features.otf local registerotffeature=otffeatures.register local onetimemessage=fonts.loggers.onetimemessage or function() end +local getrandom=utilities and utilities.randomizer and utilities.randomizer.get otf.defaultnodealternate="none" local tfmdata=false local characters=false @@ -18206,6 +18471,7 @@ local marks=false local currentfont=false local factor=0 local threshold=0 +local checkmarks=false local sweepnode=nil local sweepprev=nil local sweepnext=nil @@ -18214,17 +18480,17 @@ local notmatchpre={} local notmatchpost={} local notmatchreplace={} local handlers={} -local function isspace(n) - if getid(n)==glue_code then - local w=getfield(n,"width") - if w>=threshold then - return 32 - end - end -end +local isspace=injections.isspace +local getthreshold=injections.getthreshold 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 checkdisccontent(d) + local pre,post,replace=getdisc(d) + if pre then for n in traverse_id(glue_code,pre) do print("pre",nodes.idstostring(pre)) break end end + if post then for n in traverse_id(glue_code,post) do print("pos",nodes.idstostring(post)) break end end + if replace then for n in traverse_id(glue_code,replace) do print("rep",nodes.idstostring(replace)) break end end +end local function logprocess(...) if trace_steps then registermessage(...) @@ -18285,7 +18551,7 @@ end local function copy_glyph(g) local components=getfield(g,"components") if components then - setfield(g,"components",nil) + setfield(g,"components") local n=copy_node(g) copyinjection(n,g) setfield(g,"components",components) @@ -18297,11 +18563,18 @@ local function copy_glyph(g) end end local function flattendisk(head,disc) - local _,_,replace,_,_,replacetail=getdisc(disc,true) - setfield(disc,"replace",nil) - free_node(disc) - if head==disc then - local next=getnext(disc) + local pre,post,replace,pretail,posttail,replacetail=getdisc(disc,true) + local prev,next=getboth(disc) + local ishead=head==disc + setdisc(disc) + flush_node(disc) + if pre then + flush_node_list(pre) + end + if post then + flush_node_list(post) + end + if ishead then if replace then if next then setlink(replacetail,next) @@ -18313,7 +18586,6 @@ local function flattendisk(head,disc) return end else - local prev,next=getboth(disc) if replace then if next then setlink(replacetail,next) @@ -18348,8 +18620,8 @@ local function markstoligature(head,start,stop,char) else local prev=getprev(start) local next=getnext(stop) - setprev(start,nil) - setnext(stop,nil) + setprev(start) + setnext(stop) local base=copy_glyph(start) if head==start then head=base @@ -18396,8 +18668,8 @@ local function toligature(head,start,stop,char,dataset,sequence,markflag,discfou local prev=getprev(start) local next=getnext(stop) local comp=start - setprev(start,nil) - setnext(stop,nil) + setprev(start) + setnext(stop) local base=copy_glyph(start) if start==head then head=base @@ -18461,45 +18733,45 @@ local function toligature(head,start,stop,char,dataset,sequence,markflag,discfou local pre,post,replace,pretail,posttail,replacetail=getdisc(discfound,true) if not replace then local prev=getprev(base) -local current=comp -local previous=nil -local copied=nil -while current do - if getid(current)==glyph_code then - local n=copy_node(current) - if copied then - setlink(previous,n) - else - copied=n - end - previous=n - end - current=getnext(current) -end - setprev(discnext,nil) - setnext(discprev,nil) + local current=comp + local previous=nil + local copied=nil + while current do + if getid(current)==glyph_code then + local n=copy_node(current) + if copied then + setlink(previous,n) + else + copied=n + end + previous=n + end + current=getnext(current) + end + setprev(discnext) + setnext(discprev) if pre then setlink(discprev,pre) end pre=comp if post then setlink(posttail,discnext) - setprev(post,nil) + setprev(post) else post=discnext end setlink(prev,discfound) setlink(discfound,next) - setboth(base,nil,nil) + setboth(base) setfield(base,"components",copied) - setdisc(discfound,pre,post,base,discretionary_code) + setdisc(discfound,pre,post,base) base=prev end end end return head,base end -local function multiple_glyphs(head,start,multiple,ignoremarks) +local function multiple_glyphs(head,start,multiple,ignoremarks,what) local nofmultiples=#multiple if nofmultiples>0 then resetinjection(start) @@ -18513,6 +18785,17 @@ local function multiple_glyphs(head,start,multiple,ignoremarks) insert_node_after(head,start,n) start=n end + if what==true then + elseif what>1 then + local m=multiple[nofmultiples] + for i=2,what do + local n=copy_node(start) + resetinjection(n) + setchar(n,m) + insert_node_after(head,start,n) + start=n + end + end end return head,start,true else @@ -18525,7 +18808,7 @@ end local function get_alternative_glyph(start,alternatives,value) local n=#alternatives if value=="random" then - local r=random(1,n) + local r=getrandom and getrandom("glyph",1,n) or 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) @@ -18583,7 +18866,7 @@ function handlers.gsub_multiple(head,start,dataset,sequence,multiple) if trace_multiples then logprocess("%s: replacing %s by multiple %s",pref(dataset,sequence),gref(getchar(start)),gref(multiple)) end - return multiple_glyphs(head,start,multiple,sequence.flags[1]) + return multiple_glyphs(head,start,multiple,sequence.flags[1],dataset[1]) end function handlers.gsub_ligature(head,start,dataset,sequence,ligature) local current=getnext(start) @@ -18804,7 +19087,7 @@ function handlers.gpos_mark2base(head,start,dataset,sequence,markanchors,rlmode) local ba=markanchors[1][basechar] if ba then local ma=markanchors[2] - local dx,dy,bound=setmark(start,base,factor,rlmode,ba,ma,characters[basechar]) + local dx,dy,bound=setmark(start,base,factor,rlmode,ba,ma,characters[basechar],false,checkmarks) if trace_marks then logprocess("%s, anchor %s, bound %s: anchoring mark %s to basechar %s => (%p,%p)", pref(dataset,sequence),anchor,bound,gref(markchar),gref(basechar),dx,dy) @@ -18859,7 +19142,7 @@ function handlers.gpos_mark2ligature(head,start,dataset,sequence,markanchors,rlm local index=getligaindex(start) ba=ba[index] if ba then - local dx,dy,bound=setmark(start,base,factor,rlmode,ba,ma,characters[basechar]) + local dx,dy,bound=setmark(start,base,factor,rlmode,ba,ma,characters[basechar],false,checkmarks) if trace_marks then logprocess("%s, anchor %s, index %s, bound %s: anchoring mark %s to baselig %s at index %s => (%p,%p)", pref(dataset,sequence),anchor,index,bound,gref(markchar),gref(basechar),index,dx,dy) @@ -18906,7 +19189,7 @@ function handlers.gpos_mark2mark(head,start,dataset,sequence,markanchors,rlmode) local ba=markanchors[1][basechar] if ba then local ma=markanchors[2] - local dx,dy,bound=setmark(start,base,factor,rlmode,ba,ma,characters[basechar],true) + local dx,dy,bound=setmark(start,base,factor,rlmode,ba,ma,characters[basechar],true,checkmarks) if trace_marks then logprocess("%s, anchor %s, bound %s: anchoring mark %s to basemark %s => (%p,%p)", pref(dataset,sequence),anchor,bound,gref(markchar),gref(basechar),dx,dy) @@ -19038,7 +19321,7 @@ function chainprocs.gsub_multiple(head,start,stop,dataset,sequence,currentlookup if trace_multiples then logprocess("%s: replacing %s by multiple characters %s",cref(dataset,sequence),gref(startchar),gref(replacement)) end - return multiple_glyphs(head,start,replacement,sequence.flags[1]) + return multiple_glyphs(head,start,replacement,sequence.flags[1],dataset[1]) end return head,start,false end @@ -19050,7 +19333,7 @@ function chainprocs.gsub_alternate(head,start,stop,dataset,sequence,currentlooku end local kind=dataset[4] local what=dataset[1] - local value=what==true and tfmdata.shared.features[kind] or what + local value=what==true and tfmdata.shared.features[kind] or what local current=start while current do local currentchar=ischar(current) @@ -19288,7 +19571,7 @@ function chainprocs.gpos_mark2base(head,start,stop,dataset,sequence,currentlooku if ba then local ma=markanchors[2] if ma then - local dx,dy,bound=setmark(start,base,factor,rlmode,ba,ma,characters[basechar]) + local dx,dy,bound=setmark(start,base,factor,rlmode,ba,ma,characters[basechar],false,checkmarks) if trace_marks then logprocess("%s, anchor %s, bound %s: anchoring mark %s to basechar %s => (%p,%p)", cref(dataset,sequence),anchor,bound,gref(markchar),gref(basechar),dx,dy) @@ -19354,7 +19637,7 @@ function chainprocs.gpos_mark2ligature(head,start,stop,dataset,sequence,currentl local index=getligaindex(start) ba=ba[index] if ba then - local dx,dy,bound=setmark(start,base,factor,rlmode,ba,ma,characters[basechar]) + local dx,dy,bound=setmark(start,base,factor,rlmode,ba,ma,characters[basechar],false,checkmarks) if trace_marks then logprocess("%s, anchor %s, bound %s: anchoring mark %s to baselig %s at index %s => (%p,%p)", cref(dataset,sequence),anchor,a or bound,gref(markchar),gref(basechar),index,dx,dy) @@ -19406,7 +19689,7 @@ function chainprocs.gpos_mark2mark(head,start,stop,dataset,sequence,currentlooku if ba then local ma=markanchors[2] if ma then - local dx,dy,bound=setmark(start,base,factor,rlmode,ba,ma,characters[basechar],true) + local dx,dy,bound=setmark(start,base,factor,rlmode,ba,ma,characters[basechar],true,checkmarks) if trace_marks then logprocess("%s, anchor %s, bound %s: anchoring mark %s to basemark %s => (%p,%p)", cref(dataset,sequence),anchor,bound,gref(markchar),gref(basechar),dx,dy) @@ -19483,6 +19766,39 @@ end local function show_skip(dataset,sequence,char,ck,class) logwarning("%s: skipping char %s, class %a, rule %a, lookuptype %a",cref(dataset,sequence),gref(char),class,ck[1],ck[8] or ck[2]) end +local new_kern=nuts.pool.kern +local function checked(head) + local current=head + while current do + if getid(current)==glue_code then + local kern=new_kern(getfield(current,"width")) + if head==current then + local next=getnext(current) + if next then + setlink(kern,next) + end + flush_node(current) + head=kern + current=next + else + local prev,next=getboth(current) + setlink(prev,kern) + setlink(kern,next) + flush_node(current) + current=next + end + else + current=getnext(current) + end + end + return head +end +local function setdiscchecked(d,pre,post,replace) + if pre then pre=checked(pre) end + if post then post=checked(post) end + if replace then replace=checked(replace) end + setdisc(d,pre,post,replace) +end local function chaindisk(head,start,last,dataset,sequence,chainlookup,rlmode,k,ck,chainproc) if not start then return head,start,false @@ -19503,6 +19819,7 @@ local function chaindisk(head,start,last,dataset,sequence,chainlookup,rlmode,k,c local current=start local last=start local prev=getprev(start) + local hasglue=false local i=f while i<=l do local id=getid(current) @@ -19510,6 +19827,11 @@ local function chaindisk(head,start,last,dataset,sequence,chainlookup,rlmode,k,c i=i+1 last=current current=getnext(current) + elseif id==glue_code then + i=i+1 + last=current + current=getnext(current) + hasglue=true elseif id==disc_code then if keepdisc then keepdisc=false @@ -19559,8 +19881,8 @@ local function chaindisk(head,start,last,dataset,sequence,chainlookup,rlmode,k,c tail=find_node_tail(head) end setnext(sweepnode,current) - setprev(head,nil) - setnext(tail,nil) + setprev(head) + setnext(tail) appenddisc(sweepnode,head) end end @@ -19572,6 +19894,10 @@ local function chaindisk(head,start,last,dataset,sequence,chainlookup,rlmode,k,c if id==glyph_code then i=i+1 current=getnext(current) + elseif id==glue_code then + i=i+1 + current=getnext(current) + hasglue=true elseif id==disc_code then if keepdisc then keepdisc=false @@ -19613,6 +19939,9 @@ local function chaindisk(head,start,last,dataset,sequence,chainlookup,rlmode,k,c local id=getid(current) if id==glyph_code then i=i-1 + elseif id==glue_code then + i=i-1 + hasglue=true elseif id==disc_code then if keepdisc then keepdisc=false @@ -19657,8 +19986,8 @@ local function chaindisk(head,start,last,dataset,sequence,chainlookup,rlmode,k,c if cprev then setnext(cprev,lookaheaddisc) end - setprev(cf,nil) - setnext(cl,nil) + setprev(cf) + setnext(cl) if startishead then head=lookaheaddisc end @@ -19685,7 +20014,11 @@ local function chaindisk(head,start,last,dataset,sequence,chainlookup,rlmode,k,c local tail=find_node_tail(new) setlink(tail,replace) end - setdisc(lookaheaddisc,cf,post,new) + if hasglue then + setdiscchecked(lookaheaddisc,cf,post,new) + else + setdisc(lookaheaddisc,cf,post,new) + end start=getprev(lookaheaddisc) sweephead[cf]=getnext(clast) sweephead[new]=getnext(last) @@ -19708,8 +20041,8 @@ local function chaindisk(head,start,last,dataset,sequence,chainlookup,rlmode,k,c setprev(cnext,backtrackdisc) end setnext(backtrackdisc,cnext) - setprev(cf,nil) - setnext(cl,nil) + setprev(cf) + setnext(cl) local pre,post,replace,pretail,posttail,replacetail=getdisc(backtrackdisc,true) local new=copy_node_list(cf) local cnew=find_node_tail(new) @@ -19736,7 +20069,11 @@ local function chaindisk(head,start,last,dataset,sequence,chainlookup,rlmode,k,c else replace=new end - setdisc(backtrackdisc,pre,post,replace) + if hasglue then + setdiscchecked(backtrackdisc,pre,post,replace) + else + setdisc(backtrackdisc,pre,post,replace) + end start=getprev(backtrackdisc) sweephead[post]=getnext(clast) sweephead[replace]=getnext(last) @@ -19991,16 +20328,13 @@ local function handle_contextchain(head,start,dataset,sequence,contexts,rlmode) end end prev=getprev(prev) - elseif seq[n][32] then + elseif seq[n][32] and isspace(prev,threshold) then n=n-1 prev=getprev(prev) else match=false break end - elseif seq[n][32] then - n=n-1 - prev=getprev(prev) else match=false break @@ -20114,15 +20448,13 @@ local function handle_contextchain(head,start,dataset,sequence,contexts,rlmode) else end current=getnext(current) - elseif seq[n][32] then + elseif seq[n][32] and isspace(current,threshold) then n=n+1 + current=getnext(current) else match=false break end - elseif seq[n][32] then - n=n+1 - current=getnext(current) else match=false break @@ -20217,7 +20549,7 @@ local function handle_contextchain(head,start,dataset,sequence,contexts,rlmode) if replacements then head,start,done=reversesub(head,start,last,dataset,sequence,replacements,rlmode) else - done=quit_on_no_replacement + done=true if trace_contexts then logprocess("%s: skipping match",cref(dataset,sequence)) end @@ -20337,10 +20669,12 @@ function otf.dataset(tfmdata,font) } rs[language]=rl local sequences=tfmdata.resources.sequences - for s=1,#sequences do - local v=enabled and initialize(sequences[s],script,language,enabled,autoscript,autolanguage) - if v then - rl[#rl+1]=v + if sequences then + for s=1,#sequences do + local v=enabled and initialize(sequences[s],script,language,enabled,autoscript,autolanguage) + if v then + rl[#rl+1]=v + end end end end @@ -20366,10 +20700,10 @@ local function kernrun(disc,k_run,font,attr,...) break end end - if prev and (pre or replace) and not ischar(prev,font) then + if prev and not ischar(prev,font) then prev=false end - if next and (post or replace) and not ischar(next,font) then + if next and not ischar(next,font) then next=false end if pre then @@ -20395,7 +20729,7 @@ local function kernrun(disc,k_run,font,attr,...) if k_run(posttail,"postinjections",next,font,attr,...) then done=true end - setnext(posttail,nil) + setnext(posttail) setprev(next,disc) end end @@ -20417,7 +20751,7 @@ local function kernrun(disc,k_run,font,attr,...) if k_run(replacetail,"replaceinjections",next,font,attr,...) then done=true end - setnext(replacetail,nil) + setnext(replacetail) setprev(next,disc) end elseif prev and next then @@ -20484,7 +20818,7 @@ local function testrun(disc,t_run,c_run,...) setlink(replacetail,next) local ok,overflow=t_run(replace,next,...) if ok and overflow then - setfield(disc,"replace",nil) + setfield(disc,"replace") setlink(prev,replace) setboth(disc) flush_node_list(disc) @@ -20599,6 +20933,7 @@ local function t_run_single(start,stop,font,attr,lookupcache) return true,d>1 end end + else end start=getnext(start) else @@ -20712,6 +21047,7 @@ local function t_run_multiple(start,stop,font,attr,steps,nofsteps) report_missing_coverage(dataset,sequence) end end + else end start=getnext(start) else @@ -20783,6 +21119,9 @@ local function pardirstate(start) end return getnext(start),new,new end +otf.helpers=otf.helpers or {} +otf.helpers.txtdirstate=txtdirstate +otf.helpers.pardirstate=pardirstate local function featuresprocessor(head,font,attr) local sequences=sequencelists[font] if not sequencelists then @@ -20795,8 +21134,9 @@ local function featuresprocessor(head,font,attr) descriptions=tfmdata.descriptions characters=tfmdata.characters marks=tfmdata.resources.marks - factor=tfmdata.parameters.factor - threshold=tfmdata.parameters.spacing.width or 65536*10 + threshold, + factor=getthreshold(font) + checkmarks=tfmdata.properties.checkmarks elseif currentfont~=font then report_warning("nested call with a different font, level %s, quitting",nesting) nesting=nesting-1 @@ -20809,6 +21149,7 @@ local function featuresprocessor(head,font,attr) local rlmode=0 local done=false local datasets=otf.dataset(tfmdata,font,attr) + local forcedisc=alwaysdisc or not attr local dirstack={} sweephead={} for s=1,#datasets do @@ -20824,15 +21165,12 @@ local function featuresprocessor(head,font,attr) local steps=sequence.steps local nofsteps=sequence.nofsteps if not steps then - local h,d,ok=handler(head,start,dataset,sequence,nil,nil,nil,0,font,attr) + local h,d,ok=handler(head,head,dataset,sequence,nil,nil,nil,0,font,attr) if ok then success=true if h then head=h end - if d then - start=d - end end elseif typ=="gsub_reversecontextchain" then local start=find_node_tail(head) @@ -20904,16 +21242,21 @@ local function featuresprocessor(head,font,attr) elseif char==false then start=getnext(start) elseif id==disc_code then - local ok - if gpossing then - start,ok=kernrun(start,k_run_single,font,attr,lookupcache,step,dataset,sequence,rlmode,handler) - elseif typ=="gsub_ligature" then - start,ok=testrun(start,t_run_single,c_run_single,font,attr,lookupcache,step,dataset,sequence,rlmode,handler) + local a=forcedisc or getsubtype(start)==discretionary_code or getattr(start,0)==attr + if a then + local ok + if gpossing then + start,ok=kernrun(start,k_run_single,font,attr,lookupcache,step,dataset,sequence,rlmode,handler) + elseif typ=="gsub_ligature" then + start,ok=testrun(start,t_run_single,c_run_single,font,attr,lookupcache,step,dataset,sequence,rlmode,handler) + else + start,ok=comprun(start,c_run_single,font,attr,lookupcache,step,dataset,sequence,rlmode,handler) + end + if ok then + success=true + end else - start,ok=comprun(start,c_run_single,font,attr,lookupcache,step,dataset,sequence,rlmode,handler) - end - if ok then - success=true + start=getnext(start) end elseif id==math_code then start=getnext(end_of_math(start)) @@ -20965,16 +21308,21 @@ local function featuresprocessor(head,font,attr) elseif char==false then start=getnext(start) elseif id==disc_code then - local ok - if gpossing then - start,ok=kernrun(start,k_run_multiple,font,attr,steps,nofsteps,dataset,sequence,rlmode,handler) - elseif typ=="gsub_ligature" then - start,ok=testrun(start,t_run_multiple,c_run_multiple,font,attr,steps,nofsteps,dataset,sequence,rlmode,handler) + local a=forcedisc or getsubtype(start)==discretionary_code or getattr(start,0)==attr + if a then + local ok + if gpossing then + start,ok=kernrun(start,k_run_multiple,font,attr,steps,nofsteps,dataset,sequence,rlmode,handler) + elseif typ=="gsub_ligature" then + start,ok=testrun(start,t_run_multiple,c_run_multiple,font,attr,steps,nofsteps,dataset,sequence,rlmode,handler) + else + start,ok=comprun(start,c_run_multiple,font,attr,steps,nofsteps,dataset,sequence,rlmode,handler) + end + if ok then + success=true + end else - start,ok=comprun(start,c_run_multiple,font,attr,steps,nofsteps,dataset,sequence,rlmode,handler) - end - if ok then - success=true + start=getnext(start) end elseif id==math_code then start=getnext(end_of_math(start)) @@ -20999,6 +21347,30 @@ local function featuresprocessor(head,font,attr) head=tonode(head) return head,done end +local plugins={} +otf.plugins=plugins +function otf.registerplugin(name,f) + if type(name)=="string" and type(f)=="function" then + plugins[name]={ name,f } + end +end +local function plugininitializer(tfmdata,value) + if type(value)=="string" then + tfmdata.shared.plugin=plugins[value] + end +end +local function pluginprocessor(head,font) + local s=fontdata[font].shared + local p=s and s.plugin + if p then + if trace_plugins then + report_process("applying plugin %a",p[1]) + end + return p[2](head,font) + else + return head,false + end +end local function featuresinitializer(tfmdata,value) end registerotffeature { @@ -21008,18 +21380,36 @@ registerotffeature { initializers={ position=1, node=featuresinitializer, + plug=plugininitializer, }, processors={ node=featuresprocessor, + plug=pluginprocessor, } } otf.nodemodeinitializer=featuresinitializer otf.featuresprocessor=featuresprocessor otf.handlers=handlers local setspacekerns=nodes.injections.setspacekerns if not setspacekerns then os.exit() end -function otf.handlers.trigger_space_kerns(head,start,dataset,sequence,_,_,_,_,font,attr) - setspacekerns(font,sequence) - return head,start,true +if fontfeatures then + function otf.handlers.trigger_space_kerns(head,start,dataset,sequence,_,_,_,_,font,attr) + local features=fontfeatures[font] + local enabled=features and features.spacekern and features.kern + if enabled then + setspacekerns(font,sequence) + end + return head,start,enabled + end +else + function otf.handlers.trigger_space_kerns(head,start,dataset,sequence,_,_,_,_,font,attr) + local shared=fontdata[font].shared + local features=shared and shared.features + local enabled=features and features.spacekern and features.kern + if enabled then + setspacekerns(font,sequence) + end + return head,start,enabled + end end local function hasspacekerns(data) local sequences=data.resources.sequences @@ -21053,8 +21443,8 @@ otf.readers.registerextender { local function spaceinitializer(tfmdata,value) local resources=tfmdata.resources local spacekerns=resources and resources.spacekerns - if spacekerns==nil then - local properties=tfmdata.properties + local properties=tfmdata.properties + if value and spacekerns==nil then if properties and properties.hasspacekerns then local sequences=resources.sequences local left={} @@ -21067,28 +21457,55 @@ local function spaceinitializer(tfmdata,value) if steps then local kern=sequence.features.kern if kern then - feat=feat or kern + if feat then + for script,languages in next,kern do + local f=feat[script] + if f then + for l in next,languages do + f[l]=true + end + else + feat[script]=languages + end + end + else + feat=kern + end for i=1,#steps do local step=steps[i] local coverage=step.coverage - if coverage then + local rules=step.rules + local format=step.format + if rules then + elseif coverage then + local single=format==gpos_single local kerns=coverage[32] if kerns then for k,v in next,kerns do - if type(v)=="table" then - right[k]=v[3] - else + if type(v)~="table" then right[k]=v + elseif single then + right[k]=v[3] + else + local one=v[1] + if one then + right[k]=one[3] + end end end end for k,v in next,coverage do local kern=v[32] if kern then - if type(kern)=="table" then - left[k]=kern[3] - else + if type(kern)~="table" then left[k]=kern + elseif single then + left[k]=v[3] + else + local one=v[1] + if one then + left[k]=one[3] + end end end end @@ -21136,6 +21553,18 @@ registerotffeature { node=spaceinitializer, }, } +local function markinitializer(tfmdata,value) + local properties=tfmdata.properties + properties.checkmarks=value +end +registerotffeature { + name="checkmarks", + description="check mark widths", + default=true, + initializers={ + node=markinitializer, + }, +} end -- closure @@ -21178,9 +21607,9 @@ local setprop=nuts.setprop local ischar=nuts.is_char local insert_node_after=nuts.insert_after local copy_node=nuts.copy -local free_node=nuts.free local remove_node=nuts.remove local flush_list=nuts.flush_list +local flush_node=nuts.flush_node local copyinjection=nodes.injections.copy local unsetvalue=attributes.unsetvalue local fontdata=fonts.hashes.identifiers @@ -21539,9 +21968,11 @@ local function initializedevanagi(tfmdata) local steps=sequence.steps local nofsteps=sequence.nofsteps local features=sequence.features - if features["rphf"] then + local has_rphf=features.rphf + local has_blwf=features.blwf + if has_rphf and has_rphf.deva then devanagari.reph=true - elseif features["blwf"] then + elseif has_blwf and has_blwf.deva then devanagari.vattu=true for i=1,nofsteps do local step=steps[i] @@ -21555,57 +21986,60 @@ local function initializedevanagi(tfmdata) end end end - if valid[kind] then - for i=1,nofsteps do - local step=steps[i] - local coverage=step.coverage - if coverage then - local reph=false - if step.osdstep then - for k,v in next,ra do - local r=coverage[k] - if r then - local h=false - for k,v in next,halant do - local h=r[k] - if h then - reph=h.ligature or false - break + for kind,spec in next,features do + if spec.dev2 and valid[kind] then + for i=1,nofsteps do + local step=steps[i] + local coverage=step.coverage + if coverage then + local reph=false + if kind=="rphf" then + if true then + for k,v in next,ra do + local r=coverage[k] + if r then + local h=false + for k,v in next,halant do + local h=r[k] + if h then + reph=h.ligature or false + break + end + end + if reph then + break + end end end - if reph then - break - end + else end end - else + seqsubset[#seqsubset+1]={ kind,coverage,reph } end - seqsubset[#seqsubset+1]={ kind,coverage,reph } end end - end - if kind=="pref" then - local sequence=dataset[3] - local steps=sequence.steps - local nofsteps=sequence.nofsteps - for i=1,nofsteps do - local step=steps[i] - local coverage=step.coverage - if coverage then - for k,v in next,halant do - local h=coverage[k] - if h then - local found=false - for k,v in next,h do - found=v and v.ligature + if kind=="pref" then + local steps=sequence.steps + local nofsteps=sequence.nofsteps + for i=1,nofsteps do + local step=steps[i] + local coverage=step.coverage + if coverage then + for k,v in next,halant do + local h=coverage[k] + if h then + local found=false + for k,v in next,h do + found=v and v.ligature + if found then + pre_base_reordering_consonants[k]=found + break + end + end if found then - pre_base_reordering_consonants[k]=found break end end - if found then - break - end end end end @@ -21690,7 +22124,7 @@ local function deva_reorder(head,start,stop,font,attr,nbspaces) if current==stop then stop=getprev(stop) head=remove_node(head,current) - free_node(current) + flush_node(current) return head,stop,nbspaces else nbspaces=nbspaces+1 @@ -21728,7 +22162,7 @@ local function deva_reorder(head,start,stop,font,attr,nbspaces) setchar(current,getchar(tempcurrent)) local freenode=getnext(current) setlink(current,tmp) - free_node(freenode) + flush_node(freenode) flush_list(tempcurrent) if changestop then stop=current @@ -21965,7 +22399,7 @@ local function deva_reorder(head,start,stop,font,attr,nbspaces) if getchar(base)==c_nbsp then nbspaces=nbspaces-1 head=remove_node(head,base) - free_node(base) + flush_node(base) end return head,stop,nbspaces end @@ -21992,6 +22426,8 @@ function handlers.devanagari_reorder_matras(head,start) start=startnext break end + else + break end current=next end @@ -22004,12 +22440,12 @@ function handlers.devanagari_reorder_reph(head,start) local startfont=getfont(start) local startattr=getprop(start,a_syllabe) while current do - local char=ischar(current,font) + local char=ischar(current,startfont) if char and getprop(current,a_syllabe)==startattr then if halant[char] and not getprop(current,a_state) then local next=getnext(current) if next then - local nextchar=ischar(next,font) + local nextchar=ischar(next,startfont) if nextchar and zw_char[nextchar] and getprop(next,a_syllabe)==startattr then current=next next=getnext(current) @@ -22031,7 +22467,7 @@ function handlers.devanagari_reorder_reph(head,start) if not startnext then current=getnext(start) while current do - local char=ischar(current,font) + local char=ischar(current,startfont) if char and getprop(current,a_syllabe)==startattr then if getprop(current,a_state)==s_pstf then startnext=getnext(start) @@ -22053,7 +22489,7 @@ function handlers.devanagari_reorder_reph(head,start) current=getnext(start) local c=nil while current do - local char=ischar(current,font) + local char=ischar(current,startfont) if char and getprop(current,a_syllabe)==startattr then if not c and mark_above_below_post[char] and reorder_class[char]~="after subscript" then c=current @@ -22077,7 +22513,7 @@ function handlers.devanagari_reorder_reph(head,start) current=start local next=getnext(current) while next do - local nextchar=ischar(next,font) + local nextchar=ischar(next,startfont) if nextchar and getprop(next,a_syllabe)==startattr then current=next next=getnext(current) @@ -22090,7 +22526,7 @@ function handlers.devanagari_reorder_reph(head,start) head=remove_node(head,start) local next=getnext(current) setlink(start,next) - setlink(current,"next",start) + setlink(current,start) start=startnext end end @@ -22103,12 +22539,12 @@ function handlers.devanagari_reorder_pre_base_reordering_consonants(head,start) local startfont=getfont(start) local startattr=getprop(start,a_syllabe) while current do - local char=ischar(current,font) + local char=ischar(current,startfont) if char and getprop(current,a_syllabe)==startattr then local next=getnext(current) if halant[char] and not getprop(current,a_state) then if next then - local nextchar=ischar(next,font) + local nextchar=ischar(next,startfont) if nextchar and getprop(next,a_syllabe)==startattr then if nextchar==c_zwnj or nextchar==c_zwj then current=next @@ -22132,13 +22568,13 @@ function handlers.devanagari_reorder_pre_base_reordering_consonants(head,start) current=getnext(start) startattr=getprop(start,a_syllabe) while current do - local char=ischar(current,font) + local char=ischar(current,startfont) if char and getprop(current,a_syllabe)==startattr then if not consonant[char] and getprop(current,a_state) then startnext=getnext(start) removenode(start,start) local prev=getprev(current) - setlink(start,prev) + setlink(prev,start) setlink(start,current) start=startnext break @@ -22198,21 +22634,7 @@ local function dev2_reorder(head,start,stop,font,attr,nbspaces) local kind=subset[1] local lookupcache=subset[2] if kind=="rphf" then - for k,v in next,ra do - local r=lookupcache[k] - if r then - for k,v in next,halant do - local h=r[k] - if h then - reph=h.ligature or false - break - end - end - if reph then - break - end - end - end + reph=subset[3] local current=start local last=getnext(stop) while current~=last do @@ -22245,7 +22667,7 @@ local function dev2_reorder(head,start,stop,font,attr,nbspaces) if current~=stop then local c=locl[current] or getchar(current) local found=lookupcache[c] - if found then + if found then local next=getnext(current) local n=locl[next] or getchar(next) if found[n] then @@ -22333,7 +22755,7 @@ local function dev2_reorder(head,start,stop,font,attr,nbspaces) if current==stop then stop=getprev(stop) head=remove_node(head,current) - free_node(current) + flush_node(current) return head,stop,nbspaces else nbspaces=nbspaces+1 @@ -22472,7 +22894,7 @@ local function dev2_reorder(head,start,stop,font,attr,nbspaces) local prev=getprev(current) if prev~=target then local next=getnext(current) - setlink(next,prev) + setlink(prev,next) if current==stop then stop=prev end @@ -22503,7 +22925,7 @@ local function dev2_reorder(head,start,stop,font,attr,nbspaces) stop=current end local prev=getprev(c) - setlink(next,prev) + setlink(prev,next) local nextnext=getnext(next) setnext(current,nextnext) local nextnextnext=getnext(nextnext) @@ -22516,9 +22938,12 @@ local function dev2_reorder(head,start,stop,font,attr,nbspaces) current=getnext(current) end if getchar(base)==c_nbsp then + if base==stop then + stop=getprev(stop) + end nbspaces=nbspaces-1 head=remove_node(head,base) - free_node(base) + flush_node(base) end return head,stop,nbspaces end @@ -22557,7 +22982,7 @@ local function analyze_next_chars_one(c,font,variant) elseif (vv==c_zwnj or vv==c_zwj) and halant[vvv] then local nnnn=getnext(nnn) if nnnn then - local vvvv=ischar(nnnn) + local vvvv=ischar(nnnn,font) if vvvv and consonant[vvvv] then c=nnnn end @@ -22580,7 +23005,7 @@ local function analyze_next_chars_one(c,font,variant) local nn=getnext(n) if nn then local vv=ischar(nn,font) - if vv and zw_char[vv] then + if vv and zw_char[v] then n=nn v=vv nn=getnext(nn) @@ -23117,32 +23542,77 @@ if not modules then modules={} end modules ['font-ocl']={ copyright="PRAGMA ADE / ConTeXt Development Team", license="see context related readme files" } -local tostring,next=tostring,next +local tostring,next,format=tostring,next,string.format +local round,max=math.round,math.round local formatters=string.formatters +local tounicode=fonts.mappings.tounicode local otf=fonts.handlers.otf -local f_color_start=formatters["pdf:direct: %f %f %f rg"] -local s_color_stop="pdf:direct:" +local f_color=formatters["pdf:direct:%f %f %f rg"] +local f_gray=formatters["pdf:direct:%f g"] +local s_black="pdf:direct:0 g" if context then local startactualtext=nil local stopactualtext=nil - function otf.getactualtext(n) + function otf.getactualtext(s) if not startactualtext then - startactualtext=backends.codeinjections.startunicodetoactualtext - stopactualtext=backends.codeinjections.stopunicodetoactualtext + startactualtext=backends.codeinjections.startunicodetoactualtextdirect + stopactualtext=backends.codeinjections.stopunicodetoactualtextdirect end - return startactualtext(n),stopactualtext() + return startactualtext(s),stopactualtext() end else local tounicode=fonts.mappings.tounicode16 - function otf.getactualtext(n) - return "/Span << /ActualText <feff"..tounicode(n).."> >> BDC","EMC" + function otf.getactualtext(s) + return + "/Span << /ActualText <feff"..n.."> >> BDC", + "EMC" + end +end +local sharedpalettes={} +if context then + local graytorgb=attributes.colors.graytorgb + local cmyktorgb=attributes.colors.cmyktorgb + function otf.registerpalette(name,values) + sharedpalettes[name]=values + for i=1,#values do + local v=values[i] + local r,g,b + local s=v.s + if s then + r,g,b=graytorgb(s) + else + local c,m,y,k=v.c,v.m,v.y,v.k + if c or m or y or k then + r,g,b=cmyktorgb(c or 0,m or 0,y or 0,k or 0) + else + r,g,b=v.r,v.g,v.b + end + end + values[i]={ + max(r and round(r*255) or 0,255), + max(g and round(g*255) or 0,255), + max(b and round(b*255) or 0,255) + } + end + end +else + function otf.registerpalette(name,values) + sharedpalettes[name]=values + for i=1,#values do + local v=values[i] + values[i]={ + max(round((v.r or 0)*255),255), + max(round((v.g or 0)*255),255), + max(round((v.b or 0)*255),255) + } + end end end local function initializecolr(tfmdata,kind,value) if value then local palettes=tfmdata.resources.colorpalettes if palettes then - local palette=palettes[tonumber(value) or 1] or palettes[1] or {} + local palette=sharedpalettes[value] or palettes[tonumber(value) or 1] or palettes[1] or {} local classes=#palette if classes==0 then return @@ -23157,7 +23627,12 @@ local function initializecolr(tfmdata,kind,value) } for i=1,classes do local p=palette[i] - colorvalues[i]={ "special",f_color_start(p[1]/255,p[2]/255,p[3]/255) } + local r,g,b=p[1],p[2],p[3] + if r==g and g==b then + colorvalues[i]={ "special",f_gray(r/255) } + else + colorvalues[i]={ "special",f_color(r/255,g/255,b/255) } + end end local getactualtext=otf.getactualtext for unicode,character in next,characters do @@ -23165,22 +23640,24 @@ local function initializecolr(tfmdata,kind,value) if description then local colorlist=description.colors if colorlist then - local b,e=getactualtext(unicode) + local b,e=getactualtext(tounicode(characters[unicode].unicode or 0xFFFD)) local w=character.width or 0 local s=#colorlist - local n=1 local t={ - { "special","pdf:direct: q "..b } + { "special","pdf:page:q" }, + { "special","pdf:raw:"..b } } + local n=#t for i=1,s do local entry=colorlist[i] - n=n+1 t[n]=colorvalues[entry.class] + n=n+1 t[n]=colorvalues[entry.class] or s_black n=n+1 t[n]={ "char",entry.slot } if s>1 and i<s and w~=0 then n=n+1 t[n]={ "right",-w } end end - n=n+1 t[n]={ "special","pdf:direct:"..e.." Q" } + n=n+1 t[n]={ "special","pdf:page:"..e } + n=n+1 t[n]={ "special","pdf:raw:Q" } character.commands=t end end @@ -23234,70 +23711,62 @@ do end end end -if context and xml.convert then +do local report_svg=logs.reporter("fonts","svg conversion") - local xmlconvert=xml.convert - local xmlfirst=xml.first local loaddata=io.loaddata local savedata=io.savedata local remove=os.remove + if context and xml.convert then + local xmlconvert=xml.convert + local xmlfirst=xml.first + function otfsvg.filterglyph(entry,index) + local svg=xmlconvert(entry.data) + local root=svg and xmlfirst(svg,"/svg[@id='glyph"..index.."']") + local data=root and tostring(root) + return data + end + else + function otfsvg.filterglyph(entry,index) + return entry.data + end + end function otfsvg.topdf(svgshapes) - local inkscape=io.popen("inkscape --shell 2>&1","w") + local inkscape=io.popen("inkscape --shell > temp-otf-svg-shape.log","w") local pdfshapes={} local nofshapes=#svgshapes local f_svgfile=formatters["temp-otf-svg-shape-%i.svg"] local f_pdffile=formatters["temp-otf-svg-shape-%i.pdf"] local f_convert=formatters["%s --export-pdf=%s\n"] + local filterglyph=otfsvg.filterglyph report_svg("processing %i svg containers",nofshapes) statistics.starttiming() for i=1,nofshapes do local entry=svgshapes[i] - for j=entry.first,entry.last do - local svg=xmlconvert(entry.data) - local root=svg and xmlfirst(svg,"/svg[@id='glyph"..j.."']") - local data=root and tostring(root) + for index=entry.first,entry.last do + local data=filterglyph(entry,index) if data and data~="" then - local svgfile=f_svgfile(j) - local pdffile=f_pdffile(j) + local svgfile=f_svgfile(index) + local pdffile=f_pdffile(index) savedata(svgfile,data) inkscape:write(f_convert(svgfile,pdffile)) - pdfshapes[j]=true + pdfshapes[index]=true end end end inkscape:write("quit\n") inkscape:close() report_svg("processing %i pdf results",nofshapes) - for i in next,pdfshapes do - local svgfile=f_svgfile(i) - local pdffile=f_pdffile(i) - pdfshapes[i]=loaddata(pdffile) + for index in next,pdfshapes do + local svgfile=f_svgfile(index) + local pdffile=f_pdffile(index) + pdfshapes[index]=loaddata(pdffile) remove(svgfile) remove(pdffile) end statistics.stoptiming() - report_svg("conversion time: %0.3f",statistics.elapsedtime()) - return pdfshapes - end -else - function otfsvg.topdf(svgshapes) - local svgfile="temp-otf-svg-shape.svg" - local pdffile="temp-otf-svg-shape.pdf" - local command="inkscape "..svgfile.." --export-pdf="..pdffile - local pdfshapes={} - local nofshapes=#svgshapes - texio.write(formatters["[converting %i svg glyphs to pdf using command %q : "](nofshapes,command)) - for i=1,nofshapes do - local entry=svgshapes[i] - for j=entry.first,entry.last do - texio.write(formatters["%i "](j)) - io.savedata(svgfile,tostring(entry.data)) - os.execute(command) - pdfshapes[j]=io.loaddata(pdffile) - end + if statistics.elapsedseconds then + report_svg("svg conversion time %s",statistics.elapsedseconds()) end - os.remove(svgfile) - texio.write("done]") return pdfshapes end end @@ -23372,6 +23841,777 @@ end -- closure do -- begin closure to overcome local limits and interference +if not modules then modules={} end modules ['font-otc']={ + version=1.001, + comment="companion to font-otf.lua (context)", + author="Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright="PRAGMA ADE / ConTeXt Development Team", + license="see context related readme files" +} +local format,insert,sortedkeys,tohash=string.format,table.insert,table.sortedkeys,table.tohash +local type,next=type,next +local lpegmatch=lpeg.match +local utfbyte,utflen=utf.byte,utf.len +local trace_loading=false trackers.register("otf.loading",function(v) trace_loading=v end) +local report_otf=logs.reporter("fonts","otf loading") +local fonts=fonts +local otf=fonts.handlers.otf +local registerotffeature=otf.features.register +local setmetatableindex=table.setmetatableindex +local normalized={ + substitution="substitution", + single="substitution", + ligature="ligature", + alternate="alternate", + multiple="multiple", + kern="kern", + pair="pair", + chainsubstitution="chainsubstitution", + chainposition="chainposition", +} +local types={ + substitution="gsub_single", + ligature="gsub_ligature", + alternate="gsub_alternate", + multiple="gsub_multiple", + kern="gpos_pair", + pair="gpos_pair", + chainsubstitution="gsub_contextchain", + chainposition="gpos_contextchain", +} +local names={ + gsub_single="gsub", + gsub_multiple="gsub", + gsub_alternate="gsub", + gsub_ligature="gsub", + gsub_context="gsub", + gsub_contextchain="gsub", + gsub_reversecontextchain="gsub", + gpos_single="gpos", + gpos_pair="gpos", + gpos_cursive="gpos", + gpos_mark2base="gpos", + gpos_mark2ligature="gpos", + gpos_mark2mark="gpos", + gpos_context="gpos", + gpos_contextchain="gpos", +} +setmetatableindex(types,function(t,k) t[k]=k return k end) +local everywhere={ ["*"]={ ["*"]=true } } +local noflags={ false,false,false,false } +local function getrange(sequences,category) + local count=#sequences + local first=nil + local last=nil + for i=1,count do + local t=sequences[i].type + if t and names[t]==category then + if not first then + first=i + end + last=i + end + end + return first or 1,last or count +end +local function validspecification(specification,name) + local dataset=specification.dataset + if dataset then + elseif specification[1] then + dataset=specification + specification={ dataset=dataset } + else + dataset={ { data=specification.data } } + specification.data=nil + specification.dataset=dataset + end + local first=dataset[1] + if first then + first=first.data + end + if not first then + report_otf("invalid feature specification, no dataset") + return + end + if type(name)~="string" then + name=specification.name or first.name + end + if type(name)~="string" then + report_otf("invalid feature specification, no name") + return + end + local n=#dataset + if n>0 then + for i=1,n do + setmetatableindex(dataset[i],specification) + end + return specification,name + end +end +local function addfeature(data,feature,specifications) + if not specifications then + report_otf("missing specification") + return + end + local descriptions=data.descriptions + local resources=data.resources + local features=resources.features + local sequences=resources.sequences + if not features or not sequences then + report_otf("missing specification") + return + end + local alreadydone=resources.alreadydone + if not alreadydone then + alreadydone={} + resources.alreadydone=alreadydone + end + if alreadydone[specifications] then + return + else + alreadydone[specifications]=true + end + local fontfeatures=resources.features or everywhere + local unicodes=resources.unicodes + local splitter=lpeg.splitter(" ",unicodes) + local done=0 + local skip=0 + local aglunicodes=false + local specifications=validspecification(specifications,feature) + if not specifications then + return + end + local function tounicode(code) + if not code then + return + end + if type(code)=="number" then + return code + end + local u=unicodes[code] + if u then + return u + end + if utflen(code)==1 then + u=utfbyte(code) + if u then + return u + end + end + if not aglunicodes then + aglunicodes=fonts.encodings.agl.unicodes + end + return aglunicodes[code] + end + local coverup=otf.coverup + local coveractions=coverup.actions + local stepkey=coverup.stepkey + local register=coverup.register + local function prepare_substitution(list,featuretype) + local coverage={} + local cover=coveractions[featuretype] + for code,replacement in next,list do + local unicode=tounicode(code) + local description=descriptions[unicode] + if description then + if type(replacement)=="table" then + replacement=replacement[1] + end + replacement=tounicode(replacement) + if replacement and descriptions[replacement] then + cover(coverage,unicode,replacement) + done=done+1 + else + skip=skip+1 + end + else + skip=skip+1 + end + end + return coverage + end + local function prepare_alternate(list,featuretype) + local coverage={} + local cover=coveractions[featuretype] + for code,replacement in next,list do + local unicode=tounicode(code) + local description=descriptions[unicode] + if not description then + skip=skip+1 + elseif type(replacement)=="table" then + local r={} + for i=1,#replacement do + local u=tounicode(replacement[i]) + r[i]=descriptions[u] and u or unicode + end + cover(coverage,unicode,r) + done=done+1 + else + local u=tounicode(replacement) + if u then + cover(coverage,unicode,{ u }) + done=done+1 + else + skip=skip+1 + end + end + end + return coverage + end + local function prepare_multiple(list,featuretype) + local coverage={} + local cover=coveractions[featuretype] + for code,replacement in next,list do + local unicode=tounicode(code) + local description=descriptions[unicode] + if not description then + skip=skip+1 + elseif type(replacement)=="table" then + local r,n={},0 + for i=1,#replacement do + local u=tounicode(replacement[i]) + if descriptions[u] then + n=n+1 + r[n]=u + end + end + if n>0 then + cover(coverage,unicode,r) + done=done+1 + else + skip=skip+1 + end + else + local u=tounicode(replacement) + if u then + cover(coverage,unicode,{ u }) + done=done+1 + else + skip=skip+1 + end + end + end + return coverage + end + local function prepare_ligature(list,featuretype) + local coverage={} + local cover=coveractions[featuretype] + for code,ligature in next,list do + local unicode=tounicode(code) + local description=descriptions[unicode] + if description then + if type(ligature)=="string" then + ligature={ lpegmatch(splitter,ligature) } + end + local present=true + for i=1,#ligature do + local l=ligature[i] + local u=tounicode(l) + if descriptions[u] then + ligature[i]=u + else + present=false + break + end + end + if present then + cover(coverage,unicode,ligature) + done=done+1 + else + skip=skip+1 + end + else + skip=skip+1 + end + end + return coverage + end + local function prepare_kern(list,featuretype) + local coverage={} + local cover=coveractions[featuretype] + for code,replacement in next,list do + local unicode=tounicode(code) + local description=descriptions[unicode] + if description and type(replacement)=="table" then + local r={} + for k,v in next,replacement do + local u=tounicode(k) + if u then + r[u]=v + end + end + if next(r) then + cover(coverage,unicode,r) + done=done+1 + else + skip=skip+1 + end + else + skip=skip+1 + end + end + return coverage + end + local function prepare_pair(list,featuretype) + local coverage={} + local cover=coveractions[featuretype] + if cover then + for code,replacement in next,list do + local unicode=tounicode(code) + local description=descriptions[unicode] + if description and type(replacement)=="table" then + local r={} + for k,v in next,replacement do + local u=tounicode(k) + if u then + r[u]=v + end + end + if next(r) then + cover(coverage,unicode,r) + done=done+1 + else + skip=skip+1 + end + else + skip=skip+1 + end + end + else + report_otf("unknown cover type %a",featuretype) + end + return coverage + end + local function prepare_chain(list,featuretype,sublookups) + local rules=list.rules + local coverage={} + if rules then + local rulehash={} + local rulesize=0 + local sequence={} + local nofsequences=0 + local lookuptype=types[featuretype] + 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 or false + 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 + local lookups=rule.lookups or false + local subtype=nil + if lookups and sublookups then + for k,v in next,lookups do + local lookup=sublookups[v] + if lookup then + lookups[k]=lookup + if not subtype then + subtype=lookup.type + end + else + end + end + end + if nofsequences>0 then + local hashed={} + for i=1,nofsequences do + local t={} + local s=sequence[i] + for i=1,#s do + local u=tounicode(s[i]) + if u then + t[u]=true + end + end + hashed[i]=t + end + sequence=hashed + rulesize=rulesize+1 + rulehash[rulesize]={ + nofrules, + lookuptype, + sequence, + start, + stop, + lookups, + replacements, + subtype, + } + for unic in next,sequence[start] do + local cu=coverage[unic] + if not cu then + coverage[unic]=rulehash + end + end + end + end + end + return coverage + end + local dataset=specifications.dataset + local function report(name,category,position,first,last,sequences) + report_otf("injecting name %a of category %a at position %i in [%i,%i] of [%i,%i]", + name,category,position,first,last,1,#sequences) + end + local function inject(specification,sequences,sequence,first,last,category,name) + local position=specification.position or false + if not position then + position=specification.prepend + if position==true then + if trace_loading then + report(name,category,first,first,last,sequences) + end + insert(sequences,first,sequence) + return + end + end + if not position then + position=specification.append + if position==true then + if trace_loading then + report(name,category,last+1,first,last,sequences) + end + insert(sequences,last+1,sequence) + return + end + end + local kind=type(position) + if kind=="string" then + local index=false + for i=first,last do + local s=sequences[i] + local f=s.features + if f then + for k in next,f do + if k==position then + index=i + break + end + end + if index then + break + end + end + end + if index then + position=index + else + position=last+1 + end + elseif kind=="number" then + if position<0 then + position=last-position+1 + end + if position>last then + position=last+1 + elseif position<first then + position=first + end + else + position=last+1 + end + if trace_loading then + report(name,category,position,first,last,sequences) + end + insert(sequences,position,sequence) + end + for s=1,#dataset do + local specification=dataset[s] + local valid=specification.valid + local feature=specification.name or feature + if not feature or feature=="" then + report_otf("no valid name given for extra feature") + elseif not valid or valid(data,specification,feature) then + local initialize=specification.initialize + if initialize then + specification.initialize=initialize(specification,data) and initialize or nil + end + local askedfeatures=specification.features or everywhere + local askedsteps=specification.steps or specification.subtables or { specification.data } or {} + local featuretype=normalized[specification.type or "substitution"] or "substitution" + local featureflags=specification.flags or noflags + local featureorder=specification.order or { feature } + local featurechain=(featuretype=="chainsubstitution" or featuretype=="chainposition") and 1 or 0 + local nofsteps=0 + local steps={} + local sublookups=specification.lookups + local category=nil + if sublookups then + local s={} + for i=1,#sublookups do + local specification=sublookups[i] + local askedsteps=specification.steps or specification.subtables or { specification.data } or {} + local featuretype=normalized[specification.type or "substitution"] or "substitution" + local featureflags=specification.flags or noflags + local nofsteps=0 + local steps={} + for i=1,#askedsteps do + local list=askedsteps[i] + local coverage=nil + local format=nil + if featuretype=="substitution" then + coverage=prepare_substitution(list,featuretype) + elseif featuretype=="ligature" then + coverage=prepare_ligature(list,featuretype) + elseif featuretype=="alternate" then + coverage=prepare_alternate(list,featuretype) + elseif featuretype=="multiple" then + coverage=prepare_multiple(list,featuretype) + elseif featuretype=="kern" then + format="kern" + coverage=prepare_kern(list,featuretype) + elseif featuretype=="pair" then + format="pair" + coverage=prepare_pair(list,featuretype) + end + if coverage and next(coverage) then + nofsteps=nofsteps+1 + steps[nofsteps]=register(coverage,featuretype,format,feature,nofsteps,descriptions,resources) + end + end + s[i]={ + [stepkey]=steps, + nofsteps=nofsteps, + type=types[featuretype], + } + end + sublookups=s + end + for i=1,#askedsteps do + local list=askedsteps[i] + local coverage=nil + local format=nil + if featuretype=="substitution" then + category="gsub" + coverage=prepare_substitution(list,featuretype) + elseif featuretype=="ligature" then + category="gsub" + coverage=prepare_ligature(list,featuretype) + elseif featuretype=="alternate" then + category="gsub" + coverage=prepare_alternate(list,featuretype) + elseif featuretype=="multiple" then + category="gsub" + coverage=prepare_multiple(list,featuretype) + elseif featuretype=="kern" then + category="gpos" + format="kern" + coverage=prepare_kern(list,featuretype) + elseif featuretype=="pair" then + category="gpos" + format="pair" + coverage=prepare_pair(list,featuretype) + elseif featuretype=="chainsubstitution" then + category="gsub" + coverage=prepare_chain(list,featuretype,sublookups) + elseif featuretype=="chainposition" then + category="gpos" + coverage=prepare_chain(list,featuretype,sublookups) + else + report_otf("not registering feature %a, unknown category",feature) + return + end + if coverage and next(coverage) then + nofsteps=nofsteps+1 + steps[nofsteps]=register(coverage,featuretype,format,feature,nofsteps,descriptions,resources) + end + end + if nofsteps>0 then + for k,v in next,askedfeatures do + if v[1] then + askedfeatures[k]=tohash(v) + end + end + if featureflags[1] then featureflags[1]="mark" end + if featureflags[2] then featureflags[2]="ligature" end + if featureflags[3] then featureflags[3]="base" end + local steptype=types[featuretype] + local sequence={ + chain=featurechain, + features={ [feature]=askedfeatures }, + flags=featureflags, + name=feature, + order=featureorder, + [stepkey]=steps, + nofsteps=nofsteps, + type=steptype, + } + local first,last=getrange(sequences,category) + inject(specification,sequences,sequence,first,last,category,feature) + local features=fontfeatures[category] + if not features then + features={} + fontfeatures[category]=features + end + local k=features[feature] + if not k then + k={} + features[feature]=k + end + for script,languages in next,askedfeatures do + local kk=k[script] + if not kk then + kk={} + k[script]=kk + end + for language,value in next,languages do + kk[language]=value + end + end + end + end + end + if trace_loading then + report_otf("registering feature %a, affected glyphs %a, skipped glyphs %a",feature,done,skip) + end +end +otf.enhancers.addfeature=addfeature +local extrafeatures={} +local knownfeatures={} +function otf.addfeature(name,specification) + if type(name)=="table" then + specification=name + end + if type(specification)~="table" then + report_otf("invalid feature specification, no valid table") + return + end + specification,name=validspecification(specification,name) + if name and specification then + local slot=knownfeatures[name] + if slot then + else + slot=#extrafeatures+1 + knownfeatures[name]=slot + end + specification.name=name + extrafeatures[slot]=specification + end +end +local function enhance(data,filename,raw) + for slot=1,#extrafeatures do + local specification=extrafeatures[slot] + addfeature(data,specification.name,specification) + end +end +otf.enhancers.enhance=enhance +otf.enhancers.register("check extra features",enhance) +local tlig={ + [0x2013]={ 0x002D,0x002D }, + [0x2014]={ 0x002D,0x002D,0x002D }, +} +local tlig_specification={ + type="ligature", + features=everywhere, + data=tlig, + order={ "tlig" }, + flags=noflags, + prepend=true, +} +otf.addfeature("tlig",tlig_specification) +registerotffeature { + name='tlig', + description='tex ligatures', +} +local trep={ + [0x0027]=0x2019, +} +local trep_specification={ + type="substitution", + features=everywhere, + data=trep, + order={ "trep" }, + flags=noflags, + prepend=true, +} +otf.addfeature("trep",trep_specification) +registerotffeature { + name='trep', + description='tex replacements', +} +local anum_arabic={ + [0x0030]=0x0660, + [0x0031]=0x0661, + [0x0032]=0x0662, + [0x0033]=0x0663, + [0x0034]=0x0664, + [0x0035]=0x0665, + [0x0036]=0x0666, + [0x0037]=0x0667, + [0x0038]=0x0668, + [0x0039]=0x0669, +} +local anum_persian={ + [0x0030]=0x06F0, + [0x0031]=0x06F1, + [0x0032]=0x06F2, + [0x0033]=0x06F3, + [0x0034]=0x06F4, + [0x0035]=0x06F5, + [0x0036]=0x06F6, + [0x0037]=0x06F7, + [0x0038]=0x06F8, + [0x0039]=0x06F9, +} +local function valid(data) + local features=data.resources.features + if features then + for k,v in next,features do + for k,v in next,v do + if v.arab then + return true + end + end + end + end +end +local anum_specification={ + { + type="substitution", + features={ arab={ urd=true,dflt=true } }, + order={ "anum" }, + data=anum_arabic, + flags=noflags, + valid=valid, + }, + { + type="substitution", + features={ arab={ urd=true } }, + order={ "anum" }, + data=anum_persian, + flags=noflags, + valid=valid, + }, +} +otf.addfeature("anum",anum_specification) +registerotffeature { + name='anum', + description='arabic digits', +} + +end -- closure + +do -- begin closure to overcome local limits and interference + if not modules then modules={} end modules ['font-onr']={ version=1.001, comment="companion to font-ini.mkiv", @@ -23380,51 +24620,25 @@ if not modules then modules={} end modules ['font-onr']={ license="see context related readme files" } local fonts,logs,trackers,resolvers=fonts,logs,trackers,resolvers -local next,type,tonumber,rawget=next,type,tonumber,rawget +local next,type,tonumber,rawget,rawset=next,type,tonumber,rawget,rawset local match,lower,gsub,strip,find=string.match,string.lower,string.gsub,string.strip,string.find local char,byte,sub=string.char,string.byte,string.sub local abs=math.abs local bxor,rshift=bit32.bxor,bit32.rshift -local P,S,R,Cmt,C,Ct,Cs,Carg=lpeg.P,lpeg.S,lpeg.R,lpeg.Cmt,lpeg.C,lpeg.Ct,lpeg.Cs,lpeg.Carg +local P,S,R,Cmt,C,Ct,Cs,Carg,Cf,Cg=lpeg.P,lpeg.S,lpeg.R,lpeg.Cmt,lpeg.C,lpeg.Ct,lpeg.Cs,lpeg.Carg,lpeg.Cf,lpeg.Cg local lpegmatch,patterns=lpeg.match,lpeg.patterns 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 report_afm=logs.reporter("fonts","afm loading") -local report_afm=logs.reporter("fonts","pfb loading") -fonts=fonts or {} -local handlers=fonts.handlers or {} -fonts.handlers=handlers +local report_pfb=logs.reporter("fonts","pfb loading") +local handlers=fonts.handlers local afm=handlers.afm or {} handlers.afm=afm local readers=afm.readers or {} afm.readers=readers afm.version=1.512 -local get_indexes +local get_indexes,get_shapes do - local n,m - local progress=function(str,position,name,size) - local forward=position+tonumber(size)+3+2 - n=n+1 - if n>=m then - return #str,name - elseif forward<#str then - return forward,name - else - return #str,name - end - end - local initialize=function(str,position,size) - n=0 - m=tonumber(size) - return position+1 - end - local charstrings=P("/CharStrings") - local name=P("/")*C((R("az")+R("AZ")+R("09")+S("-_."))^1) - local size=C(R("09")^1) - local spaces=P(" ")^1 - local p_filternames=Ct ( - (1-charstrings)^0*charstrings*spaces*Cmt(size,initialize)*(Cmt(name*P(" ")^1*C(R("09")^1),progress)+P(1))^1 - ) local decrypt do local r,c1,c2,n=0,0,0,0 @@ -23434,13 +24648,75 @@ do r=((cipher+r)*c1+c2)%65536 return char(plain) end - decrypt=function(binary) - r,c1,c2,n=55665,52845,22719,4 + decrypt=function(binary,initial,seed) + r,c1,c2,n=initial,52845,22719,seed binary=gsub(binary,".",step) return sub(binary,n+1) end end - local function loadpfbvector(filename) + local charstrings=P("/CharStrings") + local subroutines=P("/Subrs") + local encoding=P("/Encoding") + local dup=P("dup") + local put=P("put") + local array=P("array") + local name=P("/")*C((R("az")+R("AZ")+R("09")+S("-_."))^1) + local digits=R("09")^1 + local cardinal=digits/tonumber + local spaces=P(" ")^1 + local spacing=patterns.whitespace^0 + local routines,vector,chars,n,m + local initialize=function(str,position,size) + n=0 + m=size + return position+1 + end + local setroutine=function(str,position,index,size) + local forward=position+tonumber(size) + local stream=sub(str,position+1,forward) + routines[index]=decrypt(stream,4330,4) + return forward + end + local setvector=function(str,position,name,size) + local forward=position+tonumber(size) + if n>=m then + return #str + elseif forward<#str then + vector[n]=name + n=n+1 + return forward + else + return #str + end + end + local setshapes=function(str,position,name,size) + local forward=position+tonumber(size) + local stream=sub(str,position+1,forward) + if n>m then + return #str + elseif forward<#str then + vector[n]=name + n=n+1 + chars [n]=decrypt(stream,4330,4) + return forward + else + return #str + end + end + local p_rd=spacing*(P("RD")+P("-|")) + local p_np=spacing*(P("NP")+P("|")) + local p_nd=spacing*(P("ND")+P("|")) + local p_filterroutines= + (1-subroutines)^0*subroutines*spaces*Cmt(cardinal,initialize)*(Cmt(cardinal*spaces*cardinal*p_rd,setroutine)*p_np+P(1))^1 + local p_filtershapes= + (1-charstrings)^0*charstrings*spaces*Cmt(cardinal,initialize)*(Cmt(name*spaces*cardinal*p_rd,setshapes)*p_nd+P(1))^1 + local p_filternames=Ct ( + (1-charstrings)^0*charstrings*spaces*Cmt(cardinal,initialize)*(Cmt(name*spaces*cardinal,setvector)+P(1))^1 + ) + local p_filterencoding=(1-encoding)^0*encoding*spaces*digits*spaces*array*(1-dup)^0*Cf( + Ct("")*Cg(spacing*dup*spaces*cardinal*spaces*name*spaces*put)^1 +,rawset) + local function loadpfbvector(filename,shapestoo) local data=io.loaddata(resolvers.findfile(filename)) if not data then report_pfb("no data in %a",filename) @@ -23455,17 +24731,34 @@ do report_pfb("no binary data in %a",filename) return end - binary=decrypt(binary,4) - local vector=lpegmatch(p_filternames,binary) - if vector[1]==".notdef" then - vector[0]=table.remove(vector,1) - end - if not vector then - report_pfb("no vector in %a",filename) - return + binary=decrypt(binary,55665,4) + local names={} + local encoding=lpegmatch(p_filterencoding,ascii) + local glyphs={} + routines,vector,chars={},{},{} + if shapestoo then + lpegmatch(p_filterroutines,binary) + lpegmatch(p_filtershapes,binary) + local data={ + dictionaries={ + { + charstrings=chars, + charset=vector, + subroutines=routines, + } + }, + } + fonts.handlers.otf.readers.parsecharstrings(data,glyphs,true,true) + else + lpegmatch(p_filternames,binary) end - return vector + names=vector + routines,vector,chars=nil,nil,nil + return names,encoding,glyphs end + local pfb=handlers.pfb or {} + handlers.pfb=pfb + pfb.loadvector=loadpfbvector get_indexes=function(data,pfbname) local vector=loadpfbvector(pfbname) if vector then @@ -23485,6 +24778,10 @@ do end end end + get_shapes=function(pfbname) + local vector,encoding,glyphs=loadpfbvector(pfbname,true) + return glyphs + end end local spacer=patterns.spacer local whitespace=patterns.whitespace @@ -23633,6 +24930,22 @@ function readers.loadfont(afmname,pfbname) return data end end +function readers.loadshapes(filename) + local fullname=resolvers.findfile(filename) or "" + if fullname=="" then + return { + filename="not found: "..filename, + glyphs={} + } + else + return { + filename=fullname, + format="opentype", + glyphs=get_shapes(fullname) or {}, + units=1000, + } + end +end function readers.getinfo(filename) local data=read(resolvers.findfile(filename),infoparser) if data then @@ -23677,36 +24990,15 @@ local otfreaders=otf.readers local otfenhancers=otf.enhancers local afmfeatures=constructors.features.afm local registerafmfeature=afmfeatures.register +local afmenhancers=constructors.enhancers.afm +local registerafmenhancer=afmenhancers.register afm.version=1.512 -afm.cache=containers.define("fonts","afm",afm.version,true) +afm.cache=containers.define("fonts","one",afm.version,true) afm.autoprefixed=true afm.helpdata={} afm.syncspace=true local overloads=fonts.mappings.overloads local applyruntimefixes=fonts.treatments and fonts.treatments.applyfixes -local enhancers={ -} -local steps={ - "unify names", - "add ligatures", - "add extra kerns", - "normalize features", - "fix names", -} -local function applyenhancers(data,filename) - for i=1,#steps do - local step=steps[i] - local enhancer=enhancers[step] - if enhancer then - if trace_loading then - report_afm("applying enhancer %a",step) - end - enhancer(data,filename) - else - report_afm("invalid enhancer %a",step) - end - end -end function afm.load(filename) filename=resolvers.findfile(filename,'afm') or "" if filename~="" and not fonts.names.ignoredfile(filename) then @@ -23729,7 +25021,7 @@ function afm.load(filename) report_afm("reading %a",filename) data=afm.readers.loadfont(filename,pfbname) if data then - applyenhancers(data,filename) + afmenhancers.apply(data,filename) fonts.mappings.addtounicode(data,filename) otfreaders.pack(data) data.size=size @@ -23754,7 +25046,7 @@ function afm.load(filename) end end local uparser=fonts.mappings.makenameparser() -enhancers["unify names"]=function(data,filename) +local function enhance_unify_names(data,filename) local unicodevector=fonts.encodings.agl.unicodes local unicodes={} local names={} @@ -23806,7 +25098,7 @@ enhancers["unify names"]=function(data,filename) end local everywhere={ ["*"]={ ["*"]=true } } local noflags={ false,false,false,false } -enhancers["normalize features"]=function(data) +local function enhance_normalize_features(data) local ligatures=setmetatableindex("table") local kerns=setmetatableindex("table") local extrakerns=setmetatableindex("table") @@ -23904,7 +25196,7 @@ enhancers["normalize features"]=function(data) data.resources.features=features data.resources.sequences=sequences end -enhancers["fix names"]=function(data) +local function enhance_fix_names(data) for k,v in next,data.descriptions do local n=v.name local r=overloads[n] @@ -23943,10 +25235,10 @@ local addthem=function(rawdata,ligatures) end end end -enhancers["add ligatures"]=function(rawdata) +local function enhance_add_ligatures(rawdata) addthem(rawdata,afm.helpdata.ligatures) end -enhancers["add extra kerns"]=function(rawdata) +local function enhance_add_extra_kerns(rawdata) local descriptions=rawdata.descriptions local resources=rawdata.resources local unicodes=resources.unicodes @@ -24265,17 +25557,12 @@ local function read_from_afm(specification) end return tfmdata end -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, + base=otf.modeinitializer, + node=otf.modeinitializer, } } registerafmfeature { @@ -24290,7 +25577,6 @@ registerafmfeature { node=otf.featuresprocessor, } } -local check_tfm=readers.check_tfm fonts.formats.afm="type1" fonts.formats.pfb="type1" local function check_afm(specification,fullname) @@ -24325,7 +25611,8 @@ function readers.afm(specification,method) tfmdata=check_afm(specification,specification.name.."."..forced) end if not tfmdata then - method=method or definers.method or "afm or tfm" + local check_tfm=readers.check_tfm + method=(check_tfm and (method or definers.method or "afm or tfm")) or "afm" if method=="tfm" then tfmdata=check_tfm(specification,specification.name) elseif method=="afm" then @@ -24359,6 +25646,12 @@ function readers.pfb(specification,method) swap("specification") return readers.afm(specification,method) end +registerafmenhancer("unify names",enhance_unify_names) +registerafmenhancer("add ligatures",enhance_add_ligatures) +registerafmenhancer("add extra kerns",enhance_add_extra_kerns) +registerafmenhancer("normalize features",enhance_normalize_features) +registerafmenhancer("check extra features",otfenhancers.enhance) +registerafmenhancer("fix names",enhance_fix_names) end -- closure @@ -24533,6 +25826,514 @@ 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,type=next,type +local match,format=string.match,string.format +local concat,sortedhash=table.concat,table.sortedhash +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 setmetatableindex=table.setmetatableindex +local fonts=fonts +local handlers=fonts.handlers +local readers=fonts.readers +local constructors=fonts.constructors +local encodings=fonts.encodings +local tfm=constructors.handlers.tfm +tfm.version=1.000 +tfm.maxnestingdepth=5 +tfm.maxnestingsize=65536*1024 +local otf=fonts.handlers.otf +local otfenhancers=otf.enhancers +local tfmfeatures=constructors.features.tfm +local registertfmfeature=tfmfeatures.register +local tfmenhancers=constructors.enhancers.tfm +local registertfmenhancer=tfmenhancers.register +constructors.resolvevirtualtoo=false +fonts.formats.tfm="type1" +fonts.formats.ofm="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 depth={} +local function read_from_tfm(specification) + local filename=specification.filename + local size=specification.size + depth[filename]=(depth[filename] or 0)+1 + 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 features=constructors.checkedfeatures("tfm",features) + specification.features.normal=features + local newtfmdata=(depth[filename]==1) and tfm.reencode(tfmdata,specification) + if newtfmdata then + tfmdata=newtfmdata + end + local resources=tfmdata.resources or {} + local properties=tfmdata.properties or {} + local parameters=tfmdata.parameters or {} + local shared=tfmdata.shared or {} + shared.features=features + shared.resources=resources + properties.name=tfmdata.name + properties.fontname=tfmdata.fontname + properties.psname=tfmdata.psname + properties.fullname=tfmdata.fullname + properties.filename=specification.filename + properties.format=fonts.formats.tfm + tfmdata.properties=properties + tfmdata.resources=resources + tfmdata.parameters=parameters + tfmdata.shared=shared + shared.rawdata={ resources=resources } + shared.features=features + if newtfmdata then + if not resources.marks then + resources.marks={} + end + if not resources.sequences then + resources.sequences={} + end + if not resources.features then + resources.features={ + gsub={}, + gpos={}, + } + end + if not tfmdata.changed then + tfmdata.changed={} + end + if not tfmdata.descriptions then + tfmdata.descriptions=tfmdata.characters + end + otf.readers.addunicodetable(tfmdata) + tfmenhancers.apply(tfmdata,filename) + constructors.applymanipulators("tfm",tfmdata,features,trace_features,report_tfm) + otf.readers.unifymissing(tfmdata) + fonts.mappings.addtounicode(tfmdata,filename) + tfmdata.tounicode=1 + local tounicode=fonts.mappings.tounicode + for unicode,v in next,tfmdata.characters do + local u=v.unicode + if u then + v.tounicode=tounicode(u) + end + end + if tfmdata.usedbitmap then + tfm.addtounicode(tfmdata) + end + end + shared.processes=next(features) and tfm.setfeatures(tfmdata,features) or nil + parameters.factor=1 + parameters.size=size + 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 newtfmdata then + elseif 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 + tfmdata.type="virtual" + local fontlist=vfdata.fonts + local name=file.nameonly(filename) + for i=1,#fontlist do + local n=fontlist[i].name + local s=fontlist[i].size + local d=depth[filename] + s=constructors.scaled(s,vfdata.designsize) + if d>tfm.maxnestingdepth then + report_defining("too deeply nested virtual font %a with size %a, max nesting depth %s",n,s,tfm.maxnestingdepth) + fontlist[i]={ id=0 } + elseif (d>1) and (s>tfm.maxnestingsize) then + report_defining("virtual font %a exceeds size %s",n,s) + fontlist[i]={ id=0 } + else + local t,id=fonts.constructors.readanddefine(n,s) + fontlist[i]={ id=id } + end + end + end + end + end + properties.haskerns=true + properties.hasligatures=true + resources.unicodes={} + resources.lookuptags={} + depth[filename]=depth[filename]-1 + return tfmdata + else + depth[filename]=depth[filename]-1 + 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 +readers.ofm=readers.tfm +do + local outfiles={} + local tfmcache=table.setmetatableindex(function(t,tfmdata) + local id=font.define(tfmdata) + t[tfmdata]=id + return id + end) + local encdone=table.setmetatableindex("table") + function tfm.reencode(tfmdata,specification) + local features=specification.features + if not features then + return + end + local features=features.normal + if not features then + return + end + local tfmfile=file.basename(tfmdata.name) + local encfile=features.reencode + local pfbfile=features.pfbfile + local bitmap=features.bitmap + if not encfile then + return + end + local pfbfile=outfiles[tfmfile] + if pfbfile==nil then + if bitmap then + pfbfile=false + elseif type(pfbfile)~="string" then + pfbfile=tfmfile + end + if type(pfbfile)=="string" then + pfbfile=file.addsuffix(pfbfile,"pfb") + report_tfm("using type1 shapes from %a for %a",pfbfile,tfmfile) + else + report_tfm("using bitmap shapes for %a",tfmfile) + pfbfile=false + end + outfiles[tfmfile]=pfbfile + end + local encoding=false + local vector=false + if type(pfbfile)=="string" then + local pfb=fonts.constructors.handlers.pfb + if pfb and pfb.loadvector then + local v,e=pfb.loadvector(pfbfile) + if v then + vector=v + end + if e then + encoding=e + end + end + end + if type(encfile)=="string" and encfile~="auto" then + encoding=fonts.encodings.load(file.addsuffix(encfile,"enc")) + if encoding then + encoding=encoding.vector + end + end + if not encoding then + report_tfm("bad encoding for %a, quitting",tfmfile) + return + end + local unicoding=fonts.encodings.agl and fonts.encodings.agl.unicodes + local virtualid=tfmcache[tfmdata] + local tfmdata=table.copy(tfmdata) + local characters={} + local originals=tfmdata.characters + local indices={} + local parentfont={ "font",1 } + local private=fonts.constructors.privateoffset + local reported=encdone[tfmfile][encfile] + local backmap=vector and table.swapped(vector) + local done={} + for index,name in sortedhash(encoding) do + local unicode=unicoding[name] + local original=originals[index] + if original then + if unicode then + original.unicode=unicode + else + unicode=private + private=private+1 + if not reported then + report_tfm("glyph %a in font %a with encoding %a gets unicode %U",name,tfmfile,encfile,unicode) + end + end + characters[unicode]=original + indices[index]=unicode + original.name=name + if backmap then + original.index=backmap[name] + else + original.commands={ parentfont,{ "char",index } } + original.oindex=index + end + done[name]=true + elseif not done[name] then + report_tfm("bad index %a in font %a with name %a",index,tfmfile,name) + end + end + encdone[tfmfile][encfile]=true + for k,v in next,characters do + local kerns=v.kerns + if kerns then + local t={} + for k,v in next,kerns do + local i=indices[k] + if i then + t[i]=v + end + end + v.kerns=next(t) and t or nil + end + local ligatures=v.ligatures + if ligatures then + local t={} + for k,v in next,ligatures do + local i=indices[k] + if i then + t[i]=v + v.char=indices[v.char] + end + end + v.ligatures=next(t) and t or nil + end + end + tfmdata.fonts={ { id=virtualid } } + tfmdata.characters=characters + tfmdata.fullname=tfmdata.fullname or tfmdata.name + tfmdata.psname=file.nameonly(pfbfile or tfmdata.name) + tfmdata.filename=pfbfile + tfmdata.encodingbytes=2 + tfmdata.format="type1" + tfmdata.tounicode=1 + tfmdata.embedding="subset" + tfmdata.usedbitmap=bitmap and virtualid + return tfmdata + end +end +do + local template=[[ +/CIDInit /ProcSet findresource begin + 12 dict begin + begincmap + /CIDSystemInfo << /Registry (TeX) /Ordering (bitmap-%s) /Supplement 0 >> def + /CMapName /TeX-bitmap-%s def + /CMapType 2 def + 1 begincodespacerange + <00> <FF> + endcodespacerange + %s beginbfchar +%s + endbfchar + endcmap +CMapName currentdict /CMap defineresource pop end +end +end +]] + local flushstreamobject=lpdf and lpdf.flushstreamobject + local setfontattributes=pdf.setfontattributes + if not flushstreamobject then + flushstreamobject=function(data) + return pdf.obj { + immediate=true, + type="stream", + string=data, + } + end + end + if not setfontattributes then + setfontattributes=function(id,data) + print(format("your luatex is too old so no tounicode bitmap font%i",id)) + end + end + function tfm.addtounicode(tfmdata) + local id=tfmdata.usedbitmap + local map={} + local char={} + for k,v in next,tfmdata.characters do + local index=v.oindex + local tounicode=v.tounicode + if index and tounicode then + map[index]=tounicode + end + end + for k,v in sortedhash(map) do + char[#char+1]=format("<%02X> <%s>",k,v) + end + char=concat(char,"\n") + local stream=format(template,id,id,#char,char) + local reference=flushstreamobject(stream,nil,true) + setfontattributes(id,format("/ToUnicode %i 0 R",reference)) + end +end +do + local everywhere={ ["*"]={ ["*"]=true } } + local noflags={ false,false,false,false } + local function enhance_normalize_features(data) + local ligatures=setmetatableindex("table") + local kerns=setmetatableindex("table") + local characters=data.characters + for u,c in next,characters do + local l=c.ligatures + local k=c.kerns + if l then + ligatures[u]=l + for u,v in next,l do + l[u]={ ligature=v.char } + end + c.ligatures=nil + end + if k then + kerns[u]=k + for u,v in next,k do + k[u]=v + end + c.kerns=nil + end + end + for u,l in next,ligatures do + for k,v in next,l do + local vl=v.ligature + local dl=ligatures[vl] + if dl then + for kk,vv in next,dl do + v[kk]=vv + end + end + end + end + local features={ + gpos={}, + gsub={}, + } + local sequences={ + } + if next(ligatures) then + features.gsub.liga=everywhere + data.properties.hasligatures=true + sequences[#sequences+1]={ + features={ + liga=everywhere, + }, + flags=noflags, + name="s_s_0", + nofsteps=1, + order={ "liga" }, + type="gsub_ligature", + steps={ + { + coverage=ligatures, + }, + }, + } + end + if next(kerns) then + features.gpos.kern=everywhere + data.properties.haskerns=true + sequences[#sequences+1]={ + features={ + kern=everywhere, + }, + flags=noflags, + name="p_s_0", + nofsteps=1, + order={ "kern" }, + type="gpos_pair", + steps={ + { + format="kern", + coverage=kerns, + }, + }, + } + end + data.resources.features=features + data.resources.sequences=sequences + data.shared.resources=data.shared.resources or resources + end + registertfmenhancer("normalize features",enhance_normalize_features) + registertfmenhancer("check extra features",otfenhancers.enhance) +end +registertfmfeature { + name="mode", + description="mode", + initializers={ + base=otf.modeinitializer, + node=otf.modeinitializer, + } +} +registertfmfeature { + name="features", + description="features", + default=true, + initializers={ + base=otf.basemodeinitializer, + node=otf.nodemodeinitializer, + }, + processors={ + node=otf.featuresprocessor, + } +} + +end -- closure + +do -- begin closure to overcome local limits and interference + if not modules then modules={} end modules ['font-lua']={ version=1.001, comment="companion to font-ini.mkiv", @@ -24582,10 +26383,11 @@ if not modules then modules={} end modules ['font-def']={ 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 lower,gsub=string.lower,string.gsub local tostring,next=tostring,next local lpegmatch=lpeg.match local suffixonly,removesuffix=file.suffix,file.removesuffix +local formatters=string.formatters 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) @@ -24746,7 +26548,7 @@ function definers.applypostprocessors(tfmdata) 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) + properties.fullname=formatters["%s-%s"](properties.fullname,extrahash) end end end @@ -25204,6 +27006,19 @@ otffeatures.register { node=reencode, } } +local function ignore(tfmdata,key,value) + if value then + tfmdata.mathparameters=nil + end +end +otffeatures.register { + name="ignoremathconstants", + description="ignore math constants table", + initializers={ + base=ignore, + node=ignore, + } +} end -- closure @@ -25224,7 +27039,7 @@ local fonts=fonts local nodes=nodes local nuts=nodes.nuts local traverse_id=nuts.traverse_id -local free_node=nuts.free +local flush_node=nuts.flush_node local glyph_code=nodes.nodecodes.glyph local disc_code=nodes.nodecodes.disc local tonode=nuts.tonode @@ -25348,7 +27163,7 @@ function nodes.handlers.nodepass(head) end end end - free_node(r) + flush_node(r) end end for d in traverse_id(disc_code,nuthead) do |