diff options
| -rw-r--r-- | src/fontloader/runtime/fontloader-basics-gen.lua | 38 | ||||
| -rw-r--r-- | src/fontloader/runtime/fontloader-reference.lua | 17888 | 
2 files changed, 12577 insertions, 5349 deletions
diff --git a/src/fontloader/runtime/fontloader-basics-gen.lua b/src/fontloader/runtime/fontloader-basics-gen.lua index c4d6536..c298f6d 100644 --- a/src/fontloader/runtime/fontloader-basics-gen.lua +++ b/src/fontloader/runtime/fontloader-basics-gen.lua @@ -63,15 +63,19 @@ logs = {  }  callbacks = { -    register = function(n,f) return callback.register(n,f) end, +    register = function(n,f) +        return callback.register(n,f) +    end,  } -utilities = { -    storage = { -        allocate = function(t) return t or { } end, -        mark     = function(t) return t or { } end, -    }, +utilities = utilities or { } utilities.storage = { +    allocate = function(t) +        return t or { } +    end, +    mark     = function(t) +        return t or { } +    end,  }  characters = characters or { @@ -355,12 +359,28 @@ end  -- +-- function table.setmetatableindex(t,f) +--     if type(t) ~= "table" then +--         f = f or t +--         t = { } +--     end +--     setmetatable(t,{ __index = f }) +--     return t +-- end +  function table.setmetatableindex(t,f)      if type(t) ~= "table" then -        f = f or t -        t = { } +        f, t = t, { } +    end +    local m = getmetatable(t) +    if f == "table" then +        f = function(t,k) local v = { } t[k] = v return v end +    end +    if m then +        m.__index = f +    else +        setmetatable(t,{ __index = f })      end -    setmetatable(t,{ __index = f })      return t  end diff --git a/src/fontloader/runtime/fontloader-reference.lua b/src/fontloader/runtime/fontloader-reference.lua index b135c44..e4ae182 100644 --- a/src/fontloader/runtime/fontloader-reference.lua +++ b/src/fontloader/runtime/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  : 01/08/16 19:09:31 +-- merge date  : 04/04/16 13:06:24  do -- begin closure to overcome local limits and interference @@ -11,10 +11,14 @@ if not modules then modules={} end modules ['l-lua']={    copyright="PRAGMA ADE / ConTeXt Development Team",    license="see context related readme files"  } -local major,minor=string.match(_VERSION,"^[^%d]+(%d+)%.(%d+).*$") -_MAJORVERSION=tonumber(major) or 5 -_MINORVERSION=tonumber(minor) or 1 +_MAJORVERSION,_MINORVERSION=string.match(_VERSION,"^[^%d]+(%d+)%.(%d+).*$") +_MAJORVERSION=tonumber(_MAJORVERSION) or 5 +_MINORVERSION=tonumber(_MINORVERSION) or 1  _LUAVERSION=_MAJORVERSION+_MINORVERSION/10 +if _LUAVERSION<5.2 and jit then +  _MINORVERSION=2 +  _LUAVERSION=5.2 +end  if not lpeg then    lpeg=require("lpeg")  end @@ -3470,6 +3474,149 @@ end -- closure  do -- begin closure to overcome local limits and interference +if not modules then modules={} end modules ['util-fil']={ +  version=1.001, +  comment="companion to luat-lib.mkiv", +  author="Hans Hagen, PRAGMA-ADE, Hasselt NL", +  copyright="PRAGMA ADE / ConTeXt Development Team", +  license="see context related readme files" +} +local byte=string.byte +local extract=bit32.extract +utilities=utilities or {} +local files={} +utilities.files=files +local zerobased={} +function files.open(filename,zb) +  local f=io.open(filename,"rb") +  if f then +    zerobased[f]=zb or false +  end +  return f +end +function files.close(f) +  zerobased[f]=nil +  f:close() +end +function files.size(f) +  return f:seek("end") +end +function files.setposition(f,n) +  if zerobased[f] then +    f:seek("set",n) +  else +    f:seek("set",n-1) +  end +end +function files.getposition(f) +  if zerobased[f] then +    return f:seek() +  else +    return f:seek()+1 +  end +end +function files.look(f,n,chars) +  local p=f:seek() +  local s=f:read(n) +  f:seek("set",p) +  if chars then +    return s +  else +    return byte(s,1,#s) +  end +end +function files.skip(f,n) +  if n==1 then +    f:read(n) +  else +    f:seek("set",f:seek()+n) +  end +end +function files.readbyte(f) +  return byte(f:read(1)) +end +function files.readbytes(f,n) +  return byte(f:read(n),1,n) +end +function files.readchar(f) +  return f:read(1) +end +function files.readstring(f,n) +  return f:read(n or 1) +end +function files.readinteger1(f)  +  local n=byte(f:read(1)) +  if n>=0x80 then +    return n-0xFF-1 +  else +    return n +  end +end +files.readcardinal1=files.readbyte  +files.readcardinal=files.readcardinal1 +files.readinteger=files.readinteger1 +function files.readcardinal2(f) +  local a,b=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 +  else +    return n +  end +end +function files.readcardinal3(f) +  local a,b,c=byte(f:read(3),1,3) +  return 0x10000*a+0x100*b+c +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.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 +  else +    return n +  end +end +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 +  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 +  end +end +function files.skipshort(f,n) +  f:read(2*(n or 1)) +end +function files.skiplong(f,n) +  f:read(4*(n or 1)) +end + +end -- closure + +do -- begin closure to overcome local limits and interference +  if not modules then modules={} end modules ['luat-basics-gen']={    version=1.100,    comment="companion to luatex-*.tex", @@ -3525,13 +3672,17 @@ logs={    report=dummyfunction,  }  callbacks={ -  register=function(n,f) return callback.register(n,f) end, +  register=function(n,f) +    return callback.register(n,f) +  end,  } -utilities={ -  storage={ -    allocate=function(t) return t or {} end, -    mark=function(t) return t or {} end, -  }, +utilities=utilities or {} utilities.storage={ +  allocate=function(t) +    return t or {} +  end, +  mark=function(t) +    return t or {} +  end,  }  characters=characters or {    data={} @@ -3731,10 +3882,17 @@ function caches.compile(data,luaname,lucname)  end  function table.setmetatableindex(t,f)    if type(t)~="table" then -    f=f or t -    t={} +    f,t=t,{} +  end +  local m=getmetatable(t) +  if f=="table" then +    f=function(t,k) local v={} t[k]=v return v end +  end +  if m then +    m.__index=f +  else +    setmetatable(t,{ __index=f })    end -  setmetatable(t,{ __index=f })    return t  end  arguments={} @@ -4028,6 +4186,42 @@ nuts.getlist=direct.getlist  nuts.setlist=direct.setlist  or function(n,l) setfield(n,"list",l) end  nuts.getleader=direct.getleader  nuts.setleader=direct.setleader or function(n,l) setfield(n,"leader",l) end +if not direct.is_glyph then +  local getchar=direct.getchar +  local getid=direct.getid +  local getfont=direct.getfont +  local glyph_code=nodes.nodecodes.glyph +  function direct.is_glyph(n,f) +    local id=getid(n) +    if id==glyph_code then +      if f and getfont(n)==f then +        return getchar(n) +      else +        return false +      end +    else +      return nil,id +    end +  end +  function direct.is_char(n,f) +    local id=getid(n) +    if id==glyph_code then +      if getsubtype(n)>=256 then +        return false +      elseif f and getfont(n)==f then +        return getchar(n) +      else +        return false +      end +    else +      return nil,id +    end +  end +end +nuts.ischar=direct.is_char +nuts.is_char=direct.is_char +nuts.isglyph=direct.is_glyph +nuts.is_glyph=direct.is_glyph  nuts.insert_before=direct.insert_before  nuts.insert_after=direct.insert_after  nuts.delete=direct.delete @@ -4081,6 +4275,760 @@ end -- closure  do -- begin closure to overcome local limits and interference + +characters=characters or {} +characters.blockrange={} +characters.classifiers={ + [1536]=4, + [1537]=4, + [1538]=4, + [1539]=4, + [1540]=4, + [1541]=4, + [1542]=6, + [1543]=6, + [1544]=4, + [1545]=6, + [1546]=6, + [1547]=4, + [1548]=6, + [1549]=6, + [1550]=6, + [1551]=6, + [1552]=5, + [1553]=5, + [1554]=5, + [1555]=5, + [1556]=5, + [1557]=5, + [1558]=5, + [1559]=5, + [1560]=5, + [1561]=5, + [1562]=5, + [1563]=6, + [1564]=6, + [1566]=6, + [1567]=6, + [1568]=2, + [1569]=4, + [1570]=3, + [1571]=3, + [1572]=3, + [1573]=3, + [1574]=2, + [1575]=3, + [1576]=2, + [1577]=3, + [1578]=2, + [1579]=2, + [1580]=2, + [1581]=2, + [1582]=2, + [1583]=3, + [1584]=3, + [1585]=3, + [1586]=3, + [1587]=2, + [1588]=2, + [1589]=2, + [1590]=2, + [1591]=2, + [1592]=2, + [1593]=2, + [1594]=2, + [1595]=2, + [1596]=2, + [1597]=2, + [1598]=2, + [1599]=2, + [1600]=2, + [1601]=2, + [1602]=2, + [1603]=2, + [1604]=2, + [1605]=2, + [1606]=2, + [1607]=2, + [1608]=3, + [1609]=2, + [1610]=2, + [1611]=5, + [1612]=5, + [1613]=5, + [1614]=5, + [1615]=5, + [1616]=5, + [1617]=5, + [1618]=5, + [1619]=5, + [1620]=5, + [1621]=5, + [1622]=5, + [1623]=5, + [1624]=5, + [1625]=5, + [1626]=5, + [1627]=5, + [1628]=5, + [1629]=5, + [1630]=5, + [1631]=5, + [1632]=6, + [1633]=6, + [1634]=6, + [1635]=6, + [1636]=6, + [1637]=6, + [1638]=6, + [1639]=6, + [1640]=6, + [1641]=6, + [1642]=6, + [1643]=6, + [1644]=6, + [1645]=6, + [1646]=2, + [1647]=2, + [1648]=5, + [1649]=3, + [1650]=3, + [1651]=3, + [1652]=4, + [1653]=3, + [1654]=3, + [1655]=3, + [1656]=2, + [1657]=2, + [1658]=2, + [1659]=2, + [1660]=2, + [1661]=2, + [1662]=2, + [1663]=2, + [1664]=2, + [1665]=2, + [1666]=2, + [1667]=2, + [1668]=2, + [1669]=2, + [1670]=2, + [1671]=2, + [1672]=3, + [1673]=3, + [1674]=3, + [1675]=3, + [1676]=3, + [1677]=3, + [1678]=3, + [1679]=3, + [1680]=3, + [1681]=3, + [1682]=3, + [1683]=3, + [1684]=3, + [1685]=3, + [1686]=3, + [1687]=3, + [1688]=3, + [1689]=3, + [1690]=2, + [1691]=2, + [1692]=2, + [1693]=2, + [1694]=2, + [1695]=2, + [1696]=2, + [1697]=2, + [1698]=2, + [1699]=2, + [1700]=2, + [1701]=2, + [1702]=2, + [1703]=2, + [1704]=2, + [1705]=2, + [1706]=2, + [1707]=2, + [1708]=2, + [1709]=2, + [1710]=2, + [1711]=2, + [1712]=2, + [1713]=2, + [1714]=2, + [1715]=2, + [1716]=2, + [1717]=2, + [1718]=2, + [1719]=2, + [1720]=2, + [1721]=2, + [1722]=2, + [1723]=2, + [1724]=2, + [1725]=2, + [1726]=2, + [1727]=2, + [1728]=3, + [1729]=2, + [1730]=2, + [1731]=3, + [1732]=3, + [1733]=3, + [1734]=3, + [1735]=3, + [1736]=3, + [1737]=3, + [1738]=3, + [1739]=3, + [1740]=2, + [1741]=3, + [1742]=2, + [1743]=3, + [1744]=2, + [1745]=2, + [1746]=3, + [1747]=3, + [1748]=6, + [1749]=3, + [1750]=5, + [1751]=5, + [1752]=5, + [1753]=5, + [1754]=5, + [1755]=5, + [1756]=5, + [1757]=4, + [1758]=6, + [1759]=5, + [1760]=5, + [1761]=5, + [1762]=5, + [1763]=5, + [1764]=5, + [1765]=6, + [1766]=6, + [1767]=5, + [1768]=5, + [1769]=6, + [1770]=5, + [1771]=5, + [1772]=5, + [1773]=5, + [1774]=3, + [1775]=3, + [1776]=6, + [1777]=6, + [1778]=6, + [1779]=6, + [1780]=6, + [1781]=6, + [1782]=6, + [1783]=6, + [1784]=6, + [1785]=6, + [1786]=2, + [1787]=2, + [1788]=2, + [1789]=6, + [1790]=6, + [1791]=2, + [1792]=6, + [1793]=6, + [1794]=6, + [1795]=6, + [1796]=6, + [1797]=6, + [1798]=6, + [1799]=6, + [1800]=6, + [1801]=6, + [1802]=6, + [1803]=6, + [1804]=6, + [1805]=6, + [1807]=6, + [1808]=3, + [1809]=5, + [1810]=2, + [1811]=2, + [1812]=2, + [1813]=3, + [1814]=3, + [1815]=3, + [1816]=3, + [1817]=3, + [1818]=2, + [1819]=2, + [1820]=2, + [1821]=2, + [1822]=3, + [1823]=2, + [1824]=2, + [1825]=2, + [1826]=2, + [1827]=2, + [1828]=2, + [1829]=2, + [1830]=2, + [1831]=2, + [1832]=3, + [1833]=2, + [1834]=3, + [1835]=2, + [1836]=3, + [1837]=2, + [1838]=2, + [1839]=3, + [1840]=5, + [1841]=5, + [1842]=5, + [1843]=5, + [1844]=5, + [1845]=5, + [1846]=5, + [1847]=5, + [1848]=5, + [1849]=5, + [1850]=5, + [1851]=5, + [1852]=5, + [1853]=5, + [1854]=5, + [1855]=5, + [1856]=5, + [1857]=5, + [1858]=5, + [1859]=5, + [1860]=5, + [1861]=5, + [1862]=5, + [1863]=5, + [1864]=5, + [1865]=5, + [1866]=5, + [1869]=3, + [1870]=2, + [1871]=2, + [1872]=2, + [1873]=2, + [1874]=2, + [1875]=2, + [1876]=2, + [1877]=2, + [1878]=2, + [1879]=2, + [1880]=2, + [1881]=3, + [1882]=3, + [1883]=3, + [1884]=2, + [1885]=2, + [1886]=2, + [1887]=2, + [1888]=2, + [1889]=2, + [1890]=2, + [1891]=2, + [1892]=2, + [1893]=2, + [1894]=2, + [1895]=2, + [1896]=2, + [1897]=2, + [1898]=2, + [1899]=3, + [1900]=3, + [1901]=2, + [1902]=2, + [1903]=2, + [1904]=2, + [1905]=3, + [1906]=2, + [1907]=3, + [1908]=3, + [1909]=2, + [1910]=2, + [1911]=2, + [1912]=3, + [1913]=3, + [1914]=2, + [1915]=2, + [1916]=2, + [1917]=2, + [1918]=2, + [1919]=2, + [1984]=6, + [1985]=6, + [1986]=6, + [1987]=6, + [1988]=6, + [1989]=6, + [1990]=6, + [1991]=6, + [1992]=6, + [1993]=6, + [1994]=2, + [1995]=2, + [1996]=2, + [1997]=2, + [1998]=2, + [1999]=2, + [2000]=2, + [2001]=2, + [2002]=2, + [2003]=2, + [2004]=2, + [2005]=2, + [2006]=2, + [2007]=2, + [2008]=2, + [2009]=2, + [2010]=2, + [2011]=2, + [2012]=2, + [2013]=2, + [2014]=2, + [2015]=2, + [2016]=2, + [2017]=2, + [2018]=2, + [2019]=2, + [2020]=2, + [2021]=2, + [2022]=2, + [2023]=2, + [2024]=2, + [2025]=2, + [2026]=2, + [2027]=5, + [2028]=5, + [2029]=5, + [2030]=5, + [2031]=5, + [2032]=5, + [2033]=5, + [2034]=5, + [2035]=5, + [2036]=6, + [2037]=6, + [2038]=6, + [2039]=6, + [2040]=6, + [2041]=6, + [2042]=2, + [2112]=3, + [2113]=2, + [2114]=2, + [2115]=2, + [2116]=2, + [2117]=2, + [2118]=3, + [2119]=3, + [2120]=2, + [2121]=3, + [2122]=2, + [2123]=2, + [2124]=2, + [2125]=2, + [2126]=2, + [2127]=2, + [2128]=2, + [2129]=2, + [2130]=2, + [2131]=2, + [2132]=3, + [2133]=2, + [2134]=4, + [2135]=4, + [2136]=4, + [2208]=2, + [2209]=2, + [2210]=2, + [2211]=2, + [2212]=2, + [2213]=2, + [2214]=2, + [2215]=2, + [2216]=2, + [2217]=2, + [2218]=3, + [2219]=3, + [2220]=3, + [2221]=4, + [2222]=3, + [2223]=2, + [2224]=2, + [2225]=3, + [2226]=3, + [2227]=2, + [2228]=2, + [6150]=4, + [6151]=2, + [6154]=2, + [6158]=4, + [6176]=2, + [6177]=2, + [6178]=2, + [6179]=2, + [6180]=2, + [6181]=2, + [6182]=2, + [6183]=2, + [6184]=2, + [6185]=2, + [6186]=2, + [6187]=2, + [6188]=2, + [6189]=2, + [6190]=2, + [6191]=2, + [6192]=2, + [6193]=2, + [6194]=2, + [6195]=2, + [6196]=2, + [6197]=2, + [6198]=2, + [6199]=2, + [6200]=2, + [6201]=2, + [6202]=2, + [6203]=2, + [6204]=2, + [6205]=2, + [6206]=2, + [6207]=2, + [6208]=2, + [6209]=2, + [6210]=2, + [6211]=2, + [6212]=2, + [6213]=2, + [6214]=2, + [6215]=2, + [6216]=2, + [6217]=2, + [6218]=2, + [6219]=2, + [6220]=2, + [6221]=2, + [6222]=2, + [6223]=2, + [6224]=2, + [6225]=2, + [6226]=2, + [6227]=2, + [6228]=2, + [6229]=2, + [6230]=2, + [6231]=2, + [6232]=2, + [6233]=2, + [6234]=2, + [6235]=2, + [6236]=2, + [6237]=2, + [6238]=2, + [6239]=2, + [6240]=2, + [6241]=2, + [6242]=2, + [6243]=2, + [6244]=2, + [6245]=2, + [6246]=2, + [6247]=2, + [6248]=2, + [6249]=2, + [6250]=2, + [6251]=2, + [6252]=2, + [6253]=2, + [6254]=2, + [6255]=2, + [6256]=2, + [6257]=2, + [6258]=2, + [6259]=2, + [6260]=2, + [6261]=2, + [6262]=2, + [6263]=2, + [6272]=4, + [6273]=4, + [6274]=4, + [6275]=4, + [6276]=4, + [6277]=4, + [6278]=4, + [6279]=2, + [6280]=2, + [6281]=2, + [6282]=2, + [6283]=2, + [6284]=2, + [6285]=2, + [6286]=2, + [6287]=2, + [6288]=2, + [6289]=2, + [6290]=2, + [6291]=2, + [6292]=2, + [6293]=2, + [6294]=2, + [6295]=2, + [6296]=2, + [6297]=2, + [6298]=2, + [6299]=2, + [6300]=2, + [6301]=2, + [6302]=2, + [6303]=2, + [6304]=2, + [6305]=2, + [6306]=2, + [6307]=2, + [6308]=2, + [6309]=2, + [6310]=2, + [6311]=2, + [6312]=2, + [6314]=2, + [8204]=4, + [8205]=2, + [8294]=4, + [8295]=4, + [8296]=4, + [8297]=4, + [43072]=2, + [43073]=2, + [43074]=2, + [43075]=2, + [43076]=2, + [43077]=2, + [43078]=2, + [43079]=2, + [43080]=2, + [43081]=2, + [43082]=2, + [43083]=2, + [43084]=2, + [43085]=2, + [43086]=2, + [43087]=2, + [43088]=2, + [43089]=2, + [43090]=2, + [43091]=2, + [43092]=2, + [43093]=2, + [43094]=2, + [43095]=2, + [43096]=2, + [43097]=2, + [43098]=2, + [43099]=2, + [43100]=2, + [43101]=2, + [43102]=2, + [43103]=2, + [43104]=2, + [43105]=2, + [43106]=2, + [43107]=2, + [43108]=2, + [43109]=2, + [43110]=2, + [43111]=2, + [43112]=2, + [43113]=2, + [43114]=2, + [43115]=2, + [43116]=2, + [43117]=2, + [43118]=2, + [43119]=2, + [43120]=2, + [43121]=2, + [43122]=1, + [43123]=4, + [68288]=2, + [68289]=2, + [68290]=2, + [68291]=2, + [68292]=2, + [68293]=3, + [68294]=4, + [68295]=3, + [68296]=4, + [68297]=3, + [68298]=3, + [68299]=4, + [68300]=4, + [68301]=1, + [68302]=3, + [68303]=3, + [68304]=3, + [68305]=3, + [68306]=3, + [68307]=2, + [68308]=2, + [68309]=2, + [68310]=2, + [68311]=1, + [68312]=2, + [68313]=2, + [68314]=2, + [68315]=2, + [68316]=2, + [68317]=3, + [68318]=2, + [68319]=2, + [68320]=2, + [68321]=3, + [68322]=4, + [68323]=4, + [68324]=3, + [68331]=2, + [68332]=2, + [68333]=2, + [68334]=2, + [68335]=3, + [68480]=2, + [68481]=3, + [68482]=2, + [68483]=3, + [68484]=3, + [68485]=3, + [68486]=2, + [68487]=2, + [68488]=2, + [68489]=3, + [68490]=2, + [68491]=2, + [68492]=3, + [68493]=2, + [68494]=3, + [68495]=3, + [68496]=2, + [68497]=3, + [68521]=3, + [68522]=3, + [68523]=3, + [68524]=3, + [68525]=2, + [68526]=2, + [68527]=4, +} + +end -- closure + +do -- begin closure to overcome local limits and interference +  if not modules then modules={} end modules ['font-ini']={    version=1.001,    comment="companion to font-ini.mkiv", @@ -5489,25 +6437,22 @@ end  local f_single=formatters["%04X"]  local f_double=formatters["%04X%04X"]  local function tounicode16(unicode,name) -  if unicode<0x10000 then +  if unicode<0xD7FF or (unicode>0xDFFF and unicode<=0xFFFF) then      return f_single(unicode) -  elseif unicode<0x1FFFFFFFFF then -    return f_double(floor(unicode/1024),unicode%1024+0xDC00)    else -    report_fonts("can't convert %a in %a into tounicode",unicode,name) +    unicode=unicode-0x10000 +    return f_double(floor(unicode/1024)+0xD800,unicode%1024+0xDC00)    end  end  local function tounicode16sequence(unicodes,name)    local t={}    for l=1,#unicodes do      local u=unicodes[l] -    if u<0x10000 then +    if u<0xD7FF or (u>0xDFFF and u<=0xFFFF) then        t[l]=f_single(u) -    elseif unicode<0x1FFFFFFFFF then -      t[l]=f_double(floor(u/1024),u%1024+0xDC00)      else -      report_fonts ("can't convert %a in %a into tounicode",u,name) -      return +      u=u-0x10000 +      t[l]=f_double(floor(u/1024)+0xD800,u%1024+0xDC00)      end    end    return concat(t) @@ -5517,23 +6462,20 @@ local function tounicode(unicode,name)      local t={}      for l=1,#unicode do        local u=unicode[l] -      if u<0x10000 then +      if u<0xD7FF or (u>0xDFFF and u<=0xFFFF) then          t[l]=f_single(u) -      elseif u<0x1FFFFFFFFF then -        t[l]=f_double(floor(u/1024),u%1024+0xDC00)        else -        report_fonts ("can't convert %a in %a into tounicode",u,name) -        return +        u=u-0x10000 +        t[l]=f_double(floor(u/1024)+0xD800,u%1024+0xDC00)        end      end      return concat(t)    else -    if unicode<0x10000 then +    if unicode<0xD7FF or (unicode>0xDFFF and unicode<=0xFFFF) then        return f_single(unicode) -    elseif unicode<0x1FFFFFFFFF then -      return f_double(floor(unicode/1024),unicode%1024+0xDC00)      else -      report_fonts("can't convert %a in %a into tounicode",unicode,name) +      unicode=unicode-0x10000 +      return f_double(floor(unicode/1024)+0xD800,unicode%1024+0xDC00)      end    end  end @@ -5542,7 +6484,7 @@ local function fromunicode16(str)      return tonumber(str,16)    else      local l,r=match(str,"(....)(....)") -    return (tonumber(l,16))*0x400+tonumber(r,16)-0xDC00 +    return 0x10000+(tonumber(l,16)-0xD800)*0x400+tonumber(r,16)-0xDC00    end  end  mappings.makenameparser=makenameparser @@ -5775,10 +6717,9 @@ local fonts=fonts  fonts.names=fonts.names or {}  fonts.names.version=1.001   fonts.names.basename="luatex-fonts-names" -fonts.names.new_to_old={} -fonts.names.old_to_new={}  fonts.names.cache=containers.define("fonts","data",fonts.names.version,true) -local data,loaded=nil,false +local data=nil +local loaded=false  local fileformats={ "lua","tex","other text files" }  function fonts.names.reportmissingbase()    texio.write("<missing font database, run: mtxrun --script fonts --reload --simple>") @@ -5864,7 +6805,8 @@ tfm.maxnestingsize=65536*1024  local tfmfeatures=constructors.newfeatures("tfm")  local registertfmfeature=tfmfeatures.register  constructors.resolvevirtualtoo=false  -fonts.formats.tfm="type1" +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 @@ -5990,6 +6932,7 @@ function readers.tfm(specification)    end    return check_tfm(specification,fullname)  end +readers.ofm=readers.tfm  end -- closure @@ -7018,46 +7961,6 @@ end -- closure  do -- begin closure to overcome local limits and interference -if not modules then modules={} end modules ['luatex-fonts-tfm']={ -  version=1.001, -  comment="companion to luatex-*.tex", -  author="Hans Hagen, PRAGMA-ADE, Hasselt NL", -  copyright="PRAGMA ADE / ConTeXt Development Team", -  license="see context related readme files" -} -if context then -  texio.write_nl("fatal error: this module is not for context") -  os.exit() -end -local fonts=fonts -local tfm={} -fonts.handlers.tfm=tfm -fonts.formats.tfm="type1"  -function fonts.readers.tfm(specification) -  local fullname=specification.filename or "" -  if fullname=="" then -    local forced=specification.forced or "" -    if forced~="" then -      fullname=specification.name.."."..forced -    else -      fullname=specification.name -    end -  end -  local foundname=resolvers.findbinfile(fullname,'tfm') or "" -  if foundname=="" then -    foundname=resolvers.findbinfile(fullname,'ofm') or "" -  end -  if foundname~="" then -    specification.filename=foundname -    specification.format="ofm" -    return font.read_tfm(specification.filename,specification.size) -  end -end - -end -- closure - -do -- begin closure to overcome local limits and interference -  if not modules then modules={} end modules ['font-oti']={    version=1.001,    comment="companion to font-ini.mkiv", @@ -7199,1371 +8102,5600 @@ end -- closure  do -- begin closure to overcome local limits and interference -if not modules then modules={} end modules ['font-otf']={ +if not modules then modules={} end modules ['font-otr']={    version=1.001,    comment="companion to font-ini.mkiv",    author="Hans Hagen, PRAGMA-ADE, Hasselt NL",    copyright="PRAGMA ADE / ConTeXt Development Team",    license="see context related readme files"  } -local utfbyte=utf.byte -local gmatch,gsub,find,match,lower,strip=string.gmatch,string.gsub,string.find,string.match,string.lower,string.strip -local type,next,tonumber,tostring=type,next,tonumber,tostring -local abs=math.abs -local reversed,concat,insert,remove,sortedkeys=table.reversed,table.concat,table.insert,table.remove,table.sortedkeys -local ioflush=io.flush -local fastcopy,tohash,derivetable=table.fastcopy,table.tohash,table.derive -local formatters=string.formatters -local P,R,S,C,Ct,lpegmatch=lpeg.P,lpeg.R,lpeg.S,lpeg.C,lpeg.Ct,lpeg.match +if not characters then +  require("char-def") +  require("char-ini") +end +local next,type,unpack=next,type,unpack +local byte,lower,char,strip,gsub=string.byte,string.lower,string.char,string.strip,string.gsub +local bittest=bit32.btest +local concat,remove,unpack=table.concat,table.remov,table.unpack +local floor,mod,abs,sqrt,round=math.floor,math.mod,math.abs,math.sqrt,math.round +local P,R,S,C,Cs,Cc,Ct,Carg,Cmt=lpeg.P,lpeg.R,lpeg.S,lpeg.C,lpeg.Cs,lpeg.Cc,lpeg.Ct,lpeg.Carg,lpeg.Cmt +local lpegmatch=lpeg.match  local setmetatableindex=table.setmetatableindex -local allocate=utilities.storage.allocate -local registertracker=trackers.register -local registerdirective=directives.register -local starttiming=statistics.starttiming -local stoptiming=statistics.stoptiming -local elapsedtime=statistics.elapsedtime -local findbinfile=resolvers.findbinfile -local trace_private=false registertracker("otf.private",function(v) trace_private=v end) -local trace_subfonts=false registertracker("otf.subfonts",function(v) trace_subfonts=v end) -local trace_loading=false registertracker("otf.loading",function(v) trace_loading=v end) -local trace_features=false registertracker("otf.features",function(v) trace_features=v end) -local trace_dynamics=false registertracker("otf.dynamics",function(v) trace_dynamics=v end) -local trace_sequences=false registertracker("otf.sequences",function(v) trace_sequences=v end) -local trace_markwidth=false registertracker("otf.markwidth",function(v) trace_markwidth=v end) -local trace_defining=false registertracker("fonts.defining",function(v) trace_defining=v end) -local compact_lookups=true  registertracker("otf.compactlookups",function(v) compact_lookups=v end) -local purge_names=true  registertracker("otf.purgenames",function(v) purge_names=v end) -local report_otf=logs.reporter("fonts","otf loading") -local fonts=fonts -local otf=fonts.handlers.otf -otf.glists={ "gsub","gpos" } -otf.version=2.820  -otf.cache=containers.define("fonts","otf",otf.version,true) -local hashes=fonts.hashes -local definers=fonts.definers -local readers=fonts.readers -local constructors=fonts.constructors -local fontdata=hashes   and hashes.identifiers -local chardata=characters and characters.data  -local otffeatures=constructors.newfeatures("otf") -local registerotffeature=otffeatures.register -local enhancers=allocate() -otf.enhancers=enhancers -local patches={} -enhancers.patches=patches -local forceload=false -local cleanup=0    -local packdata=true -local syncspace=true -local forcenotdef=false -local includesubfonts=false -local overloadkerns=false  -local applyruntimefixes=fonts.treatments and fonts.treatments.applyfixes -local wildcard="*" -local default="dflt" -local fontloader=fontloader -local open_font=fontloader.open -local close_font=fontloader.close -local font_fields=fontloader.fields -local apply_featurefile=fontloader.apply_featurefile -local mainfields=nil -local glyphfields=nil  -local formats=fonts.formats -formats.otf="opentype" -formats.ttf="truetype" -formats.ttc="truetype" -formats.dfont="truetype" -registerdirective("fonts.otf.loader.cleanup",function(v) cleanup=tonumber(v) or (v and 1) or 0 end) -registerdirective("fonts.otf.loader.force",function(v) forceload=v end) -registerdirective("fonts.otf.loader.pack",function(v) packdata=v end) -registerdirective("fonts.otf.loader.syncspace",function(v) syncspace=v end) -registerdirective("fonts.otf.loader.forcenotdef",function(v) forcenotdef=v end) -registerdirective("fonts.otf.loader.overloadkerns",function(v) overloadkerns=v end) -function otf.fileformat(filename) -  local leader=lower(io.loadchunk(filename,4)) -  local suffix=lower(file.suffix(filename)) -  if leader=="otto" then -    return formats.otf,suffix=="otf" -  elseif leader=="ttcf" then -    return formats.ttc,suffix=="ttc" -  elseif suffix=="ttc" then -    return formats.ttc,true -  elseif suffix=="dfont" then -    return formats.dfont,true +local formatters=string.formatters +local sortedkeys=table.sortedkeys +local sortedhash=table.sortedhash +local stripstring=string.strip +local utf16_to_utf8_be=utf.utf16_to_utf8_be +local report=logs.reporter("otf reader") +local trace_cmap=false  +fonts=fonts or {} +local handlers=fonts.handlers or {} +fonts.handlers=handlers +local otf=handlers.otf or {} +handlers.otf=otf +local readers=otf.readers or {} +otf.readers=readers +local streamreader=utilities.files   +readers.streamreader=streamreader +local openfile=streamreader.open +local closefile=streamreader.close +local skipbytes=streamreader.skip +local setposition=streamreader.setposition +local skipshort=streamreader.skipshort +local readbytes=streamreader.readbytes +local readstring=streamreader.readstring +local readbyte=streamreader.readcardinal1  +local readushort=streamreader.readcardinal2  +local readuint=streamreader.readcardinal3  +local readulong=streamreader.readcardinal4  +local readchar=streamreader.readinteger1   +local readshort=streamreader.readinteger2   +local readlong=streamreader.readinteger4   +local readfixed=streamreader.readfixed4 +local readfword=readshort           +local readufword=readushort          +local readoffset=readushort +local read2dot14=streamreader.read2dot14    +function streamreader.readtag(f) +  return lower(strip(readstring(f,4))) +end +local function readlongdatetime(f) +  local a,b,c,d,e,f,g,h=readbytes(f,8) +  return 0x100000000*d+0x1000000*e+0x10000*f+0x100*g+h +end +local tableversion=0.004 +local privateoffset=fonts.constructors and fonts.constructors.privateoffset or 0xF0000  +readers.tableversion=tableversion +local reportedskipped={} +local function reportskippedtable(tag) +  if not reportedskipped[tag] then +    report("loading of table %a skipped (reported once only)",tag) +    reportedskipped[tag]=true +  end +end +local reservednames={ [0]="copyright", +  "family", +  "subfamily", +  "uniqueid", +  "fullname", +  "version", +  "postscriptname", +  "trademark", +  "manufacturer", +  "designer", +  "description", +  "venderurl", +  "designerurl", +  "license", +  "licenseurl", +  "reserved", +  "typographicfamily", +  "typographicsubfamily", +  "compatiblefullname", +  "sampletext", +  "cidfindfontname", +  "wwsfamily", +  "wwssubfamily", +  "lightbackgroundpalette", +  "darkbackgroundpalette", +} +local platforms={ [0]="unicode", +  "macintosh", +  "iso", +  "windows", +  "custom", +} +local encodings={ +  unicode={ [0]="unicode 1.0 semantics", +    "unicode 1.1 semantics", +    "iso/iec 10646", +    "unicode 2.0 bmp", +    "unicode 2.0 full", +    "unicode variation sequences", +    "unicode full repertoire", +  }, +  macintosh={ [0]="roman","japanese","chinese (traditional)","korean","arabic","hebrew","greek","russian", +    "rsymbol","devanagari","gurmukhi","gujarati","oriya","bengali","tamil","telugu","kannada", +    "malayalam","sinhalese","burmese","khmer","thai","laotian","georgian","armenian", +    "chinese (simplified)","tibetan","mongolian","geez","slavic","vietnamese","sindhi", +    "uninterpreted", +  }, +  iso={ [0]="7-bit ascii", +    "iso 10646", +    "iso 8859-1", +  }, +  windows={ [0]="symbol", +    "unicode bmp", +    "shiftjis", +    "prc", +    "big5", +    "wansung", +    "johab", +    "reserved 7", +    "reserved 8", +    "reserved 9", +    "unicode ucs-4", +  }, +  custom={ +  } +} +local decoders={ +  unicode={}, +  macintosh={}, +  iso={}, +  windows={ +    ["unicode bmp"]=utf16_to_utf8_be +  }, +  custom={}, +} +local languages={ +  unicode={ +    [ 0]="english", +  }, +  macintosh={ +    [ 0]="english", +  }, +  iso={}, +  windows={ +    [0x0409]="english - united states", +  }, +  custom={}, +} +local standardromanencoding={ [0]= +  "notdef",".null","nonmarkingreturn","space","exclam","quotedbl", +  "numbersign","dollar","percent","ampersand","quotesingle","parenleft", +  "parenright","asterisk","plus","comma","hyphen","period","slash", +  "zero","one","two","three","four","five","six","seven","eight", +  "nine","colon","semicolon","less","equal","greater","question","at", +  "A","B","C","D","E","F","G","H","I","J","K","L","M","N","O", +  "P","Q","R","S","T","U","V","W","X","Y","Z","bracketleft", +  "backslash","bracketright","asciicircum","underscore","grave","a","b", +  "c","d","e","f","g","h","i","j","k","l","m","n","o","p","q", +  "r","s","t","u","v","w","x","y","z","braceleft","bar", +  "braceright","asciitilde","Adieresis","Aring","Ccedilla","Eacute", +  "Ntilde","Odieresis","Udieresis","aacute","agrave","acircumflex", +  "adieresis","atilde","aring","ccedilla","eacute","egrave", +  "ecircumflex","edieresis","iacute","igrave","icircumflex","idieresis", +  "ntilde","oacute","ograve","ocircumflex","odieresis","otilde","uacute", +  "ugrave","ucircumflex","udieresis","dagger","degree","cent","sterling", +  "section","bullet","paragraph","germandbls","registered","copyright", +  "trademark","acute","dieresis","notequal","AE","Oslash","infinity", +  "plusminus","lessequal","greaterequal","yen","mu","partialdiff", +  "summation","product","pi","integral","ordfeminine","ordmasculine", +  "Omega","ae","oslash","questiondown","exclamdown","logicalnot", +  "radical","florin","approxequal","Delta","guillemotleft", +  "guillemotright","ellipsis","nonbreakingspace","Agrave","Atilde", +  "Otilde","OE","oe","endash","emdash","quotedblleft","quotedblright", +  "quoteleft","quoteright","divide","lozenge","ydieresis","Ydieresis", +  "fraction","currency","guilsinglleft","guilsinglright","fi","fl", +  "daggerdbl","periodcentered","quotesinglbase","quotedblbase", +  "perthousand","Acircumflex","Ecircumflex","Aacute","Edieresis","Egrave", +  "Iacute","Icircumflex","Idieresis","Igrave","Oacute","Ocircumflex", +  "apple","Ograve","Uacute","Ucircumflex","Ugrave","dotlessi", +  "circumflex","tilde","macron","breve","dotaccent","ring","cedilla", +  "hungarumlaut","ogonek","caron","Lslash","lslash","Scaron","scaron", +  "Zcaron","zcaron","brokenbar","Eth","eth","Yacute","yacute","Thorn", +  "thorn","minus","multiply","onesuperior","twosuperior","threesuperior", +  "onehalf","onequarter","threequarters","franc","Gbreve","gbreve", +  "Idotaccent","Scedilla","scedilla","Cacute","cacute","Ccaron","ccaron", +  "dcroat", +} +local weights={ +  [100]="thin", +  [200]="extralight", +  [300]="light", +  [400]="normal", +  [500]="medium", +  [600]="semibold", +  [700]="bold", +  [800]="extrabold", +  [900]="black", +} +local widths={ +  [1]="ultracondensed", +  [2]="extracondensed", +  [3]="condensed", +  [4]="semicondensed", +  [5]="normal", +  [6]="semiexpanded", +  [7]="expanded", +  [8]="extraexpanded", +  [9]="ultraexpanded", +} +setmetatableindex(weights,function(t,k) +  local r=floor((k+50)/100)*100 +  local v=(r>900 and "black") or rawget(t,r) or "normal" +  return v +end) +setmetatableindex(widths,function(t,k) +  return "normal" +end) +local panoseweights={ +  [ 0]="normal", +  [ 1]="normal", +  [ 2]="verylight", +  [ 3]="light", +  [ 4]="thin", +  [ 5]="book", +  [ 6]="medium", +  [ 7]="demi", +  [ 8]="bold", +  [ 9]="heavy", +  [10]="black", +} +local panosewidths={ +  [ 0]="normal", +  [ 1]="normal", +  [ 2]="normal", +  [ 3]="normal", +  [ 4]="normal", +  [ 5]="expanded", +  [ 6]="condensed", +  [ 7]="veryexpanded", +  [ 8]="verycondensed", +  [ 9]="monospaced", +} +function readers.name(f,fontdata) +  local datatable=fontdata.tables.name +  if datatable then +    setposition(f,datatable.offset) +    local format=readushort(f) +    local nofnames=readushort(f) +    local offset=readushort(f) +    local namelists={ +      unicode={}, +      windows={}, +      macintosh={}, +    } +    for i=1,nofnames do +      local platform=platforms[readushort(f)] +      if platform then +        local namelist=namelists[platform] +        if namelist then +          local encoding=readushort(f) +          local language=readushort(f) +          local encodings=encodings[platform] +          local languages=languages[platform] +          if encodings and languages then +            local encoding=encodings[encoding] +            local language=languages[language] +            if encoding and language then +              local name=reservednames[readushort(f)] +              if name then +                namelist[#namelist+1]={ +                  platform=platform, +                  encoding=encoding, +                  language=language, +                  name=name, +                  length=readushort(f), +                  offset=readushort(f), +                } +              else +                skipshort(f,2) +              end +            else +              skipshort(f,3) +            end +          else +            skipshort(f,3) +          end +        else +          skipshort(f,5) +        end +      else +        skipshort(f,5) +      end +    end +    local start=datatable.offset+offset +    local names={} +    local done={} +    local function filter(platform,e,l) +      local namelist=namelists[platform] +      for i=1,#namelist do +        local name=namelist[i] +        local nametag=name.name +        if not done[nametag] then +          local encoding=name.encoding +          local language=name.language +          if (not e or encoding==e) and (not l or language==l) then +            setposition(f,start+name.offset) +            local content=readstring(f,name.length) +            local decoder=decoders[platform] +            if decoder then +              decoder=decoder[encoding] +            end +            if decoder then +              content=decoder(content) +            end +            names[nametag]={ +              content=content, +              platform=platform, +              encoding=encoding, +              language=language, +            } +            done[nametag]=true +          end +        end +      end +    end +    filter("windows","unicode bmp","english - united states") +    filter("macintosh","roman","english") +    filter("windows") +    filter("macintosh") +    filter("unicode") +    fontdata.names=names +  else +    fontdata.names={} +  end +end +readers["os/2"]=function(f,fontdata) +  local datatable=fontdata.tables["os/2"] +  if datatable then +    setposition(f,datatable.offset) +    local version=readushort(f) +    local windowsmetrics={ +      version=version, +      averagewidth=readshort(f), +      weightclass=readushort(f), +      widthclass=readushort(f), +      fstype=readushort(f), +      subscriptxsize=readshort(f), +      subscriptysize=readshort(f), +      subscriptxoffset=readshort(f), +      subscriptyoffset=readshort(f), +      superscriptxsize=readshort(f), +      superscriptysize=readshort(f), +      superscriptxoffset=readshort(f), +      superscriptyoffset=readshort(f), +      strikeoutsize=readshort(f), +      strikeoutpos=readshort(f), +      familyclass=readshort(f), +      panose={ readbytes(f,10) }, +      unicoderanges={ readulong(f),readulong(f),readulong(f),readulong(f) }, +      vendor=readstring(f,4), +      fsselection=readushort(f), +      firstcharindex=readushort(f), +      lastcharindex=readushort(f), +      typoascender=readshort(f), +      typodescender=readshort(f), +      typolinegap=readshort(f), +      winascent=readushort(f), +      windescent=readushort(f), +    } +    if version>=1 then +      windowsmetrics.codepageranges={ readulong(f),readulong(f) } +    end +    if version>=3 then +      windowsmetrics.xheight=readshort(f) +      windowsmetrics.capheight=readshort(f) +      windowsmetrics.defaultchar=readushort(f) +      windowsmetrics.breakchar=readushort(f) +    end +    windowsmetrics.weight=windowsmetrics.weightclass and weights[windowsmetrics.weightclass] +    windowsmetrics.width=windowsmetrics.widthclass and widths [windowsmetrics.widthclass] +    windowsmetrics.panoseweight=panoseweights[windowsmetrics.panose[3]] +    windowsmetrics.panosewidth=panosewidths [windowsmetrics.panose[4]] +    fontdata.windowsmetrics=windowsmetrics    else -    return formats.ttf,suffix=="ttf" +    fontdata.windowsmetrics={} +  end +end +readers.head=function(f,fontdata) +  local datatable=fontdata.tables.head +  if datatable then +    setposition(f,datatable.offset) +    local fontheader={ +      version=readfixed(f), +      revision=readfixed(f), +      checksum=readulong(f), +      magic=readulong(f), +      flags=readushort(f), +      units=readushort(f), +      created=readlongdatetime(f), +      modified=readlongdatetime(f), +      xmin=readshort(f), +      ymin=readshort(f), +      xmax=readshort(f), +      ymax=readshort(f), +      macstyle=readushort(f), +      smallpixels=readushort(f), +      directionhint=readshort(f), +      indextolocformat=readshort(f), +      glyphformat=readshort(f), +    } +    fontdata.fontheader=fontheader +  else +    fontdata.fontheader={} +  end +  fontdata.nofglyphs=0 +end +readers.hhea=function(f,fontdata,specification) +  if specification.details then +    local datatable=fontdata.tables.hhea +    if datatable then +      setposition(f,datatable.offset) +      fontdata.horizontalheader={ +        version=readfixed(f), +        ascender=readfword(f), +        descender=readfword(f), +        linegap=readfword(f), +        maxadvancewidth=readufword(f), +        minleftsidebearing=readfword(f), +        minrightsidebearing=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), +        nofhmetrics=readushort(f), +      } +    else +      fontdata.horizontalheader={ +        nofhmetrics=0, +      } +    end    end  end -local function otf_format(filename) -  local format,okay=otf.fileformat(filename) -  if not okay then -    report_otf("font %a is actually an %a file",filename,format) +readers.maxp=function(f,fontdata,specification) +  if specification.details then +    local datatable=fontdata.tables.maxp +    if datatable then +      setposition(f,datatable.offset) +      local version=readfixed(f) +      local nofglyphs=readushort(f) +      fontdata.nofglyphs=nofglyphs +      if version==0.5 then +        fontdata.maximumprofile={ +          version=version, +          nofglyphs=nofglyphs, +        } +        return +      elseif version==1.0 then +        fontdata.maximumprofile={ +          version=version, +          nofglyphs=nofglyphs, +          points=readushort(f), +          contours=readushort(f), +          compositepoints=readushort(f), +          compositecontours=readushort(f), +          zones=readushort(f), +          twilightpoints=readushort(f), +          storage=readushort(f), +          functiondefs=readushort(f), +          instructiondefs=readushort(f), +          stackelements=readushort(f), +          sizeofinstructions=readushort(f), +          componentelements=readushort(f), +          componentdepth=readushort(f), +        } +        return +      end +    end +    fontdata.maximumprofile={ +      version=version, +      nofglyphs=0, +    }    end -  return format  end -local function load_featurefile(raw,featurefile) -  if featurefile and featurefile~="" then -    if trace_loading then -      report_otf("using featurefile %a",featurefile) +readers.hmtx=function(f,fontdata,specification) +  if specification.glyphs then +    local datatable=fontdata.tables.hmtx +    if datatable then +      setposition(f,datatable.offset) +      local nofmetrics=fontdata.horizontalheader.nofhmetrics +      local glyphs=fontdata.glyphs +      local nofglyphs=fontdata.nofglyphs +      local nofrepeated=nofglyphs-nofmetrics +      local width=0  +      local leftsidebearing=0 +      for i=0,nofmetrics-1 do +        local glyph=glyphs[i] +        width=readshort(f) +        leftsidebearing=readshort(f) +        if advance~=0 then +          glyph.width=width +        end +      end +      for i=nofmetrics,nofrepeated do +        local glyph=glyphs[i] +        if width~=0 then +          glyph.width=width +        end +      end +    end +  end +end +readers.post=function(f,fontdata,specification) +  local datatable=fontdata.tables.post +  if datatable then +    setposition(f,datatable.offset) +    local version=readfixed(f) +    fontdata.postscript={ +      version=version, +      italicangle=round(1000*readfixed(f))/1000, +      underlineposition=readfword(f), +      underlinethickness=readfword(f), +      monospaced=readulong(f), +      minmemtype42=readulong(f), +      maxmemtype42=readulong(f), +      minmemtype1=readulong(f), +      maxmemtype1=readulong(f), +    } +    if not specification.glyphs then +    elseif version==1.0 then +      for index=0,#standardromanencoding do +        glyphs[index].name=standardromanencoding[index] +      end +    elseif version==2.0 then +      local glyphs=fontdata.glyphs +      local nofglyphs=readushort(f) +      local indices={} +      local names={} +      local maxnames=0 +      for i=0,nofglyphs-1 do +        local nameindex=readushort(f) +        if nameindex>=258 then +          maxnames=maxnames+1 +          nameindex=nameindex-257 +          indices[nameindex]=i +        else +          glyphs[i].name=standardromanencoding[nameindex] +        end +      end +      for i=1,maxnames do +        local mapping=indices[i] +        if not mapping then +          report("quit post name fetching at %a of %a: %s",i,maxnames,"no index") +          break +        else +          local length=readbyte(f) +          if length>0 then +            glyphs[mapping].name=readstring(f,length) +          else +            report("quit post name fetching at %a of %a: %s",i,maxnames,"overflow") +            break +          end +        end +      end +    elseif version==2.5 then +    elseif version==3.0 then      end -    apply_featurefile(raw,featurefile) +  else +    fontdata.postscript={}    end  end -local function showfeatureorder(rawdata,filename) -  local sequences=rawdata.resources.sequences -  if sequences and #sequences>0 then -    if trace_loading then -      report_otf("font %a has %s sequences",filename,#sequences) -      report_otf(" ") -    end -    for nos=1,#sequences do -      local sequence=sequences[nos] -      local typ=sequence.type   or "no-type" -      local name=sequence.name   or "no-name" -      local subtables=sequence.subtables or { "no-subtables" } -      local features=sequence.features -      if trace_loading then -        report_otf("%3i  %-15s  %-20s  [% t]",nos,name,typ,subtables) +readers.cff=function(f,fontdata,specification) +  if specification.glyphs then +    reportskippedtable("cff") +  end +end +local formatreaders={} +local duplicatestoo=true +local sequence={ +  { 3,1,4 }, +  { 3,10,12 }, +  { 0,3,4 }, +  { 0,1,4 }, +  { 0,0,6 }, +  { 3,0,6 }, +  { 0,5,14 }, +} +local supported={} +for i=1,#sequence do +  local sp,se,sf=unpack(sequence[i]) +  local p=supported[sp] +  if not p then +    p={} +    supported[sp]=p +  end +  local e=p[se] +  if not e then +    e={} +    p[se]=e +  end +  e[sf]=true +end +formatreaders[4]=function(f,fontdata,offset) +  setposition(f,offset+2) +  local length=readushort(f)  +  local language=readushort(f) +  local nofsegments=readushort(f)/2 +  skipshort(f,3) +  local endchars={} +  local startchars={} +  local deltas={} +  local offsets={} +  local indices={} +  local mapping=fontdata.mapping +  local glyphs=fontdata.glyphs +  local duplicates=fontdata.duplicates +  local nofdone=0 +  for i=1,nofsegments do +    endchars[i]=readushort(f) +  end +  local reserved=readushort(f)  +  for i=1,nofsegments do +    startchars[i]=readushort(f) +  end +  for i=1,nofsegments do +    deltas[i]=readshort(f) +  end +  for i=1,nofsegments do +    offsets[i]=readushort(f) +  end +  local size=(length-2*2-5*2-4*nofsegments*2)/2 +  for i=1,size-1 do +    indices[i]=readushort(f) +  end +  for segment=1,nofsegments do +    local startchar=startchars[segment] +    local endchar=endchars[segment] +    local offset=offsets[segment] +    local delta=deltas[segment] +    if startchar==0xFFFF and endchar==0xFFFF then +    elseif startchar==0xFFFF and offset==0 then +    elseif offset==0xFFFF then +    elseif offset==0 then +      if trace_cmap then +        report("format 4.%i segment %2i from %C upto %C at index %H",1,segment,startchar,endchar,mod(startchar+delta,65536)) +      end +      for unicode=startchar,endchar do +        local index=mod(unicode+delta,65536) +        if index and index>0 then +          local glyph=glyphs[index] +          if glyph then +            local gu=glyph.unicode +            if not gu then +              glyph.unicode=unicode +              nofdone=nofdone+1 +            elseif gu~=unicode then +              if duplicatestoo then +                local d=duplicates[gu] +                if d then +                  d[unicode]=true +                else +                  duplicates[gu]={ [unicode]=true } +                end +              else +                report("duplicate case 1: %C %04i %s",unicode,index,glyphs[index].name) +              end +            end +            if not mapping[index] then +              mapping[index]=unicode +            end +          end +        end        end -      if features then -        for feature,scripts in next,features do -          local tt={} -          if type(scripts)=="table" then -            for script,languages in next,scripts do -              local ttt={} -              for language,_ in next,languages do -                ttt[#ttt+1]=language +    else +      local shift=(segment-nofsegments+offset/2)-startchar +      if trace_cmap then +        report("format 4.%i segment %2i from %C upto %C at index %H",0,segment,startchar,endchar,mod(startchar+delta,65536)) +      end +      for unicode=startchar,endchar do +        local slot=shift+unicode +        local index=indices[slot] +        if index and index>0 then +          index=mod(index+delta,65536) +          local glyph=glyphs[index] +          if glyph then +            local gu=glyph.unicode +            if not gu then +              glyph.unicode=unicode +              nofdone=nofdone+1 +            elseif gu~=unicode then +              if duplicatestoo then +                local d=duplicates[gu] +                if d then +                  d[unicode]=true +                else +                  duplicates[gu]={ [unicode]=true } +                end +              else +                report("duplicate case 2: %C %04i %s",unicode,index,glyphs[index].name)                end -              tt[#tt+1]=formatters["[%s: % t]"](script,ttt)              end -            if trace_loading then -              report_otf("       %s: % t",feature,tt) +            if not mapping[index] then +              mapping[index]=unicode              end +          end +        end +      end +    end +  end +  return nofdone +end +formatreaders[6]=function(f,fontdata,offset) +  setposition(f,offset)  +  local format=readushort(f) +  local length=readushort(f) +  local language=readushort(f) +  local mapping=fontdata.mapping +  local glyphs=fontdata.glyphs +  local duplicates=fontdata.duplicates +  local start=readushort(f) +  local count=readushort(f) +  local stop=start+count-1 +  local nofdone=0 +  if trace_cmap then +    report("format 6 from %C to %C",2,start,stop) +  end +  for unicode=start,stop do +    local index=readushort(f) +    if index>0 then +      local glyph=glyphs[index] +      if glyph then +        local gu=glyph.unicode +        if not gu then +          glyph.unicode=unicode +          nofdone=nofdone+1 +        elseif gu~=unicode then +        end +        if not mapping[index] then +          mapping[index]=unicode +        end +      end +    end +  end +  return nofdone +end +formatreaders[12]=function(f,fontdata,offset) +  setposition(f,offset+2+2+4+4)  +  local mapping=fontdata.mapping +  local glyphs=fontdata.glyphs +  local duplicates=fontdata.duplicates +  local nofgroups=readulong(f) +  local nofdone=0 +  for i=1,nofgroups do +    local first=readulong(f) +    local last=readulong(f) +    local index=readulong(f) +    if trace_cmap then +      report("format 12 from %C to %C",first,last) +    end +    for unicode=first,last do +      local glyph=glyphs[index] +      if glyph then +        local gu=glyph.unicode +        if not gu then +          glyph.unicode=unicode +          nofdone=nofdone+1 +        elseif gu~=unicode then +          local d=duplicates[gu] +          if d then +            d[unicode]=true            else -            if trace_loading then -              report_otf("       %s: %S",feature,scripts) +            duplicates[gu]={ [unicode]=true } +          end +        end +        if not mapping[index] then +          mapping[index]=unicode +        end +      end +      index=index+1 +    end +  end +  return nofdone +end +formatreaders[14]=function(f,fontdata,offset) +  if offset and offset~=0 then +    setposition(f,offset) +    local format=readushort(f) +    local length=readulong(f) +    local nofrecords=readulong(f) +    local records={} +    local variants={} +    local nofdone=0 +    fontdata.variants=variants +    for i=1,nofrecords do +      records[i]={ +        selector=readuint(f), +        default=readulong(f), +        other=readulong(f), +      } +    end +    for i=1,nofrecords do +      local record=records[i] +      local selector=record.selector +      local default=record.default +      local other=record.other +      local other=record.other +      if other~=0 then +        setposition(f,offset+other) +        local mapping={} +        local count=readulong(f) +        for i=1,count do +          mapping[readuint(f)]=readushort(f) +        end +        nofdone=nofdone+count +        variants[selector]=mapping +      end +    end +    return nofdone +  else +    return 0 +  end +end +local function checkcmap(f,fontdata,records,platform,encoding,format) +  local data=records[platform] +  if not data then +    return 0 +  end +  data=data[encoding] +  if not data then +    return 0 +  end +  data=data[format] +  if not data then +    return 0 +  end +  local reader=formatreaders[format] +  if not reader then +    return 0 +  end +  local p=platforms[platform] +  local e=encodings[p] +  local n=reader(f,fontdata,data) or 0 +  report("cmap checked: platform %i (%s), encoding %i (%s), format %i, new unicodes %i",platform,p,encoding,e and e[encoding] or "?",format,n) +  return n +end +function readers.cmap(f,fontdata,specification) +  if specification.glyphs then +    local datatable=fontdata.tables.cmap +    if datatable then +      local tableoffset=datatable.offset +      setposition(f,tableoffset) +      local version=readushort(f) +      local noftables=readushort(f) +      local records={} +      local unicodecid=false +      local variantcid=false +      local variants={} +      local duplicates=fontdata.duplicates or {} +      fontdata.duplicates=duplicates +      for i=1,noftables do +        local platform=readushort(f) +        local encoding=readushort(f) +        local offset=readulong(f) +        local record=records[platform] +        if not record then +          records[platform]={ +            [encoding]={ +              offsets={ offset }, +              formats={}, +            } +          } +        else +          local subtables=record[encoding] +          if not subtables then +            record[encoding]={ +              offsets={ offset }, +              formats={}, +            } +          else +            local offsets=subtables.offsets +            offsets[#offsets+1]=offset +          end +        end +      end +      report("found cmaps:") +      for platform,record in sortedhash(records) do +        local p=platforms[platform] +        local e=encodings[p] +        local sp=supported[platform] +        local ps=p or "?" +        if sp then +          report("  platform %i: %s",platform,ps) +        else +          report("  platform %i: %s (unsupported)",platform,ps) +        end +        for encoding,subtables in sortedhash(record) do +          local se=sp and sp[encoding] +          local es=e and e[encoding] or "?" +          if se then +            report("    encoding %i: %s",encoding,es) +          else +            report("    encoding %i: %s (unsupported)",encoding,es) +          end +          local offsets=subtables.offsets +          local formats=subtables.formats +          for i=1,#offsets do +            local offset=tableoffset+offsets[i] +            setposition(f,offset) +            formats[readushort(f)]=offset +          end +          record[encoding]=formats +          local list=sortedkeys(formats) +          for i=1,#list do +            if not (se and se[list[i]]) then +              list[i]=list[i].." (unsupported)"              end            end +          report("      formats: % t",list)          end        end +      local ok=false +      for i=1,#sequence do +        local sp,se,sf=unpack(sequence[i]) +        if checkcmap(f,fontdata,records,sp,se,sf)>0 then +          ok=true +        end +      end +      if not ok then +        report("no useable unicode cmap found") +      end +      fontdata.cidmaps={ +        version=version, +        noftables=noftables, +        records=records, +      } +    else +      fontdata.cidmaps={}      end -    if trace_loading then -      report_otf("\n") +  end +end +function readers.loca(f,fontdata,specification) +  if specification.glyphs then +    reportskippedtable("loca") +  end +end +function readers.glyf(f,fontdata,specification)  +  if specification.glyphs then +    reportskippedtable("glyf") +  end +end +function readers.kern(f,fontdata,specification) +  if specification.kerns then +    local datatable=fontdata.tables.kern +    if datatable then +      setposition(f,datatable.offset) +      local version=readushort(f) +      local noftables=readushort(f) +      for i=1,noftables do +        local version=readushort(f) +        local length=readushort(f) +        local coverage=readushort(f) +        local format=bit32.rshift(coverage,8)  +        if format==0 then +          local nofpairs=readushort(f) +          local searchrange=readushort(f) +          local entryselector=readushort(f) +          local rangeshift=readushort(f) +          local kerns={} +          local glyphs=fontdata.glyphs +          for i=1,nofpairs do +            local left=readushort(f) +            local right=readushort(f) +            local kern=readfword(f) +            local glyph=glyphs[left] +            local kerns=glyph.kerns +            if kerns then +              kerns[right]=kern +            else +              glyph.kerns={ [right]=kern } +            end +          end +        elseif format==2 then +          report("todo: kern classes") +        else +          report("todo: kerns") +        end +      end      end -  elseif trace_loading then -    report_otf("font %a has no sequences",filename) -  end -end -local valid_fields=table.tohash { -  "ascent", -  "cidinfo", -  "copyright", -  "descent", -  "design_range_bottom", -  "design_range_top", -  "design_size", -  "encodingchanged", -  "extrema_bound", -  "familyname", -  "fontname", -  "fontstyle_id", -  "fontstyle_name", -  "fullname", -  "hasvmetrics", -  "horiz_base", -  "issans", -  "isserif", -  "italicangle", -  "macstyle", -  "onlybitmaps", -  "origname", -  "os2_version", -  "pfminfo", -  "serifcheck", -  "sfd_version", -  "strokedfont", -  "strokewidth", -  "table_version", -  "ttf_tables", -  "uni_interp", -  "uniqueid", -  "units_per_em", -  "upos", -  "use_typo_metrics", -  "uwidth", -  "validation_state", -  "version", -  "vert_base", -  "weight", -  "weight_width_slope_only", -} -local ordered_enhancers={ -  "prepare tables", -  "prepare glyphs", -  "prepare lookups", -  "analyze glyphs", -  "analyze math", -  "reorganize lookups", -  "reorganize mark classes", -  "reorganize anchor classes", -  "reorganize glyph kerns", -  "reorganize glyph lookups", -  "reorganize glyph anchors", -  "merge kern classes", -  "reorganize features", -  "reorganize subtables", -  "check glyphs", -  "check metadata", -  "prepare tounicode", -  "check encoding", -  "add duplicates", -  "expand lookups", -  "check extra features", -  "cleanup tables", -  "compact lookups", -  "purge names", -} -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 +end +function readers.gdef(f,fontdata,specification) +  if specification.details then +    reportskippedtable("gdef") +  end +end +function readers.gsub(f,fontdata,specification) +  if specification.details then +    reportskippedtable("gsub") +  end +end +function readers.gpos(f,fontdata,specification) +  if specification.details then +    reportskippedtable("gpos") +  end +end +function readers.math(f,fontdata,specification) +  if specification.glyphs then +    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 -    enhancer(data,filename,raw)    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 -function enhancers.apply(data,filename,raw) -  local basename=file.basename(lower(filename)) -  if trace_loading then -    report_otf("%s enhancing file %a","start",filename) +local function unpackoutlines(data) +  local subfonts=data.subfonts +  if subfonts then +    for i=1,#subfonts do +      unpackoutlines(subfonts[i]) +    end +    return    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) +  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 -    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 +  data.segments=nil +end +otf.packoutlines=packoutlines +otf.unpackoutlines=unpackoutlines +local validutf=lpeg.patterns.validutf8 +local function getname(fontdata,key) +  local names=fontdata.names +  if names then +    local value=names[key] +    if value then +      local content=value.content +      return lpegmatch(validutf,content) and content or nil +    end +  end +end +local function getinfo(maindata,sub) +  local fontdata=sub and maindata.subfonts and maindata.subfonts[sub] or maindata +  local names=fontdata.names +  if names then +    local metrics=fontdata.windowsmetrics or {} +    local postscript=fontdata.postscript or {} +    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 +    return {  +      subfontindex=fontdata.subfontindex or sub or 0, +      fontname=getname(fontdata,"postscriptname"), +      fullname=getname(fontdata,"fullname"), +      familyname=getname(fontdata,"typographicfamily") or getname(fontdata,"family"), +      subfamily=getname(fontdata,"subfamily"), +      modifiers=getname(fontdata,"typographicsubfamily"), +      weight=weight and lower(weight), +      width=width and lower(width), +      pfmweight=metrics.weightclass or 400, +      pfmwidth=metrics.widthclass or 5, +      panosewidth=metrics.panosewidth, +      panoseweight=metrics.panoseweight, +      italicangle=postscript.italicangle or 0, +      units=fontheader.units or 0, +      designsize=fontdata.designsize, +      minsize=fontdata.minsize, +      maxsize=fontdata.maxsize, +      monospaced=(tonumber(postscript.monospaced or 0)>0) or metrics.panosewidth=="monospaced", +      averagewidth=metrics.averagewidth, +      xheight=metrics.xheight, +      ascender=metrics.typoascender, +      descender=metrics.typodescender, +    } +  elseif n then +    return { +      filename=fontdata.filename, +      comment="there is no info for subfont "..n, +    } +  else +    return { +      filename=fontdata.filename, +      comment="there is no info", +    } +  end +end +local function loadtables(f,specification,offset) +  if offset then +    setposition(f,offset) +  end +  local tables={} +  local basename=file.basename(specification.filename) +  local filesize=specification.filesize +  local filetime=specification.filetime +  local fontdata={  +    filename=basename, +    filesize=filesize, +    filetime=filetime, +    version=readstring(f,4), +    noftables=readushort(f), +    searchrange=readushort(f), +    entryselector=readushort(f), +    rangeshift=readushort(f), +    tables=tables, +  } +  for i=1,fontdata.noftables do +    local tag=lower(stripstring(readstring(f,4))) +    local checksum=readulong(f)  +    local offset=readulong(f) +    local length=readulong(f) +    if offset+length>filesize then +      report("bad %a table in file %a",tag,basename) +    end +    tables[tag]={ +      checksum=checksum, +      offset=offset, +      length=length, +    } +  end +  if tables.cff then +    fontdata.format="opentype" +  else +    fontdata.format="truetype" +  end +  return fontdata +end +local function prepareglyps(fontdata) +  local glyphs=setmetatableindex(function(t,k) +    local v={ +      index=k, +    } +    t[k]=v +    return v +  end) +  fontdata.glyphs=glyphs +  fontdata.mapping={} +end +local function readdata(f,offset,specification) +  local fontdata=loadtables(f,specification,offset) +  if specification.glyphs then +    prepareglyps(fontdata) +  end +  readers["name"](f,fontdata,specification) +  local askedname=specification.askedname +  if askedname then +    local fullname=getname(fontdata,"fullname") or "" +    local cleanname=gsub(askedname,"[^a-zA-Z0-9]","") +    local foundname=gsub(fullname,"[^a-zA-Z0-9]","") +    if lower(cleanname)~=lower(foundname) then +      return  +    end +  end +  readers["os/2"](f,fontdata,specification) +  readers["head"](f,fontdata,specification) +  readers["maxp"](f,fontdata,specification) +  readers["hhea"](f,fontdata,specification) +  readers["hmtx"](f,fontdata,specification) +  readers["post"](f,fontdata,specification) +  readers["cff" ](f,fontdata,specification) +  readers["cmap"](f,fontdata,specification) +  readers["loca"](f,fontdata,specification) +  readers["glyf"](f,fontdata,specification) +  readers["kern"](f,fontdata,specification) +  readers["gdef"](f,fontdata,specification) +  readers["gsub"](f,fontdata,specification) +  readers["gpos"](f,fontdata,specification) +  readers["math"](f,fontdata,specification) +  fontdata.locations=nil +  fontdata.tables=nil +  fontdata.cidmaps=nil +  fontdata.dictionaries=nil +  return fontdata +end +local function loadfontdata(specification) +  local filename=specification.filename +  local fileattr=lfs.attributes(filename) +  local filesize=fileattr and fileattr.size or 0 +  local filetime=fileattr and fileattr.modification or 0 +  local f=openfile(filename,true)  +  if not f then +    report("unable to open %a",filename) +  elseif filesize==0 then +    report("empty file %a",filename) +    closefile(f) +  else +    specification.filesize=filesize +    specification.filetime=filetime +    local version=readstring(f,4) +    local fontdata=nil +    if version=="OTTO" or version=="true" or version=="\0\1\0\0" then +      fontdata=readdata(f,0,specification) +    elseif version=="ttcf" then +      local subfont=tonumber(specification.subfont) +      local offsets={} +      local ttcversion=readulong(f) +      local nofsubfonts=readulong(f) +      for i=1,nofsubfonts do +        offsets[i]=readulong(f) +      end +      if subfont then  +        if subfont>=1 and subfont<=nofsubfonts then +          fontdata=readdata(f,offsets[subfont],specification) +        else +          report("no subfont %a in file %a",subfont,filename) +        end +      else +        subfont=specification.subfont +        if type(subfont)=="string" and subfont~="" then +          specification.askedname=subfont +          for i=1,nofsubfonts do +            fontdata=readdata(f,offsets[i],specification) +            if fontdata then +              fontdata.subfontindex=i +              report("subfont named %a has index %a",subfont,i) +              break +            end +          end +          if not fontdata then +            report("no subfont named %a",subfont) +          end +        else +          local subfonts={} +          fontdata={ +            filename=filename, +            filesize=filesize, +            filetime=filetime, +            version=version, +            subfonts=subfonts, +            ttcversion=ttcversion, +            nofsubfonts=nofsubfonts, +          } +          for i=1,fontdata.nofsubfonts do +            subfonts[i]=readdata(f,offsets[i],specification) +          end          end        end +    else +      report("unknown version %a in file %a",version,filename)      end -    ioflush()  +    closefile(f) +    return fontdata or {}    end -  if trace_loading then -    report_otf("%s enhancing file %a","stop",filename) +end +local function loadfont(specification,n) +  if type(specification)=="string" then +    specification={ +      filename=specification, +      info=true, +      details=true, +      glyphs=true, +      shapes=true, +      kerns=true, +      globalkerns=true, +      lookups=true, +      subfont=n or true, +      tounicode=false, +    } +  end +  if specification.shapes or specification.lookups or specification.kerns then +    specification.glyphs=true +  end +  if specification.glyphs then +    specification.details=true +  end +  if specification.details then +    specification.info=true +  end +  local function message(str) +    report("fatal error in file %a: %s\n%s",specification.filename,str,debug.traceback()) +  end +  local ok,result=xpcall(loadfontdata,message,specification) +  if ok then +    return result    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 +function readers.loadshapes(filename,n) +  local fontdata=loadfont { +    filename=filename, +    shapes=true, +    subfont=n, +  } +  return fontdata and { +    filename=filename, +    format=fontdata.format, +    glyphs=fontdata.glyphs, +    units=fontdata.fontheader.units, +  } or { +    filename=filename, +    format="unknown", +    glyphs={}, +    units=0, +  } +end +function readers.loadfont(filename,n) +  local fontdata=loadfont { +    filename=filename, +    glyphs=true, +    shapes=false, +    lookups=true, +    subfont=n, +  } +  if fontdata then +    return { +      tableversion=tableversion, +      creator="context mkiv", +      size=fontdata.filesize, +      time=fontdata.filetime, +      glyphs=fontdata.glyphs, +      descriptions=fontdata.descriptions, +      format=fontdata.format, +      goodies={}, +      metadata=getinfo(fontdata,n), +      properties={ +        hasitalics=fontdata.hasitalics or false, +      }, +      resources={ +        filename=filename, +        private=privateoffset, +        duplicates=fontdata.duplicates or {}, +        features=fontdata.features  or {}, +        sublookups=fontdata.sublookups or {}, +        marks=fontdata.marks    or {}, +        markclasses=fontdata.markclasses or {}, +        marksets=fontdata.marksets  or {}, +        sequences=fontdata.sequences  or {}, +        variants=fontdata.variants, +        version=getname(fontdata,"version"), +        cidinfo=fontdata.cidinfo, +        mathconstants=fontdata.mathconstants, +      }, +    } +  end +end +function readers.getinfo(filename,n,details) +  local fontdata=loadfont { +    filename=filename, +    details=true, +  } +  if fontdata then +    local subfonts=fontdata.subfonts +    if not subfonts then +      return getinfo(fontdata) +    elseif type(n)~="number" then +      local info={} +      for i=1,#subfonts do +        info[i]=getinfo(fontdata,i) +      end +      return info +    elseif n>1 and n<=subfonts then +      return getinfo(fontdata,n)      else -      pw[where]={ [pattern]=action} +      return { +        filename=filename, +        comment="there is no subfont "..n.." in this file" +      }      end +  else +    return { +      filename=filename, +      comment="the file cannot be opened for reading", +    }    end  end -function patches.report(fmt,...) -  if trace_loading then -    report_otf("patching: %s",formatters[fmt](...)) +function readers.rehash(fontdata,hashmethod) +  report("the %a helper is not yet implemented","rehash") +end +function readers.checkhash(fontdata) +  report("the %a helper is not yet implemented","checkhash") +end +function readers.pack(fontdata,hashmethod) +  report("the %a helper is not yet implemented","pack") +end +function readers.unpack(fontdata) +  report("the %a helper is not yet implemented","unpack") +end +function readers.expand(fontdata) +  report("the %a helper is not yet implemented","unpack") +end +function readers.compact(fontdata) +  report("the %a helper is not yet implemented","compact") +end +local extenders={} +function readers.registerextender(extender) +  extenders[#extenders+1]=extender +end +function readers.extend(fontdata) +  for i=1,#extenders do +    local extender=extenders[i] +    local name=extender.name or "unknown" +    local action=extender.action +    if action then +      action(fontdata) +    end    end  end -function enhancers.register(what,action)  -  actions[what]=action +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 -function otf.load(filename,sub,featurefile)  -  local base=file.basename(file.removesuffix(filename)) -  local name=file.removesuffix(base) -  local attr=lfs.attributes(filename) -  local size=attr and attr.size or 0 -  local time=attr and attr.modification or 0 -  if featurefile then -    name=name.."@"..file.removesuffix(file.basename(featurefile)) + +end -- closure + +do -- begin closure to overcome local limits and interference + +if not modules then modules={} end modules ['font-cff']={ +  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,tonumber=next,type,tonumber +local byte=string.byte +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 readers=fonts.handlers.otf.readers +local streamreader=readers.streamreader +local readbytes=streamreader.readbytes +local readstring=streamreader.readstring +local readbyte=streamreader.readcardinal1  +local readushort=streamreader.readcardinal2  +local readuint=streamreader.readcardinal3  +local readulong=streamreader.readcardinal4  +local setposition=streamreader.setposition +local getposition=streamreader.getposition +local setmetatableindex=table.setmetatableindex +local trace_charstrings=false trackers.register("fonts.cff.charstrings",function(v) trace_charstrings=v end) +local report=logs.reporter("otf reader","cff") +local parsedictionaries +local parsecharstring +local parsecharstrings +local resetcharstrings +local parseprivates +local defaultstrings={ [0]= +  ".notdef","space","exclam","quotedbl","numbersign","dollar","percent", +  "ampersand","quoteright","parenleft","parenright","asterisk","plus", +  "comma","hyphen","period","slash","zero","one","two","three","four", +  "five","six","seven","eight","nine","colon","semicolon","less", +  "equal","greater","question","at","A","B","C","D","E","F","G","H", +  "I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W", +  "X","Y","Z","bracketleft","backslash","bracketright","asciicircum", +  "underscore","quoteleft","a","b","c","d","e","f","g","h","i","j", +  "k","l","m","n","o","p","q","r","s","t","u","v","w","x","y", +  "z","braceleft","bar","braceright","asciitilde","exclamdown","cent", +  "sterling","fraction","yen","florin","section","currency", +  "quotesingle","quotedblleft","guillemotleft","guilsinglleft", +  "guilsinglright","fi","fl","endash","dagger","daggerdbl", +  "periodcentered","paragraph","bullet","quotesinglbase","quotedblbase", +  "quotedblright","guillemotright","ellipsis","perthousand","questiondown", +  "grave","acute","circumflex","tilde","macron","breve","dotaccent", +  "dieresis","ring","cedilla","hungarumlaut","ogonek","caron","emdash", +  "AE","ordfeminine","Lslash","Oslash","OE","ordmasculine","ae", +  "dotlessi","lslash","oslash","oe","germandbls","onesuperior", +  "logicalnot","mu","trademark","Eth","onehalf","plusminus","Thorn", +  "onequarter","divide","brokenbar","degree","thorn","threequarters", +  "twosuperior","registered","minus","eth","multiply","threesuperior", +  "copyright","Aacute","Acircumflex","Adieresis","Agrave","Aring", +  "Atilde","Ccedilla","Eacute","Ecircumflex","Edieresis","Egrave", +  "Iacute","Icircumflex","Idieresis","Igrave","Ntilde","Oacute", +  "Ocircumflex","Odieresis","Ograve","Otilde","Scaron","Uacute", +  "Ucircumflex","Udieresis","Ugrave","Yacute","Ydieresis","Zcaron", +  "aacute","acircumflex","adieresis","agrave","aring","atilde", +  "ccedilla","eacute","ecircumflex","edieresis","egrave","iacute", +  "icircumflex","idieresis","igrave","ntilde","oacute","ocircumflex", +  "odieresis","ograve","otilde","scaron","uacute","ucircumflex", +  "udieresis","ugrave","yacute","ydieresis","zcaron","exclamsmall", +  "Hungarumlautsmall","dollaroldstyle","dollarsuperior","ampersandsmall", +  "Acutesmall","parenleftsuperior","parenrightsuperior","twodotenleader", +  "onedotenleader","zerooldstyle","oneoldstyle","twooldstyle", +  "threeoldstyle","fouroldstyle","fiveoldstyle","sixoldstyle", +  "sevenoldstyle","eightoldstyle","nineoldstyle","commasuperior", +  "threequartersemdash","periodsuperior","questionsmall","asuperior", +  "bsuperior","centsuperior","dsuperior","esuperior","isuperior", +  "lsuperior","msuperior","nsuperior","osuperior","rsuperior","ssuperior", +  "tsuperior","ff","ffi","ffl","parenleftinferior","parenrightinferior", +  "Circumflexsmall","hyphensuperior","Gravesmall","Asmall","Bsmall", +  "Csmall","Dsmall","Esmall","Fsmall","Gsmall","Hsmall","Ismall", +  "Jsmall","Ksmall","Lsmall","Msmall","Nsmall","Osmall","Psmall", +  "Qsmall","Rsmall","Ssmall","Tsmall","Usmall","Vsmall","Wsmall", +  "Xsmall","Ysmall","Zsmall","colonmonetary","onefitted","rupiah", +  "Tildesmall","exclamdownsmall","centoldstyle","Lslashsmall", +  "Scaronsmall","Zcaronsmall","Dieresissmall","Brevesmall","Caronsmall", +  "Dotaccentsmall","Macronsmall","figuredash","hypheninferior", +  "Ogoneksmall","Ringsmall","Cedillasmall","questiondownsmall","oneeighth", +  "threeeighths","fiveeighths","seveneighths","onethird","twothirds", +  "zerosuperior","foursuperior","fivesuperior","sixsuperior", +  "sevensuperior","eightsuperior","ninesuperior","zeroinferior", +  "oneinferior","twoinferior","threeinferior","fourinferior", +  "fiveinferior","sixinferior","seveninferior","eightinferior", +  "nineinferior","centinferior","dollarinferior","periodinferior", +  "commainferior","Agravesmall","Aacutesmall","Acircumflexsmall", +  "Atildesmall","Adieresissmall","Aringsmall","AEsmall","Ccedillasmall", +  "Egravesmall","Eacutesmall","Ecircumflexsmall","Edieresissmall", +  "Igravesmall","Iacutesmall","Icircumflexsmall","Idieresissmall", +  "Ethsmall","Ntildesmall","Ogravesmall","Oacutesmall","Ocircumflexsmall", +  "Otildesmall","Odieresissmall","OEsmall","Oslashsmall","Ugravesmall", +  "Uacutesmall","Ucircumflexsmall","Udieresissmall","Yacutesmall", +  "Thornsmall","Ydieresissmall","001.000","001.001","001.002","001.003", +  "Black","Bold","Book","Light","Medium","Regular","Roman","Semibold", +} +local cffreaders={ +  readbyte, +  readushort, +  readuint, +  readulong, +} +local function readheader(f) +  local offset=getposition(f) +  local header={ +    offset=offset, +    major=readbyte(f), +    minor=readbyte(f), +    size=readbyte(f), +    osize=readbyte(f), +  } +  setposition(f,offset+header.size) +  return header +end +local function readlengths(f) +  local count=readushort(f) +  if count==0 then +    return {}    end -  if sub=="" then -    sub=false +  local osize=readbyte(f) +  local read=cffreaders[osize] +  if not read then +    report("bad offset size: %i",osize) +    return {}    end -  local hash=name -  if sub then -    hash=hash.."-"..sub +  local lengths={} +  local previous=read(f) +  for i=1,count do +    local offset=read(f) +    lengths[i]=offset-previous +    previous=offset    end -  hash=containers.cleanname(hash) -  local featurefiles -  if featurefile then -    featurefiles={} -    for s in gmatch(featurefile,"[^,]+") do -      local name=resolvers.findfile(file.addsuffix(s,'fea'),'fea') or "" -      if name=="" then -        report_otf("loading error, no featurefile %a",s) -      else -        local attr=lfs.attributes(name) -        featurefiles[#featurefiles+1]={ -          name=name, -          size=attr and attr.size or 0, -          time=attr and attr.modification or 0, +  return lengths +end +local function readfontnames(f) +  local names=readlengths(f) +  for i=1,#names do +    names[i]=readstring(f,names[i]) +  end +  return names +end +local function readtopdictionaries(f) +  local dictionaries=readlengths(f) +  for i=1,#dictionaries do +    dictionaries[i]=readstring(f,dictionaries[i]) +  end +  return dictionaries +end +local function readstrings(f) +  local lengths=readlengths(f) +  local strings=setmetatableindex({},defaultstrings) +  local index=#defaultstrings +  for i=1,#lengths do +    index=index+1 +    strings[index]=readstring(f,lengths[i]) +  end +  return strings +end +do +  local stack={} +  local top=0 +  local result={} +  local strings={} +  local p_single=P("\00")/function() +      result.version=strings[stack[top]] or "unset" +      top=0 +    end+P("\01")/function() +      result.notice=strings[stack[top]] or "unset" +      top=0 +    end+P("\02")/function() +      result.fullname=strings[stack[top]] or "unset" +      top=0 +    end+P("\03")/function() +      result.familyname=strings[stack[top]] or "unset" +      top=0 +    end+P("\04")/function() +      result.weight=strings[stack[top]] or "unset" +      top=0 +    end+P("\05")/function() +      result.fontbbox={ unpack(stack,1,4) } +      top=0 +    end ++P("\13")/function() +      result.uniqueid=stack[top] +      top=0 +    end+P("\14")/function() +      result.xuid=concat(stack,"",1,top) +      top=0 +    end+P("\15")/function() +      result.charset=stack[top] +      top=0 +    end+P("\16")/function() +      result.encoding=stack[top] +      top=0 +    end+P("\17")/function() +      result.charstrings=stack[top] +      top=0 +    end+P("\18")/function() +      result.private={ +        size=stack[top-1], +        offset=stack[top], +      } +      top=0 +    end+P("\19")/function() +      result.subroutines=stack[top] +    end+P("\20")/function() +      result.defaultwidthx=stack[top] +    end+P("\21")/function() +      result.nominalwidthx=stack[top] +    end +  local p_double=P("\12")*( +    P("\00")/function() +      result.copyright=stack[top] +      top=0 +    end+P("\01")/function() +      result.monospaced=stack[top]==1 and true or false  +      top=0 +    end+P("\02")/function() +      result.italicangle=stack[top] +      top=0 +    end+P("\03")/function() +      result.underlineposition=stack[top] +      top=0 +    end+P("\04")/function() +      result.underlinethickness=stack[top] +      top=0 +    end+P("\05")/function() +      result.painttype=stack[top] +      top=0 +    end+P("\06")/function() +      result.charstringtype=stack[top] +      top=0 +    end+P("\07")/function() +      result.fontmatrix={ unpack(stack,1,6) } +      top=0 +    end+P("\08")/function() +      result.strokewidth=stack[top] +      top=0 +    end+P("\20")/function() +      result.syntheticbase=stack[top] +      top=0 +    end+P("\21")/function() +      result.postscript=strings[stack[top]] or "unset" +      top=0 +    end+P("\22")/function() +      result.basefontname=strings[stack[top]] or "unset" +      top=0 +    end+P("\21")/function() +      result.basefontblend=stack[top] +      top=0 +    end+P("\30")/function() +      result.cid.registry=strings[stack[top-2]] or "unset" +      result.cid.ordering=strings[stack[top-1]] or "unset" +      result.cid.supplement=stack[top] +      top=0 +    end+P("\31")/function() +      result.cid.fontversion=stack[top] +      top=0 +    end+P("\32")/function() +      result.cid.fontrevision=stack[top] +      top=0 +    end+P("\33")/function() +      result.cid.fonttype=stack[top] +      top=0 +    end+P("\34")/function() +      result.cid.count=stack[top] +      top=0 +    end+P("\35")/function() +      result.cid.uidbase=stack[top] +      top=0 +    end+P("\36")/function() +      result.cid.fdarray=stack[top] +      top=0 +    end+P("\37")/function() +      result.cid.fdselect=stack[top] +      top=0 +    end+P("\38")/function() +      result.cid.fontname=strings[stack[top]] or "unset" +      top=0 +    end +  ) +  local p_last=P("\x0F")/"0"+P("\x1F")/"1"+P("\x2F")/"2"+P("\x3F")/"3"+P("\x4F")/"4"+P("\x5F")/"5"+P("\x6F")/"6"+P("\x7F")/"7"+P("\x8F")/"8"+P("\x9F")/"9"+P("\xAF")/""+P("\xBF")/""+P("\xCF")/""+P("\xDF")/""+P("\xEF")/""+R("\xF0\xFF")/"" +  local remap={ +    ["\x00"]="00",["\x01"]="01",["\x02"]="02",["\x03"]="03",["\x04"]="04",["\x05"]="05",["\x06"]="06",["\x07"]="07",["\x08"]="08",["\x09"]="09",["\x0A"]="0.",["\x0B"]="0E",["\x0C"]="0E-",["\x0D"]="0",["\x0E"]="0-",["\x0F"]="0", +    ["\x10"]="10",["\x11"]="11",["\x12"]="12",["\x13"]="13",["\x14"]="14",["\x15"]="15",["\x16"]="16",["\x17"]="17",["\x18"]="18",["\x19"]="19",["\x1A"]="0.",["\x1B"]="0E",["\x1C"]="0E-",["\x1D"]="0",["\x1E"]="0-",["\x1F"]="0", +    ["\x20"]="20",["\x21"]="21",["\x22"]="22",["\x23"]="23",["\x24"]="24",["\x25"]="25",["\x26"]="26",["\x27"]="27",["\x28"]="28",["\x29"]="29",["\x2A"]="0.",["\x2B"]="0E",["\x2C"]="0E-",["\x2D"]="0",["\x2E"]="0-",["\x2F"]="0", +    ["\x30"]="30",["\x31"]="31",["\x32"]="32",["\x33"]="33",["\x34"]="34",["\x35"]="35",["\x36"]="36",["\x37"]="37",["\x38"]="38",["\x39"]="39",["\x3A"]="0.",["\x3B"]="0E",["\x3C"]="0E-",["\x3D"]="0",["\x3E"]="0-",["\x3F"]="0", +    ["\x40"]="40",["\x41"]="41",["\x42"]="42",["\x43"]="43",["\x44"]="44",["\x45"]="45",["\x46"]="46",["\x47"]="47",["\x48"]="48",["\x49"]="49",["\x4A"]="0.",["\x4B"]="0E",["\x4C"]="0E-",["\x4D"]="0",["\x4E"]="0-",["\x4F"]="0", +    ["\x50"]="50",["\x51"]="51",["\x52"]="52",["\x53"]="53",["\x54"]="54",["\x55"]="55",["\x56"]="56",["\x57"]="57",["\x58"]="58",["\x59"]="59",["\x5A"]="0.",["\x5B"]="0E",["\x5C"]="0E-",["\x5D"]="0",["\x5E"]="0-",["\x5F"]="0", +    ["\x60"]="60",["\x61"]="61",["\x62"]="62",["\x63"]="63",["\x64"]="64",["\x65"]="65",["\x66"]="66",["\x67"]="67",["\x68"]="68",["\x69"]="69",["\x6A"]="0.",["\x6B"]="0E",["\x6C"]="0E-",["\x6D"]="0",["\x6E"]="0-",["\x6F"]="0", +    ["\x70"]="70",["\x71"]="71",["\x72"]="72",["\x73"]="73",["\x74"]="74",["\x75"]="75",["\x76"]="76",["\x77"]="77",["\x78"]="78",["\x79"]="79",["\x7A"]="0.",["\x7B"]="0E",["\x7C"]="0E-",["\x7D"]="0",["\x7E"]="0-",["\x7F"]="0", +    ["\x80"]="80",["\x81"]="81",["\x82"]="82",["\x83"]="83",["\x84"]="84",["\x85"]="85",["\x86"]="86",["\x87"]="87",["\x88"]="88",["\x89"]="89",["\x8A"]="0.",["\x8B"]="0E",["\x8C"]="0E-",["\x8D"]="0",["\x8E"]="0-",["\x8F"]="0", +    ["\x90"]="90",["\x91"]="91",["\x92"]="92",["\x93"]="93",["\x94"]="94",["\x95"]="95",["\x96"]="96",["\x97"]="97",["\x98"]="98",["\x99"]="99",["\x9A"]="0.",["\x9B"]="0E",["\x9C"]="0E-",["\x9D"]="0",["\x9E"]="0-",["\x9F"]="0", +    ["\xA0"]=".0",["\xA1"]=".1",["\xA2"]=".2",["\xA3"]=".3",["\xA4"]=".4",["\xA5"]=".5",["\xA6"]=".6",["\xA7"]=".7",["\xA8"]=".8",["\xA9"]=".9",["\xAA"]="..",["\xAB"]=".E",["\xAC"]=".E-",["\xAD"]=".",["\xAE"]=".-",["\xAF"]=".", +    ["\xB0"]="E0",["\xB1"]="E1",["\xB2"]="E2",["\xB3"]="E3",["\xB4"]="E4",["\xB5"]="E5",["\xB6"]="E6",["\xB7"]="E7",["\xB8"]="E8",["\xB9"]="E9",["\xBA"]="E.",["\xBB"]="EE",["\xBC"]="EE-",["\xBD"]="E",["\xBE"]="E-",["\xBF"]="E", +    ["\xC0"]="E-0",["\xC1"]="E-1",["\xC2"]="E-2",["\xC3"]="E-3",["\xC4"]="E-4",["\xC5"]="E-5",["\xC6"]="E-6",["\xC7"]="E-7",["\xC8"]="E-8",["\xC9"]="E-9",["\xCA"]="E-.",["\xCB"]="E-E",["\xCC"]="E-E-",["\xCD"]="E-",["\xCE"]="E--",["\xCF"]="E-", +    ["\xD0"]="-0",["\xD1"]="-1",["\xD2"]="-2",["\xD3"]="-3",["\xD4"]="-4",["\xD5"]="-5",["\xD6"]="-6",["\xD7"]="-7",["\xD8"]="-8",["\xD9"]="-9",["\xDA"]="-.",["\xDB"]="-E",["\xDC"]="-E-",["\xDD"]="-",["\xDE"]="--",["\xDF"]="-", +  } +  local p_nibbles=P("\30")*Cs(((1-p_last)/remap)^0+p_last)/function(n) +    top=top+1 +    stack[top]=tonumber(n) or 0 +  end +  local p_byte=C(R("\32\246"))/function(b0) +    top=top+1 +    stack[top]=byte(b0)-139 +  end +  local p_positive=C(R("\247\250"))*C(1)/function(b0,b1) +    top=top+1 +    stack[top]=(byte(b0)-247)*256+byte(b1)+108 +  end +  local p_negative=C(R("\251\254"))*C(1)/function(b0,b1) +    top=top+1 +    stack[top]=-(byte(b0)-251)*256-byte(b1)-108 +  end +  local p_short=P("\28")*C(1)*C(1)/function(b1,b2) +    top=top+1 +    local n=0x100*byte(b1)+byte(b2) +    if n>=0x8000 then +      stack[top]=n-0xFFFF-1 +    else +      stack[top]=n +    end +  end +  local p_long=P("\29")*C(1)*C(1)*C(1)*C(1)/function(b1,b2,b3,b4) +    top=top+1 +    local n=0x1000000*byte(b1)+0x10000*byte(b2)+0x100*byte(b3)+byte(b4) +    if n>=0x8000000 then +      stack[top]=n-0xFFFFFFFF-1 +    else +      stack[top]=n +    end +  end +  local p_unsupported=P(1)/function(detail) +    top=0 +  end +  local p_dictionary=( +    p_byte+p_positive+p_negative+p_short+p_long+p_nibbles+p_single+p_double+p_unsupported +  )^1 +  parsedictionaries=function(data,dictionaries) +    stack={} +    strings=data.strings +    for i=1,#dictionaries do +      top=0 +      result={ +        monospaced=false, +        italicangle=0, +        underlineposition=-100, +        underlinethickness=50, +        painttype=0, +        charstringtype=2, +        fontmatrix={ 0.001,0,0,0.001,0,0 }, +        fontbbox={ 0,0,0,0 }, +        strokewidth=0, +        charset=0, +        encoding=0, +        cid={ +          fontversion=0, +          fontrevision=0, +          fonttype=0, +          count=8720, +        } +      } +      lpegmatch(p_dictionary,dictionaries[i]) +      dictionaries[i]=result +    end +    result={} +    top=0 +    stack={} +  end +  parseprivates=function(data,dictionaries) +    stack={} +    strings=data.strings +    for i=1,#dictionaries do +      local private=dictionaries[i].private +      if private and private.data then +        top=0 +        result={ +          forcebold=false, +          languagegroup=0, +          expansionfactor=0.06, +          initialrandomseed=0, +          subroutines=0, +          defaultwidthx=0, +          nominalwidthx=0, +          cid={ +          },          } +        lpegmatch(p_dictionary,private.data) +        private.data=result +      end +    end +    result={} +    top=0 +    stack={} +  end +  local x=0 +  local y=0 +  local width=false +  local r=0 +  local stems=0 +  local globalbias=0 +  local localbias=0 +  local globals=false +  local locals=false +  local depth=1 +  local xmin=0 +  local xmax=0 +  local ymin=0 +  local ymax=0 +  local checked=false +  local keepcurve=false +  local function showstate(where) +    report("%w%-10s : [%s] n=%i",depth*2,where,concat(stack," ",1,top),top) +  end +  local function showvalue(where,value,showstack) +    if showstack then +      report("%w%-10s : %s : [%s] n=%i",depth*2,where,tostring(value),concat(stack," ",1,top),top) +    else +      report("%w%-10s : %s",depth*2,where,tostring(value)) +    end +  end +  local function moveto(x,y) +    if keepcurve then +      r=r+1 +      result[r]={ x,y,"m" } +    end +    if checked then +      if x<xmin then xmin=x elseif x>xmax then xmax=x end +      if y<ymin then ymin=y elseif y>ymax then ymax=y end +    else +      xmin=x +      ymin=y +      xmax=x +      ymax=y +      checked=true +    end +  end +  local function lineto(x,y) +    if keepcurve then +      r=r+1 +      result[r]={ x,y,"l" } +    end +    if checked then +      if x<xmin then xmin=x elseif x>xmax then xmax=x end +      if y<ymin then ymin=y elseif y>ymax then ymax=y end +    else +      xmin=x +      ymin=y +      xmax=x +      ymax=y +      checked=true +    end +  end +  local function curveto(x1,y1,x2,y2,x3,y3) +    if keepcurve then +      r=r+1 +      result[r]={ x1,y1,x2,y2,x3,y3,"c" } +    end +    if checked then +      if x1<xmin then xmin=x1 elseif x1>xmax then xmax=x1 end +      if y1<ymin then ymin=y1 elseif y1>ymax then ymax=y1 end +    else +      xmin=x1 +      ymin=y1 +      xmax=x1 +      ymax=y1 +      checked=true +    end +    if x2<xmin then xmin=x2 elseif x2>xmax then xmax=x2 end +    if y2<ymin then ymin=y2 elseif y2>ymax then ymax=y2 end +    if x3<xmin then xmin=x3 elseif x3>xmax then xmax=x3 end +    if y3<ymin then ymin=y3 elseif y3>ymax then ymax=y3 end +  end +  local function rmoveto() +    if top>2 then +      if not width then +        width=stack[1] +        if trace_charstrings then +          showvalue("width",width) +        end        end +    elseif not width then +      width=true      end -    if #featurefiles==0 then -      featurefiles=nil +    if trace_charstrings then +      showstate("rmoveto")      end +    x=x+stack[top-1]  +    y=y+stack[top]   +    top=0 +    moveto(x,y)    end -  local data=containers.read(otf.cache,hash) -  local reload=not data or data.size~=size or data.time~=time -  if forceload then -    report_otf("forced reload of %a due to hard coded flag",filename) -    reload=true +  local function hmoveto() +    if top>1 then +      if not width then +        width=stack[1] +        if trace_charstrings then +          showvalue("width",width) +        end +      end +    elseif not width then +      width=true +    end +    if trace_charstrings then +      showstate("hmoveto") +    end +    x=x+stack[top]  +    top=0 +    moveto(x,y)    end -  if not reload then -    local featuredata=data.featuredata -    if featurefiles then -      if not featuredata or #featuredata~=#featurefiles then -        reload=true -      else -        for i=1,#featurefiles do -          local fi,fd=featurefiles[i],featuredata[i] -          if fi.name~=fd.name or fi.size~=fd.size or fi.time~=fd.time then -            reload=true -            break -          end +  local function vmoveto() +    if top>1 then +      if not width then +        width=stack[1] +        if trace_charstrings then +          showvalue("width",width)          end        end -    elseif featuredata then -      reload=true +    elseif not width then +      width=true      end -    if reload then -      report_otf("loading: forced reload due to changed featurefile specification %a",featurefile) +    if trace_charstrings then +      showstate("vmoveto")      end -   end -   if reload then -    starttiming("fontloader") -    report_otf("loading %a, hash %a",filename,hash) -    local fontdata,messages -    if sub then -      fontdata,messages=open_font(filename,sub) +    y=y+stack[top]  +    top=0 +    moveto(x,y) +  end +  local function rlineto() +    if trace_charstrings then +      showstate("rlineto") +    end +    for i=1,top,2 do +      x=x+stack[i]   +      y=y+stack[i+1]  +      lineto(x,y) +    end +    top=0 +  end +  local function xlineto(swap)  +    for i=1,top do +      if swap then +        x=x+stack[i] +        swap=false +      else +        y=y+stack[i] +        swap=true +      end +      lineto(x,y) +    end +    top=0 +  end +  local function hlineto()  +    if trace_charstrings then +      showstate("hlineto") +    end +    xlineto(true) +  end +  local function vlineto()  +    if trace_charstrings then +      showstate("vlineto") +    end +    xlineto(false) +  end +  local function rrcurveto() +    if trace_charstrings then +      showstate("rrcurveto") +    end +    for i=1,top,6 do +      local ax=x+stack[i]   +      local ay=y+stack[i+1]  +      local bx=ax+stack[i+2]  +      local by=ay+stack[i+3]  +      x=bx+stack[i+4]  +      y=by+stack[i+5]  +      curveto(ax,ay,bx,by,x,y) +    end +    top=0 +  end +  local function hhcurveto() +    if trace_charstrings then +      showstate("hhcurveto") +    end +    local s=1 +    if top%2~=0 then +      y=y+stack[1]  +      s=2 +    end +    for i=s,top,4 do +      local ax=x+stack[i]  +      local ay=y +      local bx=ax+stack[i+1]  +      local by=ay+stack[i+2]  +      x=bx+stack[i+3]  +      y=by +      curveto(ax,ay,bx,by,x,y) +    end +    top=0 +  end +  local function vvcurveto() +    if trace_charstrings then +      showstate("vvcurveto") +    end +    local s=1 +    local d=0 +    if top%2~=0 then +      d=stack[1]  +      s=2 +    end +    for i=s,top,4 do +      local ax=x+d +      local ay=y+stack[i]  +      local bx=ax+stack[i+1]  +      local by=ay+stack[i+2]  +      x=bx +      y=by+stack[i+3]  +      curveto(ax,ay,bx,by,x,y) +      d=0 +    end +    top=0 +  end +  local function xxcurveto(swap) +    local last=top%4~=0 and stack[top] +    if last then +      top=top-1 +    end +    local sw=swap +    for i=1,top,4 do +      local ax,ay,bx,by +      if swap then +        ax=x+stack[i] +        ay=y +        bx=ax+stack[i+1] +        by=ay+stack[i+2] +        y=by+stack[i+3] +        if last and i+3==top then +          x=bx+last +        else +          x=bx +        end +        swap=false +      else +        ax=x +        ay=y+stack[i] +        bx=ax+stack[i+1] +        by=ay+stack[i+2] +        x=bx+stack[i+3] +        if last and i+3==top then +          y=by+last +        else +          y=by +        end +        swap=true +      end +      curveto(ax,ay,bx,by,x,y) +    end +    top=0 +  end +  local function hvcurveto() +    if trace_charstrings then +      showstate("hvcurveto") +    end +    xxcurveto(true) +  end +  local function vhcurveto() +    if trace_charstrings then +      showstate("vhcurveto") +    end +    xxcurveto(false) +  end +  local function rcurveline() +    if trace_charstrings then +      showstate("rcurveline") +    end +    for i=1,top-2,6 do +      local ax=x+stack[i]   +      local ay=y+stack[i+1]  +      local bx=ax+stack[i+2]  +      local by=ay+stack[i+3]  +      x=bx+stack[i+4]  +      y=by+stack[i+5]  +      curveto(ax,ay,bx,by,x,y) +    end +    x=x+stack[top-1]  +    y=y+stack[top]   +    lineto(x,y) +    top=0 +  end +  local function rlinecurve() +    if trace_charstrings then +      showstate("rlinecurve") +    end +    if top>6 then +      for i=1,top-6,2 do +        x=x+stack[i] +        y=y+stack[i+1] +        lineto(x,y) +      end +    end +    local ax=x+stack[top-5] +    local ay=y+stack[top-4] +    local bx=ax+stack[top-3] +    local by=ay+stack[top-2] +    x=bx+stack[top-1] +    y=by+stack[top] +    curveto(ax,ay,bx,by,x,y) +    top=0 +  end +  local function flex()  +    if trace_charstrings then +      showstate("flex") +    end +    local ax=x+stack[1]  +    local ay=y+stack[2]  +    local bx=ax+stack[3]  +    local by=ay+stack[4]  +    local cx=bx+stack[5]  +    local cy=by+stack[6]  +    curveto(ax,ay,bx,by,cx,cy) +    local dx=cx+stack[7]  +    local dy=cy+stack[8]  +    local ex=dx+stack[9]  +    local ey=dy+stack[10]  +    x=ex+stack[11]     +    y=ey+stack[12]     +    curveto(dx,dy,ex,ey,x,y) +    top=0 +  end +  local function hflex() +    if trace_charstrings then +      showstate("hflex") +    end +    local ax=x+stack[1]   +    local ay=y +    local bx=ax+stack[2]  +    local by=ay+stack[3]  +    local cx=bx+stack[4]  +    local cy=by +    curveto(ax,ay,bx,by,cx,cy) +    local dx=cx+stack[5]  +    local dy=by +    local ex=dx+stack[6]  +    local ey=y +    x=ex+stack[7]     +    curveto(dx,dy,ex,ey,x,y) +    top=0 +  end +  local function hflex1() +    if trace_charstrings then +      showstate("hflex1") +    end +    local ax=x+stack[1]  +    local ay=y+stack[2]  +    local bx=ax+stack[3]  +    local by=ay+stack[4]  +    local cx=bx+stack[5]  +    local cy=by +    curveto(ax,ay,bx,by,cx,cy) +    local dx=cx+stack[6]  +    local dy=by +    local ex=dx+stack[7]  +    local ey=dy+stack[8]  +    x=ex+stack[9]     +    curveto(dx,dy,ex,ey,x,y) +    top=0 +  end +  local function flex1() +    if trace_charstrings then +      showstate("flex1") +    end +    local ax=x+stack[1]  +    local ay=y+stack[2]  +    local bx=ax+stack[3]  +    local by=ay+stack[4]  +    local cx=bx+stack[5]  +    local cy=by+stack[6]  +    curveto(ax,ay,bx,by,cx,cy) +    local dx=cx+stack[7]  +    local dy=cy+stack[8]  +    local ex=dx+stack[9]  +    local ey=dy+stack[10]  +    if abs(ex-x)>abs(ey-y) then  +      x=ex+stack[11]      else -      fontdata,messages=open_font(filename) +      y=ey+stack[11]      end -    if fontdata then -      mainfields=mainfields or (font_fields and font_fields(fontdata)) +    curveto(dx,dy,ex,ey,x,y) +    top=0 +  end +  local function getstem() +    if top==0 then +    elseif top%2~=0 then +      if width then +        remove(stack,1) +      else +        width=remove(stack,1) +        if trace_charstrings then +          showvalue("width",width) +        end +      end +      top=top-1 +    end +    if trace_charstrings then +      showstate("stem")      end -    if trace_loading and messages and #messages>0 then -      if type(messages)=="string" then -        report_otf("warning: %s",messages) +    stems=stems+top/2 +    top=0 +  end +  local function getmask() +    if top==0 then +    elseif top%2~=0 then +      if width then +        remove(stack,1)        else -        for m=1,#messages do -          report_otf("warning: %S",messages[m]) +        width=remove(stack,1) +        if trace_charstrings then +          showvalue("width",width)          end        end +      top=top-1 +    end +    if trace_charstrings then +      showstate(operator==19 and "hintmark" or "cntrmask") +    end +    stems=stems+top/2 +    top=0 +    if stems==0 then +    elseif stems<=8 then +      return 1      else -      report_otf("loading done") +      return floor((stems+7)/8) +    end +  end +  local function unsupported() +    if trace_charstrings then +      showstate("unsupported") +    end +    top=0 +  end +  local actions={ [0]=unsupported, +    getstem, +    unsupported, +    getstem, +    vmoveto, +    rlineto, +    hlineto, +    vlineto, +    rrcurveto, +    unsupported, +    unsupported, +    unsupported, +    unsupported, +    unsupported, +    unsupported, +    unsupported, +    unsupported, +    unsupported, +    getstem, +    getmask, +    getmask, +    rmoveto, +    hmoveto, +    getstem, +    rcurveline, +    rlinecurve, +    vvcurveto, +    hhcurveto, +    unsupported, +    unsupported, +    vhcurveto, +    hvcurveto, +  } +  local subactions={ +    [034]=hflex, +    [035]=flex, +    [036]=hflex1, +    [037]=flex1, +  } +  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 +    else +      report("unknown %s %i",scope,index)      end -    if fontdata then -      if featurefiles then -        for i=1,#featurefiles do -          load_featurefile(fontdata,featurefiles[i].name) +  end +  local function process(tab) +    local i=1 +    local n=#tab +    while i<=n do +      local t=tab[i] +      if t>=32 and t<=246 then +        top=top+1 +        stack[top]=t-139 +        i=i+1 +      elseif t>=247 and t<=250 then +        top=top+1 +        stack[top]=(t-247)*256+tab[i+1]+108 +        i=i+2 +      elseif t>=251 and t<=254 then +        top=top+1 +        stack[top]=-(t-251)*256-tab[i+1]-108 +        i=i+2 +      elseif t==28 then +        top=top+1 +        local n=0x100*tab[i+1]+tab[i+2] +        if n>=0x8000 then +          stack[top]=n-0xFFFF-1 +        else +          stack[top]=n +        end +        i=i+3 +      elseif t==255 then +        local n=0x100*tab[i+1]+tab[i+2] +        top=top+1 +        if n>=0x8000 then +          stack[top]=n-0xFFFF-1+(0x100*tab[i+3]+tab[i+4])/0xFFFF +        else +          stack[top]=n+(0x100*tab[i+3]+tab[i+4])/0xFFFF +        end +        i=i+5 +      elseif t==11 then +        if trace_charstrings then +          showstate("return") +        end +        return +      elseif t==10 then +        call("local",locals,localbias,process) +        i=i+1 +       elseif t==14 then  +        if width then +        elseif top>0 then +          width=stack[1] +          if trace_charstrings then +            showvalue("width",width) +          end +        else +          width=true          end +        if trace_charstrings then +          showstate("endchar") +        end +        return +      elseif t==29 then +        call("global",globals,globalbias,process) +        i=i+1 +      elseif t==12 then +        i=i+1 +        local t=tab[i] +        local a=subactions[t] +        if a then +          a() +        else +          if trace_charstrings then +            showvalue("<subaction>",t) +          end +          top=0 +        end +        i=i+1 +      else +        local a=actions[t] +        if a then +          local s=a() +          if s then +            i=i+s +          end +        else +          if trace_charstrings then +            showvalue("<action>",t) +          end +          top=0 +        end +        i=i+1        end -      local unicodes={ -      } -      local splitter=lpeg.splitter(" ",unicodes) -      data={ -        size=size, -        time=time, -        subfont=sub, -        format=otf_format(filename), -        featuredata=featurefiles, -        resources={ -          filename=resolvers.unresolve(filename), -          version=otf.version, -          creator="context mkiv", -          unicodes=unicodes, -          indices={ -          }, -          duplicates={ -          }, -          variants={ -          }, -          lookuptypes={}, -        }, -        warnings={}, -        metadata={ -        }, -        properties={ -        }, -        descriptions={}, -        goodies={}, -        helpers={  -          tounicodelist=splitter, -          tounicodetable=Ct(splitter), -        }, +    end +  end +  parsecharstrings=function(data,glyphs,doshapes) +    local dictionary=data.dictionaries[1] +    local charstrings=dictionary.charstrings +    local charset=dictionary.charset +    keepcurve=doshapes +    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 +    for i=1,#charstrings do +      local str=charstrings[i] +      local tab=lpegmatch(p_bytes,str) +      local index=i-1 +      x=0 +      y=0 +      width=false +      r=0 +      top=0 +      stems=0 +      result={} +      xmin=0 +      xmax=0 +      ymin=0 +      ymax=0 +      checked=false +      if trace_charstrings then +        report("glyph: %i",index) +        report("data: % t",tab) +      end +      process(tab) +      local boundingbox={ round(xmin),round(ymin),round(xmax),round(ymax) } +      if width==true or width==false then +        width=defaultwidth +      else +        width=nominalwidth+width +      end +      local glyph=glyphs[index]  +      if not glyph then +        glyphs[index]={ +          segments=doshapes~=false and result or nil, +          boundingbox=boundingbox, +          width=width, +          name=charset[index], +        } +      else +        glyph.segments=doshapes~=false and result or nil +        glyph.boundingbox=boundingbox +        if not glyph.width then +          glyph.width=width +        end +        if charset and not glyph.name then +          glyph.name=charset[index] +        end +      end +      if trace_charstrings then +        report("width: %s",tostring(width)) +        report("boundingbox: % t",boundingbox) +      end +      charstrings[i]=nil  +    end +    return glyphs +  end +  parsecharstring=function(data,dictionary,charstring,glyphs,index,doshapes) +    local private=dictionary.private +    keepcurve=doshapes +    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 +    local nominalwidth=private and private.data.nominalwidthx or 0 +    local defaultwidth=private and private.data.defaultwidthx or 0 +    local tab=lpegmatch(p_bytes,charstring) +    x=0 +    y=0 +    width=false +    r=0 +    top=0 +    stems=0 +    result={} +    xmin=0 +    xmax=0 +    ymin=0 +    ymax=0 +    checked=false +    if trace_charstrings then +      report("glyph: %i",index) +      report("data: % t",tab) +    end +    process(tab) +    local boundingbox={ xmin,ymin,xmax,ymax } +    if width==true or width==false then +      width=defaultwidth +    else +      width=nominalwidth+width +    end +index=index-1 +    local glyph=glyphs[index]  +    if not glyph then +      glyphs[index]={ +        segments=doshapes~=false and result or nil, +        boundingbox=boundingbox, +        width=width, +        name=charset[index],        } -      report_otf("file size: %s",size) -      enhancers.apply(data,filename,fontdata) -      local packtime={} -      if packdata then -        if cleanup>0 then -          collectgarbage("collect") +    else +      glyph.segments=doshapes~=false and result or nil +      glyph.boundingbox=boundingbox +      if not glyph.width then +        glyph.width=width +      end +      if charset and not glyph.name then +        glyph.name=charset[index] +      end +    end +    if trace_charstrings then +      report("width: %s",tostring(width)) +      report("boundingbox: % t",boundingbox) +    end +    return charstring +  end +  resetcharstrings=function() +    result={} +    top=0 +    stack={} +  end +end +local function readglobals(f,data) +  local routines=readlengths(f) +  for i=1,#routines do +    routines[i]=readstring(f,routines[i]) +  end +  data.routines=routines +end +local function readencodings(f,data) +  data.encodings={} +end +local function readcharsets(f,data,dictionary) +  local header=data.header +  local strings=data.strings +  local nofglyphs=data.nofglyphs +  local charsetoffset=dictionary.charset +  if charsetoffset~=0 then +    setposition(f,header.offset+charsetoffset) +    local format=readbyte(f) +    local charset={ [0]=".notdef" } +    dictionary.charset=charset +    if format==0 then +      for i=1,nofglyphs do +        charset[i]=strings[readushort(f)] +      end +    elseif format==1 or format==2 then +      local readcount=format==1 and readbyte or readushort +      local i=1 +      while i<=nofglyphs do +        local sid=readushort(f) +        local n=readcount(f) +        for s=sid,sid+n do +          charset[i]=strings[s] +          i=i+1 +          if i>nofglyphs then +            break +          end          end -        starttiming(packtime) -        enhance("pack",data,filename,nil) -        stoptiming(packtime)        end -      report_otf("saving %a in cache",filename) -      data=containers.write(otf.cache,hash,data) -      if cleanup>1 then -        collectgarbage("collect") +    else +      report("cff parser: unsupported charset format %a",format) +    end +  end +end +local function readprivates(f,data) +  local header=data.header +  local dictionaries=data.dictionaries +  local private=dictionaries[1].private +  if private then +    setposition(f,header.offset+private.offset) +    private.data=readstring(f,private.size) +  end +end +local function readlocals(f,data,dictionary) +  local header=data.header +  local private=dictionary.private +  if private then +    local subroutineoffset=private.data.subroutines +    if subroutineoffset~=0 then +      setposition(f,header.offset+private.offset+subroutineoffset) +      local subroutines=readlengths(f) +      for i=1,#subroutines do +        subroutines[i]=readstring(f,subroutines[i])        end -      stoptiming("fontloader") -      if elapsedtime then  -        report_otf("loading, optimizing, packing and caching time %s, pack time %s", -          elapsedtime("fontloader"),packdata and elapsedtime(packtime) or 0) +      dictionary.subroutines=subroutines +      private.data.subroutines=nil +    else +      dictionary.subroutines={} +    end +  else +    dictionary.subroutines={} +  end +end +local function readcharstrings(f,data) +  local header=data.header +  local dictionaries=data.dictionaries +  local dictionary=dictionaries[1] +  local type=dictionary.charstringtype +  local offset=dictionary.charstrings +  if type==2 then +    setposition(f,header.offset+offset) +    local charstrings=readlengths(f) +    local nofglyphs=#charstrings +    for i=1,nofglyphs do +      charstrings[i]=readstring(f,charstrings[i]) +    end +    data.nofglyphs=nofglyphs +    dictionary.charstrings=charstrings +  else +    report("unsupported charstr type %i",type) +    data.nofglyphs=0 +    dictionary.charstrings={} +  end +end +local function readcidprivates(f,data) +  local header=data.header +  local dictionaries=data.dictionaries[1].cid.dictionaries +  for i=1,#dictionaries do +    local dictionary=dictionaries[i] +    local private=dictionary.private +    if private then +      setposition(f,header.offset+private.offset) +      private.data=readstring(f,private.size) +    end +  end +  parseprivates(data,dictionaries) +end +local function readnoselect(f,data,glyphs,doshapes) +  local dictionaries=data.dictionaries +  local dictionary=dictionaries[1] +  readglobals(f,data) +  readcharstrings(f,data) +  readencodings(f,data) +  readcharsets(f,data,dictionary) +  readprivates(f,data) +  parseprivates(data,data.dictionaries) +  readlocals(f,data,dictionary) +  parsecharstrings(data,glyphs,doshapes) +  resetcharstrings() +end +local function readfdselect(f,data,glyphs,doshapes) +  local header=data.header +  local dictionaries=data.dictionaries +  local dictionary=dictionaries[1] +  local cid=dictionary.cid +  local cidselect=cid and cid.fdselect +  readglobals(f,data) +  readcharstrings(f,data) +  readencodings(f,data) +  local charstrings=dictionary.charstrings +  local fdindex={} +  local nofglyphs=data.nofglyphs +  local maxindex=-1 +  setposition(f,header.offset+cidselect) +  local format=readbyte(f) +  if format==1 then +    for i=0,nofglyphs do  +      local index=readbyte(i) +      fdindex[i]=index +      if index>maxindex then +        maxindex=index +      end +    end +  elseif format==3 then +    local nofranges=readushort(f) +    local first=readushort(f) +    local index=readbyte(f) +    while true do +      local last=readushort(f) +      if index>maxindex then +        maxindex=index        end -      close_font(fontdata)  -      if cleanup>3 then -        collectgarbage("collect") +      for i=first,last do +        fdindex[i]=index        end -      data=containers.read(otf.cache,hash)  -      if cleanup>2 then -        collectgarbage("collect") +      if last>=nofglyphs then +        break +      else +        first=last+1 +        index=readbyte(f)        end -    else -      stoptiming("fontloader") -      data=nil -      report_otf("loading failed due to read error")      end +  else    end -  if data then -    if trace_defining then -      report_otf("loading from cache using hash %a",hash) -    end -    enhance("unpack",data,filename,nil,false) -    local resources=data.resources -    local lookuptags=resources.lookuptags -    local unicodes=resources.unicodes -    if not lookuptags then -      lookuptags={} -      resources.lookuptags=lookuptags +  if maxindex>=0 then +    local cidarray=cid.fdarray +    setposition(f,header.offset+cidarray) +    local dictionaries=readlengths(f) +    for i=1,#dictionaries do +      dictionaries[i]=readstring(f,dictionaries[i]) +    end +    parsedictionaries(data,dictionaries) +    cid.dictionaries=dictionaries +    readcidprivates(f,data) +    for i=1,#dictionaries do +      readlocals(f,data,dictionaries[i]) +    end +    for i=1,#charstrings do +      parsecharstring(data,dictionaries[fdindex[i]+1],charstrings[i],glyphs,i,doshapes) +    end +    resetcharstrings() +  end +end +function readers.cff(f,fontdata,specification) +  if specification.details then +    local datatable=fontdata.tables.cff +    if datatable then +      local offset=datatable.offset +      local glyphs=fontdata.glyphs +      if not f then +        report("invalid filehandle") +        return +      end +      if offset then +        setposition(f,offset) +      end +      local header=readheader(f) +      if header.major>1 then +        report("version mismatch") +        return +      end +      local names=readfontnames(f) +      local dictionaries=readtopdictionaries(f) +      local strings=readstrings(f) +      local data={ +        header=header, +        names=names, +        dictionaries=dictionaries, +        strings=strings, +        nofglyphs=fontdata.nofglyphs, +      } +      parsedictionaries(data,data.dictionaries) +      local d=dictionaries[1] +      local c=d.cid +      fontdata.cffinfo={ +        familynamename=d.familyname, +        fullname=d.fullname, +        boundingbox=d.boundingbox, +        weight=d.weight, +        italicangle=d.italicangle, +        underlineposition=d.underlineposition, +        underlinethickness=d.underlinethickness, +        monospaced=d.monospaced, +      } +      fontdata.cidinfo=c and { +        registry=c.registry, +        ordering=c.ordering, +        supplement=c.supplement, +      } +      if not specification.glyphs then +      else +        local cid=d.cid +        if cid and cid.fdselect then +          readfdselect(f,data,glyphs,specification.shapes or false) +        else +          readnoselect(f,data,glyphs,specification.shapes or false) +        end +      end      end -    setmetatableindex(lookuptags,function(t,k) -      local v=type(k)=="number" and ("lookup "..k) or k -      t[k]=v -      return v -    end) -    if not unicodes then -      unicodes={} -      resources.unicodes=unicodes -      setmetatableindex(unicodes,function(t,k) -        setmetatableindex(unicodes,nil) -        for u,d in next,data.descriptions do -          local n=d.name -          if n then -            t[n]=u -          else +  end +end + +end -- closure + +do -- begin closure to overcome local limits and interference + +if not modules then modules={} end modules ['font-ttf']={ +  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,unpack=next,type,unpack +local bittest=bit32.btest +local sqrt=math.sqrt +local report=logs.reporter("otf reader","ttf") +local readers=fonts.handlers.otf.readers +local streamreader=readers.streamreader +local setposition=streamreader.setposition +local getposition=streamreader.getposition +local skipbytes=streamreader.skip +local readbyte=streamreader.readcardinal1  +local readushort=streamreader.readcardinal2  +local readulong=streamreader.readcardinal4  +local readchar=streamreader.readinteger1   +local readshort=streamreader.readinteger2   +local read2dot14=streamreader.read2dot14    +local function mergecomposites(glyphs,shapes) +  local function merge(index,shape,components) +    local contours={} +    local nofcontours=0 +    for i=1,#components do +      local component=components[i] +      local subindex=component.index +      local subshape=shapes[subindex] +      local subcontours=subshape.contours +      if not subcontours then +        local subcomponents=subshape.components +        if subcomponents then +          subcontours=merge(subindex,subshape,subcomponents) +        end +      end +      if subcontours then +        local matrix=component.matrix +        local xscale=matrix[1] +        local xrotate=matrix[2] +        local yrotate=matrix[3] +        local yscale=matrix[4] +        local xoffset=matrix[5] +        local yoffset=matrix[6] +        for i=1,#subcontours do +          local points=subcontours[i] +          local result={} +          for i=1,#points do +            local p=points[i] +            local x=p[1] +            local y=p[2] +            result[i]={ +              xscale*x+xrotate*y+xoffset, +              yscale*y+yrotate*x+yoffset, +              p[3] +            }            end +          nofcontours=nofcontours+1 +          contours[nofcontours]=result          end -        return rawget(t,k) -      end) +      else +        report("missing contours composite %s, component %s of %s, glyph %s",index,i,#components,subindex) +      end      end -    constructors.addcoreunicodes(unicodes) -    if applyruntimefixes then -      applyruntimefixes(filename,data) +    shape.contours=contours +    shape.components=nil +    return contours +  end +  for index=1,#glyphs do +    local shape=shapes[index] +    local components=shape.components +    if components then +      merge(index,shape,components)      end -    enhance("add dimensions",data,filename,nil,false) -    if trace_sequences then -      showfeatureorder(data,filename) +  end +end +local function readnothing(f,nofcontours) +  return { +    type="nothing", +  } +end +local function curveto(m_x,m_y,l_x,l_y,r_x,r_y)  +  return { +    l_x+2/3*(m_x-l_x),l_y+2/3*(m_y-l_y), +    r_x+2/3*(m_x-r_x),r_y+2/3*(m_y-r_y), +    r_x,r_y,"c"  +  } +end +local function contours2outlines(glyphs,shapes) +  local quadratic=true +  for index=1,#glyphs do +    local glyph=glyphs[index] +    local shape=shapes[index] +    local contours=shape.contours +    if contours then +      local nofcontours=#contours +      local segments={} +      local nofsegments=0 +      glyph.segments=segments +      if nofcontours>0 then +        for i=1,nofcontours do +          local contour=contours[i] +          local nofcontour=#contour +          if nofcontour>0 then +            local first_pt=contour[1] +            local first_on=first_pt[3] +            if nofcontour==1 then +              first_pt[3]="m"  +              nofsegments=nofsegments+1 +              segments[nofsegments]=first_pt +            else  +              local first_on=first_pt[3] +              local last_pt=contour[nofcontour] +              local last_on=last_pt[3] +              local start=1 +              local control_pt=false +              if first_on then +                start=2 +              else +                if last_on then +                  first_pt=last_pt +                else +                  first_pt={ (first_pt[1]+last_pt[1])/2,(first_pt[2]+last_pt[2])/2,false } +                end +                control_pt=first_pt +              end +              nofsegments=nofsegments+1 +              segments[nofsegments]={ first_pt[1],first_pt[2],"m" }  +              local previous_pt=first_pt +              for i=start,nofcontour do +                local current_pt=contour[i] +                local current_on=current_pt[3] +                local previous_on=previous_pt[3] +                if previous_on then +                  if current_on then +                    nofsegments=nofsegments+1 +                    segments[nofsegments]={ current_pt[1],current_pt[2],"l" }  +                  else +                    control_pt=current_pt +                  end +                elseif current_on then +                  local ps=segments[nofsegments] +                  nofsegments=nofsegments+1 +                  if quadratic then +                    segments[nofsegments]={ control_pt[1],control_pt[2],current_pt[1],current_pt[2],"q" }  +                  else +                    local p=segments[nofsegments-1] local n=#p +                    segments[nofsegments]=curveto(control_pt[1],control_pt[2],p[n-2],p[n-1],current_pt[1],current_pt[2]) +                  end +                  control_pt=false +                else +                  nofsegments=nofsegments+1 +                  local halfway_x=(previous_pt[1]+current_pt[1])/2 +                  local halfway_y=(previous_pt[2]+current_pt[2])/2 +                  if quadratic then +                    segments[nofsegments]={ control_pt[1],control_pt[2],halfway_x,halfway_y,"q" }  +                  else +                    local p=segments[nofsegments-1] local n=#p +                    segments[nofsegments]=curveto(control_pt[1],control_pt[2],p[n-2],p[n-1],halfway_x,halfway_y) +                  end +                  control_pt=current_pt +                end +                previous_pt=current_pt +              end +              if first_pt==last_pt then +              else +                nofsegments=nofsegments+1 +                if not control_pt then +                  segments[nofsegments]={ first_pt[1],first_pt[2],"l" }  +                elseif quadratic then +                  segments[nofsegments]={ control_pt[1],control_pt[2],first_pt[1],first_pt[2],"q" }  +                else +                  local p=last_pt local n=#p +                  segments[nofsegments]=curveto(control_pt[1],control_pt[2],p[n-2],p[n-1],first_pt[1],first_pt[2]) +                end +              end +            end +          end +        end +      end      end    end -  return data  end -local mt={ -  __index=function(t,k)  -    if k=="height" then -      local ht=t.boundingbox[4] -      return ht<0 and 0 or ht -    elseif k=="depth" then -      local dp=-t.boundingbox[2] -      return dp<0 and 0 or dp -    elseif k=="width" then -      return 0 -    elseif k=="name" then  -      return forcenotdef and ".notdef" +local function readglyph(f,nofcontours) +  local points={} +  local endpoints={} +  local instructions={} +  local flags={} +  for i=1,nofcontours do +    endpoints[i]=readshort(f)+1 +  end +  local nofpoints=endpoints[nofcontours] +  local nofinstructions=readushort(f) +  skipbytes(f,nofinstructions) +  local i=1 +  while i<=nofpoints do +    local flag=readbyte(f) +    flags[i]=flag +    if bittest(flag,0x0008) then +      for j=1,readbyte(f) do +        i=i+1 +        flags[i]=flag +      end      end +    i=i+1    end -} -actions["prepare tables"]=function(data,filename,raw) -  data.properties.hasitalics=false +  local x=0 +  for i=1,nofpoints do +    local flag=flags[i] +    local short=bittest(flag,0x0002) +    local same=bittest(flag,0x0010) +    if short then +      if same then +        x=x+readbyte(f) +      else +        x=x-readbyte(f) +      end +    elseif same then +    else +      x=x+readshort(f) +    end +    points[i]={ x,y,bittest(flag,0x0001) } +  end +  local y=0 +  for i=1,nofpoints do +    local flag=flags[i] +    local short=bittest(flag,0x0004) +    local same=bittest(flag,0x0020) +    if short then +      if same then +        y=y+readbyte(f) +      else +        y=y-readbyte(f) +      end +    elseif same then +    else +      y=y+readshort(f) +    end +    points[i][2]=y +  end +  local first=1 +  for i=1,#endpoints do +    local last=endpoints[i] +    endpoints[i]={ unpack(points,first,last) } +    first=last+1 +  end +  return { +    type="glyph", +    contours=endpoints, +  }  end -actions["add dimensions"]=function(data,filename) -  if data then -    local descriptions=data.descriptions -    local resources=data.resources -    local defaultwidth=resources.defaultwidth or 0 -    local defaultheight=resources.defaultheight or 0 -    local defaultdepth=resources.defaultdepth or 0 -    local basename=trace_markwidth and file.basename(filename) -    for _,d in next,descriptions do -      local bb,wd=d.boundingbox,d.width -      if not wd then -        d.width=defaultwidth -      elseif trace_markwidth and wd~=0 and d.class=="mark" then -        report_otf("mark %a with width %b found in %a",d.name or "<noname>",wd,basename) +local function readcomposite(f) +  local components={} +  local nofcomponents=0 +  local instructions=false +  while true do +    local flags=readushort(f) +    local index=readushort(f) +    local f_xyarg=bittest(flags,0x0002) +    local f_offset=bittest(flags,0x0800) +    local xscale=1 +    local xrotate=0 +    local yrotate=0 +    local yscale=1 +    local xoffset=0 +    local yoffset=0 +    local base=false +    local reference=false +    if f_xyarg then +      if bittest(flags,0x0001) then  +        xoffset=readshort(f) +        yoffset=readshort(f) +      else +        xoffset=readchar(f)  +        yoffset=readchar(f)         end -      if bb then -        local ht=bb[4] -        local dp=-bb[2] -          if ht==0 or ht<0 then -          else -            d.height=ht -          end -          if dp==0 or dp<0 then +    else +      if bittest(flags,0x0001) then  +        base=readshort(f) +        reference=readshort(f) +      else +        base=readchar(f)  +        reference=readchar(f)  +      end +    end +    if bittest(flags,0x0008) then  +      xscale=read2dot14(f) +      yscale=xscale +      if f_xyarg and f_offset then +        xoffset=xoffset*xscale +        yoffset=yoffset*yscale +      end +    elseif bittest(flags,0x0040) then  +      xscale=read2dot14(f) +      yscale=read2dot14(f) +      if f_xyarg and f_offset then +        xoffset=xoffset*xscale +        yoffset=yoffset*yscale +      end +    elseif bittest(flags,0x0080) then  +      xscale=read2dot14(f) +      xrotate=read2dot14(f) +      yrotate=read2dot14(f) +      yscale=read2dot14(f) +      if f_xyarg and f_offset then +        xoffset=xoffset*sqrt(xscale^2+xrotate^2) +        yoffset=yoffset*sqrt(yrotate^2+yscale^2) +      end +    end +    nofcomponents=nofcomponents+1 +    components[nofcomponents]={ +      index=index, +      usemine=bittest(flags,0x0200), +      round=bittest(flags,0x0006), +      base=base, +      reference=reference, +      matrix={ xscale,xrotate,yrotate,yscale,xoffset,yoffset }, +    } +    if bittest(flags,0x0100) then +      instructions=true +    end +    if not bittest(flags,0x0020) then  +      break +    end +  end +  return { +    type="composite", +    components=components, +  } +end +function readers.loca(f,fontdata,specification) +  if specification.glyphs then +    local datatable=fontdata.tables.loca +    if datatable then +      local offset=fontdata.tables.glyf.offset +      local format=fontdata.fontheader.indextolocformat +      local locations={} +      setposition(f,datatable.offset) +      if format==1 then +        local nofglyphs=datatable.length/4-1 +      -1 +        for i=0,nofglyphs do +          locations[i]=offset+readulong(f) +        end +        fontdata.nofglyphs=nofglyphs +      else +        local nofglyphs=datatable.length/2-1 +      -1 +        for i=0,nofglyphs do +          locations[i]=offset+readushort(f)*2 +        end +        fontdata.nofglyphs=nofglyphs +      end +      fontdata.locations=locations +    end +  end +end +function readers.glyf(f,fontdata,specification)  +  if specification.glyphs then +    local datatable=fontdata.tables.glyf +    if datatable then +      local locations=fontdata.locations +      if locations then +        local glyphs=fontdata.glyphs +        local nofglyphs=fontdata.nofglyphs +        local filesize=fontdata.filesize +        local nothing={ 0,0,0,0 } +        local shapes={} +        local loadshapes=specification.shapes +        for index=0,nofglyphs do +          local location=locations[index] +          if location>=filesize then +            report("discarding %s glyphs due to glyph location bug",nofglyphs-index+1) +            fontdata.nofglyphs=index-1 +            fontdata.badfont=true +            break +          elseif location>0 then +            setposition(f,location) +            local nofcontours=readshort(f) +            glyphs[index].boundingbox={ +              readshort(f), +              readshort(f), +              readshort(f), +              readshort(f), +            } +            if not loadshapes then +            elseif nofcontours==0 then +              shapes[index]=readnothing(f,nofcontours) +            elseif nofcontours>0 then +              shapes[index]=readglyph(f,nofcontours) +            else +              shapes[index]=readcomposite(f,nofcontours) +            end            else -            d.depth=dp +            if loadshapes then +              shapes[index]={} +            end +            glyphs[index].boundingbox=nothing            end +        end +        if loadshapes then +          mergecomposites(glyphs,shapes) +          contours2outlines(glyphs,shapes) +        end        end      end    end  end -local function somecopy(old)  -  if old then -    local new={} -    if type(old)=="table" then -      for k,v in next,old do -        if k=="glyphs" then -        elseif type(v)=="table" then -          new[k]=somecopy(v) -        else -          new[k]=v -        end + +end -- closure + +do -- begin closure to overcome local limits and interference + +if not modules then modules={} end modules ['font-dsp']={ +  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 bittest=bit32.btest +local rshift=bit32.rshift +local concat=table.concat +local lower=string.lower +local sub=string.sub +local strip=string.strip +local tohash=table.tohash +local reversed=table.reversed +local setmetatableindex=table.setmetatableindex +local formatters=string.formatters +local sortedkeys=table.sortedkeys +local sortedhash=table.sortedhash +local report=logs.reporter("otf reader") +local readers=fonts.handlers.otf.readers +local streamreader=readers.streamreader +local setposition=streamreader.setposition +local skipbytes=streamreader.skip +local skipshort=streamreader.skipshort +local readushort=streamreader.readcardinal2  +local readulong=streamreader.readcardinal4  +local readshort=streamreader.readinteger2   +local readfword=readshort +local readstring=streamreader.readstring +local readtag=streamreader.readtag +local gsubhandlers={} +local gposhandlers={} +local lookupidoffset=-1   +local classes={ +  "base", +  "ligature", +  "mark", +  "component", +} +local gsubtypes={ +  "single", +  "multiple", +  "alternate", +  "ligature", +  "context", +  "chainedcontext", +  "extension", +  "reversechainedcontextsingle", +} +local gpostypes={ +  "single", +  "pair", +  "cursive", +  "marktobase", +  "marktoligature", +  "marktomark", +  "context", +  "chainedcontext", +  "extension", +} +local chaindirections={ +  context=0, +  chainedcontext=1, +  reversechainedcontextsingle=-1, +} +local lookupnames={ +  gsub={ +    single="gsub_single", +    multiple="gsub_multiple", +    alternate="gsub_alternate", +    ligature="gsub_ligature", +    context="gsub_context", +    chainedcontext="gsub_contextchain", +    reversechainedcontextsingle="gsub_reversecontextchain", +  }, +  gpos={ +    single="gpos_single", +    pair="gpos_pair", +    cursive="gpos_cursive", +    marktobase="gpos_mark2base", +    marktoligature="gpos_mark2ligature", +    marktomark="gpos_mark2mark", +    context="gpos_context", +    chainedcontext="gpos_contextchain", +  } +} +local lookupflags=setmetatableindex(function(t,k) +  local v={ +    bittest(k,0x0008) and true or false, +    bittest(k,0x0004) and true or false, +    bittest(k,0x0002) and true or false, +    bittest(k,0x0001) and true or false, +  } +  t[k]=v +  return v +end) +local function readcoverage(f,offset,simple) +  setposition(f,offset) +  local coverageformat=readushort(f) +  local coverage={} +  if coverageformat==1 then +    local nofcoverage=readushort(f) +    if simple then +      for i=1,nofcoverage do +        coverage[i]=readushort(f)        end      else -      for i=1,#mainfields do -        local k=mainfields[i] -        local v=old[k] -        if k=="glyphs" then -        elseif type(v)=="table" then -          new[k]=somecopy(v) -        else -          new[k]=v +      for i=0,nofcoverage-1 do +        coverage[readushort(f)]=i  +      end +    end +  elseif coverageformat==2 then +    local nofranges=readushort(f) +    local n=simple and 1 or 0  +    for i=1,nofranges do +      local firstindex=readushort(f) +      local lastindex=readushort(f) +      local coverindex=readushort(f) +      if simple then +        for i=firstindex,lastindex do +          coverage[n]=i +          n=n+1 +        end +      else +        for i=firstindex,lastindex do +          coverage[i]=n +          n=n+1          end        end      end -    return new    else -    return {} +    report("unknown coverage format %a ",coverageformat) +  end +  return coverage +end +local function readclassdef(f,offset) +  setposition(f,offset) +  local classdefformat=readushort(f) +  local classdef={} +  if classdefformat==1 then +    local index=readushort(f) +    local nofclassdef=readushort(f) +    for i=1,nofclassdef do +      classdef[index]=readushort(f)+1 +      index=index+1 +    end +  elseif classdefformat==2 then +    local nofranges=readushort(f) +    local n=0 +    for i=1,nofranges do +      local firstindex=readushort(f) +      local lastindex=readushort(f) +      local class=readushort(f)+1 +      for i=firstindex,lastindex do +        classdef[i]=class +      end +    end +  else +    report("unknown classdef format %a ",classdefformat)    end +  return classdef  end -actions["prepare glyphs"]=function(data,filename,raw) -  local tableversion=tonumber(raw.table_version) or 0 -  local rawglyphs=raw.glyphs -  local rawsubfonts=raw.subfonts -  local rawcidinfo=raw.cidinfo -  local criterium=constructors.privateoffset -  local private=criterium -  local resources=data.resources -  local metadata=data.metadata -  local properties=data.properties -  local descriptions=data.descriptions -  local unicodes=resources.unicodes  -  local indices=resources.indices  -  local duplicates=resources.duplicates -  local variants=resources.variants -  if rawsubfonts then -    metadata.subfonts=includesubfonts and {} -    properties.cidinfo=rawcidinfo -    if rawcidinfo.registry then -      local cidmap=fonts.cid.getmap(rawcidinfo) -      if cidmap then -        rawcidinfo.usedname=cidmap.usedname -        local nofnames=0 -        local nofunicodes=0 -        local cidunicodes=cidmap.unicodes -        local cidnames=cidmap.names -        local cidtotal=0 -        local unique=trace_subfonts and {} -        for cidindex=1,#rawsubfonts do -          local subfont=rawsubfonts[cidindex] -          local cidglyphs=subfont.glyphs -          if includesubfonts then -            metadata.subfonts[cidindex]=somecopy(subfont) -          end -          local cidcnt,cidmin,cidmax -          if tableversion>0.3 then -            cidcnt=subfont.glyphcnt -            cidmin=subfont.glyphmin -            cidmax=subfont.glyphmax -          else -            cidcnt=subfont.glyphcnt -            cidmin=0 -            cidmax=cidcnt-1 -          end -          if trace_subfonts then -            local cidtot=cidmax-cidmin+1 -            cidtotal=cidtotal+cidtot -            report_otf("subfont: %i, min: %i, max: %i, cnt: %i, n: %i",cidindex,cidmin,cidmax,cidtot,cidcnt) -          end -          if cidcnt>0 then -            for cidslot=cidmin,cidmax do -              local glyph=cidglyphs[cidslot] -              if glyph then -                local index=tableversion>0.3 and glyph.orig_pos or cidslot -                if trace_subfonts then -                  unique[index]=true -                end -                local unicode=glyph.unicode -                if   unicode>=0x00E000 and unicode<=0x00F8FF then -                  unicode=-1 -                elseif unicode>=0x0F0000 and unicode<=0x0FFFFD then -                  unicode=-1 -                elseif unicode>=0x100000 and unicode<=0x10FFFD then -                  unicode=-1 +local function classtocoverage(defs) +  if defs then +    local list={} +    for index,class in next,defs do +      local c=list[class] +      if c then +        c[#c+1]=index +      else +        list[class]={ index } +      end +    end +    return list +  end +end +local function readposition(f,format) +  if format==0 then +    return nil +  end +  local x=bittest(format,0x0001) and readshort(f) or 0  +  local y=bittest(format,0x0002) and readshort(f) or 0  +  local h=bittest(format,0x0004) and readshort(f) or 0  +  local v=bittest(format,0x0008) and readshort(f) or 0  +  if x==0 and y==0 and h==0 and v==0 then +    return nil +  else +    return { x,y,h,v } +  end +end +local function readanchor(f,offset) +  if not offset or offset==0 then +    return nil  +  end +  setposition(f,offset) +  local format=readshort(f) +  if format==0 then +    report("invalid anchor format %i @ position %i",format,offset) +    return false +  elseif format>3 then +    report("unsupported anchor format %i @ position %i",format,offset) +    return false +  end +  return { readshort(f),readshort(f) } +end +local function readfirst(f,offset) +  if offset then +    setposition(f,offset) +  end +  return { readushort(f) } +end +local function readarray(f,offset,first) +  if offset then +    setposition(f,offset) +  end +  local n=readushort(f) +  if first then +    local t={ first } +    for i=2,n do +      t[i]=readushort(f) +    end +    return t,n +  elseif n>0 then +    local t={} +    for i=1,n do +      t[i]=readushort(f) +    end +    return t,n +  end +end +local function readcoveragearray(f,offset,t,simple) +  if not t then +    return nil +  end +  local n=#t +  if n==0 then +    return nil +  end +  for i=1,n do +    t[i]=readcoverage(f,offset+t[i],simple) +  end +  return t +end +local function covered(subset,all) +  local used,u +  for i=1,#subset do +    local s=subset[i] +    if all[s] then +      if used then +        u=u+1 +        used[u]=s +      else +        u=1 +        used={ s } +      end +    end +  end +  return used +end +local function unchainedcontext(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,what) +  local tableoffset=lookupoffset+offset +  setposition(f,tableoffset) +  local subtype=readushort(f) +  if subtype==1 then +    local coverage=readushort(f) +    local subclasssets=readarray(f) +    local rules={} +    if subclasssets then +      coverage=readcoverage(f,tableoffset+coverage,true) +      for i=1,#subclasssets do +        local offset=subclasssets[i] +        if offset>0 then +          local firstcoverage=coverage[i] +          local rulesoffset=tableoffset+offset +          local subclassrules=readarray(f,rulesoffset) +          for rule=1,#subclassrules do +            setposition(f,rulesoffset+subclassrules[rule]) +            local nofcurrent=readushort(f) +            local noflookups=readushort(f) +            local current={ { firstcoverage } } +            for i=2,nofcurrent do +              current[i]={ readushort(f) } +            end +            local lookups={} +            for i=1,noflookups do +              lookups[readushort(f)+1]=readushort(f)+1 +            end +            rules[#rules+1]={ +              current=current, +              lookups=lookups +            } +          end +        end +      end +    else +      report("empty subclassset in %a subtype %i","unchainedcontext",subtype) +    end +    return { +      format="glyphs", +      rules=rules, +    } +  elseif subtype==2 then +    local coverage=readushort(f) +    local currentclassdef=readushort(f) +    local subclasssets=readarray(f) +    local rules={} +    if subclasssets then +      coverage=readcoverage(f,tableoffset+coverage) +      currentclassdef=readclassdef(f,tableoffset+currentclassdef) +      local currentclasses=classtocoverage(currentclassdef,fontdata.glyphs) +      for class=1,#subclasssets do +        local offset=subclasssets[class] +        if offset>0 then +          local firstcoverage=currentclasses[class] +          if firstcoverage then +            firstcoverage=covered(firstcoverage,coverage)  +            if firstcoverage then +              local rulesoffset=tableoffset+offset +              local subclassrules=readarray(f,rulesoffset) +              for rule=1,#subclassrules do +                setposition(f,rulesoffset+subclassrules[rule]) +                local nofcurrent=readushort(f) +                local noflookups=readushort(f) +                local current={ firstcoverage } +                for i=2,nofcurrent do +                  current[i]=currentclasses[readushort(f)+1]                  end -                local name=glyph.name or cidnames[index] -                if not unicode or unicode==-1 then  -                  unicode=cidunicodes[index] +                local lookups={} +                for i=1,noflookups do +                  lookups[readushort(f)+1]=readushort(f)+1                  end -                if unicode and descriptions[unicode] then -                  if trace_private then -                    report_otf("preventing glyph %a at index %H to overload unicode %U",name or "noname",index,unicode) +                rules[#rules+1]={ +                  current=current, +                  lookups=lookups +                } +              end +            else +              report("no coverage") +            end +          else +            report("no coverage class") +          end +        end +      end +    else +      report("empty subclassset in %a subtype %i","unchainedcontext",subtype) +    end +    return { +      format="class", +      rules=rules, +    } +  elseif subtype==3 then +    local current=readarray(f) +    local noflookups=readushort(f) +    local lookups={} +    for i=1,noflookups do +      lookups[readushort(f)+1]=readushort(f)+1 +    end +    current=readcoveragearray(f,tableoffset,current,true) +    return { +      format="coverage", +      rules={ +        { +          current=current, +          lookups=lookups, +        } +      } +    } +  else +    report("unsupported subtype %a in %a %s",subtype,"unchainedcontext",what) +  end +end +local function chainedcontext(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,what) +  local tableoffset=lookupoffset+offset +  setposition(f,tableoffset) +  local subtype=readushort(f) +  if subtype==1 then +    local coverage=readushort(f) +    local subclasssets=readarray(f) +    local rules={} +    if subclasssets then +      coverage=readcoverage(f,tableoffset+coverage,true) +      for i=1,#subclasssets do +        local offset=subclasssets[i] +        if offset>0 then +          local firstcoverage=coverage[i] +          local rulesoffset=tableoffset+offset +          local subclassrules=readarray(f,rulesoffset) +          for rule=1,#subclassrules do +            setposition(f,rulesoffset+subclassrules[rule]) +            local nofbefore=readushort(f) +            local before +            if nofbefore>0 then +              before={} +              for i=1,nofbefore do +                before[i]={ readushort(f) } +              end +            end +            local nofcurrent=readushort(f) +            local current={ { firstcoverage } } +            for i=2,nofcurrent do +              current[i]={ readushort(f) } +            end +            local nofafter=readushort(f) +            local after +            if nofafter>0 then +              after={} +              for i=1,nofafter do +                after[i]={ readushort(f) } +              end +            end +            local noflookups=readushort(f) +            local lookups={} +            for i=1,noflookups do +              lookups[readushort(f)+1]=readushort(f)+1 +            end +            rules[#rules+1]={ +              before=before, +              current=current, +              after=after, +              lookups=lookups, +            } +          end +        end +      end +    else +      report("empty subclassset in %a subtype %i","chainedcontext",subtype) +    end +    return { +      format="glyphs", +      rules=rules, +    } +  elseif subtype==2 then +    local coverage=readushort(f) +    local beforeclassdef=readushort(f) +    local currentclassdef=readushort(f) +    local afterclassdef=readushort(f) +    local subclasssets=readarray(f) +    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 beforeclasses=classtocoverage(beforeclassdef,fontdata.glyphs) +      local currentclasses=classtocoverage(currentclassdef,fontdata.glyphs) +      local afterclasses=classtocoverage(afterclassdef,fontdata.glyphs) +      for class=1,#subclasssets do +        local offset=subclasssets[class] +        if offset>0 then +          local firstcoverage=currentclasses[class] +          if firstcoverage then +            firstcoverage=covered(firstcoverage,coverage)  +            if firstcoverage then +              local rulesoffset=tableoffset+offset +              local subclassrules=readarray(f,rulesoffset) +              for rule=1,#subclassrules do +                setposition(f,rulesoffset+subclassrules[rule]) +                local nofbefore=readushort(f) +                local before +                if nofbefore>0 then +                  before={} +                  for i=1,nofbefore do +                    before[i]=beforeclasses[readushort(f)+1]                    end -                  unicode=-1                  end -                if not unicode or unicode==-1 then  -                  if not name then -                    name=formatters["u%06X.ctx"](private) -                  end -                  unicode=private -                  unicodes[name]=private -                  if trace_private then -                    report_otf("glyph %a at index %H is moved to private unicode slot %U",name,index,private) -                  end -                  private=private+1 -                  nofnames=nofnames+1 -                else -                  if not name then -                    name=formatters["u%06X.ctx"](unicode) -                  end -                  unicodes[name]=unicode -                  nofunicodes=nofunicodes+1 +                local nofcurrent=readushort(f) +                local current={ firstcoverage } +                for i=2,nofcurrent do +                  current[i]=currentclasses[readushort(f)+1]                  end -                indices[index]=unicode  -                local description={ -                  boundingbox=glyph.boundingbox, -                  name=name or "unknown", -                  cidindex=cidindex, -                  index=cidslot, -                  glyph=glyph, -                } -                descriptions[unicode]=description -                local altuni=glyph.altuni -                if altuni then -                  for i=1,#altuni do -                    local a=altuni[i] -                    local u=a.unicode -                    if u~=unicode then -                      local v=a.variant -                      if v then -                        local vv=variants[v] -                        if vv then -                          vv[u]=unicode -                        else  -                          vv={ [u]=unicode } -                          variants[v]=vv -                        end -                      end -                    end +                local nofafter=readushort(f) +                local after +                if nofafter>0 then +                  after={} +                  for i=1,nofafter do +                    after[i]=afterclasses[readushort(f)+1]                    end                  end +                local noflookups=readushort(f) +                local lookups={} +                for i=1,noflookups do +                  lookups[readushort(f)+1]=readushort(f)+1 +                end +                rules[#rules+1]={ +                  before=before, +                  current=current, +                  after=after, +                  lookups=lookups, +                }                end +            else +              report("no coverage")              end            else -            report_otf("potential problem: no glyphs found in subfont %i",cidindex) +            report("class is not covered") +          end +        end +      end +    else +      report("empty subclassset in %a subtype %i","chainedcontext",subtype) +    end +    return { +      format="class", +      rules=rules, +    } +  elseif subtype==3 then +    local before=readarray(f) +    local current=readarray(f) +    local after=readarray(f) +    local noflookups=readushort(f) +    local lookups={} +    for i=1,noflookups do +      lookups[readushort(f)+1]=readushort(f)+1 +    end +    before=readcoveragearray(f,tableoffset,before,true) +    current=readcoveragearray(f,tableoffset,current,true) +    after=readcoveragearray(f,tableoffset,after,true) +    return { +      format="coverage", +      rules={ +        { +          before=before, +          current=current, +          after=after, +          lookups=lookups, +        } +      } +    } +  else +    report("unsupported subtype %a in %a %s",subtype,"chainedcontext",what) +  end +end +local function extension(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,types,handlers,what) +  local tableoffset=lookupoffset+offset +  setposition(f,tableoffset) +  local subtype=readushort(f) +  if subtype==1 then +    local lookuptype=types[readushort(f)] +    local faroffset=readulong(f) +    local handler=handlers[lookuptype] +    if handler then +      return handler(f,fontdata,lookupid,tableoffset+faroffset,0,glyphs,nofglyphs),lookuptype +    else +      report("no handler for lookuptype %a subtype %a in %s %s",lookuptype,subtype,what,"extension") +    end +  else +    report("unsupported subtype %a in %s %s",subtype,what,"extension") +  end +end +function gsubhandlers.single(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs) +  local tableoffset=lookupoffset+offset +  setposition(f,tableoffset) +  local subtype=readushort(f) +  if subtype==1 then +    local coverage=readushort(f) +    local delta=readshort(f)  +    local coverage=readcoverage(f,tableoffset+coverage)  +    for index in next,coverage do +      local newindex=index+delta +      if index>nofglyphs or newindex>nofglyphs then +        report("invalid index in %s format %i: %i -> %i (max %i)","single",subtype,index,newindex,nofglyphs) +        coverage[index]=nil +      else +        coverage[index]=newindex +      end +    end +    return { +      coverage=coverage +    } +  elseif subtype==2 then  +    local coverage=readushort(f) +    local nofreplacements=readushort(f) +    local replacements={} +    for i=1,nofreplacements do +      replacements[i]=readushort(f) +    end +    local coverage=readcoverage(f,tableoffset+coverage)  +    for index,newindex in next,coverage do +      newindex=newindex+1 +      if index>nofglyphs or newindex>nofglyphs then +        report("invalid index in %s format %i: %i -> %i (max %i)","single",subtype,index,newindex,nofglyphs) +        coverage[index]=nil +      else +        coverage[index]=replacements[newindex] +      end +    end +    return { +      coverage=coverage +    } +  else +    report("unsupported subtype %a in %a substitution",subtype,"single") +  end +end +local function sethandler(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,what) +  local tableoffset=lookupoffset+offset +  setposition(f,tableoffset) +  local subtype=readushort(f) +  if subtype==1 then +    local coverage=readushort(f) +    local nofsequence=readushort(f) +    local sequences={} +    for i=1,nofsequence do +      sequences[i]=readushort(f) +    end +    for i=1,nofsequence do +      setposition(f,tableoffset+sequences[i]) +      local n=readushort(f) +      local s={} +      for i=1,n do +        s[i]=readushort(f) +      end +      sequences[i]=s +    end +    local coverage=readcoverage(f,tableoffset+coverage) +    for index,newindex in next,coverage do +      newindex=newindex+1 +      if index>nofglyphs or newindex>nofglyphs then +        report("invalid index in %s format %i: %i -> %i (max %i)",what,subtype,index,newindex,nofglyphs) +        coverage[index]=nil +      else +        coverage[index]=sequences[newindex] +      end +    end +    return { +      coverage=coverage +    } +  else +    report("unsupported subtype %a in %a substitution",subtype,what) +  end +end +function gsubhandlers.multiple(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs) +  return sethandler(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,"multiple") +end +function gsubhandlers.alternate(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs) +  return sethandler(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,"alternate") +end +function gsubhandlers.ligature(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs) +  local tableoffset=lookupoffset+offset +  setposition(f,tableoffset) +  local subtype=readushort(f) +  if subtype==1 then +    local coverage=readushort(f) +    local nofsets=readushort(f) +    local ligatures={} +    for i=1,nofsets do +      ligatures[i]=readushort(f) +    end +    for i=1,nofsets do +      local offset=lookupoffset+offset+ligatures[i] +      setposition(f,offset) +      local n=readushort(f) +      local l={} +      for i=1,n do +        l[i]=offset+readushort(f) +      end +      ligatures[i]=l +    end +    local coverage=readcoverage(f,tableoffset+coverage) +    for index,newindex in next,coverage do +      local hash={} +      local ligatures=ligatures[newindex+1] +      for i=1,#ligatures do +        local offset=ligatures[i] +        setposition(f,offset) +        local lig=readushort(f) +        local cnt=readushort(f) +        local hsh=hash +        for i=2,cnt do +          local c=readushort(f) +          local h=hsh[c] +          if not h then +            h={} +            hsh[c]=h            end +          hsh=h          end -        if trace_subfonts then -          report_otf("nofglyphs: %i, unique: %i",cidtotal,table.count(unique)) +        hsh.ligature=lig +      end +      coverage[index]=hash +    end +    return { +      coverage=coverage +    } +  else +    report("unsupported subtype %a in %a substitution",subtype,"ligature") +  end +end +function gsubhandlers.context(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs) +  return unchainedcontext(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,"substitution"),"context" +end +function gsubhandlers.chainedcontext(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs) +  return chainedcontext(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,"substitution"),"chainedcontext" +end +function gsubhandlers.extension(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs) +  return extension(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,gsubtypes,gsubhandlers,"substitution") +end +function gsubhandlers.reversechainedcontextsingle(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs) +  local tableoffset=lookupoffset+offset +  setposition(f,tableoffset) +  local subtype=readushort(f) +  if subtype==1 then  +    local current=readfirst(f) +    local before=readarray(f) +    local after=readarray(f) +    local replacements=readarray(f) +    current=readcoveragearray(f,tableoffset,current,true) +    before=readcoveragearray(f,tableoffset,before,true) +    after=readcoveragearray(f,tableoffset,after,true) +    return { +      coverage={ +        format="reversecoverage", +        before=before, +        current=current, +        after=after, +        replacements=replacements, +      } +    },"reversechainedcontextsingle" +  else +    report("unsupported subtype %a in %a substitution",subtype,"reversechainedcontextsingle") +  end +end +local function readpairsets(f,tableoffset,sets,format1,format2) +  local done={} +  for i=1,#sets do +    local offset=sets[i] +    local reused=done[offset] +    if not reused then +      setposition(f,tableoffset+offset) +      local n=readushort(f) +      reused={} +      for i=1,n do +        reused[i]={ +          readushort(f), +          readposition(f,format1), +          readposition(f,format2) +        } +      end +      done[offset]=reused +    end +    sets[i]=reused +  end +  return sets +end +local function readpairclasssets(f,nofclasses1,nofclasses2,format1,format2) +  local classlist1={} +  for i=1,nofclasses1 do +    local classlist2={} +    classlist1[i]=classlist2 +    for j=1,nofclasses2 do +      local one=readposition(f,format1) +      local two=readposition(f,format2) +      if one or two then +        classlist2[j]={ one,two } +      else +        classlist2[j]=false +      end +    end +  end +  return classlist1 +end +function gposhandlers.single(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs) +  local tableoffset=lookupoffset+offset +  setposition(f,tableoffset) +  local subtype=readushort(f) +  if subtype==1 then +    local coverage=readushort(f) +    local format=readushort(f) +    local value=readposition(f,format) +    local coverage=readcoverage(f,tableoffset+coverage) +    for index,newindex in next,coverage do +      coverage[index]=value +    end +    return { +      format="pair", +      coverage=coverage +    } +  elseif subtype==2 then +    local coverage=readushort(f) +    local format=readushort(f) +    local values={} +    local nofvalues=readushort(f) +    for i=1,nofvalues do +      values[i]=readposition(f,format) +    end +    local coverage=readcoverage(f,tableoffset+coverage) +    for index,newindex in next,coverage do +      coverage[index]=values[newindex+1] +    end +    return { +      format="pair", +      coverage=coverage +    } +  else +    report("unsupported subtype %a in %a positioning",subtype,"single") +  end +end +function gposhandlers.pair(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs) +  local tableoffset=lookupoffset+offset +  setposition(f,tableoffset) +  local subtype=readushort(f) +  if subtype==1 then +    local coverage=readushort(f) +    local format1=readushort(f) +    local format2=readushort(f) +    local sets=readarray(f) +       sets=readpairsets(f,tableoffset,sets,format1,format2) +       coverage=readcoverage(f,tableoffset+coverage) +    for index,newindex in next,coverage do +      local set=sets[newindex+1] +      local hash={} +      for i=1,#set do +        local value=set[i] +        if value then +          local other=value[1] +          local first=value[2] +          local second=value[3] +          if first or second then +            hash[other]={ first,second }  +          else +            hash[other]=nil +          end          end -        if trace_loading then -          report_otf("cid font remapped, %s unicode points, %s symbolic names, %s glyphs",nofunicodes,nofnames,nofunicodes+nofnames) +      end +      coverage[index]=hash +    end +    return { +      format="pair", +      coverage=coverage +    } +  elseif subtype==2 then +    local coverage=readushort(f) +    local format1=readushort(f) +    local format2=readushort(f) +    local classdef1=readushort(f) +    local classdef2=readushort(f) +    local nofclasses1=readushort(f)  +    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) +    local usedcoverage={} +    for g1,c1 in next,classdef1 do +      if coverage[g1] then +        local l1=classlist[c1] +        if l1 then +          local hash={} +          for paired,class in next,classdef2 do +            local offsets=l1[class] +            if offsets then +              local first=offsets[1] +              local second=offsets[2] +              if first or second then +                hash[paired]={ first,second } +              else +              end +            end +          end +          usedcoverage[g1]=hash          end -      elseif trace_loading then -        report_otf("unable to remap cid font, missing cid file for %a",filename)        end -    elseif trace_loading then -      report_otf("font %a has no glyphs",filename)      end +    return { +      format="pair", +      coverage=usedcoverage +    } +  elseif subtype==3 then +    report("yet unsupported subtype %a in %a positioning",subtype,"pair")    else -    local cnt=raw.glyphcnt or 0 -    local min=tableversion>0.3 and raw.glyphmin or 0 -    local max=tableversion>0.3 and raw.glyphmax or (raw.glyphcnt-1) -    if cnt>0 then -      for index=min,max do -        local glyph=rawglyphs[index] -        if glyph then -          local unicode=glyph.unicode -          local name=glyph.name -          if not unicode or unicode==-1 then  -            unicode=private -            unicodes[name]=private -            if trace_private then -              report_otf("glyph %a at index %H is moved to private unicode slot %U",name,index,private) +    report("unsupported subtype %a in %a positioning",subtype,"pair") +  end +end +function gposhandlers.cursive(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs) +  local tableoffset=lookupoffset+offset +  setposition(f,tableoffset) +  local subtype=readushort(f) +  if subtype==1 then +    local coverage=tableoffset+readushort(f) +    local nofrecords=readushort(f) +    local records={} +    for i=1,nofrecords do +      local entry=readushort(f) +      local exit=readushort(f) +      records[i]={ +        entry=entry~=0 and (tableoffset+entry) or false, +        exit=exit~=0 and (tableoffset+exit ) or false, +      } +    end +    coverage=readcoverage(f,coverage) +    for i=1,nofrecords do +      local r=records[i] +      records[i]={ +        1, +        readanchor(f,r.entry) or nil, +        readanchor(f,r.exit ) or nil, +      } +    end +    for index,newindex in next,coverage do +      coverage[index]=records[newindex+1] +    end +    return { +      coverage=coverage +    } +  else +    report("unsupported subtype %a in %a positioning",subtype,"cursive") +  end +end +local function handlemark(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,ligature) +  local tableoffset=lookupoffset+offset +  setposition(f,tableoffset) +  local subtype=readushort(f) +  if subtype==1 then +    local markcoverage=tableoffset+readushort(f) +    local basecoverage=tableoffset+readushort(f) +    local nofclasses=readushort(f) +    local markoffset=tableoffset+readushort(f) +    local baseoffset=tableoffset+readushort(f) +    local markcoverage=readcoverage(f,markcoverage) +    local basecoverage=readcoverage(f,basecoverage,true) +    setposition(f,markoffset) +    local markclasses={} +    local nofmarkclasses=readushort(f) +    local lastanchor=fontdata.lastanchor or 0 +    local usedanchors={} +    for i=1,nofmarkclasses do +      local class=readushort(f)+1 +      local offset=readushort(f) +      if offset==0 then +        markclasses[i]=false +      else +        markclasses[i]={ class,markoffset+offset } +      end +      usedanchors[class]=true +    end +    for i=1,nofmarkclasses do +      local mc=markclasses[i] +      if mc then +        mc[2]=readanchor(f,mc[2]) +      end +    end +    setposition(f,baseoffset) +    local nofbaserecords=readushort(f) +    local baserecords={} +    if ligature then +      for i=1,nofbaserecords do  +        local offset=readushort(f) +        if offset==0 then +          baserecords[i]=false +        else +          baserecords[i]=baseoffset+offset +        end +      end +      for i=1,nofbaserecords do +        local recordoffset=baserecords[i] +        if recordoffset then +          setposition(f,recordoffset) +          local nofcomponents=readushort(f) +          local components={} +          for i=1,nofcomponents do +            local classes={} +            for i=1,nofclasses do +              local offset=readushort(f) +              if offset~=0 then +                classes[i]=recordoffset+offset +              else +                classes[i]=false +              end              end -            private=private+1 -          else -            if unicode>criterium then -              local taken=descriptions[unicode] -              if taken then -                if unicode>=private then -                  private=unicode+1  +            components[i]=classes +          end +          baserecords[i]=components +        end +      end +      local baseclasses={}  +      for i=1,nofclasses do +        baseclasses[i]={} +      end +      for i=1,nofbaserecords do +        local components=baserecords[i] +        local b=basecoverage[i] +        if components then +          for c=1,#components do +            local classes=components[i] +            if classes then +              for i=1,nofclasses do +                local anchor=readanchor(f,classes[i]) +                local bclass=baseclasses[i] +                local bentry=bclass[b] +                if bentry then +                  bentry[c]=anchor                  else -                  private=private+1  +                  bclass[b]={ [c]=anchor }                  end -                descriptions[private]=taken -                unicodes[taken.name]=private -                indices[taken.index]=private -                if trace_private then -                  report_otf("slot %U is moved to %U due to private in font",unicode) +              end +            end +            components[i]=classes +          end +        end +      end +      for index,newindex in next,markcoverage do +        markcoverage[index]=markclasses[newindex+1] or nil +      end +      return { +        format="ligature", +        baseclasses=baseclasses, +        coverage=markcoverage, +      } +    else +      for i=1,nofbaserecords do +        local r={} +        for j=1,nofclasses do +          local offset=readushort(f) +          if offset==0 then +            r[j]=false +          else +            r[j]=baseoffset+offset +          end +        end +        baserecords[i]=r +      end +      local baseclasses={}  +      for i=1,nofclasses do +        baseclasses[i]={} +      end +      for i=1,nofbaserecords do +        local r=baserecords[i] +        local b=basecoverage[i] +        for j=1,nofclasses do +          baseclasses[j][b]=readanchor(f,r[j]) +        end +      end +      for index,newindex in next,markcoverage do +        markcoverage[index]=markclasses[newindex+1] or nil +      end +      return { +        format="base", +        baseclasses=baseclasses, +        coverage=markcoverage, +      } +    end +  else +    report("unsupported subtype %a in",subtype) +  end +end +function gposhandlers.marktobase(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs) +  return handlemark(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs) +end +function gposhandlers.marktoligature(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs) +  return handlemark(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,true) +end +function gposhandlers.marktomark(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs) +  return handlemark(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs) +end +function gposhandlers.context(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs) +  return unchainedcontext(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,"positioning"),"context" +end +function gposhandlers.chainedcontext(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs) +  return chainedcontext(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,"positioning"),"chainedcontext" +end +function gposhandlers.extension(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs) +  return extension(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,gpostypes,gposhandlers,"positioning") +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 +        fontdata.designsize=designsize +        skipshort(f,2) +        fontdata.minsize=readushort(f) +        fontdata.maxsize=readushort(f) +      end +    end +  end +  local function reorderfeatures(fontdata,scripts,features) +    local scriptlangs={} +    local featurehash={} +    local featureorder={} +    for script,languages in next,scripts do +      for language,record in next,languages do +        local hash={} +        local list=record.featureindices +        for k=1,#list do +          local index=list[k] +          local feature=features[index] +          local lookups=feature.lookups +          local tag=feature.tag +          if tag then +            hash[tag]=true +          end +          if lookups then +            for i=1,#lookups do +              local lookup=lookups[i] +              local o=featureorder[lookup] +              if o then +                local okay=true +                for i=1,#o do +                  if o[i]==tag then +                    okay=false +                    break +                  end                  end -              else -                if unicode>=private then -                  private=unicode+1  +                if okay then +                  o[#o+1]=tag                  end +              else +                featureorder[lookup]={ tag }                end -            end -            unicodes[name]=unicode -          end -          indices[index]=unicode -          descriptions[unicode]={ -            boundingbox=glyph.boundingbox, -            name=name, -            index=index, -            glyph=glyph, -          } -          local altuni=glyph.altuni -          if altuni then -            for i=1,#altuni do -              local a=altuni[i] -              local u=a.unicode -              if u~=unicode then -                local v=a.variant -                if v then -                  local vv=variants[v] -                  if vv then -                    vv[u]=unicode -                  else  -                    vv={ [u]=unicode } -                    variants[v]=vv +              local f=featurehash[lookup] +              if f then +                local h=f[tag] +                if h then +                  local s=h[script] +                  if s then +                    s[language]=true +                  else +                    h[script]={ [language]=true }                    end +                else +                  f[tag]={ [script]={ [language]=true } } +                end +              else +                featurehash[lookup]={ [tag]={ [script]={ [language]=true } } } +              end +              local h=scriptlangs[tag] +              if h then +                local s=h[script] +                if s then +                  s[language]=true +                else +                  h[script]={ [language]=true }                  end +              else +                scriptlangs[tag]={ [script]={ [language]=true } }                end              end            end -        else -          report_otf("potential problem: glyph %U is used but empty",index)          end        end -    else -      report_otf("potential problem: no glyphs found")      end +    return scriptlangs,featurehash,featureorder    end -  resources.private=private -end -actions["check encoding"]=function(data,filename,raw) -  local descriptions=data.descriptions -  local resources=data.resources -  local properties=data.properties -  local unicodes=resources.unicodes  -  local indices=resources.indices  -  local duplicates=resources.duplicates -  local mapdata=raw.map or {} -  local unicodetoindex=mapdata and mapdata.map or {} -  local indextounicode=mapdata and mapdata.backmap or {} -  local encname=lower(data.enc_name or mapdata.enc_name or "") -  local criterium=0xFFFF  -  local privateoffset=constructors.privateoffset -  if find(encname,"unicode") then  -    if trace_loading then -      report_otf("checking embedded unicode map %a",encname) +  local function readscriplan(f,fontdata,scriptoffset) +    setposition(f,scriptoffset) +    local nofscripts=readushort(f) +    local scripts={} +    for i=1,nofscripts do +      scripts[readtag(f)]=scriptoffset+readushort(f)      end -    local reported={} -    for maybeunicode,index in next,unicodetoindex do -      if descriptions[maybeunicode] then -      else -        local unicode=indices[index] -        if not unicode then -        elseif maybeunicode==unicode then -        elseif unicode>privateoffset then -        else -          local d=descriptions[unicode] -          if d then -            local c=d.copies -            if c then -              c[maybeunicode]=true -            else -              d.copies={ [maybeunicode]=true } -            end -          elseif index and not reported[index] then -            report_otf("missing index %i",index) -            reported[index]=true -          end +    local languagesystems=setmetatableindex("table") +    for script,offset in next,scripts do +      setposition(f,offset) +      local defaultoffset=readushort(f) +      local noflanguages=readushort(f) +      local languages={} +      if defaultoffset>0 then +        languages.dflt=languagesystems[offset+defaultoffset] +      end +      for i=1,noflanguages do +        local language=readtag(f) +        local offset=offset+readushort(f) +        languages[language]=languagesystems[offset] +      end +      scripts[script]=languages +    end +    for offset,usedfeatures in next,languagesystems do +      if offset>0 then +        setposition(f,offset) +        local featureindices={} +        usedfeatures.featureindices=featureindices +        usedfeatures.lookuporder=readushort(f)  +        usedfeatures.requiredindex=readushort(f)  +        local noffeatures=readushort(f) +        for i=1,noffeatures do +          featureindices[i]=readushort(f)+1          end        end      end -    for unicode,data in next,descriptions do -      local d=data.copies -      if d then -        duplicates[unicode]=sortedkeys(d) -        data.copies=nil +    return scripts +  end +  local function readfeatures(f,fontdata,featureoffset) +    setposition(f,featureoffset) +    local features={} +    local noffeatures=readushort(f) +    for i=1,noffeatures do +      features[i]={ +        tag=readtag(f), +        offset=readushort(f) +      } +    end +    for i=1,noffeatures do +      local feature=features[i] +      local offset=featureoffset+feature.offset +      setposition(f,offset) +      local parameters=readushort(f)  +      local noflookups=readushort(f) +      if noflookups>0 then +        local lookups={} +        feature.lookups=lookups +        for j=1,noflookups do +          lookups[j]=readushort(f)+1 +        end +      end +      if parameters>0 then +        feature.parameters=parameters +        local plugin=plugins[feature.tag] +        if plugin then +          plugin(f,fontdata,offset,parameters) +        end        end      end -  elseif properties.cidinfo then -    report_otf("warning: no unicode map, used cidmap %a",properties.cidinfo.usedname) -  else -    report_otf("warning: non unicode map %a, only using glyph unicode data",encname or "whatever") +    return features    end -  if mapdata then -    mapdata.map={}  -    mapdata.backmap={}  +  local function readlookups(f,lookupoffset,lookuptypes,featurehash,featureorder) +    setposition(f,lookupoffset) +    local lookups={} +    local noflookups=readushort(f) +    for i=1,noflookups do +      lookups[i]=readushort(f) +    end +    for lookupid=1,noflookups do +      local index=lookups[lookupid] +      setposition(f,lookupoffset+index) +      local subtables={} +      local typebits=readushort(f) +      local flagbits=readushort(f) +      local lookuptype=lookuptypes[typebits] +      local lookupflags=lookupflags[flagbits] +      local nofsubtables=readushort(f) +      for j=1,nofsubtables do +        local offset=readushort(f) +        subtables[j]=offset+index  +      end +      local markclass=bittest(flagbits,0x0010)  +      if markclass then +        markclass=readushort(f)  +      end +      local markset=rshift(flagbits,8) +      if markset>0 then +        markclass=markset  +      end +      lookups[lookupid]={ +        type=lookuptype, +        flags=lookupflags, +        name=lookupid, +        subtables=subtables, +        markclass=markclass, +        features=featurehash[lookupid], +        order=featureorder[lookupid], +      } +    end +    return lookups    end -end -actions["add duplicates"]=function(data,filename,raw) -  local descriptions=data.descriptions -  local resources=data.resources -  local properties=data.properties -  local unicodes=resources.unicodes  -  local indices=resources.indices  -  local duplicates=resources.duplicates -  for unicode,d in next,duplicates do -    local nofduplicates=#d -    if nofduplicates>4 then -      if trace_loading then -        report_otf("ignoring excessive duplicates of %U (n=%s)",unicode,nofduplicates) -      end -    else -      for i=1,nofduplicates do -        local u=d[i] -        if not descriptions[u] then -          local description=descriptions[unicode] -          local n=0 -          for _,description in next,descriptions do -            local kerns=description.kerns -            if kerns then -              for _,k in next,kerns do -                local ku=k[unicode] -                if ku then -                  k[u]=ku -                  n=n+1 +  local function readscriptoffsets(f,fontdata,tableoffset) +    if not tableoffset then +      return +    end +    setposition(f,tableoffset) +    local version=readulong(f) +    if version~=0x00010000 then +      report("table version %a of %a is not supported (yet), maybe font %s is bad",version,what,fontdata.filename) +      return +    end +    return tableoffset+readushort(f),tableoffset+readushort(f),tableoffset+readushort(f) +  end +  local f_lookupname=formatters["%s_%s_%s"] +  local function resolvelookups(f,lookupoffset,fontdata,lookups,lookuptypes,lookuphandlers,what) +    local sequences=fontdata.sequences  or {} +    local sublookuplist=fontdata.sublookups or {} +    fontdata.sequences=sequences +    fontdata.sublookups=sublookuplist +    local nofsublookups=#sublookuplist +    local nofsequences=#sequences  +    local lastsublookup=nofsublookups +    local lastsequence=nofsequences +    local lookupnames=lookupnames[what] +    local sublookuphash={} +    local sublookupcheck={} +    local glyphs=fontdata.glyphs +    local nofglyphs=fontdata.nofglyphs or #glyphs +    local noflookups=#lookups +    local lookupprefix=sub(what,2,2) +    for lookupid=1,noflookups do +      local lookup=lookups[lookupid] +      local lookuptype=lookup.type +      local subtables=lookup.subtables +      local features=lookup.features +      local handler=lookuphandlers[lookuptype] +      if handler then +        local nofsubtables=#subtables +        local order=lookup.order +        local flags=lookup.flags +        if flags[1] then flags[1]="mark" end +        if flags[2] then flags[2]="ligature" end +        if flags[3] then flags[3]="base" end +        local markclass=lookup.markclass +        if nofsubtables>0 then +          local steps={} +          local nofsteps=0 +          local oldtype=nil +          for s=1,nofsubtables do +            local step,lt=handler(f,fontdata,lookupid,lookupoffset,subtables[s],glyphs,nofglyphs) +            if lt then +              lookuptype=lt +              if oldtype and lt~=oldtype then +                report("messy %s lookup type %a and %a",what,lookuptype,oldtype) +              end +              oldtype=lookuptype +            end +            if not step then +              report("unsupported %s lookup type %a",what,lookuptype) +            else +              nofsteps=nofsteps+1 +              steps[nofsteps]=step +              local rules=step.rules +              if rules then +                for i=1,#rules do +                  local rule=rules[i] +                  local before=rule.before +                  local current=rule.current +                  local after=rule.after +                  if before then +                    for i=1,#before do +                      before[i]=tohash(before[i]) +                    end +                    rule.before=reversed(before) +                  end +                  if current then +                    for i=1,#current do +                      current[i]=tohash(current[i]) +                    end +                  end +                  if after then +                    for i=1,#after do +                      after[i]=tohash(after[i]) +                    end +                  end                  end                end              end            end -          if u>0 then  -            local duplicate=table.copy(description)  -            duplicate.comment=formatters["copy of %U"](unicode) -            descriptions[u]=duplicate -            if trace_loading then -              report_otf("duplicating %U to %U with index %H (%s kerns)",unicode,u,description.index,n) +          if nofsteps~=nofsubtables then +            report("bogus subtables removed in %s lookup type %a",what,lookuptype) +          end +          lookuptype=lookupnames[lookuptype] or lookuptype +          if features then +            nofsequences=nofsequences+1 +            local l={ +              index=nofsequences, +              name=f_lookupname(lookupprefix,"s",lookupid+lookupidoffset), +              steps=steps, +              nofsteps=nofsteps, +              type=lookuptype, +              markclass=markclass or nil, +              flags=flags, +              order=order, +              features=features, +            } +            sequences[nofsequences]=l +            lookup.done=l +          else +            nofsublookups=nofsublookups+1 +            local l={ +              index=nofsublookups, +              name=f_lookupname(lookupprefix,"l",lookupid+lookupidoffset), +              steps=steps, +              nofsteps=nofsteps, +              type=lookuptype, +              markclass=markclass or nil, +              flags=flags, +            } +            sublookuplist[nofsublookups]=l +            sublookuphash[lookupid]=nofsublookups +            sublookupcheck[lookupid]=0 +            lookup.done=l +          end +        else +          report("no subtables for lookup %a",lookupid) +        end +      else +        report("no handler for lookup %a with type %a",lookupid,lookuptype) +      end +    end +    local reported={} +    for i=lastsequence+1,nofsequences do +      local sequence=sequences[i] +      local steps=sequence.steps +      for i=1,#steps do +        local step=steps[i] +        local rules=step.rules +        if rules then +          for i=1,#rules do +            local rule=rules[i] +            local rlookups=rule.lookups +            if not rlookups then +              local name=sequence.name +              if not reported[name] then +                report("rule %i in %s lookup %a has %s lookups",i,what,name,"no") +                reported[name]=true +              end +            elseif not next(rlookups) then +              local name=sequence.name +              if not reported[name] then +                report("rule %i in %s lookup %a has %s lookups",i,what,name,"empty") +                reported[name]=true +              end +              rule.lookups=nil +            else +              for index,lookupid in sortedhash(rlookups) do  +                local h=sublookuphash[lookupid] +                if not h then +                  nofsublookups=nofsublookups+1 +                  local d=lookups[lookupid].done +                  h={ +                    index=nofsublookups, +                    name=f_lookupname(lookupprefix,"d",lookupid+lookupidoffset), +                    derived=true, +                    steps=d.steps, +                    nofsteps=d.nofsteps, +                    type=d.lookuptype, +                    markclass=d.markclass or nil, +                    flags=d.flags, +                  } +                  sublookuplist[nofsublookups]=h +                  sublookuphash[lookupid]=nofsublookups +                  sublookupcheck[lookupid]=1 +                else +                  sublookupcheck[lookupid]=sublookupcheck[lookupid]+1 +                end +                rlookups[index]=h +              end              end            end          end        end      end +    for i,n in sortedhash(sublookupcheck) do +      local l=lookups[i] +      local t=l.type +      if n==0 and t~="extension" then +        local d=l.done +        report("%s lookup %s of type %a is not used",what,d and d.name or l.name,t) +      end +    end    end -end -actions["analyze glyphs"]=function(data,filename,raw)  -  local descriptions=data.descriptions -  local resources=data.resources -  local metadata=data.metadata -  local properties=data.properties -  local hasitalics=false -  local widths={} -  local marks={}  -  for unicode,description in next,descriptions do -    local glyph=description.glyph -    local italic=glyph.italic_correction  -    if not italic then -    elseif italic==0 then +  local function readscripts(f,fontdata,what,lookuptypes,lookuphandlers,lookupstoo) +    local datatable=fontdata.tables[what] +    if not datatable then +      return +    end +    local tableoffset=datatable.offset +    if not tableoffset then +      return +    end +    local scriptoffset,featureoffset,lookupoffset=readscriptoffsets(f,fontdata,tableoffset) +    if not scriptoffset then +      return +    end +    local scripts=readscriplan(f,fontdata,scriptoffset) +    local features=readfeatures(f,fontdata,featureoffset) +    local scriptlangs,featurehash,featureorder=reorderfeatures(fontdata,scripts,features) +    if fontdata.features then +      fontdata.features[what]=scriptlangs      else -      description.italic=italic -      hasitalics=true +      fontdata.features={ [what]=scriptlangs }      end -    local width=glyph.width -    widths[width]=(widths[width] or 0)+1 -    local class=glyph.class -    if class then -      if class=="mark" then -        marks[unicode]=true -      end -      description.class=class +    if not lookupstoo then +      return      end -  end -  properties.hasitalics=hasitalics -  resources.marks=marks -  local wd,most=0,1 -  for k,v in next,widths do -    if v>most then -      wd,most=k,v +    local lookups=readlookups(f,lookupoffset,lookuptypes,featurehash,featureorder) +    if lookups then +      resolvelookups(f,lookupoffset,fontdata,lookups,lookuptypes,lookuphandlers,what)      end    end -  if most>1000 then  -    if trace_loading then -      report_otf("most common width: %s (%s times), sharing (cjk font)",wd,most) +  local function checkkerns(f,fontdata,specification) +    local datatable=fontdata.tables.kern +    if not datatable then +      return  +    end +    local features=fontdata.features +    local gposfeatures=features and features.gpos +    local name +    if not gposfeatures or not gposfeatures.kern then +      name="kern" +    elseif specification.globalkerns then +      name="globalkern" +    else +      report("ignoring global kern table using gpos kern feature") +      return      end -    for unicode,description in next,descriptions do -      if description.width==wd then +    report("adding global kern table as gpos feature %a",name) +    setposition(f,datatable.offset) +    local version=readushort(f) +    local noftables=readushort(f) +    local kerns=setmetatableindex("table") +    for i=1,noftables do +      local version=readushort(f) +      local length=readushort(f) +      local coverage=readushort(f) +      local format=bit32.rshift(coverage,8)  +      if format==0 then +        local nofpairs=readushort(f) +        local searchrange=readushort(f) +        local entryselector=readushort(f) +        local rangeshift=readushort(f) +        for i=1,nofpairs do +          kerns[readushort(f)][readushort(f)]=readfword(f) +        end +      elseif format==2 then        else -        description.width=description.glyph.width        end      end -    resources.defaultwidth=wd -  else -    for unicode,description in next,descriptions do -      description.width=description.glyph.width +    local feature={ dflt={ dflt=true } } +    if not features then +      fontdata.features={ gpos={ [name]=feature } } +    elseif not gposfeatures then +      fontdata.features.gpos={ [name]=feature } +    else +      gposfeatures[name]=feature +    end +    local sequences=fontdata.sequences +    if not sequences then +      sequences={} +      fontdata.sequences=sequences      end +    local nofsequences=#sequences+1 +    sequences[nofsequences]={ +      index=nofsequences, +      name=name, +      steps={ +        { +          coverage=kerns, +          format="kern", +        }, +      }, +      nofsteps=1, +      type="gpos_pair", +      flags={ false,false,false,false }, +      order={ name }, +      features={ [name]=feature }, +    }    end -end -actions["reorganize mark classes"]=function(data,filename,raw) -  local mark_classes=raw.mark_classes -  if mark_classes then -    local resources=data.resources -    local unicodes=resources.unicodes -    local markclasses={} -    resources.markclasses=markclasses  -    for name,class in next,mark_classes do -      local t={} -      for s in gmatch(class,"[^ ]+") do -        t[unicodes[s]]=true -      end -      markclasses[name]=t -    end -  end -end -actions["reorganize features"]=function(data,filename,raw)  -  local features={} -  data.resources.features=features -  for k=1,#otf.glists do -    local what=otf.glists[k] -    local dw=raw[what] -    if dw then -      local f={} -      features[what]=f -      for i=1,#dw do -        local d=dw[i] -        local dfeatures=d.features -        if dfeatures then -          for i=1,#dfeatures do -            local df=dfeatures[i] -            local tag=strip(lower(df.tag)) -            local ft=f[tag] -            if not ft then -              ft={} -              f[tag]=ft -            end -            local dscripts=df.scripts -            for i=1,#dscripts do -              local d=dscripts[i] -              local languages=d.langs -              local script=strip(lower(d.script)) -              local fts=ft[script] if not fts then fts={} ft[script]=fts end -              for i=1,#languages do -                fts[strip(lower(languages[i]))]=true +  function readers.gsub(f,fontdata,specification) +    if specification.details then +      readscripts(f,fontdata,"gsub",gsubtypes,gsubhandlers,specification.lookups) +    end +  end +  function readers.gpos(f,fontdata,specification) +    if specification.details then +      readscripts(f,fontdata,"gpos",gpostypes,gposhandlers,specification.lookups) +      if specification.lookups then +        checkkerns(f,fontdata,specification) +      end +    end +  end +end +function readers.gdef(f,fontdata,specification) +  if specification.glyphs then +    local datatable=fontdata.tables.gdef +    if datatable then +      local tableoffset=datatable.offset +      setposition(f,tableoffset) +      local version=readulong(f) +      local classoffset=tableoffset+readushort(f) +      local attachmentoffset=tableoffset+readushort(f)  +      local ligaturecarets=tableoffset+readushort(f)  +      local markclassoffset=tableoffset+readushort(f) +      local marksetsoffset=version==0x00010002 and (tableoffset+readushort(f)) +      local glyphs=fontdata.glyphs +      local marks={} +      local markclasses=setmetatableindex("table") +      local marksets=setmetatableindex("table") +      fontdata.marks=marks +      fontdata.markclasses=markclasses +      fontdata.marksets=marksets +      setposition(f,classoffset) +      local classformat=readushort(f) +      if classformat==1 then +        local firstindex=readushort(f) +        local lastindex=firstindex+readushort(f)-1 +        for index=firstindex,lastindex do +          local class=classes[readushort(f)] +          if class=="mark" then +            marks[index]=true +          end +          glyphs[index].class=class +        end +      elseif classformat==2 then +        local nofranges=readushort(f) +        for i=1,nofranges do +          local firstindex=readushort(f) +          local lastindex=readushort(f) +          local class=classes[readushort(f)] +          if class then +            for index=firstindex,lastindex do +              glyphs[index].class=class +              if class=="mark" then +                marks[index]=true                end              end            end          end        end +      setposition(f,markclassoffset) +      local classformat=readushort(f) +      if classformat==1 then +        local firstindex=readushort(f) +        local lastindex=firstindex+readushort(f)-1 +        for index=firstindex,lastindex do +          markclasses[readushort(f)][index]=true +        end +      elseif classformat==2 then +        local nofranges=readushort(f) +        for i=1,nofranges do +          local firstindex=readushort(f) +          local lastindex=readushort(f) +          local class=markclasses[readushort(f)] +          for index=firstindex,lastindex do +            class[index]=true +          end +        end +      end +      if marksetsoffset then +        setposition(f,marksetsoffset) +        local format=readushort(f) +        if format==1 then +          local nofsets=readushort(f) +          local sets={} +          for i=1,nofsets do +            sets[i]=readulong(f) +          end +          for i=1,nofsets do +            local offset=sets[i] +            if offset~=0 then +              marksets[i]=readcoverage(f,marksetsoffset+offset) +            end +          end +        end +      end      end    end  end -actions["reorganize anchor classes"]=function(data,filename,raw) -  local resources=data.resources -  local anchor_to_lookup={} -  local lookup_to_anchor={} -  resources.anchor_to_lookup=anchor_to_lookup -  resources.lookup_to_anchor=lookup_to_anchor -  local classes=raw.anchor_classes  -  if classes then -    for c=1,#classes do -      local class=classes[c] -      local anchor=class.name -      local lookups=class.lookup -      if type(lookups)~="table" then -        lookups={ lookups } -      end -      local a=anchor_to_lookup[anchor] -      if not a then -        a={} -        anchor_to_lookup[anchor]=a -      end -      for l=1,#lookups do -        local lookup=lookups[l] -        local l=lookup_to_anchor[lookup] -        if l then -          l[anchor]=true +local function readmathvalue(f) +  local v=readshort(f) +  skipshort(f,1)  +  return v +end +local function readmathconstants(f,fontdata,offset) +  setposition(f,offset) +  fontdata.mathconstants={ +    ScriptPercentScaleDown=readshort(f), +    ScriptScriptPercentScaleDown=readshort(f), +    DelimitedSubFormulaMinHeight=readushort(f), +    DisplayOperatorMinHeight=readushort(f), +    MathLeading=readmathvalue(f), +    AxisHeight=readmathvalue(f), +    AccentBaseHeight=readmathvalue(f), +    FlattenedAccentBaseHeight=readmathvalue(f), +    SubscriptShiftDown=readmathvalue(f), +    SubscriptTopMax=readmathvalue(f), +    SubscriptBaselineDropMin=readmathvalue(f), +    SuperscriptShiftUp=readmathvalue(f), +    SuperscriptShiftUpCramped=readmathvalue(f), +    SuperscriptBottomMin=readmathvalue(f), +    SuperscriptBaselineDropMax=readmathvalue(f), +    SubSuperscriptGapMin=readmathvalue(f), +    SuperscriptBottomMaxWithSubscript=readmathvalue(f), +    SpaceAfterScript=readmathvalue(f), +    UpperLimitGapMin=readmathvalue(f), +    UpperLimitBaselineRiseMin=readmathvalue(f), +    LowerLimitGapMin=readmathvalue(f), +    LowerLimitBaselineDropMin=readmathvalue(f), +    StackTopShiftUp=readmathvalue(f), +    StackTopDisplayStyleShiftUp=readmathvalue(f), +    StackBottomShiftDown=readmathvalue(f), +    StackBottomDisplayStyleShiftDown=readmathvalue(f), +    StackGapMin=readmathvalue(f), +    StackDisplayStyleGapMin=readmathvalue(f), +    StretchStackTopShiftUp=readmathvalue(f), +    StretchStackBottomShiftDown=readmathvalue(f), +    StretchStackGapAboveMin=readmathvalue(f), +    StretchStackGapBelowMin=readmathvalue(f), +    FractionNumeratorShiftUp=readmathvalue(f), +    FractionNumeratorDisplayStyleShiftUp=readmathvalue(f), +    FractionDenominatorShiftDown=readmathvalue(f), +    FractionDenominatorDisplayStyleShiftDown=readmathvalue(f), +    FractionNumeratorGapMin=readmathvalue(f), +    FractionNumeratorDisplayStyleGapMin=readmathvalue(f), +    FractionRuleThickness=readmathvalue(f), +    FractionDenominatorGapMin=readmathvalue(f), +    FractionDenominatorDisplayStyleGapMin=readmathvalue(f), +    SkewedFractionHorizontalGap=readmathvalue(f), +    SkewedFractionVerticalGap=readmathvalue(f), +    OverbarVerticalGap=readmathvalue(f), +    OverbarRuleThickness=readmathvalue(f), +    OverbarExtraAscender=readmathvalue(f), +    UnderbarVerticalGap=readmathvalue(f), +    UnderbarRuleThickness=readmathvalue(f), +    UnderbarExtraDescender=readmathvalue(f), +    RadicalVerticalGap=readmathvalue(f), +    RadicalDisplayStyleVerticalGap=readmathvalue(f), +    RadicalRuleThickness=readmathvalue(f), +    RadicalExtraAscender=readmathvalue(f), +    RadicalKernBeforeDegree=readmathvalue(f), +    RadicalKernAfterDegree=readmathvalue(f), +    RadicalDegreeBottomRaisePercent=readshort(f), +  } +end +local function readmathglyphinfo(f,fontdata,offset) +  setposition(f,offset) +  local italics=readushort(f) +  local accents=readushort(f) +  local extensions=readushort(f) +  local kerns=readushort(f) +  local glyphs=fontdata.glyphs +  if italics~=0 then +    setposition(f,offset+italics) +    local coverage=readushort(f) +    local nofglyphs=readushort(f) +    coverage=readcoverage(f,offset+italics+coverage,true) +    setposition(f,offset+italics+4) +    for i=1,nofglyphs do +      local italic=readmathvalue(f) +      if italic~=0 then +        local glyph=glyphs[coverage[i]] +        local math=glyph.math +        if not math then +          glyph.math={ italic=italic }          else -          l={ [anchor]=true } -          lookup_to_anchor[lookup]=l +          math.italic=italic          end -        a[lookup]=true        end      end +    fontdata.hasitalics=true    end -end -actions["prepare tounicode"]=function(data,filename,raw) -  fonts.mappings.addtounicode(data,filename) -end -local g_directions={ -  gsub_contextchain=1, -  gpos_contextchain=1, -  gsub_reversecontextchain=-1, -  gpos_reversecontextchain=-1, -} -actions["reorganize subtables"]=function(data,filename,raw) -  local resources=data.resources -  local sequences={} -  local lookups={} -  local chainedfeatures={} -  resources.sequences=sequences -  resources.lookups=lookups  -  for k=1,#otf.glists do -    local what=otf.glists[k] -    local dw=raw[what] -    if dw then -      for k=1,#dw do -        local gk=dw[k] -        local features=gk.features -          local typ=gk.type -          local chain=g_directions[typ] or 0 -          local subtables=gk.subtables -          if subtables then -            local t={} -            for s=1,#subtables do -              t[s]=subtables[s].name -            end -            subtables=t +  if accents~=0 then +    setposition(f,offset+accents) +    local coverage=readushort(f) +    local nofglyphs=readushort(f) +    coverage=readcoverage(f,offset+accents+coverage,true) +    setposition(f,offset+accents+4) +    for i=1,nofglyphs do +      local accent=readmathvalue(f) +      if accent~=0 then +        local glyph=glyphs[coverage[i]] +        local math=glyph.math +        if not math then +          glyph.math={ accent=accent } +        else +          math.accent=accent +        end +      end +    end +  end +  if extensions~=0 then +    setposition(f,offset+extensions) +  end +  if kerns~=0 then +    local kernoffset=offset+kerns +    setposition(f,kernoffset) +    local coverage=readushort(f) +    local nofglyphs=readushort(f) +    if nofglyphs>0 then +      local function get(offset) +        setposition(f,kernoffset+offset) +        local n=readushort(f) +        if n>0 then +          local l={} +          for i=1,n do +            l[i]={ height=readmathvalue(f) }            end -          local flags,markclass=gk.flags,nil -          if flags then -            local t={  -              (flags.ignorecombiningmarks and "mark")   or false, -              (flags.ignoreligatures   and "ligature") or false, -              (flags.ignorebaseglyphs   and "base")   or false, -               flags.r2l                 or false, -            } -            markclass=flags.mark_class -            if markclass then -              markclass=resources.markclasses[markclass] +          for i=1,n do +            l[i].kern=readmathvalue(f) +          end +          l[n+1]={ kern=readmathvalue(f) } +          return l +        end +      end +      local kernsets={} +      for i=1,nofglyphs do +        local topright=readushort(f) +        local topleft=readushort(f) +        local bottomright=readushort(f) +        local bottomleft=readushort(f) +        kernsets[i]={ +          topright=topright~=0 and topright  or nil, +          topleft=topleft~=0 and topleft   or nil, +          bottomright=bottomright~=0 and bottomright or nil, +          bottomleft=bottomleft~=0 and bottomleft or nil, +        } +      end +      coverage=readcoverage(f,kernoffset+coverage,true) +      for i=1,nofglyphs do +        local kernset=kernsets[i] +        if next(kernset) then +          local k=kernset.topright  if k then kernset.topright=get(k) end +          local k=kernset.topleft   if k then kernset.topleft=get(k) end +          local k=kernset.bottomright if k then kernset.bottomright=get(k) end +          local k=kernset.bottomleft if k then kernset.bottomleft=get(k) end +          if next(kernset) then +            local glyph=glyphs[coverage[i]] +            local math=glyph.math +            if not math then +              glyph.math={ kerns=kernset } +            else +              math.kerns=kernset              end -            flags=t -          end -          local name=gk.name -          if not name then -            report_otf("skipping weird lookup number %s",k) -          elseif features then -            local f={} -            local o={} -            for i=1,#features do -              local df=features[i] -              local tag=strip(lower(df.tag)) -              local ft=f[tag] -              if not ft then -                ft={} -                f[tag]=ft -                o[#o+1]=tag +          end +        end +      end +    end +  end +end +local function readmathvariants(f,fontdata,offset) +  setposition(f,offset) +  local glyphs=fontdata.glyphs +  local minoverlap=readushort(f) +  local vcoverage=readushort(f) +  local hcoverage=readushort(f) +  local vnofglyphs=readushort(f) +  local hnofglyphs=readushort(f) +  local vconstruction={} +  local hconstruction={} +  for i=1,vnofglyphs do +    vconstruction[i]=readushort(f) +  end +  for i=1,hnofglyphs do +    hconstruction[i]=readushort(f) +  end +  fontdata.mathconstants.MinConnectorOverlap=minoverlap +  local function get(offset,coverage,nofglyphs,construction,kvariants,kparts,kitalic) +    if coverage~=0 and nofglyphs>0 then +      local coverage=readcoverage(f,offset+coverage,true) +      for i=1,nofglyphs do +        local c=construction[i] +        if c~=0 then +          local index=coverage[i] +          local glyph=glyphs[index] +          local math=glyph.math +          setposition(f,offset+c) +          local assembly=readushort(f) +          local nofvariants=readushort(f) +          if nofvariants>0 then +            local variants,v=nil,0 +            for i=1,nofvariants do +              local variant=readushort(f) +              if variant==index then +              elseif variants then +                v=v+1 +                variants[v]=variant +              else +                v=1 +                variants={ variant }                end -              local dscripts=df.scripts -              for i=1,#dscripts do -                local d=dscripts[i] -                local languages=d.langs -                local script=strip(lower(d.script)) -                local fts=ft[script] if not fts then fts={} ft[script]=fts end -                for i=1,#languages do -                  fts[strip(lower(languages[i]))]=true -                end +              skipshort(f) +            end +            if not variants then +            elseif not math then +              math={ [kvariants]=variants } +              glyph.math=math +            else +              math[kvariants]=variants +            end +          end +          if assembly~=0 then +            setposition(f,offset+c+assembly) +            local italic=readmathvalue(f) +            local nofparts=readushort(f) +            local parts={} +            for i=1,nofparts do +              local p={ +                glyph=readushort(f), +                start=readushort(f), +                ["end"]=readushort(f), +                advance=readushort(f), +              } +              local flags=readushort(f) +              if bittest(flags,0x0001) then +                p.extender=1                 end +              parts[i]=p +            end +            if not math then +              math={ +                [kparts]=parts +              } +              glyph.math=math +            else +              math[kparts]=parts +            end +            if italic and italic~=0 then +              math[kitalic]=italic              end -            sequences[#sequences+1]={ -              type=typ, -              chain=chain, -              flags=flags, -              name=name, -              subtables=subtables, -              markclass=markclass, -              features=f, -              order=o, -            } -          else -            lookups[name]={ -              type=typ, -              chain=chain, -              flags=flags, -              subtables=subtables, -              markclass=markclass, -            }            end +        end        end      end    end +  get(offset,vcoverage,vnofglyphs,vconstruction,"vvariants","vparts","vitalic") +  get(offset,hcoverage,hnofglyphs,hconstruction,"hvariants","hparts","hitalic")  end -actions["prepare lookups"]=function(data,filename,raw) -  local lookups=raw.lookups -  if lookups then -    data.lookups=lookups +function readers.math(f,fontdata,specification) +  if specification.glyphs then +    local datatable=fontdata.tables.math +    if datatable then +      local tableoffset=datatable.offset +      setposition(f,tableoffset) +      local version=readulong(f) +      if version~=0x00010000 then +        report("table version %a of %a is not supported (yet), maybe font %s is bad",version,what,fontdata.filename) +        return +      end +      local constants=readushort(f) +      local glyphinfo=readushort(f) +      local variants=readushort(f) +      if constants==0 then +        report("the math table of %a has no constants",fontdata.filename) +      else +        readmathconstants(f,fontdata,tableoffset+constants) +      end +      if glyphinfo~=0 then +        readmathglyphinfo(f,fontdata,tableoffset+glyphinfo) +      end +      if variants~=0 then +        readmathvariants(f,fontdata,tableoffset+variants) +      end +    end    end  end -local function t_uncover(splitter,cache,covers) -  local result={} -  for n=1,#covers do -    local cover=covers[n] -    local uncovered=cache[cover] -    if not uncovered then -      uncovered=lpegmatch(splitter,cover) -      cache[cover]=uncovered + +end -- closure + +do -- begin closure to overcome local limits and interference + +if not modules then modules={} end modules ['font-oup']={ +  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 P,R,S=lpeg.P,lpeg.R,lpeg.S +local lpegmatch=lpeg.match +local insert,remove,copy=table.insert,table.remove,table.copy +local formatters=string.formatters +local sortedkeys=table.sortedkeys +local sortedhash=table.sortedhash +local tohash=table.tohash +local report=logs.reporter("otf reader") +local trace_markwidth=false trackers.register("otf.markwidth",function(v) trace_markwidth=v end) +local readers=fonts.handlers.otf.readers +local privateoffset=fonts.constructors and fonts.constructors.privateoffset or 0xF0000  +local f_private=formatters["P%05X"] +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 function replaced(list,index,replacement) +  if type(list)=="number" then +    return replacement +  elseif type(replacement)=="table" then +    local t={} +    local n=index-1 +    for i=1,n do +      t[i]=list[i] +    end +    for i=1,#replacement do +      n=n+1 +      t[n]=replacement[i]      end -    result[n]=uncovered +    for i=index+1,#list do +      n=n+1 +      t[n]=list[i] +    end +  else +    list[index]=replacement +    return list    end -  return result  end -local function s_uncover(splitter,cache,cover) -  if cover=="" then -    return nil -  else -    local uncovered=cache[cover] -    if not uncovered then -      uncovered=lpegmatch(splitter,cover) -      cache[cover]=uncovered +local function unifyresources(fontdata,indices) +  local descriptions=fontdata.descriptions +  local resources=fontdata.resources +  if not descriptions or not resources then +    return +  end +  local variants=fontdata.resources.variants +  if variants then +    for selector,unicodes in next,variants do +      for unicode,index in next,unicodes do +        unicodes[unicode]=indices[index] +      end      end -    return { uncovered }    end -end -local function t_hashed(t,cache) -  if t then -    local ht={} -    for i=1,#t do -      local ti=t[i] -      local tih=cache[ti] -      if not tih then -        local tn=#ti -        if tn==1 then -          tih={ [ti[1]]=true } +  local function remark(marks) +    if marks then +      local newmarks={} +      for k,v in next,marks do +        local u=indices[k] +        if u then +          newmarks[u]=v          else -          tih={} -          for i=1,tn do -            tih[ti[i]]=true -          end +          report("discarding mark %i",k)          end -        cache[ti]=tih        end -      ht[i]=tih +      return newmarks      end -    return ht -  else -    return nil    end -end -local function s_hashed(t,cache) -  if t then -    local tf=t[1] -    local nf=#tf -    if nf==1 then -      return { [tf[1]]=true } -    else -      local ht={} -      for i=1,nf do -        ht[i]={ [tf[i]]=true } +  local marks=resources.marks +  if marks then +    resources.marks=remark(marks) +  end +  local markclasses=resources.markclasses +  if markclasses then +    for class,marks in next,markclasses do +      markclasses[class]=remark(marks) +    end +  end +  local marksets=resources.marksets +  if marksets then +    for class,marks in next,marksets do +      marksets[class]=remark(marks) +    end +  end +  local done={} +  local duplicates=doduplicates and resources.duplicates +  if duplicates and not next(duplicates) then +    duplicates=false +  end +  local function recover(cover)  +    for i=1,#cover do +      local c=cover[i] +      if not done[c] then +        local t={} +        for k,v in next,c do +          t[indices[k]]=v +        end +        cover[i]=t +        done[c]=d        end -      return ht      end -  else -    return nil    end -end -local function r_uncover(splitter,cache,cover,replacements) -  if cover=="" then -    return nil -  else -    local uncovered=cover[1] -    local replaced=cache[replacements] -    if not replaced then -      replaced=lpegmatch(splitter,replacements) -      cache[replacements]=replaced -    end -    local nu,nr=#uncovered,#replaced -    local r={} -    if nu==nr then -      for i=1,nu do -        r[uncovered[i]]=replaced[i] -      end -    end -    return r -  end -end -actions["reorganize lookups"]=function(data,filename,raw) -  if data.lookups then -    local helpers=data.helpers -    local duplicates=data.resources.duplicates -    local splitter=helpers.tounicodetable -    local t_u_cache={} -    local s_u_cache=t_u_cache  -    local t_h_cache={} -    local s_h_cache=t_h_cache  -    local r_u_cache={}  -    helpers.matchcache=t_h_cache -    for _,lookup in next,data.lookups do -      local rules=lookup.rules -      if rules then -        local format=lookup.format -        if format=="class" then -          local before_class=lookup.before_class -          if before_class then -            before_class=t_uncover(splitter,t_u_cache,reversed(before_class)) -          end -          local current_class=lookup.current_class -          if current_class then -            current_class=t_uncover(splitter,t_u_cache,current_class) -          end -          local after_class=lookup.after_class -          if after_class then -            after_class=t_uncover(splitter,t_u_cache,after_class) -          end -          for i=1,#rules do -            local rule=rules[i] -            local class=rule.class -            local before=class.before -            if before then -              for i=1,#before do -                before[i]=before_class[before[i]] or {} +  local function recursed(c)  +    local t={} +    for g,d in next,c do +      if type(d)=="table" then +        t[indices[g]]=recursed(d) +      else +        t[g]=indices[d]  +      end +    end +    return t +  end +  local function unifythem(sequences) +    if not sequences then +      return +    end +    for i=1,#sequences do +      local sequence=sequences[i] +      local kind=sequence.type +      local steps=sequence.steps +      local features=sequence.features +      if steps then +        for i=1,#steps do +          local step=steps[i] +          if kind=="gsub_single" then +            local c=step.coverage +            if c then +              local t1=done[c] +              if not t1 then +                t1={} +                if duplicates then +                  for g1,d1 in next,c do +                    local ug1=indices[g1] +                    local ud1=indices[d1] +                    t1[ug1]=ud1 +                    local dg1=duplicates[ug1] +                    if dg1 then +                      for u in next,dg1 do +                        t1[u]=ud1 +                      end +                    end +                  end +                else +                  for g1,d1 in next,c do +                    t1[indices[g1]]=indices[d1] +                  end +                end +                done[c]=t1                end -              rule.before=t_hashed(before,t_h_cache) +              step.coverage=t1              end -            local current=class.current -            local lookups=rule.lookups -            if current then -              for i=1,#current do -                current[i]=current_class[current[i]] or {} -                if lookups and not lookups[i] then -                  lookups[i]=""  +          elseif kind=="gpos_pair" then +            local c=step.coverage +            if c then +              local t1=done[c] +              if not t1 then +                t1={} +                for g1,d1 in next,c do +                  local t2=done[d1] +                  if not t2 then +                    t2={} +                    for g2,d2 in next,d1 do +                      t2[indices[g2]]=d2 +                    end +                    done[d1]=t2 +                  end +                  t1[indices[g1]]=t2                  end +                done[c]=t1                end -              rule.current=t_hashed(current,t_h_cache) +              step.coverage=t1              end -            local after=class.after -            if after then -              for i=1,#after do -                after[i]=after_class[after[i]] or {} -              end -              rule.after=t_hashed(after,t_h_cache) +          elseif kind=="gsub_ligature" then +            local c=step.coverage +            if c then +              step.coverage=recursed(c)              end -            rule.class=nil -          end -          lookup.before_class=nil -          lookup.current_class=nil -          lookup.after_class=nil -          lookup.format="coverage" -        elseif format=="coverage" then -          for i=1,#rules do -            local rule=rules[i] -            local coverage=rule.coverage -            if coverage then -              local before=coverage.before -              if before then -                before=t_uncover(splitter,t_u_cache,reversed(before)) -                rule.before=t_hashed(before,t_h_cache) -              end -              local current=coverage.current -              if current then -                current=t_uncover(splitter,t_u_cache,current) -                local lookups=rule.lookups -                if lookups then -                  for i=1,#current do -                    if not lookups[i] then -                      lookups[i]=""  +          elseif kind=="gsub_alternate" or kind=="gsub_multiple" then +            local c=step.coverage +            if c then +              local t1=done[c] +              if not t1 then +                t1={} +                if duplicates then +                  for g1,d1 in next,c do +                    for i=1,#d1 do +                      d1[i]=indices[d1[i]] +                    end +                    local ug1=indices[g1] +                    t1[ug1]=d1 +                    local dg1=duplicates[ug1] +                    if dg1 then +                      for u in next,dg1 do +                        t1[u]=copy(d1) +                      end                      end                    end +                else +                  for g1,d1 in next,c do +                    for i=1,#d1 do +                      d1[i]=indices[d1[i]] +                    end +                    t1[indices[g1]]=d1 +                  end                  end -                rule.current=t_hashed(current,t_h_cache) +                done[c]=t1                end -              local after=coverage.after -              if after then -                after=t_uncover(splitter,t_u_cache,after) -                rule.after=t_hashed(after,t_h_cache) -              end -              rule.coverage=nil +              step.coverage=t1              end -          end -        elseif format=="reversecoverage" then  -          for i=1,#rules do -            local rule=rules[i] -            local reversecoverage=rule.reversecoverage -            if reversecoverage then -              local before=reversecoverage.before -              if before then -                before=t_uncover(splitter,t_u_cache,reversed(before)) -                rule.before=t_hashed(before,t_h_cache) +          elseif kind=="gpos_mark2base" or kind=="gpos_mark2mark" or kind=="gpos_mark2ligature" then +            local c=step.coverage +            if c then +              local t1=done[c] +              if not t1 then +                t1={} +                for g1,d1 in next,c do +                  t1[indices[g1]]=d1 +                end +                done[c]=t1                end -              local current=reversecoverage.current -              if current then -                current=t_uncover(splitter,t_u_cache,current) -                rule.current=t_hashed(current,t_h_cache) +              step.coverage=t1 +            end +            local c=step.baseclasses +            if c then +              local t1=done[c] +              if not t1 then +                for g1,d1 in next,c do +                  local t2=done[d1] +                  if not t2 then +                    t2={} +                    for g2,d2 in next,d1 do +                      t2[indices[g2]]=d2 +                    end +                    done[d1]=t2 +                  end +                  c[g1]=t2 +                end +                done[c]=c                end -              local after=reversecoverage.after -              if after then -                after=t_uncover(splitter,t_u_cache,after) -                rule.after=t_hashed(after,t_h_cache) +            end +          elseif kind=="gpos_single" then +            local c=step.coverage +            if c then +              local t1=done[c] +              if not t1 then +                t1={} +                if duplicates then +                  for g1,d1 in next,c do +                    local ug1=indices[g1] +                    t1[ug1]=d1 +                    local dg1=duplicates[ug1] +                    if dg1 then +                      for u in next,dg1 do +                        t1[u]=d1 +                      end +                    end +                  end +                else +                  for g1,d1 in next,c do +                    t1[indices[g1]]=d1 +                  end +                end +                done[c]=t1                end -              local replacements=reversecoverage.replacements -              if replacements then -                rule.replacements=r_uncover(splitter,r_u_cache,current,replacements) +              step.coverage=t1 +            end +          elseif kind=="gpos_cursive" then +            local c=step.coverage +            if c then +              local t1=done[c] +              if not t1 then +                t1={} +                if duplicates then +                  for g1,d1 in next,c do +                    local ug1=indices[g1] +                    t1[ug1]=d1 +                    local dg1=duplicates[ug1] +                    if dg1 then +                      for u in next,dg1 do +                        t1[u]=copy(d1) +                      end +                    end +                  end +                else +                  for g1,d1 in next,c do +                    t1[indices[g1]]=d1 +                  end +                end +                done[c]=t1                end -              rule.reversecoverage=nil +              step.coverage=t1              end            end -        elseif format=="glyphs" then -          for i=1,#rules do -            local rule=rules[i] -            local glyphs=rule.glyphs -            if glyphs then -              local fore=glyphs.fore -              if fore and fore~="" then -                fore=s_uncover(splitter,s_u_cache,fore) -                rule.after=s_hashed(fore,s_h_cache) -              end -              local back=glyphs.back -              if back then -                back=s_uncover(splitter,s_u_cache,back) -                rule.before=s_hashed(back,s_h_cache) -              end -              local names=glyphs.names -              if names then -                names=s_uncover(splitter,s_u_cache,names) -                rule.current=s_hashed(names,s_h_cache) -              end -              rule.glyphs=nil -              local lookups=rule.lookups -              if lookups then -                for i=1,#names do -                  if not lookups[i] then -                    lookups[i]=""  +          local rules=step.rules +          if rules then +            for i=1,#rules do +              local rule=rules[i] +              local before=rule.before  if before then recover(before) end +              local after=rule.after  if after  then recover(after)  end +              local current=rule.current if current then recover(current) end +              local replacements=rule.replacements +              if replacements then +                if not done[replacements] then +                  local r={} +                  for k,v in next,replacements do +                    r[indices[k]]=indices[v]                    end +                  rule.replacements=r +                  done[replacements]=r                  end                end              end @@ -8572,644 +13704,1788 @@ actions["reorganize lookups"]=function(data,filename,raw)        end      end    end +  unifythem(resources.sequences) +  unifythem(resources.sublookups)  end -actions["expand lookups"]=function(data,filename,raw)  -  if data.lookups then -    local cache=data.helpers.matchcache -    if cache then -      local duplicates=data.resources.duplicates -      for key,hash in next,cache do -        local done=nil -        for key in next,hash do -          local unicode=duplicates[key] -          if not unicode then -          elseif type(unicode)=="table" then -            for i=1,#unicode do -              local u=unicode[i] -              if hash[u] then -              elseif done then -                done[u]=key -              else -                done={ [u]=key } -              end -            end -          else -            if hash[unicode] then -            elseif done then -              done[unicode]=key +local function copyduplicates(fontdata) +  if doduplicates then +    local descriptions=fontdata.descriptions +    local resources=fontdata.resources +    local duplicates=resources.duplicates +    if duplicates then +      for u,d in next,duplicates do +        local du=descriptions[u] +        if du then +          local t={ f_character_y(u),"@",f_index(du.index),"->" } +          for u in next,d do +            if descriptions[u] then +              t[#t+1]=f_character_n(u)              else -              done={ [unicode]=key } +              local c=copy(du) +              descriptions[u]=c +              t[#t+1]=f_character_y(u)              end            end +          report("duplicates: % t",t) +        else          end -        if done then -          for u in next,done do -            hash[u]=true +      end +    end +  end +end +local ignore={  +  ["notdef"]=true, +  [".notdef"]=true, +  ["null"]=true, +  [".null"]=true, +  ["nonmarkingreturn"]=true, +} +local function checklookups(fontdata,missing,nofmissing) +  local descriptions=fontdata.descriptions +  local resources=fontdata.resources +  if missing and nofmissing and nofmissing<=0 then +    return +  end +  local singles={} +  local alternates={} +  local ligatures={} +  if not missing then +    missing={} +    nofmissing=0 +    for u,d in next,descriptions do +      if not d.unicode then +        nofmissing=nofmissing+1 +        missing[u]=true +      end +    end +  end +  local function collectthem(sequences) +    if not sequences then +      return +    end +    for i=1,#sequences do +      local sequence=sequences[i] +      local kind=sequence.type +      local steps=sequence.steps +      if steps then +        for i=1,#steps do +          local step=steps[i] +          if kind=="gsub_single" then +            local c=step.coverage +            if c then +              singles[#singles+1]=c +            end +          elseif kind=="gsub_alternate" then +            local c=step.coverage +            if c then +              alternates[#alternates+1]=c +            end +          elseif kind=="gsub_ligature" then +            local c=step.coverage +            if c then +              ligatures[#ligatures+1]=c +            end            end          end        end      end    end -end -local function check_variants(unicode,the_variants,splitter,unicodes) -  local variants=the_variants.variants -  if variants then  -    local glyphs=lpegmatch(splitter,variants) -    local done={ [unicode]=true } -    local n=0 -    for i=1,#glyphs do -      local g=glyphs[i] -      if done[g] then -        if i>1 then -          report_otf("skipping cyclic reference %U in math variant %U",g,unicode) +  collectthem(resources.sequences) +  collectthem(resources.sublookups) +  local loops=0 +  while true do +    loops=loops+1 +    local old=nofmissing +    for i=1,#singles do +      local c=singles[i] +      for g1,g2 in next,c do +        if missing[g1] then +          local u2=descriptions[g2].unicode +          if u2 then +            missing[g1]=false +            descriptions[g1].unicode=u2 +            nofmissing=nofmissing-1 +          end          end -      else -        if n==0 then -          n=1 -          variants={ g } -        else +        if missing[g2] then +          local u1=descriptions[g1].unicode +          if u1 then +            missing[g2]=false +            descriptions[g2].unicode=u1 +            nofmissing=nofmissing-1 +          end +        end +      end +    end +    for i=1,#alternates do +      local c=alternates[i] +      for g1,d1 in next,c do +        if missing[g1] then +          for i=1,#d1 do +            local g2=d1[i] +            local u2=descriptions[g2].unicode +            if u2 then +              missing[g1]=false +              descriptions[g1].unicode=u2 +              nofmissing=nofmissing-1 +            end +          end +        end +        if not missing[g1] then +          for i=1,#d1 do +            local g2=d1[i] +            if missing[g2] then +              local u1=descriptions[g1].unicode +              if u1 then +                missing[g2]=false +                descriptions[g2].unicode=u1 +                nofmissing=nofmissing-1 +              end +            end +          end +        end +      end +    end +    if nofmissing<=0 then +      report("all done in %s loops",loops) +      return +    elseif old==nofmissing then +      break +    end +  end +  local t,n  +  local function recursed(c) +    for g,d in next,c do +      if g~="ligature" then +        local u=descriptions[g].unicode +        if u then            n=n+1 -          variants[n]=g +          t[n]=u +          recursed(d) +          n=n-1          end -        done[g]=true +      elseif missing[d] then +        local l={} +        local m=0 +        for i=1,n do +          local u=t[i] +          if type(u)=="table" then +            for i=1,#u do +              m=m+1 +              l[m]=u[i] +            end +          else +            m=m+1 +            l[m]=u +          end +        end +        missing[d]=false +        descriptions[d].unicode=l +        nofmissing=nofmissing-1        end      end -    if n==0 then -      variants=nil +  end +  if nofmissing>0 then +    t={} +    n=0 +    local loops=0 +    while true do +      loops=loops+1 +      local old=nofmissing +      for i=1,#ligatures do +        recursed(ligatures[i]) +      end +      if nofmissing<=0 then +        report("all done in %s loops",loops) +        return +      elseif old==nofmissing then +        break +      end      end +    t=nil +    n=0    end -  local parts=the_variants.parts -  if parts then -    local p=#parts -    if p>0 then -      for i=1,p do -        local pi=parts[i] -        pi.glyph=unicodes[pi.component] or 0 -        pi.component=nil +  if nofmissing>0 then +    local done={} +    for i,r in next,missing do +      if r then +        local name=descriptions[i].name or f_index(i) +        if not ignore[name] then +          done[#done+1]=name +        end        end +    end +    if #done>0 then +      table.sort(done) +      report("not unicoded: % t",done) +    end +  end +end +local function unifymissing(fontdata) +  if not fonts.mappings then +    require("font-map") +    require("font-agl") +  end +  local unicodes={} +  local private=fontdata.private +  local resources=fontdata.resources +  resources.unicodes=unicodes +  for unicode,d in next,fontdata.descriptions do +    if unicode<privateoffset then +      local name=d.name +      if name then +        unicodes[name]=unicode +      end +    end +  end +  fonts.mappings.addtounicode(fontdata,fontdata.filename,checklookups) +  resources.unicodes=nil +end +local function unifyglyphs(fontdata,usenames) +  local private=fontdata.private or privateoffset +  local glyphs=fontdata.glyphs +  local indices={} +  local descriptions={} +  local names=usenames and {} +  local resources=fontdata.resources +  local zero=glyphs[0] +  local zerocode=zero.unicode +  if not zerocode then +    zerocode=private +    zero.unicode=zerocode +    private=private+1 +  end +  descriptions[zerocode]=zero +  if names then +    local name=glyphs[0].name or f_private(zerocode) +    indices[0]=name +    names[name]=zerocode +  else +    indices[0]=zerocode +  end +  for index=1,#glyphs do +    local glyph=glyphs[index] +    local unicode=glyph.unicode  +    if not unicode then +      unicode=private +      if names then +        local name=glyph.name or f_private(unicode) +        indices[index]=name +        names[name]=unicode +      else +        indices[index]=unicode +      end +      private=private+1 +    elseif descriptions[unicode] then +      report("assigning private unicode %U to glyph indexed %05X (%C)",private,index,unicode) +      unicode=private +      if names then +        local name=glyph.name or f_private(unicode) +        indices[index]=name +        names[name]=unicode +      else +        indices[index]=unicode +      end +      private=private+1      else -      parts=nil +      if names then +        local name=glyph.name or f_unicode(unicode) +        indices[index]=name +        names[name]=unicode +      else +        indices[index]=unicode +      end +    end +    descriptions[unicode]=glyph +  end +  for index=1,#glyphs do +    local math=glyphs[index].math +    if math then +      local list=math.vparts +      if list then +        for i=1,#list do local l=list[i] l.glyph=indices[l.glyph] end +      end +      local list=math.hparts +      if list then +        for i=1,#list do local l=list[i] l.glyph=indices[l.glyph] end +      end +      local list=math.vvariants +      if list then +        for i=1,#list do list[i]=indices[list[i]] end +      end +      local list=math.hvariants +      if list then +        for i=1,#list do list[i]=indices[list[i]] end +      end      end    end -  local italic=the_variants.italic -  if italic and italic==0 then -    italic=nil +  fontdata.private=private +  fontdata.glyphs=nil +  fontdata.names=names +  fontdata.descriptions=descriptions +  fontdata.hashmethod=hashmethod +  return indices,names +end +local p_bogusname=( +  (P("uni")+P("UNI")+P("Uni")+P("U")+P("u"))*S("Xx")^0*R("09","AF")^1+(P("identity")+P("Identity")+P("IDENTITY"))*R("09","AF")^1+(P("index")+P("Index")+P("INDEX"))*R("09")^1 +)*P(-1) +local function stripredundant(fontdata) +  local descriptions=fontdata.descriptions +  if descriptions then +    local n=0 +    local c=0 +    for unicode,d in next,descriptions do +      local name=d.name +      if name and lpegmatch(p_bogusname,name) then +        d.name=nil +        n=n+1 +      end +      if d.class=="base" then +        d.class=nil +        c=c+1 +      end +    end +    if n>0 then +      report("%s bogus names removed (verbose unicode)",n) +    end +    if c>0 then +      report("%s base class tags removed (default is base)",c) +    end    end -  return variants,parts,italic  end -actions["analyze math"]=function(data,filename,raw) -  if raw.math then -    data.metadata.math=raw.math -    local unicodes=data.resources.unicodes -    local splitter=data.helpers.tounicodetable -    for unicode,description in next,data.descriptions do -      local glyph=description.glyph -      local mathkerns=glyph.mathkern  -      local hvariants=glyph.horiz_variants -      local vvariants=glyph.vert_variants -      local accent=glyph.top_accent -      local italic=glyph.italic_correction -      if mathkerns or hvariants or vvariants or accent or italic then -        local math={} -        if accent then -          math.accent=accent +function readers.rehash(fontdata,hashmethod)  +  if not (fontdata and fontdata.glyphs) then +    return +  end +  if hashmethod=="indices" then +    fontdata.hashmethod="indices" +  elseif hashmethod=="names" then +    fontdata.hashmethod="names" +    local indices=unifyglyphs(fontdata,true) +    unifyresources(fontdata,indices) +    copyduplicates(fontdata) +    unifymissing(fontdata) +  else +    fontdata.hashmethod="unicode" +    local indices=unifyglyphs(fontdata) +    unifyresources(fontdata,indices) +    copyduplicates(fontdata) +    unifymissing(fontdata) +    stripredundant(fontdata) +  end +end +function readers.checkhash(fontdata) +  local hashmethod=fontdata.hashmethod +  if hashmethod=="unicodes" then +    fontdata.names=nil  +  elseif hashmethod=="names" and fontdata.names then +    unifyresources(fontdata,fontdata.names) +    copyduplicates(fontdata) +    fontdata.hashmethod="unicode" +    fontdata.names=nil  +  else +    readers.rehash(fontdata,"unicode") +  end +end +function readers.addunicodetable(fontdata) +  local resources=fontdata.resources +  local unicodes=resources.unicodes +  if not unicodes then +    local descriptions=fontdata.descriptions +    if descriptions then +      unicodes={} +      resources.unicodes=unicodes +      for u,d in next,descriptions do +        local n=d.name +        if n then +          unicodes[n]=u          end -        if mathkerns then -          for k,v in next,mathkerns do -            if not next(v) then -              mathkerns[k]=nil +      end +    end +  end +end +local concat,sort=table.concat,table.sort +local next,type,tostring=next,type,tostring +local criterium=1 +local threshold=0 +local trace_packing=false trackers.register("otf.packing",function(v) trace_packing=v end) +local trace_loading=false trackers.register("otf.loading",function(v) trace_loading=v end) +local report_otf=logs.reporter("fonts","otf loading") +local function tabstr_normal(t) +  local s={} +  local n=0 +  for k,v in next,t do +    n=n+1 +    if type(v)=="table" then +      s[n]=k..">"..tabstr_normal(v) +    elseif v==true then +      s[n]=k.."+"  +    elseif v then +      s[n]=k.."="..v +    else +      s[n]=k.."-"  +    end +  end +  if n==0 then +    return "" +  elseif n==1 then +    return s[1] +  else +    sort(s)  +    return concat(s,",") +  end +end +local function tabstr_flat(t) +  local s={} +  local n=0 +  for k,v in next,t do +    n=n+1 +    s[n]=k.."="..v +  end +  if n==0 then +    return "" +  elseif n==1 then +    return s[1] +  else +    sort(s)  +    return concat(s,",") +  end +end +local function tabstr_mixed(t)  +  local s={} +  local n=#t +  if n==0 then +    return "" +  elseif n==1 then +    local k=t[1] +    if k==true then +      return "++"  +    elseif k==false then +      return "--"  +    else +      return tostring(k)  +    end +  else +    for i=1,n do +      local k=t[i] +      if k==true then +        s[i]="++"  +      elseif k==false then +        s[i]="--"  +      else +        s[i]=k  +      end +    end +    return concat(s,",") +  end +end +local function tabstr_boolean(t) +  local s={} +  local n=0 +  for k,v in next,t do +    n=n+1 +    if v then +      s[n]=k.."+" +    else +      s[n]=k.."-" +    end +  end +  if n==0 then +    return "" +  elseif n==1 then +    return s[1] +  else +    sort(s)  +    return concat(s,",") +  end +end +function readers.pack(data) +  if data then +    local h,t,c={},{},{} +    local hh,tt,cc={},{},{} +    local nt,ntt=0,0 +    local function pack_normal(v) +      local tag=tabstr_normal(v) +      local ht=h[tag] +      if ht then +        c[ht]=c[ht]+1 +        return ht +      else +        nt=nt+1 +        t[nt]=v +        h[tag]=nt +        c[nt]=1 +        return nt +      end +    end +    local function pack_flat(v) +      local tag=tabstr_flat(v) +      local ht=h[tag] +      if ht then +        c[ht]=c[ht]+1 +        return ht +      else +        nt=nt+1 +        t[nt]=v +        h[tag]=nt +        c[nt]=1 +        return nt +      end +    end +    local function pack_boolean(v) +      local tag=tabstr_boolean(v) +      local ht=h[tag] +      if ht then +        c[ht]=c[ht]+1 +        return ht +      else +        nt=nt+1 +        t[nt]=v +        h[tag]=nt +        c[nt]=1 +        return nt +      end +    end +    local function pack_indexed(v) +      local tag=concat(v," ") +      local ht=h[tag] +      if ht then +        c[ht]=c[ht]+1 +        return ht +      else +        nt=nt+1 +        t[nt]=v +        h[tag]=nt +        c[nt]=1 +        return nt +      end +    end +    local function pack_mixed(v) +      local tag=tabstr_mixed(v) +      local ht=h[tag] +      if ht then +        c[ht]=c[ht]+1 +        return ht +      else +        nt=nt+1 +        t[nt]=v +        h[tag]=nt +        c[nt]=1 +        return nt +      end +    end +    local function pack_final(v) +      if c[v]<=criterium then +        return t[v] +      else +        local hv=hh[v] +        if hv then +          return hv +        else +          ntt=ntt+1 +          tt[ntt]=t[v] +          hh[v]=ntt +          cc[ntt]=c[v] +          return ntt +        end +      end +    end +    local function success(stage,pass) +      if nt==0 then +        if trace_loading or trace_packing then +          report_otf("pack quality: nothing to pack") +        end +        return false +      elseif nt>=threshold then +        local one,two,rest=0,0,0 +        if pass==1 then +          for k,v in next,c do +            if v==1 then +              one=one+1 +            elseif v==2 then +              two=two+1              else -              for k,v in next,v do -                if v==0 then -                  k[v]=nil  -                end -              end +              rest=rest+1 +            end +          end +        else +          for k,v in next,cc do +            if v>20 then +              rest=rest+1 +            elseif v>10 then +              two=two+1 +            else +              one=one+1              end            end -          math.kerns=mathkerns +          data.tables=tt          end -        if hvariants then -          math.hvariants,math.hparts,math.hitalic=check_variants(unicode,hvariants,splitter,unicodes) +        if trace_loading or trace_packing then +          report_otf("pack quality: stage %s, pass %s, %s packed, 1-10:%s, 11-20:%s, rest:%s (criterium: %s)", +            stage,pass,one+two+rest,one,two,rest,criterium)          end -        if vvariants then -          math.vvariants,math.vparts,math.vitalic=check_variants(unicode,vvariants,splitter,unicodes) +        return true +      else +        if trace_loading or trace_packing then +          report_otf("pack quality: stage %s, pass %s, %s packed, aborting pack (threshold: %s)", +            stage,pass,nt,threshold)          end -        if italic and italic~=0 then -          math.italic=italic +        return false +      end +    end +    local function packers(pass) +      if pass==1 then +        return pack_normal,pack_indexed,pack_flat,pack_boolean,pack_mixed +      else +        return pack_final,pack_final,pack_final,pack_final,pack_final +      end +    end +    local resources=data.resources +    local sequences=resources.sequences +    local sublookups=resources.sublookups +    local features=resources.features +    local chardata=characters and characters.data +    local descriptions=data.descriptions or data.glyphs +    if not descriptions then +      return +    end +    for pass=1,2 do +      if trace_packing then +        report_otf("start packing: stage 1, pass %s",pass) +      end +      local pack_normal,pack_indexed,pack_flat,pack_boolean,pack_mixed=packers(pass) +      for unicode,description in next,descriptions do +        local boundingbox=description.boundingbox +        if boundingbox then +          description.boundingbox=pack_indexed(boundingbox) +        end +        local math=description.math +        if math then +          local kerns=math.kerns +          if kerns then +            for tag,kern in next,kerns do +              kerns[tag]=pack_normal(kern) +            end +          end +        end +      end +      local function packthem(sequences) +        for i=1,#sequences do +          local sequence=sequences[i] +          local kind=sequence.type +          local steps=sequence.steps +          local order=sequence.order +          local features=sequence.features +          local flags=sequence.flags +          if steps then +            for i=1,#steps do +              local step=steps[i] +              if kind=="gpos_pair" then +                local c=step.coverage +                if c then +                  if step.format=="kern" then +                    for g1,d1 in next,c do +                      c[g1]=pack_normal(d1) +                    end +                  else +                    for g1,d1 in next,c do +                      for g2,d2 in next,d1 do +                        local f=d2[1] if f then d2[1]=pack_indexed(f) end +                        local s=d2[2] if s then d2[2]=pack_indexed(s) end +                      end +                    end +                  end +                end +              elseif kind=="gpos_single" then +                local c=step.coverage +                if c then +                  if step.format=="kern" then +                    step.coverage=pack_normal(c) +                  else +                    for g1,d1 in next,c do +                      c[g1]=pack_indexed(d1) +                    end +                  end +                end +              elseif kind=="gpos_cursive" then +                local c=step.coverage +                if c then +                  for g1,d1 in next,c do +                    local f=d1[2] if f then d1[2]=pack_indexed(f) end +                    local s=d1[3] if s then d1[3]=pack_indexed(s) end +                  end +                end +              elseif kind=="gpos_mark2base" or kind=="gpos_mark2mark" then +                local c=step.baseclasses +                if c then +                  for g1,d1 in next,c do +                    for g2,d2 in next,d1 do +                      d1[g2]=pack_indexed(d2) +                    end +                  end +                end +                local c=step.coverage +                if c then +                  for g1,d1 in next,c do +                    d1[2]=pack_indexed(d1[2]) +                  end +                end +              elseif kind=="gpos_mark2ligature" then +                local c=step.baseclasses +                if c then +                  for g1,d1 in next,c do +                    for g2,d2 in next,d1 do +                      for g3,d3 in next,d2 do +                        d2[g3]=pack_indexed(d3) +                      end +                    end +                  end +                end +                local c=step.coverage +                if c then +                  for g1,d1 in next,c do +                    d1[2]=pack_indexed(d1[2]) +                  end +                end +              end +              local rules=step.rules +              if rules then +                for i=1,#rules do +                  local rule=rules[i] +                  local r=rule.before    if r then for i=1,#r do r[i]=pack_boolean(r[i]) end end +                  local r=rule.after    if r then for i=1,#r do r[i]=pack_boolean(r[i]) end end +                  local r=rule.current   if r then for i=1,#r do r[i]=pack_boolean(r[i]) end end +                  local r=rule.replacements if r then rule.replacements=pack_flat  (r)  end  +                end +              end +            end +          end +          if order then +            sequence.order=pack_indexed(order) +          end +          if features then +            for script,feature in next,features do +              features[script]=pack_normal(feature) +            end +          end +          if flags then +            sequence.flags=pack_normal(flags) +          end          end -        description.math=math +      end +      if sequences then +        packthem(sequences) +      end +      if sublookups then +        packthem(sublookups) +      end +      if features then +        for k,list in next,features do +          for feature,spec in next,list do +            list[feature]=pack_normal(spec) +          end +        end +      end +      if not success(1,pass) then +        return        end      end -  end -end -actions["reorganize glyph kerns"]=function(data,filename,raw) -  local descriptions=data.descriptions -  local resources=data.resources -  local unicodes=resources.unicodes -  for unicode,description in next,descriptions do -    local kerns=description.glyph.kerns -    if kerns then -      local newkerns={} -      for k,kern in next,kerns do -        local name=kern.char -        local offset=kern.off -        local lookup=kern.lookup -        if name and offset and lookup then -          local unicode=unicodes[name] -          if unicode then -            if type(lookup)=="table" then -              for l=1,#lookup do -                local lookup=lookup[l] -                local lookupkerns=newkerns[lookup] -                if lookupkerns then -                  lookupkerns[unicode]=offset -                else -                  newkerns[lookup]={ [unicode]=offset } +    if nt>0 then +      for pass=1,2 do +        if trace_packing then +          report_otf("start packing: stage 2, pass %s",pass) +        end +        local pack_normal,pack_indexed,pack_flat,pack_boolean,pack_mixed=packers(pass) +        for unicode,description in next,descriptions do +          local math=description.math +          if math then +            local kerns=math.kerns +            if kerns then +              math.kerns=pack_normal(kerns) +            end +          end +        end +        local function packthem(sequences) +          for i=1,#sequences do +            local sequence=sequences[i] +            local kind=sequence.type +            local steps=sequence.steps +            local features=sequence.features +            if steps then +              for i=1,#steps do +                local step=steps[i] +                if kind=="gpos_pair" then +                  local c=step.coverage +                  if c then +                    if step.format=="kern" then +                    else +                      for g1,d1 in next,c do +                        for g2,d2 in next,d1 do +                          d1[g2]=pack_normal(d2) +                        end +                      end +                    end +                  end +                end +                local rules=step.rules +                if rules then +                  for i=1,#rules do +                    local rule=rules[i] +                    local r=rule.before if r then rule.before=pack_normal(r) end +                    local r=rule.after  if r then rule.after=pack_normal(r) end +                    local r=rule.current if r then rule.current=pack_normal(r) end +                  end                  end                end -            else -              local lookupkerns=newkerns[lookup] -              if lookupkerns then -                lookupkerns[unicode]=offset -              else -                newkerns[lookup]={ [unicode]=offset } +            end +            if features then +              sequence.features=pack_normal(features) +            end +          end +        end +        if sequences then +          packthem(sequences) +        end +        if sublookups then +          packthem(sublookups) +        end +        if not success(2,pass) then +        end +      end +      for pass=1,2 do +        if trace_packing then +          report_otf("start packing: stage 3, pass %s",pass) +        end +        local pack_normal,pack_indexed,pack_flat,pack_boolean,pack_mixed=packers(pass) +        local function packthem(sequences) +          for i=1,#sequences do +            local sequence=sequences[i] +            local kind=sequence.type +            local steps=sequence.steps +            local features=sequence.features +            if steps then +              for i=1,#steps do +                local step=steps[i] +                if kind=="gpos_pair" then +                  local c=step.coverage +                  if c then +                    if step.format=="kern" then +                    else +                      for g1,d1 in next,c do +                        c[g1]=pack_normal(d1) +                      end +                    end +                  end +                end                end              end -          elseif trace_loading then -            report_otf("problems with unicode %a of kern %a of glyph %U",name,k,unicode)            end          end +        if sequences then +          packthem(sequences) +        end +        if sublookups then +          packthem(sublookups) +        end        end -      description.kerns=newkerns      end    end  end -actions["merge kern classes"]=function(data,filename,raw) -  local gposlist=raw.gpos -  if gposlist then -    local descriptions=data.descriptions -    local resources=data.resources -    local unicodes=resources.unicodes -    local splitter=data.helpers.tounicodetable -    local ignored=0 -    local blocked=0 -    for gp=1,#gposlist do -      local gpos=gposlist[gp] -      local subtables=gpos.subtables -      if subtables then -        local first_done={}  -        local split={}  -        for s=1,#subtables do -          local subtable=subtables[s] -          local kernclass=subtable.kernclass  -          local lookup=subtable.lookup or subtable.name -          if kernclass then -            if #kernclass>0 then -              kernclass=kernclass[1] -              lookup=type(kernclass.lookup)=="string" and kernclass.lookup or lookup -              report_otf("fixing kernclass table of lookup %a",lookup) -            end -            local firsts=kernclass.firsts -            local seconds=kernclass.seconds -            local offsets=kernclass.offsets -            for n,s in next,firsts do -              split[s]=split[s] or lpegmatch(splitter,s) +local unpacked_mt={ +  __index=function(t,k) +      t[k]=false +      return k  +    end +} +function readers.unpack(data) +  if data then +    local tables=data.tables +    if tables then +      local resources=data.resources +      local descriptions=data.descriptions or data.glyphs +      local sequences=resources.sequences +      local sublookups=resources.sublookups +      local features=resources.features +      local unpacked={} +      setmetatable(unpacked,unpacked_mt) +      for unicode,description in next,descriptions do +        local tv=tables[description.boundingbox] +        if tv then +          description.boundingbox=tv +        end +        local math=description.math +        if math then +          local kerns=math.kerns +          if kerns then +            local tm=tables[kerns] +            if tm then +              math.kerns=tm +              kerns=unpacked[tm]              end -            local maxseconds=0 -            for n,s in next,seconds do -              if n>maxseconds then -                maxseconds=n +            if kerns then +              for k,kern in next,kerns do +                local tv=tables[kern] +                if tv then +                  kerns[k]=tv +                end                end -              split[s]=split[s] or lpegmatch(splitter,s)              end -            for fk=1,#firsts do  -              local fv=firsts[fk] -              local splt=split[fv] -              if splt then -                local extrakerns={} -                local baseoffset=(fk-1)*maxseconds -                for sk=2,maxseconds do -                  local sv=seconds[sk] -                  if sv then -                    local splt=split[sv] -                    if splt then  -                      local offset=offsets[baseoffset+sk] -                      if offset then -                        for i=1,#splt do -                          extrakerns[splt[i]]=offset +          end +        end +      end +      local function unpackthem(sequences) +        for i=1,#sequences do +          local sequence=sequences[i] +          local kind=sequence.type +          local steps=sequence.steps +          local order=sequence.order +          local features=sequence.features +          local flags=sequence.flags +          local markclass=sequence.markclass +          if steps then +            for i=1,#steps do +              local step=steps[i] +              if kind=="gpos_pair" then +                local c=step.coverage +                if c then +                  if step.format=="kern" then +                    for g1,d1 in next,c do +                      local tv=tables[d1] +                      if tv then +                        c[g1]=tv +                      end +                    end +                  else +                    for g1,d1 in next,c do +                      local tv=tables[d1] +                      if tv then +                        c[g1]=tv +                        d1=tv +                      end +                      for g2,d2 in next,d1 do +                        local tv=tables[d2] +                        if tv then +                          d1[g2]=tv +                          d2=tv                          end +                        local f=tables[d2[1]] if f then d2[1]=f end +                        local s=tables[d2[2]] if s then d2[2]=s end                        end                      end                    end                  end -                for i=1,#splt do -                  local first_unicode=splt[i] -                  if first_done[first_unicode] then -                    report_otf("lookup %a: ignoring further kerns of %C",lookup,first_unicode) -                    blocked=blocked+1 +              elseif kind=="gpos_single" then +                local c=step.coverage +                if c then +                  if step.format=="kern" then +                    local tv=tables[c] +                    if tv then +                      step.coverage=tv +                    end                    else -                    first_done[first_unicode]=true -                    local description=descriptions[first_unicode] -                    if description then -                      local kerns=description.kerns -                      if not kerns then -                        kerns={}  -                        description.kerns=kerns +                    for g1,d1 in next,c do +                      local tv=tables[d1] +                      if tv then +                        c[g1]=tv                        end -                      local lookupkerns=kerns[lookup] -                      if not lookupkerns then -                        lookupkerns={} -                        kerns[lookup]=lookupkerns +                    end +                  end +                end +              elseif kind=="gpos_cursive" then +                local c=step.coverage +                if c then +                  for g1,d1 in next,c do +                    local f=tables[d1[2]] if f then d1[2]=f end +                    local s=tables[d1[3]] if s then d1[3]=s end +                  end +                end +              elseif kind=="gpos_mark2base" or kind=="gpos_mark2mark" then +                local c=step.baseclasses +                if c then +                  for g1,d1 in next,c do +                    for g2,d2 in next,d1 do +                      local tv=tables[d2] +                      if tv then +                        d1[g2]=tv                        end -                      if overloadkerns then -                        for second_unicode,kern in next,extrakerns do -                          lookupkerns[second_unicode]=kern -                        end -                      else -                        for second_unicode,kern in next,extrakerns do -                          local k=lookupkerns[second_unicode] -                          if not k then -                            lookupkerns[second_unicode]=kern -                          elseif k~=kern then -                            if trace_loading then -                              report_otf("lookup %a: ignoring overload of kern between %C and %C, rejecting %a, keeping %a",lookup,first_unicode,second_unicode,k,kern) -                            end -                            ignored=ignored+1 -                          end +                    end +                  end +                end +                local c=step.coverage +                if c then +                  for g1,d1 in next,c do +                    local tv=tables[d1[2]] +                    if tv then +                      d1[2]=tv +                    end +                  end +                end +              elseif kind=="gpos_mark2ligature" then +                local c=step.baseclasses +                if c then +                  for g1,d1 in next,c do +                    for g2,d2 in next,d1 do +                      for g3,d3 in next,d2 do +                        local tv=tables[d2[g3]] +                        if tv then +                          d2[g3]=tv                          end                        end -                    elseif trace_loading then -                      report_otf("no glyph data for %U",first_unicode) +                    end +                  end +                end +                local c=step.coverage +                if c then +                  for g1,d1 in next,c do +                    local tv=tables[d1[2]] +                    if tv then +                      d1[2]=tv +                    end +                  end +                end +              end +              local rules=step.rules +              if rules then +                for i=1,#rules do +                  local rule=rules[i] +                  local before=rule.before +                  if before then +                    local tv=tables[before] +                    if tv then +                      rule.before=tv +                      before=tv +                    end +                    for i=1,#before do +                      local tv=tables[before[i]] +                      if tv then +                        before[i]=tv +                      end +                    end +                  end +                  local after=rule.after +                  if after then +                    local tv=tables[after] +                    if tv then +                      rule.after=tv +                      after=tv +                    end +                    for i=1,#after do +                      local tv=tables[after[i]] +                      if tv then +                        after[i]=tv +                      end +                    end +                  end +                  local current=rule.current +                  if current then +                    local tv=tables[current] +                    if tv then +                      rule.current=tv +                      current=tv +                    end +                    for i=1,#current do +                      local tv=tables[current[i]] +                      if tv then +                        current[i]=tv +                      end +                    end +                  end +                  local replacements=rule.replacements +                  if replacements then +                    local tv=tables[replace] +                    if tv then +                      rule.replacements=tv                      end                    end                  end                end              end -            subtable.kernclass={} +          end +          if features then +            local tv=tables[features] +            if tv then +              sequence.features=tv +              features=tv +            end +            for script,feature in next,features do +              local tv=tables[feature] +              if tv then +                features[script]=tv +              end +            end +          end +          if order then +            local tv=tables[order] +            if tv then +              sequence.order=tv +            end +          end +          if flags then +            local tv=tables[flags] +            if tv then +              sequence.flags=tv +            end            end          end        end -    end -    if ignored>0 then -      report_otf("%s kern overloads ignored",ignored) -    end -    if blocked>0 then -      report_otf("%s successive kerns blocked",blocked) +      if sequences then +        unpackthem(sequences) +      end +      if sublookups then +        unpackthem(sublookups) +      end +      if features then +        for k,list in next,features do +          for feature,spec in next,list do +            local tv=tables[spec] +            if tv then +              list[feature]=tv +            end +          end +        end +      end +      data.tables=nil      end    end  end -actions["check glyphs"]=function(data,filename,raw) -  for unicode,description in next,data.descriptions do -    description.glyph=nil +local mt={ +  __index=function(t,k)  +    if k=="height" then +      local ht=t.boundingbox[4] +      return ht<0 and 0 or ht +    elseif k=="depth" then +      local dp=-t.boundingbox[2] +      return dp<0 and 0 or dp +    elseif k=="width" then +      return 0 +    elseif k=="name" then  +      return forcenotdef and ".notdef" +    end    end +} +local function sameformat(sequence,steps,first,nofsteps,kind) +  return true  end -local valid=(R("\x00\x7E")-S("(){}[]<>%/ \n\r\f\v"))^0*P(-1) -local function valid_ps_name(str) -  return str and str~="" and #str<64 and lpegmatch(valid,str) and true or false -end -actions["check metadata"]=function(data,filename,raw) -  local metadata=data.metadata -  for _,k in next,mainfields do -    if valid_fields[k] then -      local v=raw[k] -      if not metadata[k] then -        metadata[k]=v +local function mergesteps_1(lookup,strict) +  local steps=lookup.steps +  local nofsteps=lookup.nofsteps +  local first=steps[1] +  if strict then +    local f=first.format +    for i=2,nofsteps do +      if steps[i].format~=f then +        report("not merging %a steps of %a lookup %a, different formats",nofsteps,lookup.type,lookup.name) +        return 0 +      end +    end +  end +  report("merging %a steps of %a lookup %a",nofsteps,lookup.type,lookup.name) +  local target=first.coverage +  for i=2,nofsteps do +    for k,v in next,steps[i].coverage do +      if not target[k] then +        target[k]=v +      end +    end +  end +  lookup.nofsteps=1 +  lookup.merged=true +  lookup.steps={ first } +  return nofsteps-1 +end +local function mergesteps_2(lookup,strict)  +  local steps=lookup.steps +  local nofsteps=lookup.nofsteps +  local first=steps[1] +  if strict then +    local f=first.format +    for i=2,nofsteps do +      if steps[i].format~=f then +        report("not merging %a steps of %a lookup %a, different formats",nofsteps,lookup.type,lookup.name) +        return 0 +      end +    end +  end +  report("merging %a steps of %a lookup %a",nofsteps,lookup.type,lookup.name) +  local target=first.coverage +  for i=2,nofsteps do +    for k,v in next,steps[i].coverage do +      local tk=target[k] +      if tk then +        for k,v in next,v do +          if not tk[k] then +            tk[k]=v +          end +        end +      else +        target[k]=v +      end +    end +  end +  lookup.nofsteps=1 +  lookup.steps={ first } +  return nofsteps-1 +end +local function mergesteps_3(lookup,strict)  +  local steps=lookup.steps +  local nofsteps=lookup.nofsteps +  local first=steps[1] +  report("merging %a steps of %a lookup %a",nofsteps,lookup.type,lookup.name) +  local baseclasses={} +  local coverage={} +  local used={} +  for i=1,nofsteps do +    local offset=i*10 +    local step=steps[i] +    for k,v in sortedhash(step.baseclasses) do +      baseclasses[offset+k]=v +    end +    for k,v in next,step.coverage do +      local tk=coverage[k] +      if tk then +        for k,v in next,v do +          if not tk[k] then +            tk[k]=v +            local c=offset+v[1] +            v[1]=c +            if not used[c] then +              used[c]=true +            end +          end +        end +      else +        coverage[k]=v +        local c=offset+v[1] +        v[1]=c +        if not used[c] then +          used[c]=true +        end        end      end    end -  local ttftables=metadata.ttf_tables -  if ttftables then -    for i=1,#ttftables do -      ttftables[i].data="deleted" +  for k,v in next,baseclasses do +    if not used[k] then +      baseclasses[k]=nil +      report("discarding not used baseclass %i",k)      end    end -  local names=raw.names -  if metadata.validation_state and table.contains(metadata.validation_state,"bad_ps_fontname") then -    local function valid(what) -      if names then -        for i=1,#names do -          local list=names[i] -          local names=list.names -          if names then -            local name=names[what] -            if name and valid_ps_name(name) then -              return name -            end -          end -        end +  first.baseclasses=baseclasses +  first.coverage=coverage +  lookup.nofsteps=1 +  lookup.steps={ first } +  return nofsteps-1 +end +local function nested(old,new) +  for k,v in next,old do +    if k=="ligature" then +      if not new.ligature then +        new.ligature=v +      end +    else +      local n=new[k] +      if n then +        nested(v,n) +      else +        new[k]=v        end      end -    local function check(what) -      local oldname=metadata[what] -      if valid_ps_name(oldname) then -        report_otf("ignoring warning %a because %s %a is proper ASCII","bad_ps_fontname",what,oldname) +  end +end +local function mergesteps_4(lookup)  +  local steps=lookup.steps +  local nofsteps=lookup.nofsteps +  local first=steps[1] +  report("merging %a steps of %a lookup %a",nofsteps,lookup.type,lookup.name) +  local target=first.coverage +  for i=2,nofsteps do +    for k,v in next,steps[i].coverage do +      local tk=target[k] +      if tk then +        nested(v,tk)        else -        local newname=valid(what) -        if not newname then -          newname=formatters["bad-%s-%s"](what,file.nameonly(filename)) +        target[k]=v +      end +    end +  end +  lookup.nofsteps=1 +  lookup.steps={ first } +  return nofsteps-1 +end +local function checkkerns(lookup) +  local steps=lookup.steps +  local nofsteps=lookup.nofsteps +  for i=1,nofsteps do +    local step=steps[i] +    if step.format=="pair" then +      local coverage=step.coverage +      local kerns=true +      for g1,d1 in next,coverage do +        if d1[1]~=0 or d1[2]~=0 or d1[4]~=0 then +          kerns=false +          break          end -        local warning=formatters["overloading %s from invalid ASCII name %a to %a"](what,oldname,newname) -        data.warnings[#data.warnings+1]=warning -        report_otf(warning) -        metadata[what]=newname +      end +      if kerns then +        report("turning pairs of step %a of %a lookup %a into kerns",i,lookup.type,lookup.name) +        for g1,d1 in next,coverage do +          coverage[g1]=d1[3] +        end +        step.format="kern"        end      end -    check("fontname") -    check("fullname")    end -  if names then -    local psname=metadata.psname -    if not psname or psname=="" then -      for i=1,#names do -        local name=names[i] -        if lower(name.lang)=="english (us)" then -          local specification=name.names -          if specification then -            local postscriptname=specification.postscriptname -            if postscriptname then -              psname=postscriptname +end +local function checkpairs(lookup) +  local steps=lookup.steps +  local nofsteps=lookup.nofsteps +  local kerned=0 +  for i=1,nofsteps do +    local step=steps[i] +    if step.format=="pair" then +      local coverage=step.coverage +      local kerns=true +      for g1,d1 in next,coverage do +        for g2,d2 in next,d1 do +          if d2[2] then +            kerns=false +            break +          else +            local v=d2[1] +            if v[1]~=0 or v[2]~=0 or v[4]~=0 then +              kerns=false +              break              end            end          end -        break +      end +      if kerns then +        report("turning pairs of step %a of %a lookup %a into kerns",i,lookup.type,lookup.name) +        for g1,d1 in next,coverage do +          for g2,d2 in next,d1 do +            d1[g2]=d2[1][3] +          end +        end +        step.format="kern" +        kerned=kerned+1        end      end -    if psname~=metadata.fontname then -      report_otf("fontname %a, fullname %a, psname %a",metadata.fontname,metadata.fullname,psname) -    end -    metadata.psname=psname    end +  return kerned  end -actions["cleanup tables"]=function(data,filename,raw) -  local duplicates=data.resources.duplicates -  if duplicates then -    for k,v in next,duplicates do -      if #v==1 then -        duplicates[k]=v[1] +function readers.compact(data) +  if not data or data.compacted then +    return +  else +    data.compacted=true +  end +  local resources=data.resources +  local merged=0 +  local kerned=0 +  local allsteps=0 +  local function compact(what) +    local lookups=resources[what] +    if lookups then +      for i=1,#lookups do +        local lookup=lookups[i] +        local nofsteps=lookup.nofsteps +        allsteps=allsteps+nofsteps +        if nofsteps>1 then +          local kind=lookup.type +          if kind=="gsub_single" or kind=="gsub_alternate" or kind=="gsub_multiple" then +            merged=merged+mergesteps_1(lookup) +          elseif kind=="gsub_ligature" then +            merged=merged+mergesteps_4(lookup) +          elseif kind=="gpos_single" then +            merged=merged+mergesteps_1(lookup,true) +            checkkerns(lookup) +          elseif kind=="gpos_pair" then +            merged=merged+mergesteps_2(lookup,true) +            kerned=kerned+checkpairs(lookup) +          elseif kind=="gpos_cursive" then +            merged=merged+mergesteps_2(lookup) +          elseif kind=="gpos_mark2mark" or kind=="gpos_mark2base" or kind=="gpos_mark2ligature" then +            merged=merged+mergesteps_3(lookup) +          end +        end        end +    else +      report("no lookups in %a",what)      end    end -  data.resources.indices=nil  -  data.resources.unicodes=nil  -  data.helpers=nil  +  compact("sequences") +  compact("sublookups") +  if merged>0 then +    report("%i steps of %i removed due to merging",merged,allsteps) +  end +  if kerned>0 then +    report("%i steps of %i steps turned from pairs into kerns",kerned,allsteps) +  end  end -actions["reorganize glyph lookups"]=function(data,filename,raw) +function readers.expand(data) +  if not data or data.expanded then +    return +  else +    data.expanded=true +  end    local resources=data.resources -  local unicodes=resources.unicodes +  local sublookups=resources.sublookups +  local sequences=resources.sequences  +  local markclasses=resources.markclasses    local descriptions=data.descriptions -  local splitter=data.helpers.tounicodelist -  local lookuptypes=resources.lookuptypes -  for unicode,description in next,descriptions do -    local lookups=description.glyph.lookups -    if lookups then -      for tag,lookuplist in next,lookups do -        for l=1,#lookuplist do -          local lookup=lookuplist[l] -          local specification=lookup.specification -          local lookuptype=lookup.type -          local lt=lookuptypes[tag] -          if not lt then -            lookuptypes[tag]=lookuptype -          elseif lt~=lookuptype then -            report_otf("conflicting lookuptypes, %a points to %a and %a",tag,lt,lookuptype) -          end -          if lookuptype=="ligature" then -            lookuplist[l]={ lpegmatch(splitter,specification.components) } -          elseif lookuptype=="alternate" then -            lookuplist[l]={ lpegmatch(splitter,specification.components) } -          elseif lookuptype=="substitution" then -            lookuplist[l]=unicodes[specification.variant] -          elseif lookuptype=="multiple" then -            lookuplist[l]={ lpegmatch(splitter,specification.components) } -          elseif lookuptype=="position" then -            lookuplist[l]={ -              specification.x or 0, -              specification.y or 0, -              specification.h or 0, -              specification.v or 0 -            } -          elseif lookuptype=="pair" then -            local one=specification.offsets[1] -            local two=specification.offsets[2] -            local paired=unicodes[specification.paired] -            if one then -              if two then -                lookuplist[l]={ paired,{ one.x or 0,one.y or 0,one.h or 0,one.v or 0 },{ two.x or 0,two.y or 0,two.h or 0,two.v or 0 } } -              else -                lookuplist[l]={ paired,{ one.x or 0,one.y or 0,one.h or 0,one.v or 0 } } -              end -            else -              if two then -                lookuplist[l]={ paired,{},{ two.x or 0,two.y or 0,two.h or 0,two.v or 0} }  -              else -                lookuplist[l]={ paired } -              end -            end -          end -        end +  if descriptions then +    local defaultwidth=resources.defaultwidth or 0 +    local defaultheight=resources.defaultheight or 0 +    local defaultdepth=resources.defaultdepth or 0 +    local basename=trace_markwidth and file.basename(resources.filename) +    for u,d in next,descriptions do +      local bb=d.boundingbox +      local wd=d.width +      if not wd then +        d.width=defaultwidth +      elseif trace_markwidth and wd~=0 and d.class=="mark" then +        report("mark %a with width %b found in %a",d.name or "<noname>",wd,basename)        end -      local slookups,mlookups -      for tag,lookuplist in next,lookups do -        if #lookuplist==1 then -          if slookups then -            slookups[tag]=lookuplist[1] -          else -            slookups={ [tag]=lookuplist[1] } -          end +      if bb then +        local ht=bb[4] +        local dp=-bb[2] +        if ht==0 or ht<0 then          else -          if mlookups then -            mlookups[tag]=lookuplist -          else -            mlookups={ [tag]=lookuplist } -          end +          d.height=ht +        end +        if dp==0 or dp<0 then +        else +          d.depth=dp          end -      end -      if slookups then -        description.slookups=slookups -      end -      if mlookups then -        description.mlookups=mlookups        end      end    end -end -local zero={ 0,0 } -actions["reorganize glyph anchors"]=function(data,filename,raw) -  local descriptions=data.descriptions -  for unicode,description in next,descriptions do -    local anchors=description.glyph.anchors -    if anchors then -      for class,data in next,anchors do -        if class=="baselig" then -          for tag,specification in next,data do -            local n=0 -            for k,v in next,specification do -              if k>n then -                n=k +  local function expandlookups(sequences) +    if sequences then +      for i=1,#sequences do +        local sequence=sequences[i] +        local steps=sequence.steps +        if steps then +          local kind=sequence.type +          local markclass=sequence.markclass +          if markclass then +            if not markclasses then +              report_warning("missing markclasses") +              sequence.markclass=false +            else +              sequence.markclass=markclasses[markclass] +            end +          end +          for i=1,sequence.nofsteps do +            local step=steps[i] +            local baseclasses=step.baseclasses +            if baseclasses then +              local coverage=step.coverage +              for k,v in next,coverage do +                v[1]=baseclasses[v[1]]                 end -              local x,y=v.x,v.y -              if x or y then -                specification[k]={ x or 0,y or 0 } -              else -                specification[k]=zero +            elseif kind=="gpos_cursive" then +              local coverage=step.coverage +              for k,v in next,coverage do +                v[1]=coverage                 end              end -            local t={} -            for i=1,n do -              t[i]=specification[i] or zero -            end -            data[tag]=t  -          end -        else -          for tag,specification in next,data do -            local x,y=specification.x,specification.y -            if x or y then -              data[tag]={ x or 0,y or 0 } -            else -              data[tag]=zero +            local rules=step.rules +            if rules then +              local rulehash={} +              local rulesize=0 +              local coverage={} +              local lookuptype=sequence.type +              step.coverage=coverage  +              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 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 sequence[1] then  +                  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            end          end        end -      description.anchors=anchors      end    end +  expandlookups(sequences) +  expandlookups(sublookups)  end -local bogusname=(P("uni")+P("u"))*R("AF","09")^4+(P("index")+P("glyph")+S("Ii")*P("dentity")*P(".")^0)*R("09")^1 -local uselessname=(1-bogusname)^0*bogusname -actions["purge names"]=function(data,filename,raw)  -  if purge_names then -    local n=0 -    for u,d in next,data.descriptions do -      if lpegmatch(uselessname,d.name) then -        n=n+1 -        d.name=nil -      end -    end -    if n>0 then -      report_otf("%s bogus names removed",n) + +end -- closure + +do -- begin closure to overcome local limits and interference + +if not modules then modules={} end modules ['font-otl']={ +  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 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 +local allocate=utilities.storage.allocate +local registertracker=trackers.register +local registerdirective=directives.register +local starttiming=statistics.starttiming +local stoptiming=statistics.stoptiming +local elapsedtime=statistics.elapsedtime +local findbinfile=resolvers.findbinfile +local trace_loading=false registertracker("otf.loading",function(v) trace_loading=v end) +local trace_features=false registertracker("otf.features",function(v) trace_features=v end) +local trace_defining=false registertracker("fonts.defining",function(v) trace_defining=v end) +local report_otf=logs.reporter("fonts","otf loading") +local fonts=fonts +local otf=fonts.handlers.otf +otf.version=3.016  +otf.cache=containers.define("fonts","otl",otf.version,true) +local otfreaders=otf.readers +local hashes=fonts.hashes +local definers=fonts.definers +local readers=fonts.readers +local constructors=fonts.constructors +local otffeatures=constructors.newfeatures("otf") +local registerotffeature=otffeatures.register +local enhancers=allocate() +otf.enhancers=enhancers +local patches={} +enhancers.patches=patches +local forceload=false +local cleanup=0    +local syncspace=true +local forcenotdef=false +local applyruntimefixes=fonts.treatments and fonts.treatments.applyfixes +local wildcard="*" +local default="dflt" +local formats=fonts.formats +formats.otf="opentype" +formats.ttf="truetype" +formats.ttc="truetype" +registerdirective("fonts.otf.loader.cleanup",function(v) cleanup=tonumber(v) or (v and 1) or 0 end) +registerdirective("fonts.otf.loader.force",function(v) forceload=v end) +registerdirective("fonts.otf.loader.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 -actions["compact lookups"]=function(data,filename,raw) -  if not compact_lookups then -    report_otf("not compacting") -    return +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 -  local last=0 -  local tags=table.setmetatableindex({}, -    function(t,k) -      last=last+1 -      t[k]=last -      return last -    end -  ) -  local descriptions=data.descriptions -  local resources=data.resources -  for u,d in next,descriptions do -    local slookups=d.slookups -    if type(slookups)=="table" then -      local s={} -      for k,v in next,slookups do -        s[tags[k]]=v -      end -      d.slookups=s -    end -    local mlookups=d.mlookups -    if type(mlookups)=="table" then -      local m={} -      for k,v in next,mlookups do -        m[tags[k]]=v -      end -      d.mlookups=m -    end -    local kerns=d.kerns -    if type(kerns)=="table" then -      local t={} -      for k,v in next,kerns do -        t[tags[k]]=v -      end -      d.kerns=t -    end -  end -  local lookups=data.lookups -  if lookups then -    local l={} -    for k,v in next,lookups do -      local rules=v.rules -      if rules then -        for i=1,#rules do -          local l=rules[i].lookups -          if type(l)=="table" then -            for i=1,#l do -              l[i]=tags[l[i]] -            end -          end +  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 -      l[tags[k]]=v      end -    data.lookups=l -  end -  local lookups=resources.lookups -  if lookups then -    local l={} -    for k,v in next,lookups do -      local s=v.subtables -      if type(s)=="table" then -        for i=1,#s do -          s[i]=tags[s[i]] +    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 -      l[tags[k]]=v      end -    resources.lookups=l +    ioflush()     end -  local sequences=resources.sequences -  if sequences then -    for i=1,#sequences do -      local s=sequences[i] -      local n=s.name -      if n then -        s.name=tags[n] -      end -      local t=s.subtables -      if type(t)=="table" then -        for i=1,#t do -          t[i]=tags[t[i]] -        end -      end +  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 -  local lookuptypes=resources.lookuptypes -  if lookuptypes then -    local l={} -    for k,v in next,lookuptypes do -      l[tags[k]]=v +end +function patches.report(fmt,...) +  if trace_loading then +    report_otf("patching: %s",formatters[fmt](...)) +  end +end +function enhancers.register(what,action)  +  actions[what]=action +end +function otf.load(filename,sub,featurefile) +  local featurefile=nil +  local base=file.basename(file.removesuffix(filename)) +  local name=file.removesuffix(base) +  local attr=lfs.attributes(filename) +  local size=attr and attr.size or 0 +  local time=attr and attr.modification or 0 +  if featurefile then +    name=name.."@"..file.removesuffix(file.basename(featurefile)) +  end +  if sub=="" then +    sub=false +  end +  local hash=name +  if sub then +    hash=hash.."-"..sub +  end +  hash=containers.cleanname(hash) +  local featurefiles +  if featurefile then +    featurefiles={} +    for s in gmatch(featurefile,"[^,]+") do +      local name=resolvers.findfile(file.addsuffix(s,'fea'),'fea') or "" +      if name=="" then +        report_otf("loading error, no featurefile %a",s) +      else +        local attr=lfs.attributes(name) +        featurefiles[#featurefiles+1]={ +          name=name, +          size=attr and attr.size or 0, +          time=attr and attr.modification or 0, +        } +      end +    end +    if #featurefiles==0 then +      featurefiles=nil      end -    resources.lookuptypes=l    end -  local anchor_to_lookup=resources.anchor_to_lookup -  if anchor_to_lookup then -    for anchor,lookups in next,anchor_to_lookup do -      local l={} -      for lookup,value in next,lookups do -        l[tags[lookup]]=value +  local data=containers.read(otf.cache,hash) +  local reload=not data or data.size~=size or data.time~=time or data.tableversion~=otfreaders.tableversion +  if forceload then +    report_otf("forced reload of %a due to hard coded flag",filename) +    reload=true +  end +   if reload then +    report_otf("loading %a, hash %a",filename,hash) +    starttiming(otfreaders) +    data=otfreaders.loadfont(filename,sub or 1) +    if data then +      otfreaders.compact(data) +      otfreaders.rehash(data,"unicodes") +      otfreaders.addunicodetable(data) +      otfreaders.extend(data) +      otfreaders.pack(data) +      report_otf("loading done") +      report_otf("saving %a in cache",filename) +      data=containers.write(otf.cache,hash,data) +      if cleanup>1 then +        collectgarbage("collect") +      end +      stoptiming(otfreaders) +      if elapsedtime then  +        report_otf("loading, optimizing, packing and caching time %s",elapsedtime(otfreaders)) +      end +      if cleanup>3 then +        collectgarbage("collect") +      end +      data=containers.read(otf.cache,hash)  +      if cleanup>2 then +        collectgarbage("collect")        end -      anchor_to_lookup[anchor]=l +    else +      data=nil +      report_otf("loading failed due to read error")      end    end -  local lookup_to_anchor=resources.lookup_to_anchor -  if lookup_to_anchor then -    local l={} -    for lookup,value in next,lookup_to_anchor do -      l[tags[lookup]]=value +  if data then +    if trace_defining then +      report_otf("loading from cache using hash %a",hash) +    end +    otfreaders.unpack(data) +    otfreaders.expand(data)  +    otfreaders.addunicodetable(data) +    enhancers.apply(data,filename,data) +    constructors.addcoreunicodes(unicodes) +    if applyruntimefixes then +      applyruntimefixes(filename,data)      end -    resources.lookup_to_anchor=l +    data.metadata.math=data.resources.mathconstants    end -  tags=table.swapped(tags) -  report_otf("%s lookup tags compacted",#tags) -  resources.lookuptags=tags +  return data  end  function otf.setfeatures(tfmdata,features)    local okay=constructors.initializefeatures("otf",tfmdata,features,trace_features,report_otf) @@ -9222,7 +15498,6 @@ end  local function copytotfm(data,cache_id)    if data then      local metadata=data.metadata -    local warnings=data.warnings      local resources=data.resources      local properties=derivetable(data.properties)      local descriptions=derivetable(data.descriptions) @@ -9230,14 +15505,13 @@ local function copytotfm(data,cache_id)      local characters={}      local parameters={}      local mathparameters={} -    local pfminfo=metadata.pfminfo or {}      local resources=data.resources      local unicodes=resources.unicodes      local spaceunits=500      local spacer="space" -    local designsize=metadata.designsize or metadata.design_size or 100 -    local minsize=metadata.minsize or metadata.design_range_bottom or designsize -    local maxsize=metadata.maxsize or metadata.design_range_top  or designsize +    local designsize=metadata.designsize or 100 +    local minsize=metadata.minsize or designsize +    local maxsize=metadata.maxsize or designsize      local mathspecs=metadata.math      if designsize==0 then        designsize=100 @@ -9249,7 +15523,7 @@ local function copytotfm(data,cache_id)          mathparameters[name]=value        end      end -    for unicode,_ in next,data.descriptions do  +    for unicode in next,data.descriptions do         characters[unicode]={}      end      if mathspecs then @@ -9265,8 +15539,8 @@ local function copytotfm(data,cache_id)              local c=character              for i=1,#variants do                local un=variants[i] -                c.next=un -                c=characters[un] +              c.next=un +              c=characters[un]              end               c.horiz_variants=parts            elseif parts then @@ -9279,20 +15553,20 @@ local function copytotfm(data,cache_id)              local c=character              for i=1,#variants do                local un=variants[i] -                c.next=un -                c=characters[un] +              c.next=un +              c=characters[un]              end               c.vert_variants=parts            elseif parts then              character.vert_variants=parts            end            if italic and italic~=0 then -            character.italic=italic  +            character.italic=italic            end            if vitalic and vitalic~=0 then              character.vert_italic=vitalic            end -          local accent=m.accent +          local accent=m.accent             if accent then              character.accent=accent            end @@ -9306,18 +15580,20 @@ local function copytotfm(data,cache_id)      local filename=constructors.checkedfilename(resources)      local fontname=metadata.fontname      local fullname=metadata.fullname or fontname -    local psname=metadata.psname or fontname or fullname -    local units=metadata.units or metadata.units_per_em or 1000 +    local psname=fontname or fullname +    local units=metadata.units or 1000      if units==0 then         units=1000         metadata.units=1000        report_otf("changing %a units to %a",0,units)      end -    local monospaced=metadata.monospaced or metadata.isfixedpitch or (pfminfo.panose and pfminfo.panose.proportion=="Monospaced") -    local charwidth=pfminfo.avgwidth  -    local charxheight=pfminfo.os2_xheight and pfminfo.os2_xheight>0 and pfminfo.os2_xheight +    local monospaced=metadata.monospaced +    local charwidth=metadata.averagewidth  +    local charxheight=metadata.xheight       local italicangle=metadata.italicangle +    local hasitalics=metadata.hasitalics      properties.monospaced=monospaced +    properties.hasitalics=hasitalics      parameters.italicangle=italicangle      parameters.charwidth=charwidth      parameters.charxheight=charxheight @@ -9347,7 +15623,7 @@ local function copytotfm(data,cache_id)      spaceunits=tonumber(spaceunits) or 500      parameters.slant=0      parameters.space=spaceunits      -    parameters.space_stretch=units/2   +    parameters.space_stretch=1*units/2        parameters.space_shrink=1*units/3       parameters.x_height=2*units/5       parameters.quad=units    @@ -9380,26 +15656,18 @@ local function copytotfm(data,cache_id)      parameters.designsize=(designsize/10)*65536      parameters.minsize=(minsize/10)*65536      parameters.maxsize=(maxsize/10)*65536 -    parameters.ascender=abs(metadata.ascender or metadata.ascent or 0) -    parameters.descender=abs(metadata.descender or metadata.descent or 0) +    parameters.ascender=abs(metadata.ascender or 0) +    parameters.descender=abs(metadata.descender or 0)      parameters.units=units      properties.space=spacer      properties.encodingbytes=2 -    properties.format=data.format or otf_format(filename) or formats.otf +    properties.format=data.format or formats.otf      properties.noglyphnames=true      properties.filename=filename      properties.fontname=fontname      properties.fullname=fullname      properties.psname=psname      properties.name=filename or fullname -    if warnings and #warnings>0 then -      report_otf("warnings for font: %s",filename) -      report_otf() -      for i=1,#warnings do -        report_otf("  %s",warnings[i]) -      end -      report_otf() -    end      return {        characters=characters,        descriptions=descriptions, @@ -9408,7 +15676,6 @@ local function copytotfm(data,cache_id)        resources=resources,        properties=properties,        goodies=goodies, -      warnings=warnings,      }    end  end @@ -9418,38 +15685,13 @@ local function otftotfm(specification)    if not tfmdata then      local name=specification.name      local sub=specification.sub +    local subindex=specification.subindex      local filename=specification.filename      local features=specification.features.normal      local rawdata=otf.load(filename,sub,features and features.featurefile)      if rawdata and next(rawdata) then        local descriptions=rawdata.descriptions -      local duplicates=rawdata.resources.duplicates -      if duplicates then -        local nofduplicates,nofduplicated=0,0 -        for parent,list in next,duplicates do -          if type(list)=="table" then -            local n=#list -            for i=1,n do -              local unicode=list[i] -              if not descriptions[unicode] then -                descriptions[unicode]=descriptions[parent]  -                nofduplicated=nofduplicated+1 -              end -            end -            nofduplicates=nofduplicates+n -          else -            if not descriptions[list] then -              descriptions[list]=descriptions[parent]  -              nofduplicated=nofduplicated+1 -            end -            nofduplicates=nofduplicates+1 -          end -        end -        if trace_otf and nofduplicated~=nofduplicates then -          report_otf("%i extra duplicates copied out of %i",nofduplicated,nofduplicates) -        end -      end -      rawdata.lookuphash={} +      rawdata.lookuphash={}         tfmdata=copytotfm(rawdata,cache_id)        if tfmdata and next(tfmdata) then          local features=constructors.checkedfeatures("otf",features) @@ -9501,33 +15743,129 @@ registerotffeature {    }  }  function otf.collectlookups(rawdata,kind,script,language) -  local sequences=rawdata.resources.sequences -  if sequences then -    local featuremap,featurelist={},{} -    for s=1,#sequences do -      local sequence=sequences[s] -      local features=sequence.features -      features=features and features[kind] -      features=features and (features[script]  or features[default] or features[wildcard]) -      features=features and (features[language] or features[default] or features[wildcard]) -      if features then -        local subtables=sequence.subtables -        if subtables then -          for s=1,#subtables do -            local ss=subtables[s] -            if not featuremap[s] then -              featuremap[ss]=true -              featurelist[#featurelist+1]=ss +  if not kind then +    return +  end +  if not script then +    script=default +  end +  if not language then +    language=default +  end +  local lookupcache=rawdata.lookupcache +  if not lookupcache then +    lookupcache={} +    rawdata.lookupcache=lookupcache +  end +  local kindlookup=lookupcache[kind] +  if not kindlookup then +    kindlookup={} +    lookupcache[kind]=kindlookup +  end +  local scriptlookup=kindlookup[script] +  if not scriptlookup then +    scriptlookup={} +    kindlookup[script]=scriptlookup +  end +  local languagelookup=scriptlookup[language] +  if not languagelookup then +    local sequences=rawdata.resources.sequences +    local featuremap={} +    local featurelist={} +    if sequences then +      for s=1,#sequences do +        local sequence=sequences[s] +        local features=sequence.features +        if features then +          features=features[kind] +          if features then +            features=features[script] or features[wildcard] +            if features then +              features=features[language] or features[wildcard] +              if features then +                if not featuremap[sequence] then +                  featuremap[sequence]=true +                  featurelist[#featurelist+1]=sequence +                end +              end              end            end          end        end +      if #featurelist==0 then +        featuremap,featurelist=false,false +      end +    else +      featuremap,featurelist=false,false +    end +    languagelookup={ featuremap,featurelist } +    scriptlookup[language]=languagelookup +  end +  return unpack(languagelookup) +end +local function getgsub(tfmdata,k,kind,value) +  local shared=tfmdata.shared +  local rawdata=shared and shared.rawdata +  if rawdata then +    local sequences=rawdata.resources.sequences +    if sequences then +      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 +          local nofsteps=lookup.nofsteps +          for i=1,nofsteps do +            local coverage=steps[i].coverage +            if coverage then +              local found=coverage[k] +              if found then +                return found,lookup.type +              end +            end +          end +        end +      end +    end +  end +end +otf.getgsub=getgsub  +function otf.getsubstitution(tfmdata,k,kind,value) +  local found,kind=getgsub(tfmdata,k,kind) +  if not found then +  elseif kind=="gsub_single" then +    return found +  elseif kind=="gsub_alternate" then +    local choice=tonumber(value) or 1  +    return found[choice] or found[1] or k +  end +  return k +end +otf.getalternate=otf.getsubstitution +function otf.getmultiple(tfmdata,k,kind) +  local found,kind=getgsub(tfmdata,k,kind) +  if found and kind=="gsub_multiple" then +    return found +  end +  return { k } +end +function otf.getkern(tfmdata,left,right,kind) +  local kerns=getgsub(tfmdata,left,kind or "kern",true)  +  if kerns then +    local found=kerns[right] +    local kind=type(found) +    if kind=="table" then +      found=found[1][3]  +    elseif kind~="number" then +      found=false      end -    if #featurelist>0 then -      return featuremap,featurelist +    if found then +      return found*tfmdata.parameters.factor      end    end -  return nil,nil +  return 0  end  local function check_otf(forced,specification,suffix)    local name=specification.name @@ -9555,7 +15893,6 @@ readers.opentype=opentypereader  function readers.otf (specification) return opentypereader(specification,"otf") end  function readers.ttf (specification) return opentypereader(specification,"ttf") end  function readers.ttc (specification) return opentypereader(specification,"ttf") end -function readers.dfont(specification) return opentypereader(specification,"ttf") end  function otf.scriptandlanguage(tfmdata,attr)    local properties=tfmdata.properties    return properties.script or "dflt",properties.language or "dflt" @@ -9564,110 +15901,55 @@ local function justset(coverage,unicode,replacement)    coverage[unicode]=replacement  end  otf.coverup={ -  stepkey="subtables", +  stepkey="steps",    actions={ +    chainsubstitution=justset, +    chainposition=justset,      substitution=justset,      alternate=justset,      multiple=justset, -    ligature=justset,      kern=justset, -    chainsubstitution=justset, -    chainposition=justset, +    pair=justset, +    ligature=function(coverage,unicode,ligature) +      local first=ligature[1] +      local tree=coverage[first] +      if not tree then +        tree={} +        coverage[first]=tree +      end +      for i=2,#ligature do +        local l=ligature[i] +        local t=tree[l] +        if not t then +          t={} +          tree[l]=t +        end +        tree=t +      end +      tree.ligature=unicode +    end,    }, -  register=function(coverage,lookuptype,format,feature,n,descriptions,resources) -    local name=formatters["ctx_%s_%s_%s"](feature,lookuptype,n)  -    if lookuptype=="kern" then -      resources.lookuptypes[name]="position" -    else -      resources.lookuptypes[name]=lookuptype -    end -    for u,c in next,coverage do -      local description=descriptions[u] -      local slookups=description.slookups -      if slookups then -        slookups[name]=c -      else -        description.slookups={ [name]=c } -      end -    end -    return name +  register=function(coverage,featuretype,format) +    return { +      format=format, +      coverage=coverage, +    }    end  } -local function getgsub(tfmdata,k,kind) -  local description=tfmdata.descriptions[k] -  if description then -    local slookups=description.slookups  -    if slookups then -      local shared=tfmdata.shared -      local rawdata=shared and shared.rawdata -      if rawdata then -        local lookuptypes=rawdata.resources.lookuptypes -        if lookuptypes then -          local properties=tfmdata.properties -          local validlookups,lookuplist=otf.collectlookups(rawdata,kind,properties.script,properties.language) -          if validlookups then -            for l=1,#lookuplist do -              local lookup=lookuplist[l] -              local found=slookups[lookup] -              if found then -                return found,lookuptypes[lookup] -              end -            end -          end -        end -      end -    end -  end -end -otf.getgsub=getgsub  -function otf.getsubstitution(tfmdata,k,kind,value) -  local found,kind=getgsub(tfmdata,k,kind) -  if not found then -  elseif kind=="substitution" then -    return found -  elseif kind=="alternate" then -    local choice=tonumber(value) or 1  -    return found[choice] or found[1] or k -  end -  return k -end -otf.getalternate=otf.getsubstitution -function otf.getmultiple(tfmdata,k,kind) -  local found,kind=getgsub(tfmdata,k,kind) -  if found and kind=="multiple" then -    return found -  end -  return { k } -end -function otf.getkern(tfmdata,left,right,kind) -  local kerns=getgsub(tfmdata,left,kind or "kern",true)  -  if kerns then -    local found=kerns[right] -    local kind=type(found) -    if kind=="table" then -      found=found[1][3]  -    elseif kind~="number" then -      found=false -    end -    if found then -      return found*tfmdata.parameters.factor -    end -  end -  return 0 -end  end -- closure  do -- begin closure to overcome local limits and interference -if not modules then modules={} end modules ['font-otb']={ +if not modules then modules={} end modules ['font-oto']={     version=1.001,    comment="companion to font-ini.mkiv",    author="Hans Hagen, PRAGMA-ADE, Hasselt NL",    copyright="PRAGMA ADE / ConTeXt Development Team",    license="see context related readme files"  } -local concat=table.concat +local concat,unpack=table.concat,table.unpack +local insert,remove=table.insert,table.remove  local format,gmatch,gsub,find,match,lower,strip=string.format,string.gmatch,string.gsub,string.find,string.match,string.lower,string.strip  local type,next,tonumber,tostring,rawget=type,next,tonumber,tostring,rawget  local lpegmatch=lpeg.match @@ -9677,7 +15959,6 @@ local trace_singles=false trackers.register("otf.singles",function(v) trace_sing  local trace_multiples=false trackers.register("otf.multiples",function(v) trace_multiples=v end)  local trace_alternatives=false trackers.register("otf.alternatives",function(v) trace_alternatives=v end)  local trace_ligatures=false trackers.register("otf.ligatures",function(v) trace_ligatures=v end) -local trace_ligatures_detail=false trackers.register("otf.ligatures.detail",function(v) trace_ligatures_detail=v end)  local trace_kerns=false trackers.register("otf.kerns",function(v) trace_kerns=v end)  local trace_preparing=false trackers.register("otf.preparing",function(v) trace_preparing=v end)  local report_prepare=logs.reporter("fonts","otf prepare") @@ -9716,48 +15997,36 @@ local function gref(descriptions,n)      return "<error in base mode tracing>"    end  end -local function cref(feature,lookuptags,lookupname) -  if lookupname then -    return formatters["feature %a, lookup %a"](feature,lookuptags[lookupname]) -  else -    return formatters["feature %a"](feature) -  end +local function cref(feature,sequence) +  return formatters["feature %a, type %a, chain lookup %a"](feature,sequence.type,sequence.name)  end -local function report_alternate(feature,lookuptags,lookupname,descriptions,unicode,replacement,value,comment) +local function report_alternate(feature,sequence,descriptions,unicode,replacement,value,comment)    report_prepare("%s: base alternate %s => %s (%S => %S)", -    cref(feature,lookuptags,lookupname), +    cref(feature,sequence),      gref(descriptions,unicode),      replacement and gref(descriptions,replacement),      value,      comment)  end -local function report_substitution(feature,lookuptags,lookupname,descriptions,unicode,substitution) +local function report_substitution(feature,sequence,descriptions,unicode,substitution)    report_prepare("%s: base substitution %s => %S", -    cref(feature,lookuptags,lookupname), +    cref(feature,sequence),      gref(descriptions,unicode),      gref(descriptions,substitution))  end -local function report_ligature(feature,lookuptags,lookupname,descriptions,unicode,ligature) +local function report_ligature(feature,sequence,descriptions,unicode,ligature)    report_prepare("%s: base ligature %s => %S", -    cref(feature,lookuptags,lookupname), +    cref(feature,sequence),      gref(descriptions,ligature),      gref(descriptions,unicode))  end -local function report_kern(feature,lookuptags,lookupname,descriptions,unicode,otherunicode,value) +local function report_kern(feature,sequence,descriptions,unicode,otherunicode,value)    report_prepare("%s: base kern %s + %s => %S", -    cref(feature,lookuptags,lookupname), +    cref(feature,sequence),      gref(descriptions,unicode),      gref(descriptions,otherunicode),      value)  end -local basemethods={} -local basemethod="<unset>" -local function applybasemethod(what,...) -  local m=basemethods[basemethod][what] -  if m then -    return m(...) -  end -end  local basehash,basehashes,applied={},1,{}  local function registerbasehash(tfmdata)    local properties=tfmdata.properties @@ -9775,239 +16044,6 @@ end  local function registerbasefeature(feature,value)    applied[#applied+1]=feature.."="..tostring(value)  end -local trace=false -local function finalize_ligatures(tfmdata,ligatures) -  local nofligatures=#ligatures -  if nofligatures>0 then -    local characters=tfmdata.characters -    local descriptions=tfmdata.descriptions -    local resources=tfmdata.resources -    local unicodes=resources.unicodes  -    local private=resources.private -    local alldone=false -    while not alldone do -      local done=0 -      for i=1,nofligatures do -        local ligature=ligatures[i] -        if ligature then -          local unicode,lookupdata=ligature[1],ligature[2] -          if trace_ligatures_detail then -            report_prepare("building % a into %a",lookupdata,unicode) -          end -          local size=#lookupdata -          local firstcode=lookupdata[1]  -          local firstdata=characters[firstcode] -          local okay=false -          if firstdata then -            local firstname="ctx_"..firstcode -            for i=1,size-1 do  -              local firstdata=characters[firstcode] -              if not firstdata then -                firstcode=private -                if trace_ligatures_detail then -                  report_prepare("defining %a as %a",firstname,firstcode) -                end -                unicodes[firstname]=firstcode -                firstdata={ intermediate=true,ligatures={} } -                characters[firstcode]=firstdata -                descriptions[firstcode]={ name=firstname } -                private=private+1 -              end -              local target -              local secondcode=lookupdata[i+1] -              local secondname=firstname.."_"..secondcode -              if i==size-1 then -                target=unicode -                if not rawget(unicodes,secondname) then -                  unicodes[secondname]=unicode  -                end -                okay=true -              else -                target=rawget(unicodes,secondname) -                if not target then -                  break -                end -              end -              if trace_ligatures_detail then -                report_prepare("codes (%a,%a) + (%a,%a) -> %a",firstname,firstcode,secondname,secondcode,target) -              end -              local firstligs=firstdata.ligatures -              if firstligs then -                firstligs[secondcode]={ char=target } -              else -                firstdata.ligatures={ [secondcode]={ char=target } } -              end -              firstcode=target -              firstname=secondname -            end -          elseif trace_ligatures_detail then -            report_prepare("no glyph (%a,%a) for building %a",firstname,firstcode,target) -          end -          if okay then -            ligatures[i]=false -            done=done+1 -          end -        end -      end -      alldone=done==0 -    end -    if trace_ligatures_detail then -      for k,v in table.sortedhash(characters) do -        if v.ligatures then -          table.print(v,k) -        end -      end -    end -    resources.private=private -    return true -  end -end -local function preparesubstitutions(tfmdata,feature,value,validlookups,lookuplist) -  local characters=tfmdata.characters -  local descriptions=tfmdata.descriptions -  local resources=tfmdata.resources -  local properties=tfmdata.properties -  local changed=tfmdata.changed -  local lookuphash=resources.lookuphash -  local lookuptypes=resources.lookuptypes -  local lookuptags=resources.lookuptags -  local ligatures={} -  local alternate=tonumber(value) or true and 1 -  local defaultalt=otf.defaultbasealternate -  local trace_singles=trace_baseinit and trace_singles -  local trace_alternatives=trace_baseinit and trace_alternatives -  local trace_ligatures=trace_baseinit and trace_ligatures -  local actions={ -    substitution=function(lookupdata,lookuptags,lookupname,description,unicode) -      if trace_singles then -        report_substitution(feature,lookuptags,lookupname,descriptions,unicode,lookupdata) -      end -      changed[unicode]=lookupdata -    end, -    alternate=function(lookupdata,lookuptags,lookupname,description,unicode) -      local replacement=lookupdata[alternate] -      if replacement then -        changed[unicode]=replacement -        if trace_alternatives then -          report_alternate(feature,lookuptags,lookupname,descriptions,unicode,replacement,value,"normal") -        end -      elseif defaultalt=="first" then -        replacement=lookupdata[1] -        changed[unicode]=replacement -        if trace_alternatives then -          report_alternate(feature,lookuptags,lookupname,descriptions,unicode,replacement,value,defaultalt) -        end -      elseif defaultalt=="last" then -        replacement=lookupdata[#data] -        if trace_alternatives then -          report_alternate(feature,lookuptags,lookupname,descriptions,unicode,replacement,value,defaultalt) -        end -      else -        if trace_alternatives then -          report_alternate(feature,lookuptags,lookupname,descriptions,unicode,replacement,value,"unknown") -        end -      end -    end, -    ligature=function(lookupdata,lookuptags,lookupname,description,unicode) -      if trace_ligatures then -        report_ligature(feature,lookuptags,lookupname,descriptions,unicode,lookupdata) -      end -      ligatures[#ligatures+1]={ unicode,lookupdata } -    end, -  } -  for unicode,character in next,characters do -    local description=descriptions[unicode] -    local lookups=description.slookups -    if lookups then -      for l=1,#lookuplist do -        local lookupname=lookuplist[l] -        local lookupdata=lookups[lookupname] -        if lookupdata then -          local lookuptype=lookuptypes[lookupname] -          local action=actions[lookuptype] -          if action then -            action(lookupdata,lookuptags,lookupname,description,unicode) -          end -        end -      end -    end -    local lookups=description.mlookups -    if lookups then -      for l=1,#lookuplist do -        local lookupname=lookuplist[l] -        local lookuplist=lookups[lookupname] -        if lookuplist then -          local lookuptype=lookuptypes[lookupname] -          local action=actions[lookuptype] -          if action then -            for i=1,#lookuplist do -              action(lookuplist[i],lookuptags,lookupname,description,unicode) -            end -          end -        end -      end -    end -  end -  properties.hasligatures=finalize_ligatures(tfmdata,ligatures) -end -local function preparepositionings(tfmdata,feature,value,validlookups,lookuplist)  -  local characters=tfmdata.characters -  local descriptions=tfmdata.descriptions -  local resources=tfmdata.resources -  local properties=tfmdata.properties -  local lookuptags=resources.lookuptags -  local sharedkerns={} -  local traceindeed=trace_baseinit and trace_kerns -  local haskerns=false -  for unicode,character in next,characters do -    local description=descriptions[unicode] -    local rawkerns=description.kerns  -    if rawkerns then -      local s=sharedkerns[rawkerns] -      if s==false then -      elseif s then -        character.kerns=s -      else -        local newkerns=character.kerns -        local done=false -        for l=1,#lookuplist do -          local lookup=lookuplist[l] -          local kerns=rawkerns[lookup] -          if kerns then -            for otherunicode,value in next,kerns do -              if value==0 then -              elseif not newkerns then -                newkerns={ [otherunicode]=value } -                done=true -                if traceindeed then -                  report_kern(feature,lookuptags,lookup,descriptions,unicode,otherunicode,value) -                end -              elseif not newkerns[otherunicode] then  -                newkerns[otherunicode]=value -                done=true -                if traceindeed then -                  report_kern(feature,lookuptags,lookup,descriptions,unicode,otherunicode,value) -                end -              end -            end -          end -        end -        if done then -          sharedkerns[rawkerns]=newkerns -          character.kerns=newkerns  -          haskerns=true -        else -          sharedkerns[rawkerns]=false -        end -      end -    end -  end -  properties.haskerns=haskerns -end -basemethods.independent={ -  preparesubstitutions=preparesubstitutions, -  preparepositionings=preparepositionings, -}  local function makefake(tfmdata,name,present)    local resources=tfmdata.resources    local private=resources.private @@ -10028,13 +16064,13 @@ local function make_1(present,tree,name)      end    end  end -local function make_2(present,tfmdata,characters,tree,name,preceding,unicode,done,lookuptags,lookupname) +local function make_2(present,tfmdata,characters,tree,name,preceding,unicode,done)    for k,v in next,tree do      if k=="ligature" then        local character=characters[preceding]        if not character then          if trace_baseinit then -          report_prepare("weird ligature in lookup %a, current %C, preceding %C",lookuptags[lookupname],v,preceding) +          report_prepare("weird ligature in lookup %a, current %C, preceding %C",sequence.name,v,preceding)          end          character=makefake(tfmdata,name,present)        end @@ -10045,9 +16081,9 @@ local function make_2(present,tfmdata,characters,tree,name,preceding,unicode,don          character.ligatures={ [unicode]={ char=v } }        end        if done then -        local d=done[lookupname] +        local d=done[name]          if not d then -          done[lookupname]={ "dummy",v } +          done[name]={ "dummy",v }          else            d[#d+1]=v          end @@ -10055,7 +16091,7 @@ local function make_2(present,tfmdata,characters,tree,name,preceding,unicode,don      else        local code=present[name] or unicode        local name=name.."_"..k -      make_2(present,tfmdata,characters,v,name,code,k,done,lookuptags,lookupname) +      make_2(present,tfmdata,characters,v,name,code,k,done)      end    end  end @@ -10064,52 +16100,63 @@ local function preparesubstitutions(tfmdata,feature,value,validlookups,lookuplis    local descriptions=tfmdata.descriptions    local resources=tfmdata.resources    local changed=tfmdata.changed -  local lookuphash=resources.lookuphash -  local lookuptypes=resources.lookuptypes -  local lookuptags=resources.lookuptags    local ligatures={}    local alternate=tonumber(value) or true and 1    local defaultalt=otf.defaultbasealternate    local trace_singles=trace_baseinit and trace_singles    local trace_alternatives=trace_baseinit and trace_alternatives    local trace_ligatures=trace_baseinit and trace_ligatures -  for l=1,#lookuplist do -    local lookupname=lookuplist[l] -    local lookupdata=lookuphash[lookupname] -    local lookuptype=lookuptypes[lookupname] -    for unicode,data in next,lookupdata do -      if lookuptype=="substitution" then -        if trace_singles then -          report_substitution(feature,lookuptags,lookupname,descriptions,unicode,data) -        end -        changed[unicode]=data -      elseif lookuptype=="alternate" then -        local replacement=data[alternate] -        if replacement then -          changed[unicode]=replacement -          if trace_alternatives then -            report_alternate(feature,lookuptags,lookupname,descriptions,unicode,replacement,value,"normal") -          end -        elseif defaultalt=="first" then -          replacement=data[1] -          changed[unicode]=replacement -          if trace_alternatives then -            report_alternate(feature,lookuptags,lookupname,descriptions,unicode,replacement,value,defaultalt) -          end -        elseif defaultalt=="last" then -          replacement=data[#data] -          if trace_alternatives then -            report_alternate(feature,lookuptags,lookupname,descriptions,unicode,replacement,value,defaultalt) +  for i=1,#lookuplist do +    local sequence=lookuplist[i] +    local steps=sequence.steps +    local kind=sequence.type +    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 -        else -          if trace_alternatives then -            report_alternate(feature,lookuptags,lookupname,descriptions,unicode,replacement,value,"unknown") +        end +      end +    elseif kind=="gsub_alternate" then +      for i=1,#steps do +        for unicode,data in next,steps[i].coverage do +          if not changed[unicode] then +            local replacement=data[alternate] +            if replacement then +              changed[unicode]=replacement +              if trace_alternatives then +                report_alternate(feature,sequence,descriptions,unicode,replacement,value,"normal") +              end +            elseif defaultalt=="first" then +              replacement=data[1] +              changed[unicode]=replacement +              if trace_alternatives then +                report_alternate(feature,sequence,descriptions,unicode,replacement,value,defaultalt) +              end +            elseif defaultalt=="last" then +              replacement=data[#data] +              if trace_alternatives then +                report_alternate(feature,sequence,descriptions,unicode,replacement,value,defaultalt) +              end +            else +              if trace_alternatives then +                report_alternate(feature,sequence,descriptions,unicode,replacement,value,"unknown") +              end +            end            end          end -      elseif lookuptype=="ligature" then -        ligatures[#ligatures+1]={ unicode,data,lookupname } -        if trace_ligatures then -          report_ligature(feature,lookuptags,lookupname,descriptions,unicode,data) +      end +    elseif kind=="gsub_ligature" then +      for i=1,#steps do +        for unicode,data in next,steps[i].coverage do +          ligatures[#ligatures+1]={ unicode,data,"" }  +          if trace_ligatures then +            report_ligature(feature,sequence,descriptions,unicode,data) +          end          end        end      end @@ -10127,7 +16174,7 @@ local function preparesubstitutions(tfmdata,feature,value,validlookups,lookuplis      for i=1,nofligatures do        local ligature=ligatures[i]        local unicode,tree,lookupname=ligature[1],ligature[2],ligature[3] -      make_2(present,tfmdata,characters,tree,"ctx_"..unicode,unicode,unicode,done,lookuptags,lookupname) +      make_2(present,tfmdata,characters,tree,"ctx_"..unicode,unicode,unicode,done,sequence)      end    end  end @@ -10136,30 +16183,62 @@ local function preparepositionings(tfmdata,feature,value,validlookups,lookuplist    local descriptions=tfmdata.descriptions    local resources=tfmdata.resources    local properties=tfmdata.properties -  local lookuphash=resources.lookuphash -  local lookuptags=resources.lookuptags    local traceindeed=trace_baseinit and trace_kerns -  for l=1,#lookuplist do -    local lookupname=lookuplist[l] -    local lookupdata=lookuphash[lookupname] -    for unicode,data in next,lookupdata do -      local character=characters[unicode] -      local kerns=character.kerns -      if not kerns then -        kerns={} -        character.kerns=kerns -      end -      if traceindeed then -        for otherunicode,kern in next,data do -          if not kerns[otherunicode] and kern~=0 then -            kerns[otherunicode]=kern -            report_kern(feature,lookuptags,lookup,descriptions,unicode,otherunicode,kern) +  for i=1,#lookuplist do +    local sequence=lookuplist[i] +    local steps=sequence.steps +    local kind=sequence.type +    local format=sequence.format +    if kind=="gpos_pair" then +      for i=1,#steps do +        local step=steps[i] +        if step.format=="kern" then +          for unicode,data in next,steps[i].coverage do +            local character=characters[unicode] +            local kerns=character.kerns +            if not kerns then +              kerns={} +              character.kerns=kerns +            end +            if traceindeed then +              for otherunicode,kern in next,data do +                if not kerns[otherunicode] and kern~=0 then +                  kerns[otherunicode]=kern +                  report_kern(feature,sequence,descriptions,unicode,otherunicode,kern) +                end +              end +            else +              for otherunicode,kern in next,data do +                if not kerns[otherunicode] and kern~=0 then +                  kerns[otherunicode]=kern +                end +              end +            end            end -        end -      else -        for otherunicode,kern in next,data do -          if not kerns[otherunicode] and kern~=0 then -            kerns[otherunicode]=kern +        else +          for unicode,data in next,steps[i].coverage do +            local character=characters[unicode] +            local kerns=character.kerns +            for otherunicode,kern in next,data do +              if not kern[2] and not (kerns and kerns[otherunicode]) then +                local kern=kern[1] +                if kern[1]~=0 or kern[2]~=0 or kern[4]~=0 then +                else +                  kern=kern[3] +                  if kern~=0 then +                    if kerns then +                      kerns[otherunicode]=kern +                    else +                      kerns={ [otherunicode]=kern } +                      character.kerns=kerns +                    end +                    if traceindeed then +                      report_kern(feature,sequence,descriptions,unicode,otherunicode,kern) +                    end +                  end +                end +              end +            end            end          end        end @@ -10167,28 +16246,22 @@ local function preparepositionings(tfmdata,feature,value,validlookups,lookuplist    end  end  local function initializehashes(tfmdata) -  nodeinitializers.features(tfmdata)  end -basemethods.shared={ -  initializehashes=initializehashes, -  preparesubstitutions=preparesubstitutions, -  preparepositionings=preparepositionings, -} -basemethod="independent"  local function featuresinitializer(tfmdata,value)    if true then       local starttime=trace_preparing and os.clock()      local features=tfmdata.shared.features      local fullname=tfmdata.properties.fullname or "?"      if features then -      applybasemethod("initializehashes",tfmdata) +      initializehashes(tfmdata)        local collectlookups=otf.collectlookups        local rawdata=tfmdata.shared.rawdata        local properties=tfmdata.properties -      local script=properties.script   -      local language=properties.language  -      local basesubstitutions=rawdata.resources.features.gsub -      local basepositionings=rawdata.resources.features.gpos +      local script=properties.script +      local language=properties.language +      local rawfeatures=rawdata.resources.features +      local basesubstitutions=rawfeatures and rawfeatures.gsub +      local basepositionings=rawfeatures and rawfeatures.gpos        if basesubstitutions or basepositionings then          local sequences=tfmdata.resources.sequences          for s=1,#sequences do @@ -10207,13 +16280,13 @@ local function featuresinitializer(tfmdata,value)                      if trace_preparing then                        report_prepare("filtering base %s feature %a for %a with value %a","sub",feature,fullname,value)                      end -                    applybasemethod("preparesubstitutions",tfmdata,feature,value,validlookups,lookuplist) +                    preparesubstitutions(tfmdata,feature,value,validlookups,lookuplist)                      registerbasefeature(feature,value)                    elseif basepositionings and basepositionings[feature] then                      if trace_preparing then                        report_prepare("filtering base %a feature %a for %a with value %a","pos",feature,fullname,value)                      end -                    applybasemethod("preparepositionings",tfmdata,feature,value,validlookups,lookuplist) +                    preparepositionings(tfmdata,feature,value,validlookups,lookuplist)                      registerbasefeature(feature,value)                    end                  end @@ -10237,17 +16310,12 @@ registerotffeature {      base=featuresinitializer,    }  } -directives.register("fonts.otf.loader.basemethod",function(v) -  if basemethods[v] then -    basemethod=v -  end -end)  end -- closure  do -- begin closure to overcome local limits and interference -if not modules then modules={} end modules ['font-inj']={ +if not modules then modules={} end modules ['font-otj']={    version=1.001,    comment="companion to font-lib.mkiv",    author="Hans Hagen, PRAGMA-ADE, Hasselt NL", @@ -10258,17 +16326,30 @@ if not nodes.properties then return end  local next,rawget=next,rawget  local utfchar=utf.char  local fastcopy=table.fastcopy -local trace_injections=false trackers.register("fonts.injections",function(v) trace_injections=v end) +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 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")  local attributes,nodes,node=attributes,nodes,node  fonts=fonts -local fontdata=fonts.hashes.identifiers +local hashes=fonts.hashes +local fontdata=hashes.identifiers +local parameters=fonts.hashes.parameters +local resources=fonts.hashes.resources  nodes.injections=nodes.injections or {}  local injections=nodes.injections +local tracers=nodes.tracers +local setcolor=tracers and tracers.colors.set +local resetcolor=tracers and tracers.colors.reset  local nodecodes=nodes.nodecodes  local glyph_code=nodecodes.glyph  local disc_code=nodecodes.disc  local kern_code=nodecodes.kern +local glue_code=nodecodes.glue  local nuts=nodes.nuts  local nodepool=nuts.pool  local newkern=nodepool.kern @@ -10282,7 +16363,12 @@ local getid=nuts.getid  local getfont=nuts.getfont  local getsubtype=nuts.getsubtype  local getchar=nuts.getchar +local getboth=nuts.getboth +local ischar=nuts.is_char +local getdisc=nuts.getdisc +local setdisc=nuts.setdisc  local traverse_id=nuts.traverse_id +local traverse_char=nuts.traverse_char  local insert_node_before=nuts.insert_before  local insert_node_after=nuts.insert_after  local find_tail=nuts.tail @@ -10317,7 +16403,7 @@ function injections.copy(target,source)    local sp=rawget(properties,source)    if sp then      local tp=rawget(properties,target) -    local si=rawget(sp,"injections") +    local si=sp.injections      if si then        si=fastcopy(si)        if tp then @@ -10344,7 +16430,7 @@ end  function injections.setligaindex(n,index)    local p=rawget(properties,n)    if p then -    local i=rawget(p,"injections") +    local i=p.injections      if i then        i.ligaindex=index      else @@ -10363,7 +16449,7 @@ end  function injections.getligaindex(n,default)    local p=rawget(properties,n)    if p then -    local i=rawget(p,"injections") +    local i=p.injections      if i then        return i.ligaindex or default      end @@ -10381,9 +16467,12 @@ function injections.setcursive(start,nxt,factor,rlmode,exit,entry,tfmstart,tfmne    else      dx=dx-ws    end +  if dx==0 then +    dx=0 +  end    local p=rawget(properties,start)    if p then -    local i=rawget(p,"injections") +    local i=p.injections      if i then        i.cursiveanchor=true      else @@ -10400,7 +16489,7 @@ function injections.setcursive(start,nxt,factor,rlmode,exit,entry,tfmstart,tfmne    end    local p=rawget(properties,nxt)    if p then -    local i=rawget(p,"injections") +    local i=p.injections      if i then        i.cursivex=dx        i.cursivey=dy @@ -10518,7 +16607,7 @@ function injections.setmark(start,base,factor,rlmode,ba,ma,tfmbase,mkmk)    end    local p=rawget(properties,start)    if p then -    local i=rawget(p,"injections") +    local i=p.injections      if i then        if i.markmark then        else @@ -10622,10 +16711,9 @@ local function trace(head,where)        show(n,"preinjections",false,"<")        show(n,"postinjections",false,">")        show(n,"replaceinjections",false,"=") +      show(n,"emptyinjections",false,"*")      elseif id==disc_code then -      local pre=getfield(n,"pre") -      local post=getfield(n,"post") -      local replace=getfield(n,"replace") +      local pre,post,replace=getdisc(n)        if pre then          showsub(pre,"preinjections","pre")        end @@ -10635,6 +16723,7 @@ local function trace(head,where)        if replace then          showsub(replace,"replaceinjections","replace")        end +      show(n,"emptyinjections",false,"*")      end      n=getnext(n)    end @@ -10659,421 +16748,372 @@ local function show_result(head)      current=getnext(current)    end  end -local function collect_glyphs(head,offsets) -  local glyphs,glyphi,nofglyphs={},{},0 -  local marks,marki,nofmarks={},{},0 -  local nf,tm=nil,nil -  local n=head -  local function identify(n,what) -    local f=getfont(n) -    if f~=nf then -      nf=f -      tm=fontdata[nf].resources -      if tm then -        tm=tm.marks -      end -    end -    if tm and tm[getchar(n)] then -      nofmarks=nofmarks+1 -      marks[nofmarks]=n -      marki[nofmarks]="injections" -    else -      nofglyphs=nofglyphs+1 -      glyphs[nofglyphs]=n -      glyphi[nofglyphs]=what -    end -    if offsets then -      local p=rawget(properties,n) -      if p then -        local i=rawget(p,what) -        if i then -          local yoffset=i.yoffset -          if yoffset and yoffset~=0 then -            setfield(n,"yoffset",yoffset) -          end -        end -      end -    end +local function inject_kerns_only(head,where) +  head=tonut(head) +  if trace_injections then +    trace(head,"kerns")    end -  while n do  -    local id=getid(n) +  local current=head +  local prev=nil +  local next=nil +  local prevdisc=nil +  local prevglyph=nil +  local pre=nil  +  local post=nil  +  local replace=nil  +  local pretail=nil  +  local posttail=nil  +  local replacetail=nil  +  while current do +    local id=getid(current) +    local next=getnext(current)      if id==glyph_code then -      identify(n,"injections") -    elseif id==disc_code then -      local d=getfield(n,"pre") -      if d then -        for n in traverse_id(glyph_code,d) do -          if getsubtype(n)<256 then -            identify(n,"preinjections") -          end -        end -			end -      local d=getfield(n,"post") -      if d then -        for n in traverse_id(glyph_code,d) do -          if getsubtype(n)<256 then -            identify(n,"postinjections") -          end -        end -			end -      local d=getfield(n,"replace") -      if d then -        for n in traverse_id(glyph_code,d) do -          if getsubtype(n)<256 then -            identify(n,"replaceinjections") -          end -        end -			end -    end -		n=getnext(n) -  end -  return glyphs,glyphi,nofglyphs,marks,marki,nofmarks -end -local function inject_marks(marks,marki,nofmarks) -  for i=1,nofmarks do -    local n=marks[i] -    local pn=rawget(properties,n) -    if pn then -      local ni=marki[i] -      local pn=rawget(pn,ni) -      if pn then -        local p=pn.markbasenode +      if getsubtype(current)<256 then +        local p=rawget(properties,current)          if p then -          local px=getfield(p,"xoffset") -          local ox=0 -          local rightkern=nil -          local pp=rawget(properties,p) -          if pp then -            pp=rawget(pp,ni) -            if pp then -              rightkern=pp.rightkern +          local i=p.injections +          if i then +            local leftkern=i.leftkern +            if leftkern and leftkern~=0 then +              if use_advance then +                setfield(current,"xoffset",leftkern) +                setfield(current,"xadvance",leftkern) +              else +                insert_node_before(head,current,newkern(leftkern)) +              end              end            end -          if rightkern then  -            if pn.markdir<0 then -              ox=px-pn.markx-rightkern +          if prevdisc then +            local done=false +            if post then +              local i=p.postinjections +              if i then +                local leftkern=i.leftkern +                if leftkern and leftkern~=0 then +                  if use_advance then +                    setfield(post,"xadvance",leftkern) +                  else +                    insert_node_after(post,posttail,newkern(leftkern)) +                    done=true +                  end +                end +              end +            end +            if replace then +              local i=p.replaceinjections +              if i then +                local leftkern=i.leftkern +                if leftkern and leftkern~=0 then +                  if use_advance then +                    setfield(replace,"xadvance",leftkern) +                  else +                    insert_node_after(replace,replacetail,newkern(leftkern)) +                    done=true +                  end +                end +              end              else -							 -	 -							if false then -                local leftkern=pp.leftkern -                if leftkern then -                  ox=px-pn.markx-leftkern -                else -                  ox=px-pn.markx +              local i=p.emptyinjections +              if i then +                local leftkern=i.leftkern +                if leftkern and leftkern~=0 then +                  setfield(prev,"replace",newkern(leftkern))                   end -              else -                ox=px-pn.markx                end              end -          else -              ox=px-pn.markx -            local wn=getfield(n,"width")  -            if wn~=0 then -              pn.leftkern=-wn/2 -              pn.rightkern=-wn/2 +            if done then +              setdisc(prevdisc,pre,post,replace)              end            end -          setfield(n,"xoffset",ox) -          local py=getfield(p,"yoffset") -          local oy=getfield(n,"yoffset")+py+pn.marky -          setfield(n,"yoffset",oy) -        else          end        end -    end -  end -end -local function inject_cursives(glyphs,glyphi,nofglyphs) -  local cursiveanchor,lastanchor=nil,nil -  local minc,maxc,last=0,0,nil -  for i=1,nofglyphs do -    local n=glyphs[i] -    local pn=rawget(properties,n) -    if pn then -      pn=rawget(pn,glyphi[i]) -    end -    if pn then -      local cursivex=pn.cursivex -      if cursivex then -        if cursiveanchor then -          if cursivex~=0 then -            pn.leftkern=(pn.leftkern or 0)+cursivex -          end -          if lastanchor then -            if maxc==0 then -              minc=lastanchor +      prevdisc=nil +      prevglyph=current +    elseif id==disc_code then +      pre,post,replace,pretail,posttail,replacetail=getdisc(current,true) +      local done=false +      if pre then +        for n in traverse_char(pre) do +          local p=rawget(properties,n) +          if p then +            local i=p.injections or p.preinjections +            if i then +              local leftkern=i.leftkern +              if leftkern and leftkern~=0 then +                if use_advance then +                  setfield(pre,"xoffset",leftkern) +                  setfield(pre,"xadvance",leftkern) +                else +                  pre=insert_node_before(pre,n,newkern(leftkern)) +                  done=true +                end +              end              end -            maxc=lastanchor -            properties[cursiveanchor].cursivedy=pn.cursivey            end -          last=n -        else -          maxc=0          end -      elseif maxc>0 then -        local ny=getfield(n,"yoffset") -        for i=maxc,minc,-1 do -          local ti=glyphs[i] -          ny=ny+properties[ti].cursivedy -          setfield(ti,"yoffset",ny)  +      end +      if post then +        for n in traverse_char(post) do +          local p=rawget(properties,n) +          if p then +            local i=p.injections or p.postinjections +            if i then +              local leftkern=i.leftkern +              if leftkern and leftkern~=0 then +                if use_advance then +                  setfield(post,"xoffset",leftkern) +                  setfield(post,"xadvance",leftkern) +                else +                  post=insert_node_before(post,n,newkern(leftkern)) +                  done=true +                end +              end +            end +          end          end -        maxc=0        end -      if pn.cursiveanchor then -        cursiveanchor=n -        lastanchor=i -      else -        cursiveanchor=nil -        lastanchor=nil -        if maxc>0 then -          local ny=getfield(n,"yoffset") -          for i=maxc,minc,-1 do -            local ti=glyphs[i] -            ny=ny+properties[ti].cursivedy -            setfield(ti,"yoffset",ny)  +      if replace then +        for n in traverse_char(replace) do +          local p=rawget(properties,n) +          if p then +            local i=p.injections or p.replaceinjections +            if i then +              local leftkern=i.leftkern +              if leftkern and leftkern~=0 then +                if use_advance then +                  setfield(replace,"xoffset",leftkern) +                  setfield(replace,"xadvance",leftkern) +                else +                  replace=insert_node_before(replace,n,newkern(leftkern)) +                  done=true +                end +              end +            end            end -          maxc=0          end        end -    elseif maxc>0 then -      local ny=getfield(n,"yoffset") -      for i=maxc,minc,-1 do -        local ti=glyphs[i] -        ny=ny+properties[ti].cursivedy -        setfield(ti,"yoffset",getfield(ti,"yoffset")+ny)  +      if done then +        setdisc(current,pre,post,replace)        end -      maxc=0 -      cursiveanchor=nil -      lastanchor=nil -    end -  end -  if last and maxc>0 then -    local ny=getfield(last,"yoffset") -    for i=maxc,minc,-1 do -      local ti=glyphs[i] -      ny=ny+properties[ti].cursivedy -      setfield(ti,"yoffset",ny)  -    end -  end -end -local function inject_kerns(head,glist,ilist,length)  -  for i=1,length do -    local n=glist[i] -    local pn=rawget(properties,n) -    if pn then -			local dp=nil -			local dr=nil -      local ni=ilist[i] -      local p=nil -			if ni=="injections" then -				p=getprev(n) -				if p then -					local id=getid(p) -					if id==disc_code then -						dp=getfield(p,"post") -						dr=getfield(p,"replace") -					end -				end -			end -			if dp then -				local i=rawget(pn,"postinjections") -				if i then -					local leftkern=i.leftkern -					if leftkern and leftkern~=0 then -						local t=find_tail(dp) -						insert_node_after(dp,t,newkern(leftkern)) -            setfield(p,"post",dp)  -					end -				end -			end -			if dr then -				local i=rawget(pn,"replaceinjections") -				if i then -					local leftkern=i.leftkern -					if leftkern and leftkern~=0 then -						local t=find_tail(dr) -						insert_node_after(dr,t,newkern(leftkern)) -            setfield(p,"replace",dr)  -					end -				end -			else -				local i=rawget(pn,ni) -				if i then -					local leftkern=i.leftkern -					if leftkern and leftkern~=0 then -						insert_node_before(head,n,newkern(leftkern))  -					end -					local rightkern=i.rightkern -					if rightkern and rightkern~=0 then -						insert_node_after(head,n,newkern(rightkern))  -					end -				end -			end -    end -  end -end -local function inject_everything(head,where) -  head=tonut(head) -  if trace_injections then -    trace(head,"everything") -  end -  local glyphs,glyphi,nofglyphs,marks,marki,nofmarks=collect_glyphs(head,nofregisteredpairs>0) -  if nofglyphs>0 then -    if nofregisteredcursives>0 then -      inject_cursives(glyphs,glyphi,nofglyphs) -    end -    if nofregisteredmarks>0 then  -      inject_marks(marks,marki,nofmarks) +      prevglyph=nil +      prevdisc=current +    else +      prevglyph=nil +      prevdisc=nil      end -    inject_kerns(head,glyphs,glyphi,nofglyphs) +    prev=current +    current=next    end -  if nofmarks>0 then -    inject_kerns(head,marks,marki,nofmarks) -	end    if keepregisteredcounts then      keepregisteredcounts=false    else      nofregisteredkerns=0 -    nofregisteredpairs=0 -    nofregisteredmarks=0 -    nofregisteredcursives=0    end    return tonode(head),true  end -local function inject_kerns_only(head,where) +local function inject_pairs_only(head,where)    head=tonut(head)    if trace_injections then -    trace(head,"kerns") +    trace(head,"pairs")    end -  local n=head -  local p=nil  -  while n do -    local id=getid(n) +  local current=head +  local prev=nil +  local next=nil +  local prevdisc=nil +  local prevglyph=nil +  local pre=nil  +  local post=nil  +  local replace=nil  +  local pretail=nil  +  local posttail=nil  +  local replacetail=nil  +  while current do +    local id=getid(current) +    local next=getnext(current)      if id==glyph_code then -      if getsubtype(n)<256 then -        local pn=rawget(properties,n) -        if pn then -          if p then -            local d=getfield(p,"post") -            if d then -              local i=rawget(pn,"postinjections") +      if getsubtype(current)<256 then +        local p=rawget(properties,current) +        if p then +          local i=p.injections +          if i then +            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)) +            end +            local rightkern=i.rightkern +            if rightkern and rightkern~=0 then +              insert_node_after(head,current,newkern(rightkern)) +            end +          else +            local i=p.emptyinjections +            if i then +              local rightkern=i.rightkern +              if rightkern and rightkern~=0 then +                if next and getid(next)==disc_code then +                  if replace then +                  else +                    setfield(next,"replace",newkern(rightkern))  +                  end +                end +              end +            end +          end +          if prevdisc then +            local done=false +            if post then +              local i=p.postinjections                if i then                  local leftkern=i.leftkern                  if leftkern and leftkern~=0 then -                  local t=find_tail(d) -                  insert_node_after(d,t,newkern(leftkern)) -                  setfield(p,"post",d)  +                  insert_node_after(post,posttail,newkern(leftkern)) +                  done=true                  end                end              end -            local d=getfield(p,"replace") -            if d then -              local i=rawget(pn,"replaceinjections") +            if replace then +              local i=p.replaceinjections                if i then                  local leftkern=i.leftkern                  if leftkern and leftkern~=0 then -                  local t=find_tail(d) -                  insert_node_after(d,t,newkern(leftkern)) -                  setfield(p,"replace",d)  +                  insert_node_after(replace,replacetail,newkern(leftkern)) +                  done=true                  end                end              else -              local i=rawget(pn,"injections") +              local i=p.emptyinjections                if i then                  local leftkern=i.leftkern                  if leftkern and leftkern~=0 then -                  setfield(p,"replace",newkern(leftkern)) +                  setfield(prev,"replace",newkern(leftkern))                   end                end              end -          else -            local i=rawget(pn,"injections") +            if done then +              setdisc(prevdisc,pre,post,replace) +            end +          end +        end +      end +      prevdisc=nil +      prevglyph=current +    elseif id==disc_code then +      pre,post,replace,pretail,posttail,replacetail=getdisc(current,true) +      local done=false +      if pre then +        for n in traverse_char(pre) do +          local p=rawget(properties,n) +          if p then +            local i=p.injections or p.preinjections              if i then +              local yoffset=i.yoffset +              if yoffset and yoffset~=0 then +                setfield(n,"yoffset",yoffset) +              end                local leftkern=i.leftkern                if leftkern and leftkern~=0 then -                head=insert_node_before(head,n,newkern(leftkern)) +                pre=insert_node_before(pre,n,newkern(leftkern)) +                done=true +              end +              local rightkern=i.rightkern +              if rightkern and rightkern~=0 then +                insert_node_after(pre,n,newkern(rightkern)) +                done=true                end              end            end          end        end -      p=nil -    elseif id==disc_code then -      local d=getfield(n,"pre") -      if d then -        local h=d -        for n in traverse_id(glyph_code,d) do -          if getsubtype(n)<256 then -            local pn=rawget(properties,n) -            if pn then -              local i=rawget(pn,"preinjections") -              if i then -                local leftkern=i.leftkern -                if leftkern and leftkern~=0 then -                  h=insert_node_before(h,n,newkern(leftkern)) -                end +      if post then +        for n in traverse_char(post) do +          local p=rawget(properties,n) +          if p then +            local i=p.injections or p.postinjections +            if i then +              local yoffset=i.yoffset +              if yoffset and yoffset~=0 then +                setfield(n,"yoffset",yoffset) +              end +              local leftkern=i.leftkern +              if leftkern and leftkern~=0 then +                post=insert_node_before(post,n,newkern(leftkern)) +                done=true +              end +              local rightkern=i.rightkern +              if rightkern and rightkern~=0 then +                insert_node_after(post,n,newkern(rightkern)) +                done=true                end              end -          else -            break            end          end -        if h~=d then -          setfield(n,"pre",h) -        end        end -      local d=getfield(n,"post") -      if d then -        local h=d -        for n in traverse_id(glyph_code,d) do -          if getsubtype(n)<256 then -            local pn=rawget(properties,n) -            if pn then -              local i=rawget(pn,"postinjections") -              if i then -                local leftkern=i.leftkern -                if leftkern and leftkern~=0 then -                  h=insert_node_before(h,n,newkern(leftkern)) -                end +      if replace then +        for n in traverse_char(replace) do +          local p=rawget(properties,n) +          if p then +            local i=p.injections or p.replaceinjections +            if i then +              local yoffset=i.yoffset +              if yoffset and yoffset~=0 then +                setfield(n,"yoffset",yoffset) +              end +              local leftkern=i.leftkern +              if leftkern and leftkern~=0 then +                replace=insert_node_before(replace,n,newkern(leftkern)) +                done=true +              end +              local rightkern=i.rightkern +              if rightkern and rightkern~=0 then +                insert_node_after(replace,n,newkern(rightkern)) +                done=true                end              end -          else -            break            end          end -        if h~=d then -          setfield(n,"post",h) -        end        end -      local d=getfield(n,"replace") -      if d then -        local h=d -        for n in traverse_id(glyph_code,d) do -          if getsubtype(n)<256 then -            local pn=rawget(properties,n) -            if pn then -              local i=rawget(pn,"replaceinjections") -              if i then -                local leftkern=i.leftkern -                if leftkern and leftkern~=0 then -                  h=insert_node_before(h,n,newkern(leftkern)) -                end +      if prevglyph then +        if pre then +          local p=rawget(properties,prevglyph) +          if p then +            local i=p.preinjections +            if i then +              local rightkern=i.rightkern +              if rightkern and rightkern~=0 then +                pre=insert_node_before(pre,pre,newkern(rightkern)) +                done=true                end              end -          else -            break            end          end -        if h~=d then -          setfield(n,"replace",h) +        if replace then +          local p=rawget(properties,prevglyph) +          if p then +            local i=p.replaceinjections +            if i then +              local rightkern=i.rightkern +              if rightkern and rightkern~=0 then +                replace=insert_node_before(replace,replace,newkern(rightkern)) +                done=true +              end +            end +          end          end        end -      p=n +      if done then +        setdisc(current,pre,post,replace) +      end +      prevglyph=nil +      prevdisc=current      else -      p=nil +      prevglyph=nil +      prevdisc=nil      end -    n=getnext(n) +    prev=current +    current=next    end    if keepregisteredcounts then      keepregisteredcounts=false @@ -11082,185 +17122,484 @@ local function inject_kerns_only(head,where)    end    return tonode(head),true  end -local function inject_pairs_only(head,where) +local function showoffset(n,flag) +  local o=getfield(n,"xoffset") +  if o==0 then +    o=getfield(n,"yoffset") +  end +  if o~=0 then +    setcolor(n,flag and "darkred" or "darkgreen") +  else +    resetcolor(n) +  end +end +local function inject_everything(head,where)    head=tonut(head)    if trace_injections then -    trace(head,"pairs") +    trace(head,"everything")    end -  local n=head -  local p=nil  -  while n do -    local id=getid(n) +  local hascursives=nofregisteredcursives>0 +  local hasmarks=nofregisteredmarks>0 +  local current=head +  local last=nil +  local font=font +  local markdata=nil +  local prev=nil +  local next=nil +  local prevdisc=nil +  local prevglyph=nil +  local pre=nil  +  local post=nil  +  local replace=nil  +  local pretail=nil  +  local posttail=nil  +  local replacetail=nil +  local cursiveanchor=nil +  local minc=0 +  local maxc=0 +  local glyphs={} +  local marks={} +  local nofmarks=0 +  local function processmark(p,n,pn)  +    local px=getfield(p,"xoffset") +    local ox=0 +    local rightkern=nil +    local pp=rawget(properties,p) +    if pp then +      pp=pp.injections +      if pp then +        rightkern=pp.rightkern +      end +    end +    if rightkern then  +      if pn.markdir<0 then +        ox=px-pn.markx-rightkern +      else +        if false then +          local leftkern=pp.leftkern +          if leftkern then +            ox=px-pn.markx-leftkern +          else +            ox=px-pn.markx +          end +        else +          ox=px-pn.markx +        end +      end +    else +        ox=px-pn.markx +      local wn=getfield(n,"width")  +      if wn~=0 then +        pn.leftkern=-wn/2 +        pn.rightkern=-wn/2 +      end +    end +    local oy=getfield(n,"yoffset")+getfield(p,"yoffset")+pn.marky +    setfield(n,"xoffset",ox) +    setfield(n,"yoffset",oy) +    if trace_marks then +      showoffset(n,true) +    end +  end +  while current do +    local id=getid(current) +    local next=getnext(current)      if id==glyph_code then -      if getsubtype(n)<256 then -        local pn=rawget(properties,n) -        if pn then -          if p then -            local d=getfield(p,"post") -            if d then -              local i=rawget(pn,"postinjections") -              if i then -                local leftkern=i.leftkern -                if leftkern and leftkern~=0 then -                  local t=find_tail(d) -                  insert_node_after(d,t,newkern(leftkern)) -                  setfield(p,"post",d)  +      if getsubtype(current)<256 then +        local p=rawget(properties,current) +        if p then +          local i=p.injections +          if i then +            local pm=i.markbasenode +            if pm then +              nofmarks=nofmarks+1 +              marks[nofmarks]=current +            else +              if hascursives then +                local cursivex=i.cursivex +                if cursivex then +                  if cursiveanchor then +                    if cursivex~=0 then +                      i.leftkern=(i.leftkern or 0)+cursivex +                    end +                    if maxc==0 then +                      minc=1 +                      maxc=1 +                      glyphs[1]=cursiveanchor +                    else +                      maxc=maxc+1 +                      glyphs[maxc]=cursiveanchor +                    end +                    properties[cursiveanchor].cursivedy=i.cursivey  +                    last=current +                  else +                    maxc=0 +                  end +                elseif maxc>0 then +                  local ny=getfield(current,"yoffset") +                  for i=maxc,minc,-1 do +                    local ti=glyphs[i] +                    ny=ny+properties[ti].cursivedy +                    setfield(ti,"yoffset",ny)  +                    if trace_cursive then +                      showoffset(ti) +                    end +                  end +                  maxc=0 +                  cursiveanchor=nil +                end +                if i.cursiveanchor then +                  cursiveanchor=current  +                else +                  if maxc>0 then +                    local ny=getfield(current,"yoffset") +                    for i=maxc,minc,-1 do +                      local ti=glyphs[i] +                      ny=ny+properties[ti].cursivedy +                      setfield(ti,"yoffset",ny)  +                      if trace_cursive then +                        showoffset(ti) +                      end +                    end +                    maxc=0 +                  end +                  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)) +              end +              local rightkern=i.rightkern +              if rightkern and rightkern~=0 then +                insert_node_after(head,current,newkern(rightkern)) +              end +            end +          else +            local i=p.emptyinjections +            if i then +              local rightkern=i.rightkern +              if rightkern and rightkern~=0 then +                if next and getid(next)==disc_code then +                  if replace then +                  else +                    setfield(next,"replace",newkern(rightkern))  +                  end                  end                end              end -            local d=getfield(p,"replace") -            if d then -              local i=rawget(pn,"replaceinjections") -              if i then -                local leftkern=i.leftkern -                if leftkern and leftkern~=0 then -                  local t=find_tail(d) -                  insert_node_after(d,t,newkern(leftkern)) -                  setfield(p,"replace",d)  +          end +          if prevdisc then +            if p then +              local done=false +              if post then +                local i=p.postinjections +                if i then +                  local leftkern=i.leftkern +                  if leftkern and leftkern~=0 then +                    insert_node_after(post,posttail,newkern(leftkern)) +                    done=true +                  end                  end                end -            else -              local i=rawget(pn,"injections") -              if i then +              if replace then +                local i=p.replaceinjections +                if i then +                  local leftkern=i.leftkern +                  if leftkern and leftkern~=0 then +                    insert_node_after(replace,replacetail,newkern(leftkern)) +                    done=true +                  end +                end +              else +                local i=p.emptyinjections                  local leftkern=i.leftkern                  if leftkern and leftkern~=0 then -                  setfield(p,"replace",newkern(leftkern)) +                  setfield(prev,"replace",newkern(leftkern))                   end                end +              if done then +                setdisc(prevdisc,pre,post,replace) +              end              end -          else -            local i=rawget(pn,"injections") +          end +        else +          if hascursives and maxc>0 then +            local ny=getfield(current,"yoffset") +            for i=maxc,minc,-1 do +              local ti=glyphs[i] +              ny=ny+properties[ti].cursivedy +              setfield(ti,"yoffset",getfield(ti,"yoffset")+ny)  +            end +            maxc=0 +            cursiveanchor=nil +          end +        end +      end +      prevdisc=nil +      prevglyph=current +    elseif id==disc_code then +      pre,post,replace,pretail,posttail,replacetail=getdisc(current,true) +      local done=false +      if pre then +        for n in traverse_char(pre) do +          local p=rawget(properties,n) +          if p then +            local i=p.injections or p.preinjections              if i then +              local yoffset=i.yoffset +              if yoffset and yoffset~=0 then +                setfield(n,"yoffset",yoffset) +              end                local leftkern=i.leftkern                if leftkern and leftkern~=0 then -                head=insert_node_before(head,n,newkern(leftkern)) +                pre=insert_node_before(pre,n,newkern(leftkern)) +                done=true                end                local rightkern=i.rightkern                if rightkern and rightkern~=0 then -                insert_node_after(head,n,newkern(rightkern)) -                n=getnext(n)  +                insert_node_after(pre,n,newkern(rightkern)) +                done=true                end +            end +            if hasmarks then +              local pm=i.markbasenode +              if pm then +                processmark(pm,current,i) +              end +            end +          end +        end +      end +      if post then +        for n in traverse_char(post) do +          local p=rawget(properties,n) +          if p then +            local i=p.injections or p.postinjections +            if i then                local yoffset=i.yoffset                if yoffset and yoffset~=0 then                  setfield(n,"yoffset",yoffset)                end +              local leftkern=i.leftkern +              if leftkern and leftkern~=0 then +                post=insert_node_before(post,n,newkern(leftkern)) +                done=true +              end +              local rightkern=i.rightkern +              if rightkern and rightkern~=0 then +                insert_node_after(post,n,newkern(rightkern)) +                done=true +              end +            end +            if hasmarks then +              local pm=i.markbasenode +              if pm then +                processmark(pm,current,i) +              end              end            end          end        end -      p=nil -    elseif id==disc_code then -      local d=getfield(n,"pre") -      if d then -        local h=d -        for n in traverse_id(glyph_code,d) do -          if getsubtype(n)<256 then -            local pn=rawget(properties,n) -            if pn then -              local i=rawget(pn,"preinjections") -              if i then -                local leftkern=i.leftkern -                if leftkern and leftkern~=0 then -                  h=insert_node_before(h,n,newkern(leftkern)) -                end -                local rightkern=i.rightkern -                if rightkern and rightkern~=0 then -                  insert_node_after(head,n,newkern(rightkern)) -                  n=getnext(n)  -                end -                local yoffset=i.yoffset -                if yoffset and yoffset~=0 then -                  setfield(n,"yoffset",yoffset) -                end +      if replace then +        for n in traverse_char(replace) do +          local p=rawget(properties,n) +          if p then +            local i=p.injections or p.replaceinjections +            if i then +              local yoffset=i.yoffset +              if yoffset and yoffset~=0 then +                setfield(n,"yoffset",yoffset) +              end +              local leftkern=i.leftkern +              if leftkern and leftkern~=0 then +                replace=insert_node_before(replace,n,newkern(leftkern)) +                done=true +              end +              local rightkern=i.rightkern +              if rightkern and rightkern~=0 then +                insert_node_after(replace,n,newkern(rightkern)) +                done=true +              end +            end +            if hasmarks then +              local pm=i.markbasenode +              if pm then +                processmark(pm,current,i)                end              end -          else -            break            end          end -        if h~=d then -          setfield(n,"pre",h) -        end        end -      local d=getfield(n,"post") -      if d then -        local h=d -        for n in traverse_id(glyph_code,d) do -          if getsubtype(n)<256 then -            local pn=rawget(properties,n) -            if pn then -              local i=rawget(pn,"postinjections") -              if i then -                local leftkern=i.leftkern -                if leftkern and leftkern~=0 then -                  h=insert_node_before(h,n,newkern(leftkern)) -                end -                local rightkern=i.rightkern -                if rightkern and rightkern~=0 then -                  insert_node_after(head,n,newkern(rightkern)) -                  n=getnext(n)  -                end -                local yoffset=i.yoffset -                if yoffset and yoffset~=0 then -                  setfield(n,"yoffset",yoffset) -                end +      if prevglyph then +        if pre then +          local p=rawget(properties,prevglyph) +          if p then +            local i=p.preinjections +            if i then +              local rightkern=i.rightkern +              if rightkern and rightkern~=0 then +                pre=insert_node_before(pre,pre,newkern(rightkern)) +                done=true                end              end -          else -            break            end          end -        if h~=d then -          setfield(n,"post",h) -        end -      end -      local d=getfield(n,"replace") -      if d then -        local h=d -        for n in traverse_id(glyph_code,d) do -          if getsubtype(n)<256 then -            local pn=rawget(properties,n) -            if pn then -              local i=rawget(pn,"replaceinjections") -              if i then -                local leftkern=i.leftkern -                if leftkern and leftkern~=0 then -                  h=insert_node_before(h,n,newkern(leftkern)) -                end -                local rightkern=i.rightkern -                if rightkern and rightkern~=0 then -                  insert_node_after(head,n,newkern(rightkern)) -                  n=getnext(n)  -                end -                local yoffset=i.yoffset -                if yoffset and yoffset~=0 then -                  setfield(n,"yoffset",yoffset) -                end +        if replace then +          local p=rawget(properties,prevglyph) +          if p then +            local i=p.replaceinjections +            if i then +              local rightkern=i.rightkern +              if rightkern and rightkern~=0 then +                replace=insert_node_before(replace,replace,newkern(rightkern)) +                done=true                end              end -          else -            break            end          end -        if h~=d then -          setfield(n,"replace",h) -        end        end -      p=n +      if done then +        setdisc(current,pre,post,replace) +      end +      prevglyph=nil +      prevdisc=current      else -      p=nil +      prevglyph=nil +      prevdisc=nil      end -    n=getnext(n) +    prev=current +    current=next +  end +  if hascursives and maxc>0 then +    local ny=getfield(last,"yoffset") +    for i=maxc,minc,-1 do +      local ti=glyphs[i] +      ny=ny+properties[ti].cursivedy +      setfield(ti,"yoffset",ny)  +      if trace_cursive then +        showoffset(ti) +      end +    end +  end +  if nofmarks>0 then +    for i=1,nofmarks do +      local m=marks[i] +      local p=rawget(properties,m) +      local i=p.injections +      local b=i.markbasenode +      processmark(b,m,i) +    end +  elseif hasmarks then    end    if keepregisteredcounts then      keepregisteredcounts=false    else -    nofregisteredpairs=0      nofregisteredkerns=0 +    nofregisteredpairs=0 +    nofregisteredmarks=0 +    nofregisteredcursives=0    end    return tonode(head),true  end +local triggers=false +function nodes.injections.setspacekerns(font,sequence) +  if triggers then +    triggers[font]=sequence +  else +    triggers={ [font]=sequence } +  end +end +local function injectspaces(head) +  if not triggers then +    return head,false +  end +  local lastfont=nil +  local spacekerns=nil +  local leftkerns=nil +  local rightkerns=nil +  local factor=0 +  local threshold=0 +  local leftkern=false +  local rightkern=false +  local function updatefont(font,trig) +    leftkerns=trig.left +    rightkerns=trig.right +    local par=parameters[font] +    factor=par.factor +    threshold=par.spacing.width-1  +    lastfont=font +  end +  for n in traverse_id(glue_code,tonut(head)) do +    local prev,next=getboth(n) +    local prevchar=ischar(prev) +    local nextchar=ischar(next) +    if nextchar then +      local font=getfont(next) +      local trig=triggers[font] +      if trig then +        if lastfont~=font then +          updatefont(font,trig) +        end +        if rightkerns then +          rightkern=rightkerns[nextchar] +        end +      end +    end +    if prevchar then +      local font=getfont(next) +      local trig=triggers[font] +      if trig then +        if lastfont~=font then +          updatefont(font,trig) +        end +        if leftkerns then +          leftkern=leftkerns[prevchar] +        end +      end +    end +    if leftkern then +      local old=getfield(n,"width") +      if old>=threshold then +        if rightkern then +          local new=old+(leftkern+rightkern)*factor +          if trace_spaces then +            report_spaces("%C [%p -> %p] %C",prevchar,old,new,nextchar) +          end +          setfield(n,"width",new) +          leftkern=false +        else +          local new=old+leftkern*factor +          if trace_spaces then +            report_spaces("%C [%p -> %p]",prevchar,old,new) +          end +          setfield(n,"width",new) +        end +      end +      leftkern=false +    elseif rightkern then +      local old=getfield(n,"width") +      if old>=threshold then +        local new=old+rightkern*factor +        if trace_spaces then +          report_spaces("[%p -> %p] %C",nextchar,old,new) +        end +        setfield(n,"width",new) +      end +      rightkern=false +    end +  end +  triggers=false +  return head,true +end  function injections.handler(head,where) +  if triggers then +    head=injectspaces(head) +  end    if nofregisteredmarks>0 or nofregisteredcursives>0 then      return inject_everything(head,where)    elseif nofregisteredpairs>0 then @@ -11276,7 +17615,7 @@ end -- closure  do -- begin closure to overcome local limits and interference -if not modules then modules={} end modules ['luatex-fonts-ota']={ +if not modules then modules={} end modules ['font-ota']={    version=1.001,    comment="companion to font-otf.lua (analysing)",    author="Hans Hagen, PRAGMA-ADE, Hasselt NL", @@ -11299,21 +17638,22 @@ local tonut=nuts.tonut  local getfield=nuts.getfield  local getnext=nuts.getnext  local getprev=nuts.getprev -local getid=nuts.getid +local getprev=nuts.getprev  local getprop=nuts.getprop  local setprop=nuts.setprop  local getfont=nuts.getfont  local getsubtype=nuts.getsubtype  local getchar=nuts.getchar +local ischar=nuts.is_char  local traverse_id=nuts.traverse_id  local traverse_node_list=nuts.traverse  local end_of_math=nuts.end_of_math  local nodecodes=nodes.nodecodes -local glyph_code=nodecodes.glyph  local disc_code=nodecodes.disc  local math_code=nodecodes.math  local fontdata=fonts.hashes.identifiers  local categories=characters and characters.categories or {}  +local chardata=characters and characters.data  local otffeatures=fonts.constructors.newfeatures("otf")  local registerotffeature=otffeatures.register  local s_init=1  local s_rphf=7 @@ -11325,7 +17665,10 @@ local s_rest=6  local states={    init=s_init,    medi=s_medi, +  med2=s_medi,    fina=s_fina, +  fin2=s_fina, +  fin3=s_fina,    isol=s_isol,    mark=s_mark,    rest=s_rest, @@ -11338,7 +17681,10 @@ local states={  local features={    init=s_init,    medi=s_medi, +  med2=s_medi,    fina=s_fina, +  fin2=s_fina, +  fin3=s_fina,    isol=s_isol,    rphf=s_rphf,    half=s_half, @@ -11356,10 +17702,9 @@ function analyzers.setstate(head,font)    local first,last,current,n,done=nil,nil,head,0,false     current=tonut(current)    while current do -    local id=getid(current) -    if id==glyph_code and getfont(current)==font then +    local char,id=ischar(current,font) +    if char and not getprop(current,a_state) then        done=true -      local char=getchar(current)        local d=descriptions[char]        if d then          if d.class=="mark" then @@ -11383,6 +17728,16 @@ function analyzers.setstate(head,font)          end          first,last,n=nil,nil,0        end +    elseif char==false then +      if first and first==last then +        setprop(last,a_state,s_isol) +      elseif last then +        setprop(last,a_state,s_fina) +      end +      first,last,n=nil,nil,0 +      if id==math_code then +        current=end_of_math(current) +      end      elseif id==disc_code then        setprop(current,a_state,s_medi)        last=current @@ -11447,107 +17802,6 @@ registerotffeature {    }  }  methods.latn=analyzers.setstate -local tatweel=0x0640 -local zwnj=0x200C -local zwj=0x200D -local isolated={  -  [0x0600]=true,[0x0601]=true,[0x0602]=true,[0x0603]=true, -  [0x0604]=true, -  [0x0608]=true,[0x060B]=true,[0x0621]=true,[0x0674]=true, -  [0x06DD]=true, -  [0x0856]=true,[0x0858]=true,[0x0857]=true, -  [0x07FA]=true, -  [zwnj]=true, -  [0x08AD]=true, -} -local final={  -  [0x0622]=true,[0x0623]=true,[0x0624]=true,[0x0625]=true, -  [0x0627]=true,[0x0629]=true,[0x062F]=true,[0x0630]=true, -  [0x0631]=true,[0x0632]=true,[0x0648]=true,[0x0671]=true, -  [0x0672]=true,[0x0673]=true,[0x0675]=true,[0x0676]=true, -  [0x0677]=true,[0x0688]=true,[0x0689]=true,[0x068A]=true, -  [0x068B]=true,[0x068C]=true,[0x068D]=true,[0x068E]=true, -  [0x068F]=true,[0x0690]=true,[0x0691]=true,[0x0692]=true, -  [0x0693]=true,[0x0694]=true,[0x0695]=true,[0x0696]=true, -  [0x0697]=true,[0x0698]=true,[0x0699]=true,[0x06C0]=true, -  [0x06C3]=true,[0x06C4]=true,[0x06C5]=true,[0x06C6]=true, -  [0x06C7]=true,[0x06C8]=true,[0x06C9]=true,[0x06CA]=true, -  [0x06CB]=true,[0x06CD]=true,[0x06CF]=true,[0x06D2]=true, -  [0x06D3]=true,[0x06D5]=true,[0x06EE]=true,[0x06EF]=true, -  [0x0759]=true,[0x075A]=true,[0x075B]=true,[0x076B]=true, -  [0x076C]=true,[0x0771]=true,[0x0773]=true,[0x0774]=true, -  [0x0778]=true,[0x0779]=true, -  [0x08AA]=true,[0x08AB]=true,[0x08AC]=true, -  [0xFEF5]=true,[0xFEF7]=true,[0xFEF9]=true,[0xFEFB]=true, -  [0x0710]=true,[0x0715]=true,[0x0716]=true,[0x0717]=true, -  [0x0718]=true,[0x0719]=true,[0x0728]=true,[0x072A]=true, -  [0x072C]=true,[0x071E]=true, -  [0x072F]=true,[0x074D]=true, -  [0x0840]=true,[0x0849]=true,[0x0854]=true,[0x0846]=true, -  [0x084F]=true, -  [0x08AE]=true,[0x08B1]=true,[0x08B2]=true, -} -local medial={  -  [0x0626]=true,[0x0628]=true,[0x062A]=true,[0x062B]=true, -  [0x062C]=true,[0x062D]=true,[0x062E]=true,[0x0633]=true, -  [0x0634]=true,[0x0635]=true,[0x0636]=true,[0x0637]=true, -  [0x0638]=true,[0x0639]=true,[0x063A]=true,[0x063B]=true, -  [0x063C]=true,[0x063D]=true,[0x063E]=true,[0x063F]=true, -  [0x0641]=true,[0x0642]=true,[0x0643]=true, -  [0x0644]=true,[0x0645]=true,[0x0646]=true,[0x0647]=true, -  [0x0649]=true,[0x064A]=true,[0x066E]=true,[0x066F]=true, -  [0x0678]=true,[0x0679]=true,[0x067A]=true,[0x067B]=true, -  [0x067C]=true,[0x067D]=true,[0x067E]=true,[0x067F]=true, -  [0x0680]=true,[0x0681]=true,[0x0682]=true,[0x0683]=true, -  [0x0684]=true,[0x0685]=true,[0x0686]=true,[0x0687]=true, -  [0x069A]=true,[0x069B]=true,[0x069C]=true,[0x069D]=true, -  [0x069E]=true,[0x069F]=true,[0x06A0]=true,[0x06A1]=true, -  [0x06A2]=true,[0x06A3]=true,[0x06A4]=true,[0x06A5]=true, -  [0x06A6]=true,[0x06A7]=true,[0x06A8]=true,[0x06A9]=true, -  [0x06AA]=true,[0x06AB]=true,[0x06AC]=true,[0x06AD]=true, -  [0x06AE]=true,[0x06AF]=true,[0x06B0]=true,[0x06B1]=true, -  [0x06B2]=true,[0x06B3]=true,[0x06B4]=true,[0x06B5]=true, -  [0x06B6]=true,[0x06B7]=true,[0x06B8]=true,[0x06B9]=true, -  [0x06BA]=true,[0x06BB]=true,[0x06BC]=true,[0x06BD]=true, -  [0x06BE]=true,[0x06BF]=true,[0x06C1]=true,[0x06C2]=true, -  [0x06CC]=true,[0x06CE]=true,[0x06D0]=true,[0x06D1]=true, -  [0x06FA]=true,[0x06FB]=true,[0x06FC]=true,[0x06FF]=true, -  [0x0750]=true,[0x0751]=true,[0x0752]=true,[0x0753]=true, -  [0x0754]=true,[0x0755]=true,[0x0756]=true,[0x0757]=true, -  [0x0758]=true,[0x075C]=true,[0x075D]=true,[0x075E]=true, -  [0x075F]=true,[0x0760]=true,[0x0761]=true,[0x0762]=true, -  [0x0763]=true,[0x0764]=true,[0x0765]=true,[0x0766]=true, -  [0x0767]=true,[0x0768]=true,[0x0769]=true,[0x076A]=true, -  [0x076D]=true,[0x076E]=true,[0x076F]=true,[0x0770]=true, -  [0x0772]=true,[0x0775]=true,[0x0776]=true,[0x0777]=true, -  [0x077A]=true,[0x077B]=true,[0x077C]=true,[0x077D]=true, -  [0x077E]=true,[0x077F]=true, -  [0x08A0]=true,[0x08A2]=true,[0x08A4]=true,[0x08A5]=true, -  [0x08A6]=true,[0x0620]=true,[0x08A8]=true,[0x08A9]=true, -  [0x08A7]=true,[0x08A3]=true, -  [0x0712]=true,[0x0713]=true,[0x0714]=true,[0x071A]=true, -  [0x071B]=true,[0x071C]=true,[0x071D]=true,[0x071F]=true, -  [0x0720]=true,[0x0721]=true,[0x0722]=true,[0x0723]=true, -  [0x0724]=true,[0x0725]=true,[0x0726]=true,[0x0727]=true, -  [0x0729]=true,[0x072B]=true,[0x072D]=true,[0x072E]=true, -  [0x074E]=true,[0x074F]=true, -  [0x0841]=true,[0x0842]=true,[0x0843]=true,[0x0844]=true, -  [0x0845]=true,[0x0847]=true,[0x0848]=true,[0x0855]=true, -  [0x0851]=true,[0x084E]=true,[0x084D]=true,[0x084A]=true, -  [0x084B]=true,[0x084C]=true,[0x0850]=true,[0x0852]=true, -  [0x0853]=true, -  [0x07D7]=true,[0x07E8]=true,[0x07D9]=true,[0x07EA]=true, -  [0x07CA]=true,[0x07DB]=true,[0x07CC]=true,[0x07DD]=true, -  [0x07CE]=true,[0x07DF]=true,[0x07D4]=true,[0x07E5]=true, -  [0x07E9]=true,[0x07E7]=true,[0x07E3]=true,[0x07E2]=true, -  [0x07E0]=true,[0x07E1]=true,[0x07DE]=true,[0x07DC]=true, -  [0x07D1]=true,[0x07DA]=true,[0x07D8]=true,[0x07D6]=true, -  [0x07D2]=true,[0x07D0]=true,[0x07CF]=true,[0x07CD]=true, -  [0x07CB]=true,[0x07D3]=true,[0x07E4]=true,[0x07D5]=true, -  [0x07E6]=true, -  [tatweel]=true,[zwj]=true, -  [0x08A1]=true,[0x08AF]=true,[0x08B0]=true, -}  local arab_warned={}  local function warning(current,what)    local char=getchar(current) @@ -11556,92 +17810,173 @@ local function warning(current,what)      arab_warned[char]=true    end  end -local function finish(first,last) -  if last then -    if first==last then -      local fc=getchar(first) -      if medial[fc] or final[fc] then -        setprop(first,a_state,s_isol) -      else -        warning(first,"isol") -        setprop(first,a_state,s_error) -      end -    else -      local lc=getchar(last) -      if medial[lc] or final[lc] then -        setprop(last,a_state,s_fina) -      else -        warning(last,"fina") -        setprop(last,a_state,s_error) +local mappers={ +  l=s_init, +  d=s_medi, +  c=s_medi, +  r=s_fina, +  u=s_isol, +} +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") +  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 +        end        end      end -    first,last=nil,nil -  elseif first then -    local fc=getchar(first) -    if medial[fc] or final[fc] then -      setprop(first,a_state,s_isol) -    else -      warning(first,"isol") -      setprop(first,a_state,s_error) -    end -    first=nil -  end -  return first,last +    t[k]=v +    return v +  end)  end  function methods.arab(head,font,attr) -  local useunicodemarks=analyzers.useunicodemarks -  local tfmdata=fontdata[font] -  local marks=tfmdata.resources.marks -  local first,last,current,done=nil,nil,head,false +  local first,last=nil,nil +  local c_first,c_last=nil,nil +  local current,done=head,false    current=tonut(current)    while current do -    local id=getid(current) -    if id==glyph_code and getfont(current)==font and getsubtype(current)<256 and not getprop(current,a_state) then +    local char,id=ischar(current,font) +    if char and not getprop(current,a_state) then        done=true -      local char=getchar(current) -      if marks[char] or (useunicodemarks and categories[char]=="mn") then +      local classifier=classifiers[char] +      if not classifier then +        if last then +          if c_last==s_medi or c_last==s_fina then +            setprop(last,a_state,s_fina) +          else +            warning(last,"fina") +            setprop(last,a_state,s_error) +          end +          first,last=nil,nil +        elseif first then +          if c_first==s_medi or c_first==s_fina then +            setprop(first,a_state,s_isol) +          else +            warning(first,"isol") +            setprop(first,a_state,s_error) +          end +          first=nil +        end +      elseif classifier==s_mark then          setprop(current,a_state,s_mark) -      elseif isolated[char] then  -        first,last=finish(first,last) +      elseif classifier==s_isol then +        if last then +          if c_last==s_medi or c_last==s_fina then +            setprop(last,a_state,s_fina) +          else +            warning(last,"fina") +            setprop(last,a_state,s_error) +          end +          first,last=nil,nil +        elseif first then +          if c_first==s_medi or c_first==s_fina then +            setprop(first,a_state,s_isol) +          else +            warning(first,"isol") +            setprop(first,a_state,s_error) +          end +          first=nil +        end          setprop(current,a_state,s_isol) -        first,last=nil,nil -      elseif not first then -        if medial[char] then +      elseif classifier==s_medi then +        if first then +          last=current +          c_last=classifier +          setprop(current,a_state,s_medi) +        else            setprop(current,a_state,s_init) -          first,last=first or current,current -        elseif final[char] then -          setprop(current,a_state,s_isol) -          first,last=nil,nil -        else  -          first,last=finish(first,last) +          first=current +          c_first=classifier          end -      elseif medial[char] then -        first,last=first or current,current -        setprop(current,a_state,s_medi) -      elseif final[char] then -        if getprop(last,a_state)~=s_init then -          setprop(last,a_state,s_medi) +      elseif classifier==s_fina then +        if last then +          if getprop(last,a_state)~=s_init then +            setprop(last,a_state,s_medi) +          end +          setprop(current,a_state,s_fina) +          first,last=nil,nil +        elseif first then +          setprop(current,a_state,s_fina) +          first=nil +        else +          setprop(current,a_state,s_isol)          end -        setprop(current,a_state,s_fina) -        first,last=nil,nil -      elseif char>=0x0600 and char<=0x06FF then  -        setprop(current,a_state,s_rest) -        first,last=finish(first,last)        else  -        first,last=finish(first,last) +        setprop(current,a_state,s_rest) +        if last then +          if c_last==s_medi or c_last==s_fina then +            setprop(last,a_state,s_fina) +          else +            warning(last,"fina") +            setprop(last,a_state,s_error) +          end +          first,last=nil,nil +        elseif first then +          if c_first==s_medi or c_first==s_fina then +            setprop(first,a_state,s_isol) +          else +            warning(first,"isol") +            setprop(first,a_state,s_error) +          end +          first=nil +        end        end      else -      if first or last then -        first,last=finish(first,last) +      if last then +        if c_last==s_medi or c_last==s_fina then +          setprop(last,a_state,s_fina) +        else +          warning(last,"fina") +          setprop(last,a_state,s_error) +        end +        first,last=nil,nil +      elseif first then +        if c_first==s_medi or c_first==s_fina then +          setprop(first,a_state,s_isol) +        else +          warning(first,"isol") +          setprop(first,a_state,s_error) +        end +        first=nil        end -      if id==math_code then +      if id==math_code then           current=end_of_math(current)        end      end      current=getnext(current)    end -  if first or last then -    finish(first,last) +  if last then +    if c_last==s_medi or c_last==s_fina then +      setprop(last,a_state,s_fina) +    else +      warning(last,"fina") +      setprop(last,a_state,s_error) +    end +  elseif first then +    if c_first==s_medi or c_first==s_fina then +      setprop(first,a_state,s_isol) +    else +      warning(first,"isol") +      setprop(first,a_state,s_error) +    end    end    return head,done  end @@ -11656,7 +17991,7 @@ end -- closure  do -- begin closure to overcome local limits and interference -if not modules then modules={} end modules ['font-otn']={ +if not modules then modules={} end modules ['font-ots']={     version=1.001,    comment="companion to font-ini.mkiv",    author="Hans Hagen, PRAGMA-ADE, Hasselt NL", @@ -11666,6 +18001,7 @@ if not modules then modules={} end modules ['font-otn']={  local type,next,tonumber=type,next,tonumber  local random=math.random  local formatters=string.formatters +local insert=table.insert  local logs,trackers,nodes,attributes=logs,trackers,nodes,attributes  local registertracker=trackers.register  local registerdirective=directives.register @@ -11690,19 +18026,19 @@ local trace_directions=false registertracker("otf.directions",function(v) trace_  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 report_direct=logs.reporter("fonts","otf direct")  local report_subchain=logs.reporter("fonts","otf subchain")  local report_chain=logs.reporter("fonts","otf chain")  local report_process=logs.reporter("fonts","otf process") -local report_prepare=logs.reporter("fonts","otf prepare")  local report_warning=logs.reporter("fonts","otf warning")  local report_run=logs.reporter("fonts","otf run") -registertracker("otf.verbose_chain",function(v) otf.setcontextchain(v and "verbose") end) -registertracker("otf.normal_chain",function(v) otf.setcontextchain(v and "normal") end) +local report_check=logs.reporter("fonts","otf check")  registertracker("otf.replacements","otf.singles,otf.multiples,otf.alternatives,otf.ligatures")  registertracker("otf.positions","otf.marks,otf.kerns,otf.cursive")  registertracker("otf.actions","otf.replacements,otf.positions") @@ -11717,6 +18053,8 @@ local getnext=nuts.getnext  local setnext=nuts.setnext  local getprev=nuts.getprev  local setprev=nuts.setprev +local getboth=nuts.getboth +local setboth=nuts.setboth  local getid=nuts.getid  local getattr=nuts.getattr  local setattr=nuts.setattr @@ -11727,6 +18065,10 @@ local getsubtype=nuts.getsubtype  local setsubtype=nuts.setsubtype  local getchar=nuts.getchar  local setchar=nuts.setchar +local getdisc=nuts.getdisc +local setdisc=nuts.setdisc +local setlink=nuts.setlink +local ischar=nuts.is_char  local insert_node_before=nuts.insert_before  local insert_node_after=nuts.insert_after  local delete_node=nuts.delete @@ -11757,7 +18099,6 @@ local discretionary_code=disccodes.discretionary  local ligature_code=glyphcodes.ligature  local privateattribute=attributes.private  local a_state=privateattribute('state') -local a_cursbase=privateattribute('cursbase')   local injections=nodes.injections  local setmark=injections.setmark  local setcursive=injections.setcursive @@ -11777,23 +18118,26 @@ otf.defaultnodealternate="none"  local tfmdata=false  local characters=false  local descriptions=false -local resources=false  local marks=false  local currentfont=false -local lookuptable=false -local anchorlookups=false -local lookuptypes=false -local lookuptags=false -local handlers={} -local rlmode=0 -local featurevalue=false -local sweephead={} +local factor=0 +local threshold=0  local sweepnode=nil  local sweepprev=nil  local sweepnext=nil +local sweephead={}  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 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 @@ -11833,21 +18177,26 @@ local function gref(n)      return "<error in node mode tracing>"    end  end -local function cref(kind,chainname,chainlookupname,lookupname,index)  -  if index then -    return formatters["feature %a, chain %a, sub %a, lookup %a, index %a"](kind,chainname,chainlookupname,lookuptags[lookupname],index) -  elseif lookupname then -    return formatters["feature %a, chain %a, sub %a, lookup %a"](kind,chainname,chainlookupname,lookuptags[lookupname]) -  elseif chainlookupname then -    return formatters["feature %a, chain %a, sub %a"](kind,lookuptags[chainname],lookuptags[chainlookupname]) -  elseif chainname then -    return formatters["feature %a, chain %a"](kind,lookuptags[chainname]) +local function cref(dataset,sequence,index) +  if not dataset then +    return "no valid dataset" +  elseif index then +    return formatters["feature %a, type %a, chain lookup %a, index %a"](dataset[4],sequence.type,sequence.name,index)    else -    return formatters["feature %a"](kind) +    return formatters["feature %a, type %a, chain lookup %a"](dataset[4],sequence.type,sequence.name)    end  end -local function pref(kind,lookupname) -  return formatters["feature %a, lookup %a"](kind,lookuptags[lookupname]) +local function pref(dataset,sequence) +  return formatters["feature %a, type %a, lookup %a"](dataset[4],sequence.type,sequence.name) +end +local function mref(rlmode) +  if not rlmode or rlmode==0 then +    return "---" +  elseif rlmode==-1 or rlmode=="+TRT" then +    return "r2l" +  else +    return "l2r" +  end  end  local function copy_glyph(g)     local components=getfield(g,"components") @@ -11864,16 +18213,14 @@ local function copy_glyph(g)    end  end  local function flattendisk(head,disc) -  local replace=getfield(disc,"replace") +  local _,_,replace,_,_,replacetail=getdisc(disc,true)    setfield(disc,"replace",nil)    free_node(disc)    if head==disc then      local next=getnext(disc)      if replace then        if next then -        local tail=find_node_tail(replace) -        setnext(tail,next) -        setprev(next,tail) +        setlink(replacetail,next)        end        return replace,replace      elseif next then @@ -11882,47 +18229,36 @@ local function flattendisk(head,disc)        return       end    else -    local next=getnext(disc) -    local prev=getprev(disc) +    local prev,next=getboth(disc)      if replace then -      local tail=find_node_tail(replace)        if next then -        setnext(tail,next) -        setprev(next,tail) +        setlink(replacetail,next)        end -      setnext(prev,replace) -      setprev(replace,prev) +      setlink(prev,replace)        return head,replace      else -      if next then -        setprev(next,prev) -      end -      setnext(prev,next) +      setlink(prev,next)         return head,next      end    end  end  local function appenddisc(disc,list) -  local post=getfield(disc,"post") -  local replace=getfield(disc,"replace") -  local phead=list -  local rhead=copy_node_list(list) -  local ptail=find_node_tail(post) -  local rtail=find_node_tail(replace) +  local pre,post,replace,pretail,posttail,replacetail=getdisc(disc,true) +  local posthead=list +  local replacehead=copy_node_list(list)    if post then -    setnext(ptail,phead) -    setprev(phead,ptail) +    setlink(posttail,posthead)    else -    setfield(disc,"post",phead) +    post=phead    end    if replace then -    setnext(rtail,rhead) -    setprev(rhead,rtail) +    setlink(replacetail,replacehead)    else -    setfield(disc,"replace",rhead) +    replace=rhead    end +  setdisc(disc,pre,post,replace)  end -local function markstoligature(kind,lookupname,head,start,stop,char) +local function markstoligature(head,start,stop,char)    if start==stop and getchar(start)==char then      return head,start    else @@ -11938,14 +18274,8 @@ local function markstoligature(kind,lookupname,head,start,stop,char)      setchar(base,char)      setsubtype(base,ligature_code)      setfield(base,"components",start) -    if prev then -      setnext(prev,base) -    end -    if next then -      setprev(next,base) -    end -    setnext(base,next) -    setprev(base,prev) +    setlink(prev,base) +    setlink(base,next)      return head,base    end  end @@ -11967,7 +18297,7 @@ local function getcomponentindex(start)    end  end  local a_noligature=attributes.private("noligature") -local function toligature(kind,lookupname,head,start,stop,char,markflag,discfound)  +local function toligature(head,start,stop,char,dataset,sequence,markflag,discfound)     if getattr(start,a_noligature)==1 then      return head,start    end @@ -11998,8 +18328,7 @@ local function toligature(kind,lookupname,head,start,stop,char,markflag,discfoun    if next then      setprev(next,base)    end -  setprev(base,prev) -  setnext(base,next) +  setboth(base,prev,next)    if not discfound then      local deletemarks=markflag~="mark"      local components=start @@ -12015,65 +18344,57 @@ local function toligature(kind,lookupname,head,start,stop,char,markflag,discfoun        elseif not deletemarks then           setligaindex(start,baseindex+getligaindex(start,componentindex))          if trace_marks then -          logwarning("%s: keep mark %s, gets index %s",pref(kind,lookupname),gref(char),getligaindex(start)) +          logwarning("%s: keep mark %s, gets index %s",pref(dataset,sequence),gref(char),getligaindex(start))          end          local n=copy_node(start)          copyinjection(n,start)          head,current=insert_node_after(head,current,n)         elseif trace_marks then -        logwarning("%s: delete mark %s",pref(kind,lookupname),gref(char)) +        logwarning("%s: delete mark %s",pref(dataset,sequence),gref(char))        end        start=getnext(start)      end      local start=getnext(current) -    while start and getid(start)==glyph_code do -      local char=getchar(start) -      if marks[char] then -        setligaindex(start,baseindex+getligaindex(start,componentindex)) -        if trace_marks then -          logwarning("%s: set mark %s, gets index %s",pref(kind,lookupname),gref(char),getligaindex(start)) +    while start do +      local char=ischar(start) +      if char then +        if marks[char] then +          setligaindex(start,baseindex+getligaindex(start,componentindex)) +          if trace_marks then +            logwarning("%s: set mark %s, gets index %s",pref(dataset,sequence),gref(char),getligaindex(start)) +          end +          start=getnext(start) +        else +          break          end        else          break        end -      start=getnext(start)      end    else -    local discprev=getprev(discfound) -    local discnext=getnext(discfound) +    local discprev,discnext=getboth(discfound)      if discprev and discnext then -      local pre=getfield(discfound,"pre") -      local post=getfield(discfound,"post") -      local replace=getfield(discfound,"replace") +      local pre,post,replace,pretail,posttail,replacetail=getdisc(discfound,true)        if not replace then           local prev=getprev(base)          local copied=copy_node_list(comp)          setprev(discnext,nil)           setnext(discprev,nil)           if pre then -          setnext(discprev,pre) -          setprev(pre,discprev) +          setlink(discprev,pre)          end          pre=comp          if post then -          local tail=find_node_tail(post) -          setnext(tail,discnext) -          setprev(discnext,tail) +          setlink(posttail,discnext)            setprev(post,nil)          else            post=discnext          end -        setnext(prev,discfound) -        setprev(discfound,prev) -        setnext(discfound,next) -        setprev(next,discfound) -        setnext(base,nil) -        setprev(base,nil) +        setlink(prev,discfound) +        setlink(discfound,next) +        setboth(base,nil,nil)          setfield(base,"components",copied) -        setfield(discfound,"pre",pre) -        setfield(discfound,"post",post) -        setfield(discfound,"replace",base) -        setsubtype(discfound,discretionary_code) +        setdisc(discfound,pre,post,base,discretionary_code)          base=prev         end      end @@ -12091,12 +18412,7 @@ local function multiple_glyphs(head,start,multiple,ignoremarks)          local n=copy_node(start)           resetinjection(n)          setchar(n,multiple[k]) -        setprev(n,start) -        setnext(n,sn) -        if sn then -          setprev(sn,n) -        end -        setnext(start,n) +        insert_node_after(head,start,n)          start=n        end      end @@ -12108,7 +18424,7 @@ local function multiple_glyphs(head,start,multiple,ignoremarks)      return head,start,false    end  end -local function get_alternative_glyph(start,alternatives,value,trace_alternatives) +local function get_alternative_glyph(start,alternatives,value)    local n=#alternatives    if value=="random" then      local r=random(1,n) @@ -12117,70 +18433,73 @@ local function get_alternative_glyph(start,alternatives,value,trace_alternatives      return alternatives[1],trace_alternatives and formatters["value %a, taking %a"](value,1)    elseif value=="last" then      return alternatives[n],trace_alternatives and formatters["value %a, taking %a"](value,n) -  else -    value=tonumber(value) -    if type(value)~="number" then -      return alternatives[1],trace_alternatives and formatters["invalid value %s, taking %a"](value,1) -    elseif value>n then -      local defaultalt=otf.defaultnodealternate -      if defaultalt=="first" then -        return alternatives[n],trace_alternatives and formatters["invalid value %s, taking %a"](value,1) -      elseif defaultalt=="last" then -        return alternatives[1],trace_alternatives and formatters["invalid value %s, taking %a"](value,n) -      else -        return false,trace_alternatives and formatters["invalid value %a, %s"](value,"out of range") -      end -    elseif value==0 then -      return getchar(start),trace_alternatives and formatters["invalid value %a, %s"](value,"no change") -    elseif value<1 then -      return alternatives[1],trace_alternatives and formatters["invalid value %a, taking %a"](value,1) +  end +  value=value==true and 1 or tonumber(value) +  if type(value)~="number" then +    return alternatives[1],trace_alternatives and formatters["invalid value %s, taking %a"](value,1) +  end +  if value>n then +    local defaultalt=otf.defaultnodealternate +    if defaultalt=="first" then +      return alternatives[n],trace_alternatives and formatters["invalid value %s, taking %a"](value,1) +    elseif defaultalt=="last" then +      return alternatives[1],trace_alternatives and formatters["invalid value %s, taking %a"](value,n)      else -      return alternatives[value],trace_alternatives and formatters["value %a, taking %a"](value,value) +      return false,trace_alternatives and formatters["invalid value %a, %s"](value,"out of range")      end +  elseif value==0 then +    return getchar(start),trace_alternatives and formatters["invalid value %a, %s"](value,"no change") +  elseif value<1 then +    return alternatives[1],trace_alternatives and formatters["invalid value %a, taking %a"](value,1) +  else +    return alternatives[value],trace_alternatives and formatters["value %a, taking %a"](value,value)    end  end -function handlers.gsub_single(head,start,kind,lookupname,replacement) +function handlers.gsub_single(head,start,dataset,sequence,replacement)    if trace_singles then -    logprocess("%s: replacing %s by single %s",pref(kind,lookupname),gref(getchar(start)),gref(replacement)) +    logprocess("%s: replacing %s by single %s",pref(dataset,sequence),gref(getchar(start)),gref(replacement))    end    resetinjection(start)    setchar(start,replacement)    return head,start,true  end -function handlers.gsub_alternate(head,start,kind,lookupname,alternative,sequence) -  local value=featurevalue==true and tfmdata.shared.features[kind] or featurevalue -  local choice,comment=get_alternative_glyph(start,alternative,value,trace_alternatives) +function handlers.gsub_alternate(head,start,dataset,sequence,alternative) +  local kind=dataset[4] +  local what=dataset[1] +  local value=what==true and tfmdata.shared.features[kind] or what +  local choice,comment=get_alternative_glyph(start,alternative,value)    if choice then      if trace_alternatives then -      logprocess("%s: replacing %s by alternative %a to %s, %s",pref(kind,lookupname),gref(getchar(start)),choice,gref(choice),comment) +      logprocess("%s: replacing %s by alternative %a to %s, %s",pref(dataset,sequence),gref(getchar(start)),gref(choice),comment)      end      resetinjection(start)      setchar(start,choice)    else      if trace_alternatives then -      logwarning("%s: no variant %a for %s, %s",pref(kind,lookupname),value,gref(getchar(start)),comment) +      logwarning("%s: no variant %a for %s, %s",pref(dataset,sequence),value,gref(getchar(start)),comment)      end    end    return head,start,true  end -function handlers.gsub_multiple(head,start,kind,lookupname,multiple,sequence) +function handlers.gsub_multiple(head,start,dataset,sequence,multiple)    if trace_multiples then -    logprocess("%s: replacing %s by multiple %s",pref(kind,lookupname),gref(getchar(start)),gref(multiple)) +    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])  end -function handlers.gsub_ligature(head,start,kind,lookupname,ligature,sequence) -  local s,stop=getnext(start),nil +function handlers.gsub_ligature(head,start,dataset,sequence,ligature) +  local current=getnext(start) +  local stop=nil    local startchar=getchar(start)    if marks[startchar] then -    while s do -      local id=getid(s) -      if id==glyph_code and getfont(s)==currentfont and getsubtype(s)<256 then -        local lg=ligature[getchar(s)] +    while current do +      local char=ischar(current,currentfont) +      if char then +        local lg=ligature[char]          if lg then -          stop=s +          stop=current            ligature=lg -          s=getnext(s) +          current=getnext(current)          else            break          end @@ -12193,10 +18512,10 @@ function handlers.gsub_ligature(head,start,kind,lookupname,ligature,sequence)        if lig then          if trace_ligatures then            local stopchar=getchar(stop) -          head,start=markstoligature(kind,lookupname,head,start,stop,lig) -          logprocess("%s: replacing %s upto %s by ligature %s case 1",pref(kind,lookupname),gref(startchar),gref(stopchar),gref(getchar(start))) +          head,start=markstoligature(head,start,stop,lig) +          logprocess("%s: replacing %s upto %s by ligature %s case 1",pref(dataset,sequence),gref(startchar),gref(stopchar),gref(getchar(start)))          else -          head,start=markstoligature(kind,lookupname,head,start,stop,lig) +          head,start=markstoligature(head,start,stop,lig)          end          return head,start,true,false        else @@ -12206,52 +18525,49 @@ function handlers.gsub_ligature(head,start,kind,lookupname,ligature,sequence)      local skipmark=sequence.flags[1]      local discfound=false      local lastdisc=nil -    while s do -      local id=getid(s) -      if id==glyph_code and getsubtype(s)<256 then  -        if getfont(s)==currentfont then      -          local char=getchar(s) -          if skipmark and marks[char] then -            s=getnext(s) -          else  -            local lg=ligature[char]  -            if lg then -              if not discfound and lastdisc then -                discfound=lastdisc -                lastdisc=nil -              end -              stop=s  -              ligature=lg -              s=getnext(s) -            else -              break +    while current do +      local char,id=ischar(current,currentfont) +      if char then +        if skipmark and marks[char] then +          current=getnext(current) +        else  +          local lg=ligature[char]  +          if lg then +            if not discfound and lastdisc then +              discfound=lastdisc +              lastdisc=nil              end +            stop=current  +            ligature=lg +            current=getnext(current) +          else +            break            end -        else -          break          end +      elseif char==false then +        break        elseif id==disc_code then -        lastdisc=s -        s=getnext(s) +        lastdisc=current +        current=getnext(current)        else          break        end      end -    local lig=ligature.ligature  +    local lig=ligature.ligature      if lig then        if stop then          if trace_ligatures then            local stopchar=getchar(stop) -          head,start=toligature(kind,lookupname,head,start,stop,lig,skipmark,discfound) -          logprocess("%s: replacing %s upto %s by ligature %s case 2",pref(kind,lookupname),gref(startchar),gref(stopchar),gref(getchar(start))) +          head,start=toligature(head,start,stop,lig,dataset,sequence,skipmark,discfound) +          logprocess("%s: replacing %s upto %s by ligature %s case 2",pref(dataset,sequence),gref(startchar),gref(stopchar),gref(lig))          else -          head,start=toligature(kind,lookupname,head,start,stop,lig,skipmark,discfound) +          head,start=toligature(head,start,stop,lig,dataset,sequence,skipmark,discfound)          end        else          resetinjection(start)          setchar(start,lig)          if trace_ligatures then -          logprocess("%s: replacing %s by (no real) ligature %s case 3",pref(kind,lookupname),gref(startchar),gref(lig)) +          logprocess("%s: replacing %s by (no real) ligature %s case 3",pref(dataset,sequence),gref(startchar),gref(lig))          end        end        return head,start,true,discfound @@ -12260,190 +18576,197 @@ function handlers.gsub_ligature(head,start,kind,lookupname,ligature,sequence)    end    return head,start,false,discfound  end -function handlers.gpos_single(head,start,kind,lookupname,kerns,sequence,injection) +function handlers.gpos_single(head,start,dataset,sequence,kerns,rlmode,step,i,injection)    local startchar=getchar(start) -  local dx,dy,w,h=setpair(start,tfmdata.parameters.factor,rlmode,sequence.flags[4],kerns,injection)  -  if trace_kerns then -    logprocess("%s: shifting single %s by (%p,%p) and correction (%p,%p)",pref(kind,lookupname),gref(startchar),dx,dy,w,h) +  if step.format=="pair" then +    local dx,dy,w,h=setpair(start,factor,rlmode,sequence.flags[4],kerns,injection) +    if trace_kerns then +      logprocess("%s: shifting single %s by (%p,%p) and correction (%p,%p)",pref(dataset,sequence),gref(startchar),dx,dy,w,h) +    end +  else +    local k=setkern(start,factor,rlmode,kerns,injection) +    if trace_kerns then +      logprocess("%s: shifting single %s by %p",pref(dataset,sequence),gref(startchar),k) +    end    end    return head,start,false  end -function handlers.gpos_pair(head,start,kind,lookupname,kerns,sequence,lookuphash,i,injection) +function handlers.gpos_pair(head,start,dataset,sequence,kerns,rlmode,step,i,injection)    local snext=getnext(start)    if not snext then      return head,start,false    else      local prev=start      local done=false -    local factor=tfmdata.parameters.factor -    local lookuptype=lookuptypes[lookupname] -    while snext and getid(snext)==glyph_code and getfont(snext)==currentfont and getsubtype(snext)<256 do -      local nextchar=getchar(snext) -      local krn=kerns[nextchar] -      if not krn and marks[nextchar] then -        prev=snext -        snext=getnext(snext) -      else -        if not krn then -        elseif type(krn)=="table" then -          if lookuptype=="pair" then  -            local a,b=krn[2],krn[3] -            if a and #a>0 then -              local x,y,w,h=setpair(start,factor,rlmode,sequence.flags[4],a,injection)  +    while snext do +      local nextchar=ischar(snext,currentfont) +      if nextchar then +        local krn=kerns[nextchar] +        if not krn and marks[nextchar] then +          prev=snext +          snext=getnext(snext) +        elseif not krn then +          break +        elseif step.format=="pair" then +          local a,b=krn[1],krn[2] +          if optimizekerns then +            if not b and a[1]==0 and a[2]==0 and a[4]==0 then +              local k=setkern(snext,factor,rlmode,a[3],injection)                if trace_kerns then -                local startchar=getchar(start) -                logprocess("%s: shifting first of pair %s and %s by (%p,%p) and correction (%p,%p)",pref(kind,lookupname),gref(startchar),gref(nextchar),x,y,w,h) +                logprocess("%s: shifting single %s by %p",pref(dataset,sequence),gref(nextchar),k)                end +              done=true +              break              end -            if b and #b>0 then -              local x,y,w,h=setpair(snext,factor,rlmode,sequence.flags[4],b,injection)  -              if trace_kerns then -                local startchar=getchar(start) -                logprocess("%s: shifting second of pair %s and %s by (%p,%p) and correction (%p,%p)",pref(kind,lookupname),gref(startchar),gref(nextchar),x,y,w,h) -              end +          end +          if a and #a>0 then +            local x,y,w,h=setpair(start,factor,rlmode,sequence.flags[4],a,injection) +            if trace_kerns then +              local startchar=getchar(start) +              logprocess("%s: shifting first of pair %s and %s by (%p,%p) and correction (%p,%p) as %s",pref(dataset,sequence),gref(startchar),gref(nextchar),x,y,w,h,injection or "injections") +            end +          end +          if b and #b>0 then +            local x,y,w,h=setpair(snext,factor,rlmode,sequence.flags[4],b,injection) +            if trace_kerns then +              local startchar=getchar(snext) +              logprocess("%s: shifting second of pair %s and %s by (%p,%p) and correction (%p,%p) as %s",pref(dataset,sequence),gref(startchar),gref(nextchar),x,y,w,h,injection or "injections")              end -          else  -            report_process("%s: check this out (old kern stuff)",pref(kind,lookupname))            end            done=true +          break          elseif krn~=0 then            local k=setkern(snext,factor,rlmode,krn,injection)            if trace_kerns then -            logprocess("%s: inserting kern %s between %s and %s",pref(kind,lookupname),k,gref(getchar(prev)),gref(nextchar))  +            logprocess("%s: inserting kern %p between %s and %s as %s",pref(dataset,sequence),k,gref(getchar(prev)),gref(nextchar),injection or "injections")            end            done=true +          break +        else  +          break          end +      else          break        end      end      return head,start,done    end  end -function handlers.gpos_mark2base(head,start,kind,lookupname,markanchors,sequence) +function handlers.gpos_mark2base(head,start,dataset,sequence,markanchors,rlmode)    local markchar=getchar(start)    if marks[markchar] then      local base=getprev(start)  -    if base and getid(base)==glyph_code and getfont(base)==currentfont and getsubtype(base)<256 then -      local basechar=getchar(base) -      if marks[basechar] then -        while true do -          base=getprev(base) -          if base and getid(base)==glyph_code and getfont(base)==currentfont and getsubtype(base)<256 then -            basechar=getchar(base) -            if not marks[basechar] then -              break -            end -          else -            if trace_bugs then -              logwarning("%s: no base for mark %s",pref(kind,lookupname),gref(markchar)) -            end -            return head,start,false -          end -        end -      end -      local baseanchors=descriptions[basechar] -      if baseanchors then -        baseanchors=baseanchors.anchors -      end -      if baseanchors then -        local baseanchors=baseanchors['basechar'] -        if baseanchors then -          local al=anchorlookups[lookupname] -          for anchor,ba in next,baseanchors do -            if al[anchor] then -              local ma=markanchors[anchor] -              if ma then -                local dx,dy,bound=setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma,characters[basechar]) -                if trace_marks then -                  logprocess("%s, anchor %s, bound %s: anchoring mark %s to basechar %s => (%p,%p)", -                    pref(kind,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy) +    if base then +      local basechar=ischar(base,currentfont) +      if basechar then +        if marks[basechar] then +          while base do +            base=getprev(base) +            if base then +              basechar=ischar(base,currentfont) +              if basechar then +                if not marks[basechar] then +                  break                  end -                return head,start,true +              else +                if trace_bugs then +                  logwarning("%s: no base for mark %s, case %i",pref(dataset,sequence),gref(markchar),1) +                end +                return head,start,false +              end +            else +              if trace_bugs then +                logwarning("%s: no base for mark %s, case %i",pref(dataset,sequence),gref(markchar),2)                end +              return head,start,false              end            end -          if trace_bugs then -            logwarning("%s, no matching anchors for mark %s and base %s",pref(kind,lookupname),gref(markchar),gref(basechar)) +        end +        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]) +          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)            end +          return head,start,true          end        elseif trace_bugs then -        onetimemessage(currentfont,basechar,"no base anchors",report_fonts) +        logwarning("%s: nothing preceding, case %i",pref(dataset,sequence),1)        end      elseif trace_bugs then -      logwarning("%s: prev node is no char",pref(kind,lookupname)) +      logwarning("%s: nothing preceding, case %i",pref(dataset,sequence),2)      end    elseif trace_bugs then -    logwarning("%s: mark %s is no mark",pref(kind,lookupname),gref(markchar)) +    logwarning("%s: mark %s is no mark",pref(dataset,sequence),gref(markchar))    end    return head,start,false  end -function handlers.gpos_mark2ligature(head,start,kind,lookupname,markanchors,sequence) +function handlers.gpos_mark2ligature(head,start,dataset,sequence,markanchors,rlmode)    local markchar=getchar(start)    if marks[markchar] then      local base=getprev(start)  -    if base and getid(base)==glyph_code and getfont(base)==currentfont and getsubtype(base)<256 then -      local basechar=getchar(base) -      if marks[basechar] then -        while true do -          base=getprev(base) -          if base and getid(base)==glyph_code and getfont(base)==currentfont and getsubtype(base)<256 then -            basechar=getchar(base) -            if not marks[basechar] then -              break -            end -          else -            if trace_bugs then -              logwarning("%s: no base for mark %s",pref(kind,lookupname),gref(markchar)) -            end -            return head,start,false -          end -        end -      end -      local index=getligaindex(start) -      local baseanchors=descriptions[basechar] -      if baseanchors then -        baseanchors=baseanchors.anchors -        if baseanchors then -          local baseanchors=baseanchors['baselig'] -          if baseanchors then -            local al=anchorlookups[lookupname] -            for anchor,ba in next,baseanchors do -              if al[anchor] then -                local ma=markanchors[anchor] -                if ma then -                  ba=ba[index] -                  if ba then -                    local dx,dy,bound=setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma,characters[basechar])  -                    if trace_marks then -                      logprocess("%s, anchor %s, index %s, bound %s: anchoring mark %s to baselig %s at index %s => (%p,%p)", -                        pref(kind,lookupname),anchor,index,bound,gref(markchar),gref(basechar),index,dx,dy) -                    end -                    return head,start,true -                  else -                    if trace_bugs then -                      logwarning("%s: no matching anchors for mark %s and baselig %s with index %a",pref(kind,lookupname),gref(markchar),gref(basechar),index) -                    end -                  end +    if base then +      local basechar=ischar(base,currentfont) +      if basechar then +        if marks[basechar] then +          while base do +            base=getprev(base) +            if base then +              basechar=ischar(base,currentfont) +              if basechar then +                if not marks[basechar] then +                  break +                end +              else +                if trace_bugs then +                  logwarning("%s: no base for mark %s, case %i",pref(dataset,sequence),gref(markchar),1)                  end +                return head,start,false                end +            else +              if trace_bugs then +                logwarning("%s: no base for mark %s, case %i",pref(dataset,sequence),gref(markchar),2) +              end +              return head,start,false              end -            if trace_bugs then -              logwarning("%s: no matching anchors for mark %s and baselig %s",pref(kind,lookupname),gref(markchar),gref(basechar)) +          end +        end +        local ba=markanchors[1][basechar] +        if ba then +          local ma=markanchors[2] +          if ma then +            local index=getligaindex(start) +            ba=ba[index] +            if ba then +              local dx,dy,bound=setmark(start,base,factor,rlmode,ba,ma,characters[basechar])  +              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) +              end +              return head,start,true +            else +              if trace_bugs then +                logwarning("%s: no matching anchors for mark %s and baselig %s with index %a",pref(dataset,sequence),gref(markchar),gref(basechar),index) +              end              end            end +        elseif trace_bugs then +          onetimemessage(currentfont,basechar,"no base anchors",report_fonts)          end        elseif trace_bugs then -        onetimemessage(currentfont,basechar,"no base anchors",report_fonts) +        logwarning("%s: prev node is no char, case %i",pref(dataset,sequence),1)        end      elseif trace_bugs then -      logwarning("%s: prev node is no char",pref(kind,lookupname)) +      logwarning("%s: prev node is no char, case %i",pref(dataset,sequence),2)      end    elseif trace_bugs then -    logwarning("%s: mark %s is no mark",pref(kind,lookupname),gref(markchar)) +    logwarning("%s: mark %s is no mark",pref(dataset,sequence),gref(markchar))    end    return head,start,false  end -function handlers.gpos_mark2mark(head,start,kind,lookupname,markanchors,sequence) +function handlers.gpos_mark2mark(head,start,dataset,sequence,markanchors,rlmode)    local markchar=getchar(start)    if marks[markchar] then      local base=getprev(start)  @@ -12458,96 +18781,61 @@ function handlers.gpos_mark2mark(head,start,kind,lookupname,markanchors,sequence          end        end      end -    if base and getid(base)==glyph_code and getfont(base)==currentfont and getsubtype(base)<256 then  -      local basechar=getchar(base) -      local baseanchors=descriptions[basechar] -      if baseanchors then -        baseanchors=baseanchors.anchors -        if baseanchors then -          baseanchors=baseanchors['basemark'] -          if baseanchors then -            local al=anchorlookups[lookupname] -            for anchor,ba in next,baseanchors do -              if al[anchor] then -                local ma=markanchors[anchor] -                if ma then -                  local dx,dy,bound=setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma,characters[basechar],true) -                  if trace_marks then -                    logprocess("%s, anchor %s, bound %s: anchoring mark %s to basemark %s => (%p,%p)", -                      pref(kind,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy) -                  end -                  return head,start,true -                end -              end -            end -            if trace_bugs then -              logwarning("%s: no matching anchors for mark %s and basemark %s",pref(kind,lookupname),gref(markchar),gref(basechar)) -            end +    if base then +      local basechar=ischar(base,currentfont) +      if basechar then  +        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) +          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)            end +          return head,start,true          end -      elseif trace_bugs then -        onetimemessage(currentfont,basechar,"no base anchors",report_fonts)        end -    elseif trace_bugs then -      logwarning("%s: prev node is no mark",pref(kind,lookupname))      end    elseif trace_bugs then -    logwarning("%s: mark %s is no mark",pref(kind,lookupname),gref(markchar)) +    logwarning("%s: mark %s is no mark",pref(dataset,sequence),gref(markchar))    end    return head,start,false  end -function handlers.gpos_cursive(head,start,kind,lookupname,exitanchors,sequence)  -  local alreadydone=cursonce and getprop(start,a_cursbase) -  if not alreadydone then -    local done=false -    local startchar=getchar(start) -    if marks[startchar] then -      if trace_cursive then -        logprocess("%s: ignoring cursive for mark %s",pref(kind,lookupname),gref(startchar)) -      end -    else -      local nxt=getnext(start) -      while not done and nxt and getid(nxt)==glyph_code and getfont(nxt)==currentfont and getsubtype(nxt)<256 do -        local nextchar=getchar(nxt) -        if marks[nextchar] then -          nxt=getnext(nxt) -        else -          local entryanchors=descriptions[nextchar] -          if entryanchors then -            entryanchors=entryanchors.anchors -            if entryanchors then -              entryanchors=entryanchors['centry'] -              if entryanchors then -                local al=anchorlookups[lookupname] -                for anchor,entry in next,entryanchors do -                  if al[anchor] then -                    local exit=exitanchors[anchor] -                    if exit then -                      local dx,dy,bound=setcursive(start,nxt,tfmdata.parameters.factor,rlmode,exit,entry,characters[startchar],characters[nextchar]) -                      if trace_cursive then -                        logprocess("%s: moving %s to %s cursive (%p,%p) using anchor %s and bound %s in rlmode %s",pref(kind,lookupname),gref(startchar),gref(nextchar),dx,dy,anchor,bound,rlmode) -                      end -                      done=true -                      break -                    end -                  end -                end +function handlers.gpos_cursive(head,start,dataset,sequence,exitanchors,rlmode,step,i)  +  local done=false +  local startchar=getchar(start) +  if marks[startchar] then +    if trace_cursive then +      logprocess("%s: ignoring cursive for mark %s",pref(dataset,sequence),gref(startchar)) +    end +  else +    local nxt=getnext(start) +    while not done and nxt do +      local nextchar=ischar(nxt,currentfont) +      if not nextchar then +        break +      elseif marks[nextchar] then +        nxt=getnext(nxt) +      else +        local exit=exitanchors[3] +        if exit then +          local entry=exitanchors[1][nextchar] +          if entry then +            entry=entry[2] +            if entry then +              local dx,dy,bound=setcursive(start,nxt,factor,rlmode,exit,entry,characters[startchar],characters[nextchar]) +              if trace_cursive then +                logprocess("%s: moving %s to %s cursive (%p,%p) using anchor %s and bound %s in %s mode",pref(dataset,sequence),gref(startchar),gref(nextchar),dx,dy,anchor,bound,mref(rlmode))                end +              done=true              end -          elseif trace_bugs then -            onetimemessage(currentfont,startchar,"no entry anchors",report_fonts)            end -          break          end +        break        end      end -    return head,start,done -  else -    if trace_cursive and trace_details then -      logprocess("%s, cursive %s is already done",pref(kind,lookupname),gref(getchar(start)),alreadydone) -    end -    return head,start,false    end +  return head,start,done  end  local chainprocs={}  local function logprocess(...) @@ -12564,16 +18852,12 @@ local function logprocess(...)    report_chain(...)  end  local logwarning=report_chain -function chainprocs.chainsub(head,start,stop,kind,chainname,currentcontext,lookuphash,lookuplist,chainlookupname) -  logwarning("%s: a direct call to chainsub cannot happen",cref(kind,chainname,chainlookupname)) -  return head,start,false -end -function chainprocs.reversesub(head,start,stop,kind,chainname,currentcontext,lookuphash,replacements) +local function reversesub(head,start,stop,dataset,sequence,replacements,rlmode)    local char=getchar(start)    local replacement=replacements[char]    if replacement then      if trace_singles then -      logprocess("%s: single reverse replacement of %s by %s",cref(kind,chainname),gref(char),gref(replacement)) +      logprocess("%s: single reverse replacement of %s by %s",cref(dataset,sequence),gref(char),gref(replacement))      end      resetinjection(start)      setchar(start,replacement) @@ -12582,36 +18866,35 @@ function chainprocs.reversesub(head,start,stop,kind,chainname,currentcontext,loo      return head,start,false    end  end -function chainprocs.gsub_single(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex) -  local current=start -  local subtables=currentlookup.subtables -  if #subtables>1 then -    logwarning("todo: check if we need to loop over the replacements: % t",subtables) +chainprocs.reversesub=reversesub +local function reportmoresteps(dataset,sequence) +  logwarning("%s: more than 1 step",cref(dataset,sequence)) +end +function chainprocs.gsub_single(head,start,stop,dataset,sequence,currentlookup,chainindex) +  local steps=currentlookup.steps +  local nofsteps=currentlookup.nofsteps +  if nofsteps>1 then +    reportmoresteps(dataset,sequence)    end +  local current=start    while current do -    if getid(current)==glyph_code then -      local currentchar=getchar(current) -      local lookupname=subtables[1]  -      local replacement=lookuphash[lookupname] -      if not replacement then +    local currentchar=ischar(current) +    if currentchar then +      local replacement=steps[1].coverage[currentchar] +      if not replacement or replacement=="" then          if trace_bugs then -          logwarning("%s: no single hits",cref(kind,chainname,chainlookupname,lookupname,chainindex)) +          logwarning("%s: no single for %s",cref(dataset,sequence,chainindex),gref(currentchar))          end        else -        replacement=replacement[currentchar] -        if not replacement or replacement=="" then -          if trace_bugs then -            logwarning("%s: no single for %s",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(currentchar)) -          end -        else -          if trace_singles then -            logprocess("%s: replacing single %s by %s",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(currentchar),gref(replacement)) -          end -          resetinjection(current) -          setchar(current,replacement) +        if trace_singles then +          logprocess("%s: replacing single %s by %s",cref(dataset,sequence,chainindex),gref(currentchar),gref(replacement))          end +        resetinjection(current) +        setchar(current,replacement)        end        return head,start,true +    elseif currentchar==false then +      break      elseif current==stop then        break      else @@ -12620,63 +18903,57 @@ function chainprocs.gsub_single(head,start,stop,kind,chainname,currentcontext,lo    end    return head,start,false  end -function chainprocs.gsub_multiple(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname) +function chainprocs.gsub_multiple(head,start,stop,dataset,sequence,currentlookup) +  local steps=currentlookup.steps +  local nofsteps=currentlookup.nofsteps +  if nofsteps>1 then +    reportmoresteps(dataset,sequence) +  end    local startchar=getchar(start) -  local subtables=currentlookup.subtables -  local lookupname=subtables[1] -  local replacements=lookuphash[lookupname] -  if not replacements then +  local replacement=steps[1].coverage[startchar] +  if not replacement or replacement=="" then      if trace_bugs then -      logwarning("%s: no multiple hits",cref(kind,chainname,chainlookupname,lookupname)) +      logwarning("%s: no multiple for %s",cref(dataset,sequence),gref(startchar))      end    else -    replacements=replacements[startchar] -    if not replacements or replacement=="" then -      if trace_bugs then -        logwarning("%s: no multiple for %s",cref(kind,chainname,chainlookupname,lookupname),gref(startchar)) -      end -    else -      if trace_multiples then -        logprocess("%s: replacing %s by multiple characters %s",cref(kind,chainname,chainlookupname,lookupname),gref(startchar),gref(replacements)) -      end -      return multiple_glyphs(head,start,replacements,currentlookup.flags[1]) +    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,currentlookup.flags[1])     end    return head,start,false  end -function chainprocs.gsub_alternate(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname) +function chainprocs.gsub_alternate(head,start,stop,dataset,sequence,currentlookup) +  local steps=currentlookup.steps +  local nofsteps=currentlookup.nofsteps +  if nofsteps>1 then +    reportmoresteps(dataset,sequence) +  end +  local kind=dataset[4] +  local what=dataset[1] +  local value=what==true and tfmdata.shared.features[kind] or what    local current=start -  local subtables=currentlookup.subtables -  local value=featurevalue==true and tfmdata.shared.features[kind] or featurevalue    while current do -    if getid(current)==glyph_code then  -      local currentchar=getchar(current) -      local lookupname=subtables[1] -      local alternatives=lookuphash[lookupname] -      if not alternatives then -        if trace_bugs then -          logwarning("%s: no alternative hit",cref(kind,chainname,chainlookupname,lookupname)) -        end -      else -        alternatives=alternatives[currentchar] -        if alternatives then -          local choice,comment=get_alternative_glyph(current,alternatives,value,trace_alternatives) -          if choice then -            if trace_alternatives then -              logprocess("%s: replacing %s by alternative %a to %s, %s",cref(kind,chainname,chainlookupname,lookupname),gref(char),choice,gref(choice),comment) -            end -            resetinjection(start) -            setchar(start,choice) -          else -            if trace_alternatives then -              logwarning("%s: no variant %a for %s, %s",cref(kind,chainname,chainlookupname,lookupname),value,gref(char),comment) -            end +    local currentchar=ischar(current) +    if currentchar then +      local alternatives=steps[1].coverage[currentchar] +      if alternatives then +        local choice,comment=get_alternative_glyph(current,alternatives,value) +        if choice then +          if trace_alternatives then +            logprocess("%s: replacing %s by alternative %a to %s, %s",cref(dataset,sequence),gref(char),choice,gref(choice),comment) +          end +          resetinjection(start) +          setchar(start,choice) +        else +          if trace_alternatives then +            logwarning("%s: no variant %a for %s, %s",cref(dataset,sequence),value,gref(char),comment)            end -        elseif trace_bugs then -          logwarning("%s: no alternative for %s, %s",cref(kind,chainname,chainlookupname,lookupname),gref(currentchar),comment)          end        end        return head,start,true +    elseif currentchar==false then +      break      elseif current==stop then        break      else @@ -12685,295 +18962,311 @@ function chainprocs.gsub_alternate(head,start,stop,kind,chainname,currentcontext    end    return head,start,false  end -function chainprocs.gsub_ligature(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex) +function chainprocs.gsub_ligature(head,start,stop,dataset,sequence,currentlookup,chainindex) +  local steps=currentlookup.steps +  local nofsteps=currentlookup.nofsteps +  if nofsteps>1 then +    reportmoresteps(dataset,sequence) +  end    local startchar=getchar(start) -  local subtables=currentlookup.subtables -  local lookupname=subtables[1] -  local ligatures=lookuphash[lookupname] +  local ligatures=steps[1].coverage[startchar]    if not ligatures then      if trace_bugs then -      logwarning("%s: no ligature hits",cref(kind,chainname,chainlookupname,lookupname,chainindex)) +      logwarning("%s: no ligatures starting with %s",cref(dataset,sequence,chainindex),gref(startchar))      end    else -    ligatures=ligatures[startchar] -    if not ligatures then -      if trace_bugs then -        logwarning("%s: no ligatures starting with %s",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar)) -      end -    else -      local s=getnext(start) -      local discfound=false -      local last=stop -      local nofreplacements=1 -      local skipmark=currentlookup.flags[1] -      while s do -        local id=getid(s) -        if id==disc_code then -          if not discfound then -            discfound=s -          end -          if s==stop then -            break  -          else -            s=getnext(s) -          end +    local current=getnext(start) +    local discfound=false +    local last=stop +    local nofreplacements=1 +    local skipmark=currentlookup.flags[1]  +    while current do +      local id=getid(current) +      if id==disc_code then +        if not discfound then +          discfound=current +        end +        if current==stop then +          break           else -          local schar=getchar(s) -          if skipmark and marks[schar] then  -            s=getnext(s) -          else -            local lg=ligatures[schar] -            if lg then -              ligatures,last,nofreplacements=lg,s,nofreplacements+1 -              if s==stop then -                break -              else -                s=getnext(s) -              end -            else +          current=getnext(current) +        end +      else +        local schar=getchar(current) +        if skipmark and marks[schar] then +            current=getnext(current) +        else +          local lg=ligatures[schar] +          if lg then +            ligatures=lg +            last=current +            nofreplacements=nofreplacements+1 +            if current==stop then                break +            else +              current=getnext(current)              end -          end -        end -      end -      local l2=ligatures.ligature -      if l2 then -        if chainindex then -          stop=last -        end -        if trace_ligatures then -          if start==stop then -            logprocess("%s: replacing character %s by ligature %s case 3",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar),gref(l2))            else -            logprocess("%s: replacing character %s upto %s by ligature %s case 4",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar),gref(getchar(stop)),gref(l2)) +            break            end          end -        head,start=toligature(kind,lookupname,head,start,stop,l2,currentlookup.flags[1],discfound) -        return head,start,true,nofreplacements,discfound -      elseif trace_bugs then +      end +    end +    local ligature=ligatures.ligature +    if ligature then +      if chainindex then +        stop=last +      end +      if trace_ligatures then          if start==stop then -          logwarning("%s: replacing character %s by ligature fails",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar)) +          logprocess("%s: replacing character %s by ligature %s case 3",cref(dataset,sequence,chainindex),gref(startchar),gref(ligature))          else -          logwarning("%s: replacing character %s upto %s by ligature fails",cref(kind,chainname,chainlookupname,lookupname,chainindex),gref(startchar),gref(getchar(stop))) +          logprocess("%s: replacing character %s upto %s by ligature %s case 4",cref(dataset,sequence,chainindex),gref(startchar),gref(getchar(stop)),gref(ligature))          end        end +      head,start=toligature(head,start,stop,ligature,dataset,sequence,skipmark,discfound) +      return head,start,true,nofreplacements,discfound +    elseif trace_bugs then +      if start==stop then +        logwarning("%s: replacing character %s by ligature fails",cref(dataset,sequence,chainindex),gref(startchar)) +      else +        logwarning("%s: replacing character %s upto %s by ligature fails",cref(dataset,sequence,chainindex),gref(startchar),gref(getchar(stop))) +      end      end    end    return head,start,false,0,false  end -function chainprocs.gpos_single(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex,sequence) +function chainprocs.gpos_single(head,start,stop,dataset,sequence,currentlookup,rlmode,chainindex) +  local steps=currentlookup.steps +  local nofsteps=currentlookup.nofsteps +  if nofsteps>1 then +    reportmoresteps(dataset,sequence) +  end    local startchar=getchar(start) -  local subtables=currentlookup.subtables -  local lookupname=subtables[1] -  local kerns=lookuphash[lookupname] -  if kerns then -    kerns=kerns[startchar]  -    if kerns then -      local dx,dy,w,h=setpair(start,tfmdata.parameters.factor,rlmode,sequence.flags[4],kerns)  -      if trace_kerns then -        logprocess("%s: shifting single %s by (%p,%p) and correction (%p,%p)",cref(kind,chainname,chainlookupname),gref(startchar),dx,dy,w,h) -      end +  local step=steps[1] +  local kerns=step.coverage[startchar] +  if not kerns then +  elseif step.format=="pair" then +    local dx,dy,w,h=setpair(start,factor,rlmode,sequence.flags[4],kerns)  +    if trace_kerns then +      logprocess("%s: shifting single %s by (%p,%p) and correction (%p,%p)",cref(dataset,sequence),gref(startchar),dx,dy,w,h) +    end +  else  +    local k=setkern(start,factor,rlmode,kerns,injection) +    if trace_kerns then +      logprocess("%s: shifting single %s by %p",cref(dataset,sequence),gref(startchar),k)      end    end    return head,start,false  end -function chainprocs.gpos_pair(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname,chainindex,sequence) +function chainprocs.gpos_pair(head,start,stop,dataset,sequence,currentlookup,rlmode,chainindex)  +  local steps=currentlookup.steps +  local nofsteps=currentlookup.nofsteps +  if nofsteps>1 then +    reportmoresteps(dataset,sequence) +  end    local snext=getnext(start)    if snext then      local startchar=getchar(start) -    local subtables=currentlookup.subtables -    local lookupname=subtables[1] -    local kerns=lookuphash[lookupname] +    local step=steps[1] +    local kerns=step.coverage[startchar]       if kerns then -      kerns=kerns[startchar] -      if kerns then -        local lookuptype=lookuptypes[lookupname] -        local prev,done=start,false -        local factor=tfmdata.parameters.factor -        while snext and getid(snext)==glyph_code and getfont(snext)==currentfont and getsubtype(snext)<256 do -          local nextchar=getchar(snext) -          local krn=kerns[nextchar] -          if not krn and marks[nextchar] then -            prev=snext -            snext=getnext(snext) -          else -            if not krn then -            elseif type(krn)=="table" then -              if lookuptype=="pair" then -                local a,b=krn[2],krn[3] -                if a and #a>0 then -                  local startchar=getchar(start) -                  local x,y,w,h=setpair(start,factor,rlmode,sequence.flags[4],a)  -                  if trace_kerns then -                    logprocess("%s: shifting first of pair %s and %s by (%p,%p) and correction (%p,%p)",cref(kind,chainname,chainlookupname),gref(startchar),gref(nextchar),x,y,w,h) -                  end -                end -                if b and #b>0 then -                  local startchar=getchar(start) -                  local x,y,w,h=setpair(snext,factor,rlmode,sequence.flags[4],b)  -                  if trace_kerns then -                    logprocess("%s: shifting second of pair %s and %s by (%p,%p) and correction (%p,%p)",cref(kind,chainname,chainlookupname),gref(startchar),gref(nextchar),x,y,w,h) -                  end -                end -              else -                report_process("%s: check this out (old kern stuff)",cref(kind,chainname,chainlookupname)) -              end -              done=true -            elseif krn~=0 then -              local k=setkern(snext,factor,rlmode,krn) +      local prev=start +      local done=false +      while snext do +        local nextchar=ischar(snext,currentfont) +        if not nextchar then +          break +        end +        local krn=kerns[nextchar] +        if not krn and marks[nextchar] then +          prev=snext +          snext=getnext(snext) +        elseif not krn then +          break +        elseif step.format=="pair" then +          local a,b=krn[1],krn[2] +          if optimizekerns then +            if not b and a[1]==0 and a[2]==0 and a[4]==0 then +              local k=setkern(snext,factor,rlmode,a[3],"injections")                if trace_kerns then -                logprocess("%s: inserting kern %s between %s and %s",cref(kind,chainname,chainlookupname),k,gref(getchar(prev)),gref(nextchar)) +                logprocess("%s: shifting single %s by %p",cref(dataset,sequence),gref(startchar),k)                end                done=true +              break              end -            break            end +          if a and #a>0 then +            local startchar=getchar(start) +            local x,y,w,h=setpair(start,factor,rlmode,sequence.flags[4],a,"injections")  +            if trace_kerns then +              logprocess("%s: shifting first of pair %s and %s by (%p,%p) and correction (%p,%p)",cref(dataset,sequence),gref(startchar),gref(nextchar),x,y,w,h) +            end +          end +          if b and #b>0 then +            local startchar=getchar(start) +            local x,y,w,h=setpair(snext,factor,rlmode,sequence.flags[4],b,"injections") +            if trace_kerns then +              logprocess("%s: shifting second of pair %s and %s by (%p,%p) and correction (%p,%p)",cref(dataset,sequence),gref(startchar),gref(nextchar),x,y,w,h) +            end +          end +          done=true +          break +        elseif krn~=0 then +          local k=setkern(snext,factor,rlmode,krn) +          if trace_kerns then +            logprocess("%s: inserting kern %s between %s and %s",cref(dataset,sequence),k,gref(getchar(prev)),gref(nextchar)) +          end +          done=true +          break +        else +          break          end -        return head,start,done        end +      return head,start,done      end    end    return head,start,false  end -function chainprocs.gpos_mark2base(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname) +function chainprocs.gpos_mark2base(head,start,stop,dataset,sequence,currentlookup,rlmode) +  local steps=currentlookup.steps +  local nofsteps=currentlookup.nofsteps +  if nofsteps>1 then +    reportmoresteps(dataset,sequence) +  end    local markchar=getchar(start)    if marks[markchar] then -    local subtables=currentlookup.subtables -    local lookupname=subtables[1] -    local markanchors=lookuphash[lookupname] -    if markanchors then -      markanchors=markanchors[markchar] -    end +    local markanchors=steps[1].coverage[markchar]       if markanchors then        local base=getprev(start)  -      if base and getid(base)==glyph_code and getfont(base)==currentfont and getsubtype(base)<256 then -        local basechar=getchar(base) -        if marks[basechar] then -          while true do -            base=getprev(base) -            if base and getid(base)==glyph_code and getfont(base)==currentfont and getsubtype(base)<256 then -              basechar=getchar(base) -              if not marks[basechar] then -                break -              end -            else -              if trace_bugs then -                logwarning("%s: no base for mark %s",pref(kind,lookupname),gref(markchar)) -              end -              return head,start,false -            end -          end -        end -        local baseanchors=descriptions[basechar].anchors -        if baseanchors then -          local baseanchors=baseanchors['basechar'] -          if baseanchors then -            local al=anchorlookups[lookupname] -            for anchor,ba in next,baseanchors do -              if al[anchor] then -                local ma=markanchors[anchor] -                if ma then -                  local dx,dy,bound=setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma,characters[basechar]) -                  if trace_marks then -                    logprocess("%s, anchor %s, bound %s: anchoring mark %s to basechar %s => (%p,%p)", -                      cref(kind,chainname,chainlookupname,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy) +      if base then +        local basechar=ischar(base,currentfont) +        if basechar then +          if marks[basechar] then +            while base do +              base=getprev(base) +              if base then +                local basechar=ischar(base,currentfont) +                if basechar then +                  if not marks[basechar] then +                    break                    end -                  return head,start,true +                else +                  if trace_bugs then +                    logwarning("%s: no base for mark %s, case %i",pref(dataset,sequence),gref(markchar),1) +                  end +                  return head,start,false                  end +              else +                if trace_bugs then +                  logwarning("%s: no base for mark %s, case %i",pref(dataset,sequence),gref(markchar),2) +                end +                return head,start,false                end              end -            if trace_bugs then -              logwarning("%s, no matching anchors for mark %s and base %s",cref(kind,chainname,chainlookupname,lookupname),gref(markchar),gref(basechar)) +          end +          local ba=markanchors[1][basechar] +          if ba then +            local ma=markanchors[2] +            if ma then +              local dx,dy,bound=setmark(start,base,factor,rlmode,ba,ma,characters[basechar]) +              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) +              end +              return head,start,true              end            end +        elseif trace_bugs then +          logwarning("%s: prev node is no char, case %i",cref(dataset,sequence),1)          end        elseif trace_bugs then -        logwarning("%s: prev node is no char",cref(kind,chainname,chainlookupname,lookupname)) +        logwarning("%s: prev node is no char, case %i",cref(dataset,sequence),2)        end      elseif trace_bugs then -      logwarning("%s: mark %s has no anchors",cref(kind,chainname,chainlookupname,lookupname),gref(markchar)) +      logwarning("%s: mark %s has no anchors",cref(dataset,sequence),gref(markchar))      end    elseif trace_bugs then -    logwarning("%s: mark %s is no mark",cref(kind,chainname,chainlookupname),gref(markchar)) +    logwarning("%s: mark %s is no mark",cref(dataset,sequence),gref(markchar))    end    return head,start,false  end -function chainprocs.gpos_mark2ligature(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname) +function chainprocs.gpos_mark2ligature(head,start,stop,dataset,sequence,currentlookup,rlmode) +  local steps=currentlookup.steps +  local nofsteps=currentlookup.nofsteps +  if nofsteps>1 then +    reportmoresteps(dataset,sequence) +  end    local markchar=getchar(start)    if marks[markchar] then -    local subtables=currentlookup.subtables -    local lookupname=subtables[1] -    local markanchors=lookuphash[lookupname] -    if markanchors then -      markanchors=markanchors[markchar] -    end +    local markanchors=steps[1].coverage[markchar]       if markanchors then        local base=getprev(start)  -      if base and getid(base)==glyph_code and getfont(base)==currentfont and getsubtype(base)<256 then -        local basechar=getchar(base) -        if marks[basechar] then -          while true do -            base=getprev(base) -            if base and getid(base)==glyph_code and getfont(base)==currentfont and getsubtype(base)<256 then -              basechar=getchar(base) -              if not marks[basechar] then -                break -              end -            else -              if trace_bugs then -                logwarning("%s: no base for mark %s",cref(kind,chainname,chainlookupname,lookupname),markchar) +      if base then +        local basechar=ischar(base,currentfont) +        if basechar then +          if marks[basechar] then +            while base do +              base=getprev(base) +              if base then +                local basechar=ischar(base,currentfont) +                if basechar then +                  if not marks[basechar] then +                    break +                  end +                else +                  if trace_bugs then +                    logwarning("%s: no base for mark %s, case %i",cref(dataset,sequence),markchar,1) +                  end +                  return head,start,false +                end +              else +                if trace_bugs then +                  logwarning("%s: no base for mark %s, case %i",cref(dataset,sequence),markchar,2) +                end +                return head,start,false                end -              return head,start,false              end            end -        end -        local index=getligaindex(start) -        local baseanchors=descriptions[basechar].anchors -        if baseanchors then -          local baseanchors=baseanchors['baselig'] -          if baseanchors then -            local al=anchorlookups[lookupname] -            for anchor,ba in next,baseanchors do -              if al[anchor] then -                local ma=markanchors[anchor] -                if ma then -                  ba=ba[index] -                  if ba then -                    local dx,dy,bound=setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma,characters[basechar]) -                    if trace_marks then -                      logprocess("%s, anchor %s, bound %s: anchoring mark %s to baselig %s at index %s => (%p,%p)", -                        cref(kind,chainname,chainlookupname,lookupname),anchor,a or bound,gref(markchar),gref(basechar),index,dx,dy) -                    end -                    return head,start,true -                  end +          local ba=markanchors[1][basechar] +          if ba then +            local ma=markanchors[2] +            if ma then +              local index=getligaindex(start) +              ba=ba[index] +              if ba then +                local dx,dy,bound=setmark(start,base,factor,rlmode,ba,ma,characters[basechar]) +                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)                  end +                return head,start,true                end              end -            if trace_bugs then -              logwarning("%s: no matching anchors for mark %s and baselig %s",cref(kind,chainname,chainlookupname,lookupname),gref(markchar),gref(basechar)) -            end            end +        elseif trace_bugs then +          logwarning("%s, prev node is no char, case %i",cref(dataset,sequence),1)          end        elseif trace_bugs then -        logwarning("feature %s, lookup %s: prev node is no char",kind,lookupname) +        logwarning("%s, prev node is no char, case %i",cref(dataset,sequence),2)        end      elseif trace_bugs then -      logwarning("%s: mark %s has no anchors",cref(kind,chainname,chainlookupname,lookupname),gref(markchar)) +      logwarning("%s, mark %s has no anchors",cref(dataset,sequence),gref(markchar))      end    elseif trace_bugs then -    logwarning("%s: mark %s is no mark",cref(kind,chainname,chainlookupname),gref(markchar)) +    logwarning("%s, mark %s is no mark",cref(dataset,sequence),gref(markchar))    end    return head,start,false  end -function chainprocs.gpos_mark2mark(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname) +function chainprocs.gpos_mark2mark(head,start,stop,dataset,sequence,currentlookup,rlmode) +  local steps=currentlookup.steps +  local nofsteps=currentlookup.nofsteps +  if nofsteps>1 then +    reportmoresteps(dataset,sequence) +  end    local markchar=getchar(start)    if marks[markchar] then -    local subtables=currentlookup.subtables -    local lookupname=subtables[1] -    local markanchors=lookuphash[lookupname] -    if markanchors then -      markanchors=markanchors[markchar] -    end +    local markanchors=steps[1].coverage[markchar]       if markanchors then        local base=getprev(start)         local slc=getligaindex(start) @@ -12987,112 +19280,91 @@ function chainprocs.gpos_mark2mark(head,start,stop,kind,chainname,currentcontext            end          end        end -      if base and getid(base)==glyph_code and getfont(base)==currentfont and getsubtype(base)<256 then  -        local basechar=getchar(base) -        local baseanchors=descriptions[basechar].anchors -        if baseanchors then -          baseanchors=baseanchors['basemark'] -          if baseanchors then -            local al=anchorlookups[lookupname] -            for anchor,ba in next,baseanchors do -              if al[anchor] then -                local ma=markanchors[anchor] -                if ma then -                  local dx,dy,bound=setmark(start,base,tfmdata.parameters.factor,rlmode,ba,ma,characters[basechar],true) -                  if trace_marks then -                    logprocess("%s, anchor %s, bound %s: anchoring mark %s to basemark %s => (%p,%p)", -                      cref(kind,chainname,chainlookupname,lookupname),anchor,bound,gref(markchar),gref(basechar),dx,dy) -                  end -                  return head,start,true -                end +      if base then  +        local basechar=ischar(base,currentfont) +        if basechar then +          local ba=markanchors[1][basechar] +          if ba then +            local ma=markanchors[2] +            if ma then +              local dx,dy,bound=setmark(start,base,factor,rlmode,ba,ma,characters[basechar],true) +              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)                end -            end -            if trace_bugs then -              logwarning("%s: no matching anchors for mark %s and basemark %s",gref(kind,chainname,chainlookupname,lookupname),gref(markchar),gref(basechar)) +              return head,start,true              end            end +        elseif trace_bugs then +          logwarning("%s: prev node is no mark, case %i",cref(dataset,sequence),1)          end        elseif trace_bugs then -        logwarning("%s: prev node is no mark",cref(kind,chainname,chainlookupname,lookupname)) +        logwarning("%s: prev node is no mark, case %i",cref(dataset,sequence),2)        end      elseif trace_bugs then -      logwarning("%s: mark %s has no anchors",cref(kind,chainname,chainlookupname,lookupname),gref(markchar)) +      logwarning("%s: mark %s has no anchors",cref(dataset,sequence),gref(markchar))      end    elseif trace_bugs then -    logwarning("%s: mark %s is no mark",cref(kind,chainname,chainlookupname),gref(markchar)) +    logwarning("%s: mark %s is no mark",cref(dataset,sequence),gref(markchar))    end    return head,start,false  end -function chainprocs.gpos_cursive(head,start,stop,kind,chainname,currentcontext,lookuphash,currentlookup,chainlookupname) -  local alreadydone=cursonce and getprop(start,a_cursbase) -  if not alreadydone then -    local startchar=getchar(start) -    local subtables=currentlookup.subtables -    local lookupname=subtables[1] -    local exitanchors=lookuphash[lookupname] -    if exitanchors then -      exitanchors=exitanchors[startchar] -    end -    if exitanchors then -      local done=false -      if marks[startchar] then -        if trace_cursive then -          logprocess("%s: ignoring cursive for mark %s",pref(kind,lookupname),gref(startchar)) -        end -      else -        local nxt=getnext(start) -        while not done and nxt and getid(nxt)==glyph_code and getfont(nxt)==currentfont and getsubtype(nxt)<256 do -          local nextchar=getchar(nxt) -          if marks[nextchar] then -            nxt=getnext(nxt) -          else -            local entryanchors=descriptions[nextchar] -            if entryanchors then -              entryanchors=entryanchors.anchors -              if entryanchors then -                entryanchors=entryanchors['centry'] -                if entryanchors then -                  local al=anchorlookups[lookupname] -                  for anchor,entry in next,entryanchors do -                    if al[anchor] then -                      local exit=exitanchors[anchor] -                      if exit then -                        local dx,dy,bound=setcursive(start,nxt,tfmdata.parameters.factor,rlmode,exit,entry,characters[startchar],characters[nextchar]) -                        if trace_cursive then -                          logprocess("%s: moving %s to %s cursive (%p,%p) using anchor %s and bound %s in rlmode %s",pref(kind,lookupname),gref(startchar),gref(nextchar),dx,dy,anchor,bound,rlmode) -                        end -                        done=true -                        break -                      end -                    end -                  end +function chainprocs.gpos_cursive(head,start,stop,dataset,sequence,currentlookup,rlmode) +  local steps=currentlookup.steps +  local nofsteps=currentlookup.nofsteps +  if nofsteps>1 then +    reportmoresteps(dataset,sequence) +  end +  local startchar=getchar(start) +  local exitanchors=steps[1].coverage[startchar]  +  if exitanchors then +    local done=false +    if marks[startchar] then +      if trace_cursive then +        logprocess("%s: ignoring cursive for mark %s",pref(dataset,sequence),gref(startchar)) +      end +    else +      local nxt=getnext(start) +      while not done and nxt do +        local nextchar=ischar(nxt,currentfont) +        if not nextchar then +          break +        elseif marks[nextchar] then +          nxt=getnext(nxt) +        else +          local exit=exitanchors[3] +          if exit then +            local entry=exitanchors[1][nextchar] +            if entry then +              entry=entry[2] +              if entry then +                local dx,dy,bound=setcursive(start,nxt,factor,rlmode,exit,entry,characters[startchar],characters[nextchar]) +                if trace_cursive then +                  logprocess("%s: moving %s to %s cursive (%p,%p) using anchor %s and bound %s in %s mode",pref(dataset,sequence),gref(startchar),gref(nextchar),dx,dy,anchor,bound,mref(rlmode))                  end +                done=true +                break                end -            elseif trace_bugs then -              onetimemessage(currentfont,startchar,"no entry anchors",report_fonts)              end -            break +          elseif trace_bugs then +            onetimemessage(currentfont,startchar,"no entry anchors",report_fonts)            end +          break          end        end -      return head,start,done -    else -      if trace_cursive and trace_details then -        logprocess("%s, cursive %s is already done",pref(kind,lookupname),gref(getchar(start)),alreadydone) -      end -      return head,start,false      end -  end -  return head,start,false -end -local function show_skip(kind,chainname,char,ck,class) -  if ck[9] then -    logwarning("%s: skipping char %s, class %a, rule %a, lookuptype %a, %a => %a",cref(kind,chainname),gref(char),class,ck[1],ck[2],ck[9],ck[10]) +    return head,start,done    else -    logwarning("%s: skipping char %s, class %a, rule %a, lookuptype %a",cref(kind,chainname),gref(char),class,ck[1],ck[2]) +    if trace_cursive and trace_details then +      logprocess("%s, cursive %s is already done",pref(dataset,sequence),gref(getchar(start)),alreadydone) +    end +    return head,start,false    end  end -local function chaindisk(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,chainindex,sequence,chainproc) +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 function chaindisk(head,start,last,dataset,sequence,chainlookup,rlmode,k,ck,chainproc)    if not start then      return head,start,false    end @@ -13152,6 +19424,8 @@ local function chaindisk(head,start,last,kind,chainname,ck,lookuphash,chainlooku        else          break        end +    else +      break       end    end    if sweepoverflow then @@ -13249,11 +19523,16 @@ local function chaindisk(head,start,last,kind,chainname,ck,lookuphash,chainlooku      local cl=getprev(lookaheaddisc)      local cprev=getprev(start)      local insertedmarks=0 -    while cprev and getid(cf)==glyph_code and getfont(cf)==currentfont and getsubtype(cf)<256 and marks[getchar(cf)] do -      insertedmarks=insertedmarks+1 -      cf=cprev -      startishead=cf==head -      cprev=getprev(cprev) +    while cprev do +      local char=ischar(cf,currentfont) +      if char and marks[char] then +        insertedmarks=insertedmarks+1 +        cf=cprev +        startishead=cf==head +        cprev=getprev(cprev) +      else +        break +      end      end      setprev(lookaheaddisc,cprev)      if cprev then @@ -13264,8 +19543,7 @@ local function chaindisk(head,start,last,kind,chainname,ck,lookuphash,chainlooku      if startishead then        head=lookaheaddisc      end -    local replace=getfield(lookaheaddisc,"replace") -    local pre=getfield(lookaheaddisc,"pre") +    local pre,post,replace=getdisc(lookaheaddisc)      local new=copy_node_list(cf)      local cnew=new      for i=1,insertedmarks do @@ -13276,22 +19554,19 @@ local function chaindisk(head,start,last,kind,chainname,ck,lookuphash,chainlooku        clast=getnext(clast)      end      if not notmatchpre[lookaheaddisc] then -      cf,start,ok=chainproc(cf,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence) +      cf,start,ok=chainproc(cf,start,last,dataset,sequence,chainlookup,rlmode,k)      end      if not notmatchreplace[lookaheaddisc] then -      new,cnew,ok=chainproc(new,cnew,clast,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence) +      new,cnew,ok=chainproc(new,cnew,clast,dataset,sequence,chainlookup,rlmode,k)      end      if pre then -      setnext(cl,pre) -      setprev(pre,cl) +      setlink(cl,pre)      end      if replace then        local tail=find_node_tail(new) -      setnext(tail,replace) -      setprev(replace,tail) +      setlink(tail,replace)      end -    setfield(lookaheaddisc,"pre",cf)    -    setfield(lookaheaddisc,"replace",new)  +    setdisc(lookaheaddisc,cf,post,new)      start=getprev(lookaheaddisc)      sweephead[cf]=getnext(clast)      sweephead[new]=getnext(last) @@ -13300,10 +19575,15 @@ local function chaindisk(head,start,last,kind,chainname,ck,lookuphash,chainlooku      local cl=start      local cnext=getnext(start)      local insertedmarks=0 -    while cnext and getid(cnext)==glyph_code and getfont(cnext)==currentfont and getsubtype(cnext)<256 and marks[getchar(cnext)] do -      insertedmarks=insertedmarks+1 -      cl=cnext -      cnext=getnext(cnext) +    while cnext do +      local char=ischar(cnext,currentfont) +      if char and marks[char] then +        insertedmarks=insertedmarks+1 +        cl=cnext +        cnext=getnext(cnext) +      else +        break +      end      end      if cnext then        setprev(cnext,backtrackdisc) @@ -13311,8 +19591,7 @@ local function chaindisk(head,start,last,kind,chainname,ck,lookuphash,chainlooku      setnext(backtrackdisc,cnext)      setprev(cf,nil)      setnext(cl,nil) -    local replace=getfield(backtrackdisc,"replace") -    local post=getfield(backtrackdisc,"post") +    local pre,post,replace,pretail,posttail,replacetail=getdisc(backtrackdisc,true)      local new=copy_node_list(cf)      local cnew=find_node_tail(new)      for i=1,insertedmarks do @@ -13323,41 +19602,38 @@ local function chaindisk(head,start,last,kind,chainname,ck,lookuphash,chainlooku        clast=getnext(clast)      end      if not notmatchpost[backtrackdisc] then -      cf,start,ok=chainproc(cf,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence) +      cf,start,ok=chainproc(cf,start,last,dataset,sequence,chainlookup,rlmode,k)      end      if not notmatchreplace[backtrackdisc] then -      new,cnew,ok=chainproc(new,cnew,clast,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence) +      new,cnew,ok=chainproc(new,cnew,clast,dataset,sequence,chainlookup,rlmode,k)      end      if post then -      local tail=find_node_tail(post) -      setnext(tail,cf) -      setprev(cf,tail) +      setlink(posttail,cf)      else        post=cf      end      if replace then -      local tail=find_node_tail(replace) -      setnext(tail,new) -      setprev(new,tail) +      setlink(replacetail,new)      else        replace=new      end -    setfield(backtrackdisc,"post",post)     -    setfield(backtrackdisc,"replace",replace)  +    setdisc(backtrackdisc,pre,post,replace)      start=getprev(backtrackdisc)      sweephead[post]=getnext(clast)      sweephead[replace]=getnext(last)    else -    head,start,ok=chainproc(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence) +    head,start,ok=chainproc(head,start,last,dataset,sequence,chainlookup,rlmode,k)    end    return head,start,ok  end -local function normal_handle_contextchain(head,start,kind,chainname,contexts,sequence,lookuphash) +local noflags={ false,false,false,false } +local function handle_contextchain(head,start,dataset,sequence,contexts,rlmode)    local sweepnode=sweepnode    local sweeptype=sweeptype +  local currentfont=currentfont    local diskseen=false    local checkdisc=getprev(head) -  local flags=sequence.flags +  local flags=sequence.flags or noflags    local done=false    local skipmark=flags[1]    local skipligature=flags[2] @@ -13372,7 +19648,10 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq      local seq=ck[3]      local s=#seq      if s==1 then -      match=getid(current)==glyph_code and getfont(current)==currentfont and getsubtype(current)<256 and seq[1][getchar(current)] +      local char=ischar(current,currentfont) +      if char then +        match=seq[1][char] +      end      else        local f=ck[4]        local l=ck[5] @@ -13389,33 +19668,22 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq                sweeptype=nil              end              if last then -              local id=getid(last) -              if id==glyph_code then -                if getfont(last)==currentfont and getsubtype(last)<256 then -                  local char=getchar(last) -                  local ccd=descriptions[char] -                  if ccd then -                    local class=ccd.class or "base" -                    if class==skipmark or class==skipligature or class==skipbase or (markclass and class=="mark" and not markclass[char]) then -                      skipped=true -                      if trace_skips then -                        show_skip(kind,chainname,char,ck,class) -                      end +              local char,id=ischar(last,currentfont) +              if char then +                local ccd=descriptions[char] +                if ccd then +                  local class=ccd.class or "base" +                  if class==skipmark or class==skipligature or class==skipbase or (markclass and class=="mark" and not markclass[char]) then +                    skipped=true +                    if trace_skips then +                      show_skip(dataset,sequence,char,ck,class) +                    end +                    last=getnext(last) +                  elseif seq[n][char] then +                    if n<l then                        last=getnext(last) -                    elseif seq[n][char] then -                      if n<l then -                        last=getnext(last) -                      end -                      n=n+1 -                    else -                      if discfound then -                        notmatchreplace[discfound]=true -                        match=not notmatchpre[discfound] -                      else -                        match=false -                      end -                      break                      end +                    n=n+1                    else                      if discfound then                        notmatchreplace[discfound]=true @@ -13434,14 +19702,22 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq                    end                    break                  end +                last=getnext(last) +              elseif char==false then +                if discfound then +                  notmatchreplace[discfound]=true +                  match=not notmatchpre[discfound] +                else +                  match=false +                end +                break                elseif id==disc_code then                  diskseen=true                  discfound=last                  notmatchpre[last]=nil                  notmatchpost[last]=true                  notmatchreplace[last]=nil -                local pre=getfield(last,"pre") -                local replace=getfield(last,"replace") +                local pre,post,replace=getdisc(last)                  if pre then                    local n=n                    while pre do @@ -13501,29 +19777,18 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq              local n=f-1              while n>=1 do                if prev then -                local id=getid(prev) -                if id==glyph_code then -                  if getfont(prev)==currentfont and getsubtype(prev)<256 then  -                    local char=getchar(prev) -                    local ccd=descriptions[char] -                    if ccd then -                      local class=ccd.class -                      if class==skipmark or class==skipligature or class==skipbase or (markclass and class=="mark" and not markclass[char]) then -                        skipped=true -                        if trace_skips then -                          show_skip(kind,chainname,char,ck,class) -                        end -                      elseif seq[n][char] then -                        n=n -1 -                      else -                        if discfound then -                          notmatchreplace[discfound]=true -                          match=not notmatchpost[discfound] -                        else -                          match=false -                        end -                        break +                local char,id=ischar(prev,currentfont) +                if char then +                  local ccd=descriptions[char] +                  if ccd then +                    local class=ccd.class +                    if class==skipmark or class==skipligature or class==skipbase or (markclass and class=="mark" and not markclass[char]) then +                      skipped=true +                      if trace_skips then +                        show_skip(dataset,sequence,char,ck,class)                        end +                    elseif seq[n][char] then +                      n=n -1                      else                        if discfound then                          notmatchreplace[discfound]=true @@ -13542,19 +19807,25 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq                      end                      break                    end +                  prev=getprev(prev) +                elseif char==false then +                  if discfound then +                    notmatchreplace[discfound]=true +                    match=not notmatchpost[discfound] +                  else +                    match=false +                  end +                  break                  elseif id==disc_code then                    diskseen=true                    discfound=prev                    notmatchpre[prev]=true                    notmatchpost[prev]=nil                    notmatchreplace[prev]=nil -                  local pre=getfield(prev,"pre") -                  local post=getfield(prev,"post") -                  local replace=getfield(prev,"replace") +                  local pre,post,replace,pretail,posttail,replacetail=getdisc(prev,true)                    if pre~=start and post~=start and replace~=start then                      if post then                        local n=n -                      local posttail=find_node_tail(post)                        while posttail do                          if seq[n][getchar(posttail)] then                            n=n-1 @@ -13578,7 +19849,6 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq                        notmatchpost[prev]=true                      end                      if replace then -                      local replacetail=find_node_tail(replace)                        while replacetail do                          if seq[n][getchar(replacetail)] then                            n=n-1 @@ -13604,7 +19874,7 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq                    else                    end                  elseif seq[n][32] then -                  n=n -1 +                  n=n-1                  else                    match=false                    break @@ -13636,29 +19906,18 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq            local n=l+1            while n<=s do              if current then -              local id=getid(current) -              if id==glyph_code then -                if getfont(current)==currentfont and getsubtype(current)<256 then  -                  local char=getchar(current) -                  local ccd=descriptions[char] -                  if ccd then -                    local class=ccd.class -                    if class==skipmark or class==skipligature or class==skipbase or (markclass and class=="mark" and not markclass[char]) then -                      skipped=true -                      if trace_skips then -                        show_skip(kind,chainname,char,ck,class) -                      end -                    elseif seq[n][char] then -                      n=n+1 -                    else -                      if discfound then -                        notmatchreplace[discfound]=true -                        match=not notmatchpre[discfound] -                      else -                        match=false -                      end -                      break +              local char,id=ischar(current,currentfont) +              if char then +                local ccd=descriptions[char] +                if ccd then +                  local class=ccd.class +                  if class==skipmark or class==skipligature or class==skipbase or (markclass and class=="mark" and not markclass[char]) then +                    skipped=true +                    if trace_skips then +                      show_skip(dataset,sequence,char,ck,class)                      end +                  elseif seq[n][char] then +                    n=n+1                    else                      if discfound then                        notmatchreplace[discfound]=true @@ -13677,14 +19936,22 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq                    end                    break                  end +                current=getnext(current) +              elseif char==false then +                if discfound then +                  notmatchreplace[discfound]=true +                  match=not notmatchpre[discfound] +                else +                  match=false +                end +                break                elseif id==disc_code then                  diskseen=true                  discfound=current                  notmatchpre[current]=nil                  notmatchpost[current]=true                  notmatchreplace[current]=nil -                local pre=getfield(current,"pre") -                local replace=getfield(current,"replace") +                local pre,post,replace=getdisc(current)                  if pre then                    local n=n                    while pre do @@ -13733,6 +20000,7 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq                current=getnext(current)              elseif seq[n][32] then                n=n+1 +current=getnext(current)              else                match=false                break @@ -13746,45 +20014,39 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq      if match then        local diskchain=diskseen or sweepnode        if trace_contexts then -        local rule,lookuptype,f,l=ck[1],ck[2],ck[4],ck[5] +        local rule=ck[1] +        local lookuptype=ck[8] or ck[2] +        local first=ck[4] +        local last=ck[5]          local char=getchar(start) -        if ck[9] then -          logwarning("%s: rule %s matches at char %s for (%s,%s,%s) chars, lookuptype %a, %a => %a", -            cref(kind,chainname),rule,gref(char),f-1,l-f+1,s-l,lookuptype,ck[9],ck[10]) -        else -          logwarning("%s: rule %s matches at char %s for (%s,%s,%s) chars, lookuptype %a", -            cref(kind,chainname),rule,gref(char),f-1,l-f+1,s-l,lookuptype) -        end +        logwarning("%s: rule %s matches at char %s for (%s,%s,%s) chars, lookuptype %a", +          cref(dataset,sequence),rule,gref(char),first-1,last-first+1,s-last,lookuptype)        end        local chainlookups=ck[6]        if chainlookups then          local nofchainlookups=#chainlookups          if nofchainlookups==1 then -          local chainlookupname=chainlookups[1] -          local chainlookup=lookuptable[chainlookupname] -          if chainlookup then -            local chainproc=chainprocs[chainlookup.type] -            if chainproc then -              local ok -              if diskchain then -                head,start,ok=chaindisk(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence,chainproc) -              else -                head,start,ok=chainproc(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence) -              end -              if ok then -                done=true -              end +          local chainlookup=chainlookups[1] +          local chainkind=chainlookup.type +          local chainproc=chainprocs[chainkind] +          if chainproc then +            local ok +            if diskchain then +              head,start,ok=chaindisk(head,start,last,dataset,sequence,chainlookup,rlmode,1,ck,chainproc)              else -              logprocess("%s: %s is not yet supported",cref(kind,chainname,chainlookupname),chainlookup.type) +              head,start,ok=chainproc(head,start,last,dataset,sequence,chainlookup,rlmode,1)              end -          else  -            logprocess("%s is not yet supported",cref(kind,chainname,chainlookupname)) +            if ok then +              done=true +            end +          else +            logprocess("%s: %s is not yet supported (1)",cref(dataset,sequence),chainkind)            end           else            local i=1            while start and true do              if skipped then -              while true do  +              while start do                   local char=getchar(start)                  local ccd=descriptions[char]                  if ccd then @@ -13799,21 +20061,18 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq                  end                end              end -            local chainlookupname=chainlookups[i] -            local chainlookup=lookuptable[chainlookupname] +            local chainlookup=chainlookups[1]               if not chainlookup then -              i=i+1 +              i=i+1               else -              local chainproc=chainprocs[chainlookup.type] -              if not chainproc then -                logprocess("%s: %s is not yet supported",cref(kind,chainname,chainlookupname),chainlookup.type) -                i=i+1 -              else +              local chainkind=chainlookup.type +              local chainproc=chainprocs[chainkind] +              if chainproc then                  local ok,n                  if diskchain then -                  head,start,ok=chaindisk(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,nil,sequence,chainproc) +                  head,start,ok=chaindisk(head,start,last,dataset,sequence,chainlookup,rlmode,i,ck,chainproc)                  else -                  head,start,ok,n=chainproc(head,start,last,kind,chainname,ck,lookuphash,chainlookup,chainlookupname,i,sequence) +                  head,start,ok,n=chainproc(head,start,last,dataset,sequence,chainlookup,rlmode,i)                  end                  if ok then                    done=true @@ -13824,8 +20083,10 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq                      end                    end                  end -                i=i+1 +              else +                logprocess("%s: %s is not yet supported (2)",cref(dataset,sequence),chainkind)                end +              i=i+1              end              if i>nofchainlookups or not start then                break @@ -13837,11 +20098,11 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq        else          local replacements=ck[7]          if replacements then -          head,start,done=chainprocs.reversesub(head,start,last,kind,chainname,ck,lookuphash,replacements)  +          head,start,done=reversesub(head,start,last,dataset,sequence,replacements,rlmode)          else            done=quit_on_no_replacement             if trace_contexts then -            logprocess("%s: skipping match",cref(kind,chainname)) +            logprocess("%s: skipping match",cref(dataset,sequence))            end          end        end @@ -13850,58 +20111,32 @@ local function normal_handle_contextchain(head,start,kind,chainname,contexts,seq        end      end    end -  if diskseen then  +  if diskseen then      notmatchpre={}      notmatchpost={}      notmatchreplace={}    end    return head,start,done  end -local verbose_handle_contextchain=function(font,...) -  logwarning("no verbose handler installed, reverting to 'normal'") -  otf.setcontextchain() -  return normal_handle_contextchain(...) -end -otf.chainhandlers={ -  normal=normal_handle_contextchain, -  verbose=verbose_handle_contextchain, -} -local handle_contextchain=nil -function chained_contextchain(head,start,stop,...) +handlers.gsub_context=handle_contextchain +handlers.gsub_contextchain=handle_contextchain +handlers.gsub_reversecontextchain=handle_contextchain +handlers.gpos_contextchain=handle_contextchain +handlers.gpos_context=handle_contextchain +local function chained_contextchain(head,start,stop,dataset,sequence,currentlookup,rlmode)    local steps=currentlookup.steps    local nofsteps=currentlookup.nofsteps    if nofsteps>1 then      reportmoresteps(dataset,sequence)    end -  return handle_contextchain(head,start,...) -end -function otf.setcontextchain(method) -  if not method or method=="normal" or not otf.chainhandlers[method] then -    if handle_contextchain then  -      logwarning("installing normal contextchain handler") -    end -    handle_contextchain=normal_handle_contextchain -  else -    logwarning("installing contextchain handler %a",method) -    local handler=otf.chainhandlers[method] -    handle_contextchain=function(...) -      return handler(currentfont,...)  -    end -  end -  handlers.gsub_context=handle_contextchain -  handlers.gsub_contextchain=handle_contextchain -  handlers.gsub_reversecontextchain=handle_contextchain -  handlers.gpos_contextchain=handle_contextchain -  handlers.gpos_context=handle_contextchain -  handlers.contextchain=handle_contextchain +  return handle_contextchain(head,start,dataset,sequence,currentlookup,rlmode)  end  chainprocs.gsub_context=chained_contextchain  chainprocs.gsub_contextchain=chained_contextchain  chainprocs.gsub_reversecontextchain=chained_contextchain  chainprocs.gpos_contextchain=chained_contextchain  chainprocs.gpos_context=chained_contextchain -otf.setcontextchain() -local missing={}  +local missing=setmetatableindex("table")  local function logprocess(...)    if trace_steps then      registermessage(...) @@ -13909,23 +20144,22 @@ local function logprocess(...)    report_process(...)  end  local logwarning=report_process -local function report_missing_cache(typ,lookup) -  local f=missing[currentfont] if not f then f={} missing[currentfont]=f end -  local t=f[typ]        if not t then t={} f[typ]=t end -  if not t[lookup] then -    t[lookup]=true -    logwarning("missing cache for lookup %a, type %a, font %a, name %a",lookup,typ,currentfont,tfmdata.properties.fullname) +local function report_missing_coverage(dataset,sequence) +  local t=missing[currentfont] +  if not t[sequence] then +    t[sequence]=true +    logwarning("missing coverage for feature %a, lookup %a, type %a, font %a, name %a", +      dataset[4],sequence.name,sequence.type,currentfont,tfmdata.properties.fullname)    end  end  local resolved={} -local lookuphashes={} -setmetatableindex(lookuphashes,function(t,font) -  local lookuphash=fontdata[font].resources.lookuphash -  if not lookuphash or not next(lookuphash) then -    lookuphash=false -  end -  t[font]=lookuphash -  return lookuphash +local sequencelists=setmetatableindex(function(t,font) +  local sequences=fontdata[font].resources.sequences +  if not sequences or not next(sequences) then +    sequences=false +  end +  t[font]=sequences +  return sequences  end)  local autofeatures=fonts.analyzers.features  local featuretypes=otf.tables.featuretypes @@ -13995,244 +20229,501 @@ function otf.dataset(tfmdata,font)    end    return rl  end -local function kernrun(disc,run) +local function report_disc(n) +  report_run("kern: %s > %s",disc,languages.serializediscretionary(disc)) +end +local function kernrun(disc,k_run,font,attr,...)    if trace_kernruns then -    report_run("kern")  +    report_disc("kern")    end -  local prev=getprev(disc)  -  local next=getnext(disc) -  local pre=getfield(disc,"pre") -  local post=getfield(disc,"post") -  local replace=getfield(disc,"replace") +  local prev,next=getboth(disc) +  local nextstart=next +  local done=false +  local pre,post,replace,pretail,posttail,replacetail=getdisc(disc,true)    local prevmarks=prev -  while prevmarks and getid(prevmarks)==glyph_code and marks[getchar(prevmarks)] and getfont(prevmarks)==currentfont and getsubtype(prevmarks)<256 do -    prevmarks=getprev(prevmarks) +  while prevmarks do +    local char=ischar(prevmarks,font) +    if char and marks[char] then +      prevmarks=getprev(prevmarks) +    else +      break +    end    end -  if prev and (pre or replace) and not (getid(prev)==glyph_code and getfont(prev)==currentfont and getsubtype(prev)<256) then +  if prev and (pre or replace) and not ischar(prev,font) then      prev=false    end -  if next and (post or replace) and not (getid(next)==glyph_code and getfont(next)==currentfont and getsubtype(next)<256) then +  if next and (post or replace) and not ischar(next,font) then      next=false    end -  if not pre then -  elseif prev then -    local nest=getprev(pre) -    setprev(pre,prev) -    setnext(prev,pre) -    run(prevmarks,"preinjections") -    setprev(pre,nest) -    setnext(prev,disc) -  else -    run(pre,"preinjections") -  end -  if not post then -  elseif next then -    local tail=find_node_tail(post) -    setnext(tail,next) -    setprev(next,tail) -    run(post,"postinjections",next) -    setnext(tail,nil) -    setprev(next,disc) -  else -    run(post,"postinjections") -  end -  if not replace and prev and next then -    setnext(prev,next) -    setprev(next,prev) -    run(prevmarks,"injections",next) -    setnext(prev,disc) -    setprev(next,disc) +  if pre then +    if k_run(pre,"injections",nil,font,attr,...) then +      done=true +    end +    if prev then +      local nest=getprev(pre) +      setlink(prev,pre) +      if k_run(prevmarks,"preinjections",pre,font,attr,...) then  +        done=true +      end +      setprev(pre,nest) +      setnext(prev,disc) +    end +  end +  if post then +    if k_run(post,"injections",nil,font,attr,...) then +      done=true +    end +    if next then +      setlink(posttail,next) +      if k_run(posttail,"postinjections",next,font,attr,...) then +        done=true +      end +      setnext(posttail,nil) +      setprev(next,disc) +    end +  end +  if replace then +    if k_run(replace,"injections",nil,font,attr,...) then +      done=true +    end +    if prev then +      local nest=getprev(replace) +      setlink(prev,replace) +      if k_run(prevmarks,"replaceinjections",replace,font,attr,...) then  +        done=true +      end +      setprev(replace,nest) +      setnext(prev,disc) +    end +    if next then +      setlink(replacetail,next) +      if k_run(replacetail,"replaceinjections",next,font,attr,...) then +        done=true +      end +      setnext(replacetail,nil) +      setprev(next,disc) +    end    elseif prev and next then -    local tail=find_node_tail(replace) -    local nest=getprev(replace) -    setprev(replace,prev) -    setnext(prev,replace) -    setnext(tail,next) -    setprev(next,tail) -    run(prevmarks,"replaceinjections",next) -    setprev(replace,nest) -    setnext(prev,disc) -    setnext(tail,nil) -    setprev(next,disc) -  elseif prev then -    local nest=getprev(replace) -    setprev(replace,prev) -    setnext(prev,replace) -    run(prevmarks,"replaceinjections") -    setprev(replace,nest) -    setnext(prev,disc) -  elseif next then -    local tail=find_node_tail(replace) -    setnext(tail,next) -    setprev(next,tail) -    run(replace,"replaceinjections",next) -    setnext(tail,nil) -    setprev(next,disc) -  else -    run(replace,"replaceinjections") +    setlink(prev,next) +    if k_run(prevmarks,"emptyinjections",next,font,attr,...) then +      done=true +    end +    setlink(prev,disc) +    setlink(disc,next)    end +  return nextstart,done  end -local function comprun(disc,run) +local function comprun(disc,c_run,...)    if trace_compruns then -    report_run("comp: %s",languages.serializediscretionary(disc)) +    report_disc("comp")    end -  local pre=getfield(disc,"pre") +  local pre,post,replace=getdisc(disc) +  local renewed=false    if pre then      sweepnode=disc      sweeptype="pre"  -    local new,done=run(pre) +    local new,done=c_run(pre,...)      if done then -      setfield(disc,"pre",new) +      pre=new +      renewed=true      end    end -  local post=getfield(disc,"post")    if post then      sweepnode=disc      sweeptype="post" -    local new,done=run(post) +    local new,done=c_run(post,...)      if done then -      setfield(disc,"post",new) +      post=new +      renewed=true      end    end -  local replace=getfield(disc,"replace")    if replace then      sweepnode=disc      sweeptype="replace" -    local new,done=run(replace) +    local new,done=c_run(replace,...)      if done then -      setfield(disc,"replace",new) +      replace=new +      renewed=true      end    end    sweepnode=nil    sweeptype=nil +  if renewed then +    setdisc(disc,pre,post,replace) +  end +  return getnext(disc),done  end -local function testrun(disc,trun,crun)  -  local next=getnext(disc) -  if next then -    local replace=getfield(disc,"replace") -    if replace then -      local prev=getprev(disc) -      if prev then -        local tail=find_node_tail(replace) -        setnext(tail,next) -        setprev(next,tail) -        if trun(replace,next) then -          setfield(disc,"replace",nil)  -          setnext(prev,replace) -          setprev(replace,prev) -          setprev(next,tail) -          setnext(tail,next) -          setprev(disc,nil) -          setnext(disc,nil) -          flush_node_list(disc) -          return replace  -        else -          setnext(tail,nil) -          setprev(next,disc) +local function testrun(disc,t_run,c_run,...) +  if trace_testruns then +    report_disc("test") +  end +  local prev,next=getboth(disc) +  if not next then +    return +  end +  local pre,post,replace,pretail,posttail,replacetail=getdisc(disc) +  local done=false +  if replace and prev then +    setlink(replacetail,next) +    if t_run(replace,next,...) then +      setfield(disc,"replace",nil)  +      setlink(prev,replace) +      setlink(replacetail,next) +      setboth(disc) +      flush_node_list(disc) +      return replace,true  +    else +      setnext(replacetail) +      setprev(next,disc) +    end +  end +  local renewed=false +  if pre then +    sweepnode=disc +    sweeptype="pre" +    local new,ok=c_run(pre,...) +    if ok then +      pre=new +      renewed=true +    end +  end +  if post then +    sweepnode=disc +    sweeptype="post" +    local new,ok=c_run(post,...) +    if ok then +      post=new +      renewed=true +    end +  end +  if replace then +    sweepnode=disc +    sweeptype="replace" +    local new,ok=c_run(replace,...) +    if ok then +      replace=new +      renewed=true +    end +  end +  sweepnode=nil +  sweeptype=nil +  if renewed then +    setdisc(disc,pre,post,replace) +    return next,true +  else +    return next,done +  end +end +local nesting=0 +local function c_run_single(head,font,attr,lookupcache,step,dataset,sequence,rlmode,handler) +  local done=false +  local start=sweephead[head] +  if start then +    sweephead[head]=nil +  else +    start=head +  end +  while start do +    local char=ischar(start,font) +    if char then +      local a=getattr(start,0) +      if not a or (a==attr) then +        local lookupmatch=lookupcache[char] +        if lookupmatch then +          local ok +          head,start,ok=handler(head,start,dataset,sequence,lookupmatch,rlmode,step,1) +          if ok then +            done=true +          end +        end +        if start then +          start=getnext(start)          end        else +        start=getnext(start)        end +    elseif char==false then +      return head,done      else +      start=getnext(start) +    end +  end +  return head,done +end +local function t_run_single(start,stop,font,attr,lookupcache) +  while start~=stop do +    local char=ischar(start,font) +    if char then +      local a=getattr(start,0) +      if not a or (a==attr) then +        local lookupmatch=lookupcache[char] +        if lookupmatch then +          local s=getnext(start) +          local l=nil +          while s do +            local lg=lookupmatch[getchar(s)] +            if lg then +              l=lg +              s=getnext(s) +            else +              break +            end +          end +          if l and l.ligature then +            return true +          end +        end +      end +      start=getnext(start) +    else +      break +    end +  end +end +local function k_run_single(sub,injection,last,font,attr,lookupcache,step,dataset,sequence,rlmode,handler) +  local a=getattr(sub,0) +  if not a or (a==attr) then +    for n in traverse_nodes(sub) do  +      if n==last then +        break +      end +      local char=ischar(n) +      if char then +        local lookupmatch=lookupcache[char] +        if lookupmatch then +          local h,d,ok=handler(sub,n,dataset,sequence,lookupmatch,rlmode,step,1,injection) +          if ok then +            return true +          end +        end +      end      end +  end +end +local function c_run_multiple(head,font,attr,steps,nofsteps,dataset,sequence,rlmode,handler) +  local done=false +  local start=sweephead[head] +  if start then +    sweephead[head]=nil    else +    start=head +  end +  while start do +    local char=ischar(start,font) +    if char then +      local a=getattr(start,0) +      if not a or (a==attr) then +        for i=1,nofsteps do +          local step=steps[i] +          local lookupcache=step.coverage +          if lookupcache then +            local lookupmatch=lookupcache[char] +            if lookupmatch then +              local ok +              head,start,ok=handler(head,start,dataset,sequence,lookupmatch,rlmode,step,i) +              if ok then +                done=true +                break +              elseif not start then +                break +              end +            end +          else +            report_missing_coverage(dataset,sequence) +          end +        end +        if start then +          start=getnext(start) +        end +      else +        start=getnext(start) +      end +    elseif char==false then +      return head,done +    else +      start=getnext(start) +    end    end -  comprun(disc,crun) -  return next +  return head,done  end -local function discrun(disc,drun,krun) -  local next=getnext(disc) -  local prev=getprev(disc) -  if trace_discruns then -    report_run("disc")  +local function t_run_multiple(start,stop,font,attr,steps,nofsteps) +  while start~=stop do +    local char=ischar(start,font) +    if char then +      local a=getattr(start,0) +      if not a or (a==attr) then +        for i=1,nofsteps do +          local step=steps[i] +          local lookupcache=step.coverage +          if lookupcache then +            local lookupmatch=lookupcache[char] +            if lookupmatch then +              local s=getnext(start) +              local l=nil +              while s do +                local lg=lookupmatch[getchar(s)] +                if lg then +                  l=lg +                  s=getnext(s) +                else +                  break +                end +              end +              if l and l.ligature then +                return true +              end +            end +          else +            report_missing_coverage(dataset,sequence) +          end +        end +      end +      start=getnext(start) +    else +      break +    end    end -  if next and prev then -    setnext(prev,next) -    drun(prev) -    setnext(prev,disc) +end +local function k_run_multiple(sub,injection,last,font,attr,steps,nofsteps,dataset,sequence,rlmode,handler) +  local a=getattr(sub,0) +  if not a or (a==attr) then +    for n in traverse_nodes(sub) do  +      if n==last then +        break +      end +      local char=ischar(n) +      if char then +        for i=1,nofsteps do +          local step=steps[i] +          local lookupcache=step.coverage +          if lookupcache then +            local lookupmatch=lookupcache[char] +            if lookupmatch then +              local h,d,ok=handler(head,n,dataset,sequence,lookupmatch,step,rlmode,i,injection) +              if ok then +                return true +              end +            end +          else +            report_missing_coverage(dataset,sequence) +          end +        end +      end +    end    end -  local pre=getfield(disc,"pre") -  if not pre then -  elseif prev then -    local nest=getprev(pre) -    setprev(pre,prev) -    setnext(prev,pre) -    krun(prev,"preinjections") -    setprev(pre,nest) -    setnext(prev,disc) +end +local function txtdirstate(start,stack,top,rlparmode) +  local dir=getfield(start,"dir") +  local new=1 +  if dir=="+TRT" then +    top=top+1 +    stack[top]=dir +    new=-1 +  elseif dir=="+TLT" then +    top=top+1 +    stack[top]=dir +  elseif dir=="-TRT" or dir=="-TLT" then +    top=top-1 +    if stack[top]=="+TRT" then +      new=-1 +    end    else -    krun(pre,"preinjections") +    new=rlparmode    end -  return next +  if trace_directions then +    report_process("directions after txtdir %a: parmode %a, txtmode %a, level %a",dir,mref(rlparmode),mref(new),topstack) +  end +  return getnext(start),top,new +end +local function pardirstate(start) +  local dir=getfield(start,"dir") +  local new=0 +  if dir=="TLT" then +    new=1 +  elseif dir=="TRT" then +    new=-1 +  end +  if trace_directions then +    report_process("directions after pardir %a: parmode %a",dir,mref(new)) +  end +  return getnext(start),new,new  end  local function featuresprocessor(head,font,attr) -  local lookuphash=lookuphashes[font]  -  if not lookuphash then +  local sequences=sequencelists[font]  +  if not sequencelists then +    return head,false +  end +  nesting=nesting+1 +  if nesting==1 then +    currentfont=font +    tfmdata=fontdata[font] +    descriptions=tfmdata.descriptions +    characters=tfmdata.characters +    marks=tfmdata.resources.marks +    factor=tfmdata.parameters.factor +    threshold=tfmdata.parameters.spacing.width or 65536*10 +  elseif currentfont~=font then +    report_warning("nested call with a different font, level %s, quitting",nesting) +    nesting=nesting-1      return head,false    end    head=tonut(head)    if trace_steps then      checkstep(head)    end -  tfmdata=fontdata[font] -  descriptions=tfmdata.descriptions -  characters=tfmdata.characters -  resources=tfmdata.resources -  marks=resources.marks -  anchorlookups=resources.lookup_to_anchor -  lookuptable=resources.lookups -  lookuptypes=resources.lookuptypes -  lookuptags=resources.lookuptags -  currentfont=font -  rlmode=0 -  sweephead={} -  local sequences=resources.sequences +  local rlmode=0    local done=false    local datasets=otf.dataset(tfmdata,font,attr) -  local dirstack={} +  local dirstack={}  +  sweephead={}    for s=1,#datasets do      local dataset=datasets[s] -       featurevalue=dataset[1]       local attribute=dataset[2]      local sequence=dataset[3]  -    local kind=dataset[4]      local rlparmode=0      local topstack=0      local success=false      local typ=sequence.type      local gpossing=typ=="gpos_single" or typ=="gpos_pair"  -    local subtables=sequence.subtables      local handler=handlers[typ] -    if typ=="gsub_reversecontextchain" then -      local start=find_node_tail(head)  +    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) +      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)        while start do -        local id=getid(start) -        if id==glyph_code then -          if getfont(start)==font and getsubtype(start)<256 then -            local a=getattr(start,0) -            if a then -              a=a==attr -            else -              a=true -            end -            if a then -              local char=getchar(start) -              for i=1,#subtables do -                local lookupname=subtables[i] -                local lookupcache=lookuphash[lookupname] -                if lookupcache then -                  local lookupmatch=lookupcache[char] -                  if lookupmatch then -                    head,start,success=handler(head,start,kind,lookupname,lookupmatch,sequence,lookuphash,i) -                    if success then -                      break -                    end +        local char=ischar(start,font) +        if char then +          local a=getattr(start,0) +          if not a or (a==attr) then +            for i=1,nofsteps do +              local step=steps[i] +              local lookupcache=step.coverage +              if lookupcache then +                local lookupmatch=lookupcache[char] +                if lookupmatch then +                  local ok +                  head,start,ok=handler(head,start,dataset,sequence,lookupmatch,rlmode,step,i) +                  if ok then +                    success=true +                    break                    end -                else -                  report_missing_cache(typ,lookupname)                  end +              else +                report_missing_coverage(dataset,sequence)                end -              if start then start=getprev(start) end -            else +            end +            if start then                start=getprev(start)              end            else @@ -14243,467 +20734,119 @@ local function featuresprocessor(head,font,attr)          end        end      else -      local ns=#subtables        local start=head         rlmode=0  -      if ns==1 then  -        local lookupname=subtables[1] -        local lookupcache=lookuphash[lookupname] -        if not lookupcache then  -          report_missing_cache(typ,lookupname) +      if nofsteps==1 then  +        local step=steps[1] +        local lookupcache=step.coverage +        if not lookupcache then +          report_missing_coverage(dataset,sequence)          else -          local function c_run(head)  -            local done=false -            local start=sweephead[head] -            if start then -              sweephead[head]=nil -            else -              start=head -            end -            while start do -              local id=getid(start) -              if id~=glyph_code then -                start=getnext(start) -              elseif getfont(start)==font and getsubtype(start)<256 then -                local a=getattr(start,0) -                if a then -                  a=(a==attr) and (not attribute or getprop(start,a_state)==attribute) -                else -                  a=not attribute or getprop(start,a_state)==attribute -                end -                if a then -                  local lookupmatch=lookupcache[getchar(start)] -                  if lookupmatch then -                    local ok -                    head,start,ok=handler(head,start,kind,lookupname,lookupmatch,sequence,lookuphash,1) -                    if ok then -                      done=true -                    end -                  end -                  if start then start=getnext(start) end -                else -                  start=getnext(start) -                end -              else -                return head,false -              end -            end -            if done then -              success=true  -            end -            return head,done -          end -          local function t_run(start,stop) -            while start~=stop do -              local id=getid(start) -              if id==glyph_code and getfont(start)==font and getsubtype(start)<256 then -                local a=getattr(start,0) -                if a then -                  a=(a==attr) and (not attribute or getprop(start,a_state)==attribute) -                else -                  a=not attribute or getprop(start,a_state)==attribute -                end -                if a then -                  local lookupmatch=lookupcache[getchar(start)] -                  if lookupmatch then -                    local s=getnext(start) -                    local l=nil -                    while s do -                      local lg=lookupmatch[getchar(s)] -                      if lg then -                        l=lg -                        s=getnext(s) -                      else -                        break -                      end -                    end -                    if l and l.ligature then -                      return true -                    end -                  end -                end -                start=getnext(start) +          while start do +            local char,id=ischar(start,font) +            if char then +              local a=getattr(start,0) +              if a then +                a=(a==attr) and (not attribute or getprop(start,a_state)==attribute)                else -                break -              end -            end -          end -          local function d_run(prev)  -            local a=getattr(prev,0) -            if a then -              a=(a==attr) and (not attribute or getprop(prev,a_state)==attribute) -            else -              a=not attribute or getprop(prev,a_state)==attribute -            end -            if a then -              local lookupmatch=lookupcache[getchar(prev)] -              if lookupmatch then -                local h,d,ok=handler(head,prev,kind,lookupname,lookupmatch,sequence,lookuphash,1) -                if ok then -                  done=true -                  success=true -                end +                a=not attribute or getprop(start,a_state)==attribute                end -            end -          end -          local function k_run(sub,injection,last) -            local a=getattr(sub,0) -            if a then -              a=(a==attr) and (not attribute or getprop(sub,a_state)==attribute) -            else -              a=not attribute or getprop(sub,a_state)==attribute -            end -            if a then -              for n in traverse_nodes(sub) do  -                if n==last then -                  break -                end -                local id=getid(n) -                if id==glyph_code then -                  local lookupmatch=lookupcache[getchar(n)] -                  if lookupmatch then -                    local h,d,ok=handler(sub,n,kind,lookupname,lookupmatch,sequence,lookuphash,1,injection) -                    if ok then -                      done=true -                      success=true -                    end +              if a then +                local lookupmatch=lookupcache[char] +                if lookupmatch then +                  local ok +                  head,start,ok=handler(head,start,dataset,sequence,lookupmatch,rlmode,step,1) +                  if ok then +                    success=true                    end -                else                  end -              end -            end -          end -          while start do -            local id=getid(start) -            if id==glyph_code then -              if getfont(start)==font and getsubtype(start)<256 then  -                local a=getattr(start,0) -                if a then -                  a=(a==attr) and (not attribute or getprop(start,a_state)==attribute) -                else -                  a=not attribute or getprop(start,a_state)==attribute -                end -                if a then -                  local char=getchar(start) -                  local lookupmatch=lookupcache[char] -                  if lookupmatch then -                    local ok -                    head,start,ok=handler(head,start,kind,lookupname,lookupmatch,sequence,lookuphash,1) -                    if ok then -                      success=true -                    elseif gpossing and zwnjruns and char==zwnj then -                      discrun(start,d_run) -                    end -                  elseif gpossing and zwnjruns and char==zwnj then -                    discrun(start,d_run) -                  end -                  if start then start=getnext(start) end -                else +                if start then                    start=getnext(start)                  end                else                  start=getnext(start)                end +            elseif char==false then +              start=getnext(start)              elseif id==disc_code then +              local ok                if gpossing then -                kernrun(start,k_run) -                start=getnext(start) +                start,ok=kernrun(start,k_run_single,font,attr,lookupcache,step,dataset,sequence,rlmode,handler)                elseif typ=="gsub_ligature" then -                start=testrun(start,t_run,c_run) +                start,ok=testrun(start,t_run_single,c_run_single,font,attr,lookupcache,step,dataset,sequence,rlmode,handler)                else -                comprun(start,c_run) -                start=getnext(start) +                start,ok=comprun(start,c_run_single,font,attr,lookupcache,step,dataset,sequence,rlmode,handler) +              end +              if ok then +                success=true                end              elseif id==math_code then                start=getnext(end_of_math(start))              elseif id==dir_code then -              local dir=getfield(start,"dir") -              if dir=="+TLT" then -                topstack=topstack+1 -                dirstack[topstack]=dir -                rlmode=1 -              elseif dir=="+TRT" then -                topstack=topstack+1 -                dirstack[topstack]=dir -                rlmode=-1 -              elseif dir=="-TLT" or dir=="-TRT" then -                topstack=topstack-1 -                rlmode=dirstack[topstack]=="+TRT" and -1 or 1 -              else -                rlmode=rlparmode -              end -              if trace_directions then -                report_process("directions after txtdir %a: parmode %a, txtmode %a, # stack %a, new dir %a",dir,rlparmode,rlmode,topstack,newdir) -              end -              start=getnext(start) +              start,topstack,rlmode=txtdirstate(start,dirstack,topstack,rlparmode)              elseif id==localpar_code then -              local dir=getfield(start,"dir") -              if dir=="TRT" then -                rlparmode=-1 -              elseif dir=="TLT" then -                rlparmode=1 -              else -                rlparmode=0 -              end -              rlmode=rlparmode -              if trace_directions then -                report_process("directions after pardir %a: parmode %a, txtmode %a",dir,rlparmode,rlmode) -              end -              start=getnext(start) +              start,rlparmode,rlmode=pardirstate(start)              else                start=getnext(start)              end            end          end        else -        local function c_run(head) -          local done=false -          local start=sweephead[head] -          if start then -            sweephead[head]=nil -          else -            start=head -          end -          while start do -            local id=getid(start) -            if id~=glyph_code then -              start=getnext(start) -            elseif getfont(start)==font and getsubtype(start)<256 then -              local a=getattr(start,0) -              if a then -                a=(a==attr) and (not attribute or getprop(start,a_state)==attribute) -              else -                a=not attribute or getprop(start,a_state)==attribute -              end -              if a then -                local char=getchar(start) -                for i=1,ns do -                  local lookupname=subtables[i] -                  local lookupcache=lookuphash[lookupname] -                  if lookupcache then -                    local lookupmatch=lookupcache[char] -                    if lookupmatch then -                      local ok -                      head,start,ok=handler(head,start,kind,lookupname,lookupmatch,sequence,lookuphash,i) -                      if ok then -                        done=true -                        break -                      elseif not start then -                        break -                      end -                    end -                  else -                    report_missing_cache(typ,lookupname) -                  end -                end -                if start then start=getnext(start) end -              else -                start=getnext(start) -              end +        while start do +          local char,id=ischar(start,font) +          if char then +            local a=getattr(start,0) +            if a then +              a=(a==attr) and (not attribute or getprop(start,a_state)==attribute)              else -              return head,false -            end -          end -          if done then -            success=true -          end -          return head,done -        end -        local function d_run(prev) -          local a=getattr(prev,0) -          if a then -            a=(a==attr) and (not attribute or getprop(prev,a_state)==attribute) -          else -            a=not attribute or getprop(prev,a_state)==attribute -          end -          if a then -            local char=getchar(prev) -            for i=1,ns do -              local lookupname=subtables[i] -              local lookupcache=lookuphash[lookupname] -              if lookupcache then -                local lookupmatch=lookupcache[char] -                if lookupmatch then -                  local h,d,ok=handler(head,prev,kind,lookupname,lookupmatch,sequence,lookuphash,i) -                  if ok then -                    done=true -                    break -                  end -                end -              else -                report_missing_cache(typ,lookupname) -              end +              a=not attribute or getprop(start,a_state)==attribute              end -          end -        end -        local function k_run(sub,injection,last) -          local a=getattr(sub,0) -          if a then -            a=(a==attr) and (not attribute or getprop(sub,a_state)==attribute) -          else -            a=not attribute or getprop(sub,a_state)==attribute -          end -          if a then -            for n in traverse_nodes(sub) do  -              if n==last then -                break -              end -              local id=getid(n) -              if id==glyph_code then -                local char=getchar(n) -                for i=1,ns do -                  local lookupname=subtables[i] -                  local lookupcache=lookuphash[lookupname] -                  if lookupcache then -                    local lookupmatch=lookupcache[char] -                    if lookupmatch then -                      local h,d,ok=handler(head,n,kind,lookupname,lookupmatch,sequence,lookuphash,i,injection) -                      if ok then -                        done=true -                        break -                      end -                    end -                  else -                    report_missing_cache(typ,lookupname) -                  end -                end -              else -              end -            end -          end -        end -        local function t_run(start,stop) -          while start~=stop do -            local id=getid(start) -            if id==glyph_code and getfont(start)==font and getsubtype(start)<256 then -              local a=getattr(start,0) -              if a then -                a=(a==attr) and (not attribute or getprop(start,a_state)==attribute) -              else -                a=not attribute or getprop(start,a_state)==attribute -              end -              if a then -                local char=getchar(start) -                for i=1,ns do -                  local lookupname=subtables[i] -                  local lookupcache=lookuphash[lookupname] -                  if lookupcache then -                    local lookupmatch=lookupcache[char] -                    if lookupmatch then -                      local s=getnext(start) -                      local l=nil -                      while s do -                        local lg=lookupmatch[getchar(s)] -                        if lg then -                          l=lg -                          s=getnext(s) -                        else -                          break -                        end -                      end -                      if l and l.ligature then -                        return true -                      end +            if a then +              for i=1,nofsteps do +                local step=steps[i] +                local lookupcache=step.coverage +                if lookupcache then +                  local lookupmatch=lookupcache[char] +                  if lookupmatch then +                    local ok +                    head,start,ok=handler(head,start,dataset,sequence,lookupmatch,rlmode,step,i) +                    if ok then +                      success=true +                      break +                    elseif not start then +                      break                      end -                  else -                    report_missing_cache(typ,lookupname)                    end +                else +                  report_missing_coverage(dataset,sequence)                  end                end -              start=getnext(start) -            else -              break -            end -          end -        end -        while start do -          local id=getid(start) -          if id==glyph_code then -            if getfont(start)==font and getsubtype(start)<256 then -              local a=getattr(start,0) -              if a then -                a=(a==attr) and (not attribute or getprop(start,a_state)==attribute) -              else -                a=not attribute or getprop(start,a_state)==attribute -              end -              if a then -                for i=1,ns do -                  local lookupname=subtables[i] -                  local lookupcache=lookuphash[lookupname] -                  if lookupcache then -                    local char=getchar(start) -                    local lookupmatch=lookupcache[char] -                    if lookupmatch then -                      local ok -                      head,start,ok=handler(head,start,kind,lookupname,lookupmatch,sequence,lookuphash,i) -                      if ok then -                        success=true -                        break -                      elseif not start then -                        break -                      elseif gpossing and zwnjruns and char==zwnj then -                        discrun(start,d_run) -                      end -                    elseif gpossing and zwnjruns and char==zwnj then -                      discrun(start,d_run) -                    end -                  else -                    report_missing_cache(typ,lookupname) -                  end -                end -                if start then start=getnext(start) end -              else +              if start then                  start=getnext(start)                end              else                start=getnext(start)              end +          elseif char==false then +            start=getnext(start)            elseif id==disc_code then +            local ok              if gpossing then -              kernrun(start,k_run) -              start=getnext(start) +              start,ok=kernrun(start,k_run_multiple,font,attr,steps,nofsteps,dataset,sequence,rlmode,handler)              elseif typ=="gsub_ligature" then -              start=testrun(start,t_run,c_run) +              start,ok=testrun(start,t_run_multiple,c_run_multiple,font,attr,steps,nofsteps,dataset,sequence,rlmode,handler)              else -              comprun(start,c_run) -              start=getnext(start) +              start,ok=comprun(start,c_run_multiple,font,attr,steps,nofsteps,dataset,sequence,rlmode,handler) +            end +            if ok then +              success=true              end            elseif id==math_code then              start=getnext(end_of_math(start))            elseif id==dir_code then -            local dir=getfield(start,"dir") -            if dir=="+TLT" then -              topstack=topstack+1 -              dirstack[topstack]=dir -              rlmode=1 -            elseif dir=="+TRT" then -              topstack=topstack+1 -              dirstack[topstack]=dir -              rlmode=-1 -            elseif dir=="-TLT" or dir=="-TRT" then -              topstack=topstack-1 -              rlmode=dirstack[topstack]=="+TRT" and -1 or 1 -            else -              rlmode=rlparmode -            end -            if trace_directions then -              report_process("directions after txtdir %a: parmode %a, txtmode %a, # stack %a, new dir %a",dir,rlparmode,rlmode,topstack,newdir) -            end -            start=getnext(start) +            start,topstack,rlmode=txtdirstate(start,dirstack,topstack,rlparmode)            elseif id==localpar_code then -            local dir=getfield(start,"dir") -            if dir=="TRT" then -              rlparmode=-1 -            elseif dir=="TLT" then -              rlparmode=1 -            else -              rlparmode=0 -            end -            rlmode=rlparmode -            if trace_directions then -              report_process("directions after pardir %a: parmode %a, txtmode %a",dir,rlparmode,rlmode) -            end -            start=getnext(start) +            start,rlparmode,rlmode=pardirstate(start)            else              start=getnext(start)            end @@ -14717,1090 +20860,2147 @@ local function featuresprocessor(head,font,attr)        registerstep(head)      end    end +  nesting=nesting-1    head=tonode(head)    return head,done  end -local function generic(lookupdata,lookupname,unicode,lookuphash) -  local target=lookuphash[lookupname] -  if target then -    target[unicode]=lookupdata -  else -    lookuphash[lookupname]={ [unicode]=lookupdata } -  end -end -local function ligature(lookupdata,lookupname,unicode,lookuphash) -  local target=lookuphash[lookupname] -  if not target then -    target={} -    lookuphash[lookupname]=target -  end -  for i=1,#lookupdata do -    local li=lookupdata[i] -    local tu=target[li] -    if not tu then -      tu={} -      target[li]=tu -    end -    target=tu -  end -  target.ligature=unicode +local function featuresinitializer(tfmdata,value)  end -local function pair(lookupdata,lookupname,unicode,lookuphash) -  local target=lookuphash[lookupname] -  if not target then -    target={} -    lookuphash[lookupname]=target -  end -  local others=target[unicode] -  local paired=lookupdata[1] -  if others then -    others[paired]=lookupdata -  else -    others={ [paired]=lookupdata } -    target[unicode]=others -  end -end -local action={ -  substitution=generic, -  multiple=generic, -  alternate=generic, -  position=generic, -  ligature=ligature, -  pair=pair, -  kern=pair, +registerotffeature { +  name="features", +  description="features", +  default=true, +  initializers={ +    position=1, +    node=featuresinitializer, +  }, +  processors={ +    node=featuresprocessor, +  }  } -local function prepare_lookups(tfmdata) -  local rawdata=tfmdata.shared.rawdata -  local resources=rawdata.resources -  local lookuphash=resources.lookuphash -  local anchor_to_lookup=resources.anchor_to_lookup -  local lookup_to_anchor=resources.lookup_to_anchor -  local lookuptypes=resources.lookuptypes -  local characters=tfmdata.characters -  local descriptions=tfmdata.descriptions -  local duplicates=resources.duplicates -  for unicode,character in next,characters do  -    local description=descriptions[unicode] -    if description then -      local lookups=description.slookups -      if lookups then -        for lookupname,lookupdata in next,lookups do -          action[lookuptypes[lookupname]](lookupdata,lookupname,unicode,lookuphash,duplicates) -        end -      end -      local lookups=description.mlookups -      if lookups then -        for lookupname,lookuplist in next,lookups do -          local lookuptype=lookuptypes[lookupname] -          for l=1,#lookuplist do -            local lookupdata=lookuplist[l] -            action[lookuptype](lookupdata,lookupname,unicode,lookuphash,duplicates) -          end -        end -      end -      local list=description.kerns -      if list then -        for lookup,krn in next,list do  -          local target=lookuphash[lookup] -          if target then -            target[unicode]=krn -          else -            lookuphash[lookup]={ [unicode]=krn } -          end -        end -      end -      local list=description.anchors -      if list then -        for typ,anchors in next,list do  -          if typ=="mark" or typ=="cexit" then  -            for name,anchor in next,anchors do -              local lookups=anchor_to_lookup[name] -              if lookups then -                for lookup in next,lookups do -                  local target=lookuphash[lookup] -                  if target then -                    target[unicode]=anchors -                  else -                    lookuphash[lookup]={ [unicode]=anchors } -                  end -                end -              end +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 +end +local function hasspacekerns(data) +  local sequences=data.resources.sequences +  for i=1,#sequences do +    local sequence=sequences[i] +    local steps=sequence.steps +    if steps and sequence.features.kern then +      for i=1,#steps do +        local coverage=steps[i].coverage +        if not coverage then +        elseif coverage[32] then +          return true +        else +          for k,v in next,coverage do +            if v[32] then +              return true              end            end          end        end      end    end +  return false  end -local function split(replacement,original) -  local result={} -  for i=1,#replacement do -    result[original[i]]=replacement[i] +otf.readers.registerextender { +  name="spacekerns", +  action=function(data) +    data.properties.hasspacekerns=hasspacekerns(data)    end -  return result -end -local valid={  -  coverage={ chainsub=true,chainpos=true,contextsub=true,contextpos=true }, -  reversecoverage={ reversesub=true }, -  glyphs={ chainsub=true,chainpos=true,contextsub=true,contextpos=true },  } -local function prepare_contextchains(tfmdata) -  local rawdata=tfmdata.shared.rawdata -  local resources=rawdata.resources -  local lookuphash=resources.lookuphash -  local lookuptags=resources.lookuptags -  local lookups=rawdata.lookups -  if lookups then -    for lookupname,lookupdata in next,rawdata.lookups do -      local lookuptype=lookupdata.type -      if lookuptype then -        local rules=lookupdata.rules -        if rules then -          local format=lookupdata.format -          local validformat=valid[format] -          if not validformat then -            report_prepare("unsupported format %a",format) -          elseif not validformat[lookuptype] then -            report_prepare("unsupported format %a, lookuptype %a, lookupname %a",format,lookuptype,lookuptags[lookupname]) -          else -            local contexts=lookuphash[lookupname] -            if not contexts then -              contexts={} -              lookuphash[lookupname]=contexts -            end -            local t,nt={},0 -            for nofrules=1,#rules do -              local rule=rules[nofrules] -              local current=rule.current -              local before=rule.before -              local after=rule.after -              local replacements=rule.replacements -              local sequence={} -              local nofsequences=0 -              if before then -                for n=1,#before do -                  nofsequences=nofsequences+1 -                  sequence[nofsequences]=before[n] -                end -              end -              local start=nofsequences+1 -              for n=1,#current do -                nofsequences=nofsequences+1 -                sequence[nofsequences]=current[n] -              end -              local stop=nofsequences -              if after then -                for n=1,#after do -                  nofsequences=nofsequences+1 -                  sequence[nofsequences]=after[n] +local function spaceinitializer(tfmdata,value)  +  local resources=tfmdata.resources +  local spacekerns=resources and resources.spacekerns +  if spacekerns==nil then +    local properties=tfmdata.properties +    if properties and properties.hasspacekerns then +      local sequences=resources.sequences +      local left={} +      local right={} +      local last=0 +      local feat=nil +      for i=1,#sequences do +        local sequence=sequences[i] +        local steps=sequence.steps +        if steps then +          local kern=sequence.features.kern +          if kern then +            feat=feat or kern  +            for i=1,#steps do +              local step=steps[i] +              local coverage=step.coverage +              if coverage then +                local kerns=coverage[32] +                if kerns then +                  for k,v in next,kerns do +                    if type(v)=="table" then +                      right[k]=v[3]  +                    else +                      right[k]=v +                    end +                  end                  end -              end -              if sequence[1] then -                nt=nt+1 -                t[nt]={ nofrules,lookuptype,sequence,start,stop,rule.lookups,replacements } -                for unic in next,sequence[start] do -                  local cu=contexts[unic] -                  if not cu then -                    contexts[unic]=t +                for k,v in next,coverage do +                  local kern=v[32] +                  if kern then +                    if type(kern)=="table" then +                      left[k]=kern[3]  +                    else +                      left[k]=kern +                    end                    end                  end                end              end +            last=i            end          else          end -      else -        report_prepare("missing lookuptype for lookupname %a",lookuptags[lookupname])        end -    end -  end -end -local function featuresinitializer(tfmdata,value) -  if true then -    local rawdata=tfmdata.shared.rawdata -    local properties=rawdata.properties -    if not properties.initialized then -      local starttime=trace_preparing and os.clock() -      local resources=rawdata.resources -      resources.lookuphash=resources.lookuphash or {} -      prepare_contextchains(tfmdata) -      prepare_lookups(tfmdata) -      properties.initialized=true -      if trace_preparing then -        report_prepare("preparation time is %0.3f seconds for %a",os.clock()-starttime,tfmdata.properties.fullname) +      left=next(left) and left or false +      right=next(right) and right or false +      if left or right then +        spacekerns={ +          left=left, +          right=right, +        } +        if last>0 then +          local triggersequence={ +            features={ kern=feat or { dflt={ dflt=true,} } }, +            flags=noflags, +            name="trigger_space_kerns", +            order={ "kern" }, +            type="trigger_space_kerns", +            left=left, +            right=right, +          } +          insert(sequences,last,triggersequence) +        end +      else +        spacekerns=false        end +    else +      spacekerns=false      end +    resources.spacekerns=spacekerns    end +  return spacekerns  end  registerotffeature { -  name="features", -  description="features", +  name="spacekern", +  description="space kern injection",    default=true,    initializers={ -    position=1, -    node=featuresinitializer, +    node=spaceinitializer,    }, -  processors={ -    node=featuresprocessor, -  }  } -otf.handlers=handlers  end -- closure  do -- begin closure to overcome local limits and interference -if not modules then modules={} end modules ['font-otp']={ +if not modules then modules={} end modules ['font-osd']={     version=1.001, -  comment="companion to font-otf.lua (packing)", -  author="Hans Hagen, PRAGMA-ADE, Hasselt NL", -  copyright="PRAGMA ADE / ConTeXt Development Team", +  comment="companion to font-ini.mkiv", +  author="Kai Eigner, TAT Zetwerk / Hans Hagen, PRAGMA ADE", +  copyright="TAT Zetwerk / PRAGMA ADE / ConTeXt Development Team",    license="see context related readme files"  } -local next,type,tostring=next,type,tostring -local sort,concat=table.sort,table.concat -local trace_packing=false trackers.register("otf.packing",function(v) trace_packing=v end) -local trace_loading=false trackers.register("otf.loading",function(v) trace_loading=v end) -local report_otf=logs.reporter("fonts","otf loading") -fonts=fonts or {} -local handlers=fonts.handlers or {} -fonts.handlers=handlers -local otf=handlers.otf or {} -handlers.otf=otf -local enhancers=otf.enhancers or {} -otf.enhancers=enhancers -local glists=otf.glists or { "gsub","gpos" } -otf.glists=glists -local criterium=1 -local threshold=0 -local function tabstr_normal(t) -  local s={} -  local n=0 -  for k,v in next,t do -    n=n+1 -    if type(v)=="table" then -      s[n]=k..">"..tabstr_normal(v) -    elseif v==true then -      s[n]=k.."+"  -    elseif v then -      s[n]=k.."="..v -    else -      s[n]=k.."-"  -    end -  end -  if n==0 then -    return "" -  elseif n==1 then -    return s[1] -  else -    sort(s)  -    return concat(s,",") -  end -end -local function tabstr_flat(t) -  local s={} -  local n=0 -  for k,v in next,t do -    n=n+1 -    s[n]=k.."="..v +local insert,imerge,copy=table.insert,table.imerge,table.copy +local next,type=next,type +local report_devanagari=logs.reporter("otf","devanagari") +fonts=fonts          or {} +fonts.analyzers=fonts.analyzers     or {} +fonts.analyzers.methods=fonts.analyzers.methods or { node={ otf={} } } +local otf=fonts.handlers.otf +local nodecodes=nodes.nodecodes +local glyph_code=nodecodes.glyph +local handlers=otf.handlers +local methods=fonts.analyzers.methods +local otffeatures=fonts.constructors.newfeatures("otf") +local registerotffeature=otffeatures.register +local nuts=nodes.nuts +local tonode=nuts.tonode +local tonut=nuts.tonut +local getnext=nuts.getnext +local getprev=nuts.getprev +local getboth=nuts.getboth +local getid=nuts.getid +local getchar=nuts.getchar +local getfont=nuts.getfont +local getsubtype=nuts.getsubtype +local setlink=nuts.setlink +local setnext=nuts.setnext +local setprev=nuts.setprev +local setchar=nuts.setchar +local getprop=nuts.getprop +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 copyinjection=nodes.injections.copy  +local unsetvalue=attributes.unsetvalue +local fontdata=fonts.hashes.identifiers +local a_state=attributes.private('state') +local a_syllabe=attributes.private('syllabe') +local dotted_circle=0x25CC +local states=fonts.analyzers.states  +local s_rphf=states.rphf +local s_half=states.half +local s_pref=states.pref +local s_blwf=states.blwf +local s_pstf=states.pstf +local replace_all_nbsp=nil +replace_all_nbsp=function(head)  +  replace_all_nbsp=typesetters and typesetters.characters and typesetters.characters.replacenbspaces or function(head) +    return head +  end +  return replace_all_nbsp(head) +end +local xprocesscharacters=nil +if context then +  xprocesscharacters=function(head,font) +    xprocesscharacters=nodes.handlers.characters +    return xprocesscharacters(head,font)    end -  if n==0 then -    return "" -  elseif n==1 then -    return s[1] -  else -    sort(s)  -    return concat(s,",") +else +  xprocesscharacters=function(head,font) +    xprocesscharacters=nodes.handlers.nodepass  +    return xprocesscharacters(head,font) +  end +end +local function processcharacters(head,font) +  return tonut(xprocesscharacters(tonode(head)))  +end +local consonant={ +  [0x0915]=true,[0x0916]=true,[0x0917]=true,[0x0918]=true, +  [0x0919]=true,[0x091A]=true,[0x091B]=true,[0x091C]=true, +  [0x091D]=true,[0x091E]=true,[0x091F]=true,[0x0920]=true, +  [0x0921]=true,[0x0922]=true,[0x0923]=true,[0x0924]=true, +  [0x0925]=true,[0x0926]=true,[0x0927]=true,[0x0928]=true, +  [0x0929]=true,[0x092A]=true,[0x092B]=true,[0x092C]=true, +  [0x092D]=true,[0x092E]=true,[0x092F]=true,[0x0930]=true, +  [0x0931]=true,[0x0932]=true,[0x0933]=true,[0x0934]=true, +  [0x0935]=true,[0x0936]=true,[0x0937]=true,[0x0938]=true, +  [0x0939]=true,[0x0958]=true,[0x0959]=true,[0x095A]=true, +  [0x095B]=true,[0x095C]=true,[0x095D]=true,[0x095E]=true, +  [0x095F]=true,[0x0979]=true,[0x097A]=true, +  [0x0C95]=true,[0x0C96]=true,[0x0C97]=true,[0x0C98]=true, +  [0x0C99]=true,[0x0C9A]=true,[0x0C9B]=true,[0x0C9C]=true, +  [0x0C9D]=true,[0x0C9E]=true,[0x0C9F]=true,[0x0CA0]=true, +  [0x0CA1]=true,[0x0CA2]=true,[0x0CA3]=true,[0x0CA4]=true, +  [0x0CA5]=true,[0x0CA6]=true,[0x0CA7]=true,[0x0CA8]=true, +  [0x0CA9]=true,[0x0CAA]=true,[0x0CAB]=true,[0x0CAC]=true, +  [0x0CAD]=true,[0x0CAE]=true,[0x0CAF]=true,[0x0CB0]=true, +  [0x0CB1]=true,[0x0CB2]=true,[0x0CB3]=true,[0x0CB4]=true, +  [0x0CB5]=true,[0x0CB6]=true,[0x0CB7]=true,[0x0CB8]=true, +  [0x0CB9]=true, +  [0x0CDE]=true, +  [0x0D15]=true,[0x0D16]=true,[0x0D17]=true,[0x0D18]=true, +  [0x0D19]=true,[0x0D1A]=true,[0x0D1B]=true,[0x0D1C]=true, +  [0x0D1D]=true,[0x0D1E]=true,[0x0D1F]=true,[0x0D20]=true, +  [0x0D21]=true,[0x0D22]=true,[0x0D23]=true,[0x0D24]=true, +  [0x0D25]=true,[0x0D26]=true,[0x0D27]=true,[0x0D28]=true, +  [0x0D29]=true,[0x0D2A]=true,[0x0D2B]=true,[0x0D2C]=true, +  [0x0D2D]=true,[0x0D2E]=true,[0x0D2F]=true,[0x0D30]=true, +  [0x0D31]=true,[0x0D32]=true,[0x0D33]=true,[0x0D34]=true, +  [0x0D35]=true,[0x0D36]=true,[0x0D37]=true,[0x0D38]=true, +  [0x0D39]=true,[0x0D3A]=true, +} +local independent_vowel={ +  [0x0904]=true,[0x0905]=true,[0x0906]=true,[0x0907]=true, +  [0x0908]=true,[0x0909]=true,[0x090A]=true,[0x090B]=true, +  [0x090C]=true,[0x090D]=true,[0x090E]=true,[0x090F]=true, +  [0x0910]=true,[0x0911]=true,[0x0912]=true,[0x0913]=true, +  [0x0914]=true,[0x0960]=true,[0x0961]=true,[0x0972]=true, +  [0x0973]=true,[0x0974]=true,[0x0975]=true,[0x0976]=true, +  [0x0977]=true, +  [0x0C85]=true,[0x0C86]=true,[0x0C87]=true,[0x0C88]=true, +  [0x0C89]=true,[0x0C8A]=true,[0x0C8B]=true,[0x0C8C]=true, +  [0x0C8D]=true,[0x0C8E]=true,[0x0C8F]=true,[0x0C90]=true, +  [0x0C91]=true,[0x0C92]=true,[0x0C93]=true,[0x0C94]=true, +  [0x0D05]=true,[0x0D06]=true,[0x0D07]=true,[0x0D08]=true, +  [0x0D09]=true,[0x0D0A]=true,[0x0D0B]=true,[0x0D0C]=true, +  [0x0D0E]=true,[0x0D0F]=true,[0x0D10]=true,[0x0D12]=true, +  [0x0D13]=true,[0x0D14]=true, +} +local dependent_vowel={ +  [0x093A]=true,[0x093B]=true,[0x093E]=true,[0x093F]=true, +  [0x0940]=true,[0x0941]=true,[0x0942]=true,[0x0943]=true, +  [0x0944]=true,[0x0945]=true,[0x0946]=true,[0x0947]=true, +  [0x0948]=true,[0x0949]=true,[0x094A]=true,[0x094B]=true, +  [0x094C]=true,[0x094E]=true,[0x094F]=true,[0x0955]=true, +  [0x0956]=true,[0x0957]=true,[0x0962]=true,[0x0963]=true, +  [0x0CBE]=true,[0x0CBF]=true,[0x0CC0]=true,[0x0CC1]=true, +  [0x0CC2]=true,[0x0CC3]=true,[0x0CC4]=true,[0x0CC5]=true, +  [0x0CC6]=true,[0x0CC7]=true,[0x0CC8]=true,[0x0CC9]=true, +  [0x0CCA]=true,[0x0CCB]=true,[0x0CCC]=true, +  [0x0D3E]=true,[0x0D3F]=true,[0x0D40]=true,[0x0D41]=true, +  [0x0D42]=true,[0x0D43]=true,[0x0D44]=true,[0x0D46]=true, +  [0x0D47]=true,[0x0D48]=true,[0x0D4A]=true,[0x0D4B]=true, +  [0x0D4C]=true,[0x0D57]=true, +} +local vowel_modifier={ +  [0x0900]=true,[0x0901]=true,[0x0902]=true,[0x0903]=true, +  [0xA8E0]=true,[0xA8E1]=true,[0xA8E2]=true,[0xA8E3]=true, +  [0xA8E4]=true,[0xA8E5]=true,[0xA8E6]=true,[0xA8E7]=true, +  [0xA8E8]=true,[0xA8E9]=true,[0xA8EA]=true,[0xA8EB]=true, +  [0xA8EC]=true,[0xA8ED]=true,[0xA8EE]=true,[0xA8EF]=true, +  [0xA8F0]=true,[0xA8F1]=true, +  [0x0D02]=true,[0x0D03]=true, +} +local stress_tone_mark={ +  [0x0951]=true,[0x0952]=true,[0x0953]=true,[0x0954]=true, +  [0x0CCD]=true, +  [0x0D4D]=true, +} +local nukta={ +  [0x093C]=true, +  [0x0CBC]=true, +} +local halant={ +  [0x094D]=true, +  [0x0CCD]=true, +  [0x0D4D]=true, +} +local ra={ +  [0x0930]=true, +  [0x0CB0]=true, +  [0x0D30]=true, +} +local c_anudatta=0x0952  +local c_nbsp=0x00A0  +local c_zwnj=0x200C  +local c_zwj=0x200D  +local zw_char={  +  [0x200C]=true, +  [0x200D]=true, +} +local pre_mark={ +  [0x093F]=true,[0x094E]=true, +  [0x0D46]=true,[0x0D47]=true,[0x0D48]=true, +} +local above_mark={ +  [0x0900]=true,[0x0901]=true,[0x0902]=true,[0x093A]=true, +  [0x0945]=true,[0x0946]=true,[0x0947]=true,[0x0948]=true, +  [0x0951]=true,[0x0953]=true,[0x0954]=true,[0x0955]=true, +  [0xA8E0]=true,[0xA8E1]=true,[0xA8E2]=true,[0xA8E3]=true, +  [0xA8E4]=true,[0xA8E5]=true,[0xA8E6]=true,[0xA8E7]=true, +  [0xA8E8]=true,[0xA8E9]=true,[0xA8EA]=true,[0xA8EB]=true, +  [0xA8EC]=true,[0xA8ED]=true,[0xA8EE]=true,[0xA8EF]=true, +  [0xA8F0]=true,[0xA8F1]=true, +  [0x0D4E]=true, +} +local below_mark={ +  [0x093C]=true,[0x0941]=true,[0x0942]=true,[0x0943]=true, +  [0x0944]=true,[0x094D]=true,[0x0952]=true,[0x0956]=true, +  [0x0957]=true,[0x0962]=true,[0x0963]=true, +} +local post_mark={ +  [0x0903]=true,[0x093B]=true,[0x093E]=true,[0x0940]=true, +  [0x0949]=true,[0x094A]=true,[0x094B]=true,[0x094C]=true, +  [0x094F]=true, +} +local twopart_mark={ +  [0x0D4A]={ 0x0D46,0x0D3E,},	 +  [0x0D4B]={ 0x0D47,0x0D3E,},	 +  [0x0D4C]={ 0x0D46,0x0D57,},	 +} +local mark_four={}  +for k,v in next,pre_mark  do mark_four[k]=pre_mark  end +for k,v in next,above_mark do mark_four[k]=above_mark end +for k,v in next,below_mark do mark_four[k]=below_mark end +for k,v in next,post_mark do mark_four[k]=post_mark end +local mark_above_below_post={} +for k,v in next,above_mark do mark_above_below_post[k]=above_mark end +for k,v in next,below_mark do mark_above_below_post[k]=below_mark end +for k,v in next,post_mark do mark_above_below_post[k]=post_mark end +local reorder_class={ +  [0x0930]="before postscript", +  [0x093F]="before half", +  [0x0940]="after subscript", +  [0x0941]="after subscript", +  [0x0942]="after subscript", +  [0x0943]="after subscript", +  [0x0944]="after subscript", +  [0x0945]="after subscript", +  [0x0946]="after subscript", +  [0x0947]="after subscript", +  [0x0948]="after subscript", +  [0x0949]="after subscript", +  [0x094A]="after subscript", +  [0x094B]="after subscript", +  [0x094C]="after subscript", +  [0x0962]="after subscript", +  [0x0963]="after subscript", +  [0x093E]="after subscript", +  [0x0CB0]="after postscript", +  [0x0CBF]="before subscript", +  [0x0CC6]="before subscript", +  [0x0CCC]="before subscript", +  [0x0CBE]="before subscript", +  [0x0CE2]="before subscript", +  [0x0CE3]="before subscript", +  [0x0CC1]="before subscript", +  [0x0CC2]="before subscript", +  [0x0CC3]="after subscript", +  [0x0CC4]="after subscript", +  [0x0CD5]="after subscript", +  [0x0CD6]="after subscript", +} +local dflt_true={ +  dflt=true +} +local dev2_defaults={ +  dev2=dflt_true, +} +local deva_defaults={ +  dev2=dflt_true, +  deva=dflt_true, +} +local false_flags={ false,false,false,false } +local both_joiners_true={ +  [0x200C]=true, +  [0x200D]=true, +} +local sequence_reorder_matras={ +  features={ dv01=dev2_defaults }, +  flags=false_flags, +  name="dv01_reorder_matras", +  order={ "dv01" }, +  type="devanagari_reorder_matras", +  nofsteps=1, +  steps={ +    { +      osdstep=true, +      coverage=pre_mark, +    } +  } +} +local sequence_reorder_reph={ +  features={ dv02=dev2_defaults }, +  flags=false_flags, +  name="dv02_reorder_reph", +  order={ "dv02" }, +  type="devanagari_reorder_reph", +  nofsteps=1, +  steps={ +    { +      osdstep=true, +      coverage={}, +    } +  } +} +local sequence_reorder_pre_base_reordering_consonants={ +  features={ dv03=dev2_defaults }, +  flags=false_flags, +  name="dv03_reorder_pre_base_reordering_consonants", +  order={ "dv03" }, +  type="devanagari_reorder_pre_base_reordering_consonants", +  nofsteps=1, +  steps={ +    { +      osdstep=true, +      coverage={}, +    } +  } +} +local sequence_remove_joiners={ +  features={ dv04=deva_defaults }, +  flags=false_flags, +  name="dv04_remove_joiners", +  order={ "dv04" }, +  type="devanagari_remove_joiners", +  nofsteps=1, +  steps={ +    { osdstep=true, +      coverage=both_joiners_true, +    }, +  } +} +local basic_shaping_forms={ +  nukt=true, +  akhn=true, +  rphf=true, +  pref=true, +  rkrf=true, +  blwf=true, +  half=true, +  pstf=true, +  vatu=true, +  cjct=true, +} +local valid={ +  akhn=true, +  rphf=true, +  pref=true, +  half=true, +  blwf=true, +  pstf=true, +  pres=true, +  blws=true, +  psts=true, +} +local function initializedevanagi(tfmdata) +  local script,language=otf.scriptandlanguage(tfmdata,attr)  +  if script=="deva" or script=="dev2" or script=="mlym" or script=="mlm2" then +    local resources=tfmdata.resources +    local devanagari=resources.devanagari +    if not devanagari then +      report_devanagari("adding devanagari features to font") +      local gsubfeatures=resources.features.gsub +      local sequences=resources.sequences +      local sharedfeatures=tfmdata.shared.features +      local lastmatch=0 +      for s=1,#sequences do  +        local features=sequences[s].features +        if features then +          for k,v in next,features do +            if basic_shaping_forms[k] then +              lastmatch=s +            end +          end +        end +      end +      local insertindex=lastmatch+1 +      gsubfeatures["dv01"]=dev2_defaults  +      gsubfeatures["dv02"]=dev2_defaults  +      gsubfeatures["dv03"]=dev2_defaults  +      gsubfeatures["dv04"]=deva_defaults +      local reorder_pre_base_reordering_consonants=copy(sequence_reorder_pre_base_reordering_consonants) +      local reorder_reph=copy(sequence_reorder_reph) +      local reorder_matras=copy(sequence_reorder_matras) +      local remove_joiners=copy(sequence_remove_joiners) +      insert(sequences,insertindex,reorder_pre_base_reordering_consonants) +      insert(sequences,insertindex,reorder_reph) +      insert(sequences,insertindex,reorder_matras) +      insert(sequences,insertindex,remove_joiners) +      local blwfcache={} +      local seqsubset={} +      local rephstep={ +        coverage={}  +      } +      local devanagari={ +        reph=false, +        vattu=false, +        blwfcache=blwfcache, +        seqsubset=seqsubset, +        reorderreph=rephstep, +      } +      reorder_reph.steps={ rephstep } +      local pre_base_reordering_consonants={} +      reorder_pre_base_reordering_consonants.steps[1].coverage=pre_base_reordering_consonants +      resources.devanagari=devanagari +      for s=1,#sequences do +        local sequence=sequences[s] +        local steps=sequence.steps +        local nofsteps=sequence.nofsteps +        local features=sequence.features +        if features["rphf"] then +          devanagari.reph=true +        elseif features["blwf"] then +          devanagari.vattu=true +          for i=1,nofsteps do +            local step=steps[i] +            local coverage=step.coverage +            if coverage then +              for k,v in next,coverage do +                if not blwfcache[k] then +                  blwfcache[k]=v +                end +              end +            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 +                      end +                    end +                    if reph then +                      break +                    end +                  end +                end +              else +              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 found then +                      pre_base_reordering_consonants[k]=found +                      break +                    end +                  end +                  if found then +                    break +                  end +                end +              end +            end +          end +        end +      end +      if script=="deva" then +        sharedfeatures["dv04"]=true  +      elseif script=="dev2" then +        sharedfeatures["dv01"]=true  +        sharedfeatures["dv02"]=true  +        sharedfeatures["dv03"]=true  +        sharedfeatures["dv04"]=true  +      elseif script=="mlym" then +        sharedfeatures["pstf"]=true +      elseif script=="mlm2" then +        sharedfeatures["pstf"]=true +        sharedfeatures["pref"]=true +        sharedfeatures["dv03"]=true  +        gsubfeatures ["dv03"]=dev2_defaults  +        insert(sequences,insertindex,sequence_reorder_pre_base_reordering_consonants) +      end +    end    end  end -local function tabstr_mixed(t)  -  local s={} -  local n=#t -  if n==0 then -    return "" -  elseif n==1 then -    local k=t[1] -    if k==true then -      return "++"  -    elseif k==false then -      return "--"  -    else -      return tostring(k)  -    end -  else -    for i=1,n do -      local k=t[i] -      if k==true then -        s[i]="++"  -      elseif k==false then -        s[i]="--"  -      else -        s[i]=k  +registerotffeature { +  name="devanagari", +  description="inject additional features", +  default=true, +  initializers={ +    node=initializedevanagi, +  }, +} +local function deva_initialize(font,attr)  +  local tfmdata=fontdata[font] +  local datasets=otf.dataset(tfmdata,font,attr)  +  local devanagaridata=datasets.devanagari +  if not devanagaridata then +    devanagaridata={ +      reph=false, +      vattu=false, +      blwfcache={}, +    } +    datasets.devanagari=devanagaridata +    local resources=tfmdata.resources +    local devanagari=resources.devanagari +    for s=1,#datasets do +      local dataset=datasets[s] +      if dataset and dataset[1] then  +        local kind=dataset[4] +        if kind=="rphf" then +          devanagaridata.reph=true +        elseif kind=="blwf" then +          devanagaridata.vattu=true +          devanagaridata.blwfcache=devanagari.blwfcache +        end        end      end -    return concat(s,",")    end +  return devanagaridata.reph,devanagaridata.vattu,devanagaridata.blwfcache  end -local function tabstr_boolean(t) -  local s={} -  local n=0 -  for k,v in next,t do -    n=n+1 -    if v then -      s[n]=k.."+" +local function deva_reorder(head,start,stop,font,attr,nbspaces) +  local reph,vattu,blwfcache=deva_initialize(font,attr)  +  local current=start +  local n=getnext(start) +  local base=nil +  local firstcons=nil +  local lastcons=nil +  local basefound=false +  if reph and ra[getchar(start)] and halant[getchar(n)] then +    if n==stop then +      return head,stop,nbspaces +    end +    if getchar(getnext(n))==c_zwj then +      current=start      else -      s[n]=k.."-" +      current=getnext(n) +      setprop(start,a_state,s_rphf)      end    end -  if n==0 then -    return "" -  elseif n==1 then -    return s[1] -  else -    sort(s)  -    return concat(s,",") -  end -end -local function packdata(data) -  if data then -    local h,t,c={},{},{} -    local hh,tt,cc={},{},{} -    local nt,ntt=0,0 -    local function pack_normal(v) -      local tag=tabstr_normal(v) -      local ht=h[tag] -      if ht then -        c[ht]=c[ht]+1 -        return ht -      else -        nt=nt+1 -        t[nt]=v -        h[tag]=nt -        c[nt]=1 -        return nt +  if getchar(current)==c_nbsp then +    if current==stop then +      stop=getprev(stop) +      head=remove_node(head,current) +      free_node(current) +      return head,stop,nbspaces +    else +      nbspaces=nbspaces+1 +      base=current +      firstcons=current +      lastcons=current +      current=getnext(current) +      if current~=stop then +        if nukta[getchar(current)] then +          current=getnext(current) +        end +        if getchar(current)==c_zwj then +          if current~=stop then +            local next=getnext(current) +            if next~=stop and halant[getchar(next)] then +              current=next +              next=getnext(current) +              local tmp=next and getnext(next) or nil  +              local changestop=next==stop +              local tempcurrent=copy_node(next) +							copyinjection(tempcurrent,next) +              local nextcurrent=copy_node(current) +							copyinjection(nextcurrent,current)  +              setlink(tempcurrent,nextcurrent) +              setprop(tempcurrent,a_state,s_blwf) +              tempcurrent=processcharacters(tempcurrent,font) +              setprop(tempcurrent,a_state,unsetvalue) +              if getchar(next)==getchar(tempcurrent) then +                flush_list(tempcurrent) +                local n=copy_node(current) +								copyinjection(n,current)  +                setchar(current,dotted_circle) +                head=insert_node_after(head,current,n) +              else +                setchar(current,getchar(tempcurrent))  +                local freenode=getnext(current) +                setlink(current,tmp) +                free_node(freenode) +                flush_list(tempcurrent) +                if changestop then +                  stop=current +                end +              end +            end +          end +        end        end      end -    local function pack_flat(v) -      local tag=tabstr_flat(v) -      local ht=h[tag] -      if ht then -        c[ht]=c[ht]+1 -        return ht -      else -        nt=nt+1 -        t[nt]=v -        h[tag]=nt -        c[nt]=1 -        return nt +  end +  while not basefound do +    local char=getchar(current) +    if consonant[char] then +      setprop(current,a_state,s_half) +      if not firstcons then +        firstcons=current        end -    end -    local function pack_boolean(v) -      local tag=tabstr_boolean(v) -      local ht=h[tag] -      if ht then -        c[ht]=c[ht]+1 -        return ht +      lastcons=current +      if not base then +        base=current +      elseif blwfcache[char] then +        setprop(current,a_state,s_blwf)        else -        nt=nt+1 -        t[nt]=v -        h[tag]=nt -        c[nt]=1 -        return nt +        base=current        end      end -    local function pack_indexed(v) -      local tag=concat(v," ") -      local ht=h[tag] -      if ht then -        c[ht]=c[ht]+1 -        return ht -      else -        nt=nt+1 -        t[nt]=v -        h[tag]=nt -        c[nt]=1 -        return nt -      end +    basefound=current==stop +    current=getnext(current) +  end +  if base~=lastcons then +    local np=base +    local n=getnext(base) +    local ch=getchar(n) +    if nukta[ch] then +      np=n +      n=getnext(n) +      ch=getchar(n) +    end +    if halant[ch] then +      if lastcons~=stop then +        local ln=getnext(lastcons) +        if nukta[getchar(ln)] then +          lastcons=ln +        end +      end +      local nn=getnext(n) +      local ln=getnext(lastcons)  +      setlink(np,nn) +      setnext(lastcons,n) +      if ln then +        setprev(ln,n) +      end +      setnext(n,ln) +      setprev(n,lastcons) +      if lastcons==stop then +        stop=n +      end +    end +  end +  n=getnext(start) +  if n~=stop and ra[getchar(start)] and halant[getchar(n)] and not zw_char[getchar(getnext(n))] then +    local matra=base +    if base~=stop then +      local next=getnext(base) +      if dependent_vowel[getchar(next)] then +        matra=next +      end +    end +    local sp=getprev(start) +    local nn=getnext(n) +    local mn=getnext(matra) +    setlink(sp,nn) +    setlink(matra,start) +    setlink(n,mn) +    if head==start then +      head=nn      end -    local function pack_mixed(v) -      local tag=tabstr_mixed(v) -      local ht=h[tag] -      if ht then -        c[ht]=c[ht]+1 -        return ht -      else -        nt=nt+1 -        t[nt]=v -        h[tag]=nt -        c[nt]=1 -        return nt -      end +    start=nn +    if matra==stop then +      stop=n      end -    local function pack_final(v) -      if c[v]<=criterium then -        return t[v] -      else -        local hv=hh[v] -        if hv then -          return hv -        else -          ntt=ntt+1 -          tt[ntt]=t[v] -          hh[v]=ntt -          cc[ntt]=c[v] -          return ntt +  end +  local current=start +  while current~=stop do +    local next=getnext(current) +    if next~=stop and halant[getchar(next)] and getchar(getnext(next))==c_zwnj then +      setprop(current,a_state,unsetvalue) +    end +    current=next +  end +  if base~=stop and getprop(base,a_state) then +    local next=getnext(base) +    if halant[getchar(next)] and not (next~=stop and getchar(getnext(next))==c_zwj) then +      setprop(base,a_state,unsetvalue) +    end +  end +  local current,allreordered,moved=start,false,{ [base]=true } +  local a,b,p,bn=base,base,base,getnext(base) +  if base~=stop and nukta[getchar(bn)] then +    a,b,p=bn,bn,bn +  end +  while not allreordered do +    local c=current +    local n=getnext(current) +    local l=nil  +    if c~=stop then +      local ch=getchar(n) +      if nukta[ch] then +        c=n +        n=getnext(n) +        ch=getchar(n) +      end +      if c~=stop then +        if halant[ch] then +          c=n +          n=getnext(n) +          ch=getchar(n) +        end +        while c~=stop and dependent_vowel[ch] do +          c=n +          n=getnext(n) +          ch=getchar(n) +        end +        if c~=stop then +          if vowel_modifier[ch] then +            c=n +            n=getnext(n) +            ch=getchar(n) +          end +          if c~=stop and stress_tone_mark[ch] then +            c=n +            n=getnext(n) +          end          end        end      end -    local function success(stage,pass) -      if nt==0 then -        if trace_loading or trace_packing then -          report_otf("pack quality: nothing to pack") +    local bp=getprev(firstcons) +    local cn=getnext(current) +    local last=getnext(c) +    while cn~=last do +      if pre_mark[getchar(cn)] then +        if bp then +          setnext(bp,cn)          end -        return false -      elseif nt>=threshold then -        local one,two,rest=0,0,0 -        if pass==1 then -          for k,v in next,c do -            if v==1 then -              one=one+1 -            elseif v==2 then -              two=two+1 -            else -              rest=rest+1 -            end -          end -        else -          for k,v in next,cc do -            if v>20 then -              rest=rest+1 -            elseif v>10 then -              two=two+1 -            else -              one=one+1 -            end -          end -          data.tables=tt +        local prev,next=getboth(cn) +        if next then +          setprev(next,prev)          end -        if trace_loading or trace_packing then -          report_otf("pack quality: stage %s, pass %s, %s packed, 1-10:%s, 11-20:%s, rest:%s (criterium: %s)",stage,pass,one+two+rest,one,two,rest,criterium) +        setnext(prev,next) +        if cn==stop then +          stop=prev          end -        return true -      else -        if trace_loading or trace_packing then -          report_otf("pack quality: stage %s, pass %s, %s packed, aborting pack (threshold: %s)",stage,pass,nt,threshold) +        setprev(cn,bp) +        setlink(cn,firstcons) +        if firstcons==start then +          if head==start then +            head=cn +          end +          start=cn          end -        return false -      end -    end -    local function packers(pass) -      if pass==1 then -        return pack_normal,pack_indexed,pack_flat,pack_boolean,pack_mixed -      else -        return pack_final,pack_final,pack_final,pack_final,pack_final -      end -    end -    local resources=data.resources -    local lookuptypes=resources.lookuptypes -    for pass=1,2 do -      if trace_packing then -        report_otf("start packing: stage 1, pass %s",pass) +        break        end -      local pack_normal,pack_indexed,pack_flat,pack_boolean,pack_mixed=packers(pass) -      for unicode,description in next,data.descriptions do -        local boundingbox=description.boundingbox -        if boundingbox then -          description.boundingbox=pack_indexed(boundingbox) -        end -        local slookups=description.slookups -        if slookups then -          for tag,slookup in next,slookups do -            local what=lookuptypes[tag] -            if what=="pair" then -              local t=slookup[2] if t then slookup[2]=pack_indexed(t) end -              local t=slookup[3] if t then slookup[3]=pack_indexed(t) end -            elseif what~="substitution" then -              slookups[tag]=pack_indexed(slookup)  -            end +      cn=getnext(cn) +    end +    allreordered=c==stop +    current=getnext(c) +  end +  if reph or vattu then +    local current,cns=start,nil +    while current~=stop do +      local c=current +      local n=getnext(current) +      if ra[getchar(current)] and halant[getchar(n)] then +        c=n +        n=getnext(n) +        local b,bn=base,base +        while bn~=stop do +          local next=getnext(bn) +          if dependent_vowel[getchar(next)] then +            b=next            end +          bn=next          end -        local mlookups=description.mlookups -        if mlookups then -          for tag,mlookup in next,mlookups do -            local what=lookuptypes[tag] -            if what=="pair" then -              for i=1,#mlookup do -                local lookup=mlookup[i] -                local t=lookup[2] if t then lookup[2]=pack_indexed(t) end -                local t=lookup[3] if t then lookup[3]=pack_indexed(t) end -              end -            elseif what~="substitution" then -              for i=1,#mlookup do -                mlookup[i]=pack_indexed(mlookup[i])  +        if getprop(current,a_state)==s_rphf then +          if b~=current then +            if current==start then +              if head==start then +                head=n                end +              start=n +            end +            if b==stop then +              stop=c              end +            local prev=getprev(current) +            setlink(prev,n) +            local next=getnext(b) +            setlink(c,next) +            setlink(b,current)            end -        end -        local kerns=description.kerns -        if kerns then -          for tag,kern in next,kerns do -            kerns[tag]=pack_flat(kern) +        elseif cns and getnext(cns)~=current then +          local cp=getprev(current) +          local cnsn=getnext(cns) +          setlink(cp,n) +          setlink(cns,current) +          setlink(c,cnsn) +          if c==stop then +            stop=cp +            break            end +          current=getprev(n)          end -        local math=description.math -        if math then -          local kerns=math.kerns -          if kerns then -            for tag,kern in next,kerns do -              kerns[tag]=pack_normal(kern) -            end +      else +        local char=getchar(current) +        if consonant[char] then +          cns=current +          local next=getnext(cns) +          if halant[getchar(next)] then +            cns=next            end -        end -        local anchors=description.anchors -        if anchors then -          for what,anchor in next,anchors do -            if what=="baselig" then -              for _,a in next,anchor do -                for k=1,#a do -                  a[k]=pack_indexed(a[k]) -                end -              end -            else -              for k,v in next,anchor do -                anchor[k]=pack_indexed(v) -              end -            end +        elseif char==c_nbsp then +          nbspaces=nbspaces+1 +          cns=current +          local next=getnext(cns) +          if halant[getchar(next)] then +            cns=next            end          end -        local altuni=description.altuni -        if altuni then -          for i=1,#altuni do -            altuni[i]=pack_flat(altuni[i]) +      end +      current=getnext(current) +    end +  end +  if getchar(base)==c_nbsp then +    nbspaces=nbspaces-1 +    head=remove_node(head,base) +    free_node(base) +  end +  return head,stop,nbspaces +end +function handlers.devanagari_reorder_matras(head,start)  +  local current=start  +  local startfont=getfont(start) +  local startattr=getprop(start,a_syllabe) +  while current do +    local char=ischar(current,startfont) +    local next=getnext(current) +    if char and getprop(current,a_syllabe)==startattr then +      if halant[char] and not getprop(current,a_state) then +        if next then +          local char=ischar(next,startfont) +          if char and zw_char[char] and getprop(next,a_syllabe)==startattr then +            current=next +            next=getnext(current)            end          end +        local startnext=getnext(start) +        head=remove_node(head,start) +        setlink(start,next) +        setlink(current,start) +        start=startnext +        break        end -      local lookups=data.lookups -      if lookups then -        for _,lookup in next,lookups do -          local rules=lookup.rules -          if rules then -            for i=1,#rules do -              local rule=rules[i] -              local r=rule.before    if r then for i=1,#r do r[i]=pack_boolean(r[i]) end end -              local r=rule.after    if r then for i=1,#r do r[i]=pack_boolean(r[i]) end end -              local r=rule.current   if r then for i=1,#r do r[i]=pack_boolean(r[i]) end end -              local r=rule.replacements if r then rule.replacements=pack_flat  (r)  end  -              local r=rule.lookups   if r then rule.lookups=pack_indexed(r)  end -            end +    end +    current=next +  end +  return head,start,true +end +function handlers.devanagari_reorder_reph(head,start) +  local current=getnext(start) +  local startnext=nil +  local startprev=nil +  local startfont=getfont(start) +  local startattr=getprop(start,a_syllabe) +  while current do +    local char=ischar(current,font) +    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) +          if nextchar and zw_char[nextchar] and getprop(next,a_syllabe)==startattr then +            current=next +            next=getnext(current)            end          end +        startnext=getnext(start) +        head=remove_node(head,start) +        setlink(start,next) +        setlink(current,start) +        start=startnext +        startattr=getprop(start,a_syllabe) +        break        end -      local anchor_to_lookup=resources.anchor_to_lookup -      if anchor_to_lookup then -        for anchor,lookup in next,anchor_to_lookup do -          anchor_to_lookup[anchor]=pack_normal(lookup) +      current=getnext(current) +    else +      break +    end +  end +  if not startnext then +    current=getnext(start) +    while current do +      local char=ischar(current,font) +      if char and getprop(current,a_syllabe)==startattr then  +        if getprop(current,a_state)==s_pstf then  +          startnext=getnext(start) +          head=remove_node(head,start) +          local prev=getprev(current) +          setlink(prev,start) +          setlink(start,current) +          start=startnext +          startattr=getprop(start,a_syllabe) +          break          end +        current=getnext(current) +      else +        break        end -      local lookup_to_anchor=resources.lookup_to_anchor -      if lookup_to_anchor then -        for lookup,anchor in next,lookup_to_anchor do -          lookup_to_anchor[lookup]=pack_normal(anchor) +    end +  end +  if not startnext then +    current=getnext(start) +    local c=nil +    while current do +      local char=ischar(current,font) +      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          end +        current=getnext(current) +      else +        break        end -      local sequences=resources.sequences -      if sequences then -        for feature,sequence in next,sequences do -          local flags=sequence.flags -          if flags then -            sequence.flags=pack_normal(flags) -          end -          local subtables=sequence.subtables -          if subtables then -            sequence.subtables=pack_normal(subtables) -          end -          local features=sequence.features -          if features then -            for script,feature in next,features do -              features[script]=pack_normal(feature) +    end +    if c then +      startnext=getnext(start) +      head=remove_node(head,start) +      local prev=getprev(c) +      setlink(prev,start) +      setlink(start,c) +      start=startnext +      startattr=getprop(start,a_syllabe) +    end +  end +  if not startnext then +    current=start +    local next=getnext(current) +    while next do +      local nextchar=ischar(next,font) +      if nextchar and getprop(next,a_syllabe)==startattr then  +        current=next +        next=getnext(current) +      else +        break +      end +    end +    if start~=current then +      startnext=getnext(start) +      head=remove_node(head,start) +      local next=getnext(current) +      setlink(start,next) +      setlink(current,"next",start) +      start=startnext +    end +  end +  return head,start,true +end +function handlers.devanagari_reorder_pre_base_reordering_consonants(head,start) +  local current=start +  local startnext=nil +  local startprev=nil +  local startfont=getfont(start) +  local startattr=getprop(start,a_syllabe) +  while current do +    local char=ischar(current,font) +    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) +          if nextchar and getprop(next,a_syllabe)==startattr then +            if nextchar==c_zwnj or nextchar==c_zwj then +              current=next +              next=getnext(current)              end            end -          local order=sequence.order -          if order then -            sequence.order=pack_indexed(order) -          end -          local markclass=sequence.markclass -          if markclass then -            sequence.markclass=pack_boolean(markclass) -          end          end +        startnext=getnext(start) +        removenode(start,start) +        setlink(start,next) +        setlink(current,start) +        start=startnext +        break        end -      local lookups=resources.lookups -      if lookups then -        for name,lookup in next,lookups do -          local flags=lookup.flags -          if flags then -            lookup.flags=pack_normal(flags) +      current=next +    else +      break +    end +  end +  if not startnext then +    current=getnext(start) +    startattr=getprop(start,a_syllabe) +    while current do +      local char=ischar(current,font) +      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(start,current) +          start=startnext +          break +        end +        current=getnext(current) +      else +        break +      end +    end +  end +  return head,start,true +end +function handlers.devanagari_remove_joiners(head,start,kind,lookupname,replacement) +  local stop=getnext(start) +  local font=getfont(start) +  local last=start +  while stop do +    local char=ischar(stop,font) +    if char and (char==c_zwnj or char==c_zwj) then +      last=stop +      stop=getnext(stop) +    else +      break +    end +  end +  local prev=getprev(start) +  if stop then +    setnext(last) +    setlink(prev,stop) +  elseif prev then +    setnext(prev) +  end +  if head==start then +  	head=stop +  end +  flush_list(start) +  return head,stop,true +end +local function dev2_initialize(font,attr) +  local devanagari=fontdata[font].resources.devanagari +  if devanagari then +    return devanagari.seqsubset or {},devanagari.reorderreph or {} +  else +    return {},{} +  end +end +local function dev2_reorder(head,start,stop,font,attr,nbspaces)  +  local seqsubset,reorderreph=dev2_initialize(font,attr) +  local reph=false  +  local halfpos=nil +  local basepos=nil +  local subpos=nil +  local postpos=nil +  local locl={} +  for i=1,#seqsubset do +    local subset=seqsubset[i] +    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 -          local subtables=lookup.subtables -          if subtables then -            lookup.subtables=pack_normal(subtables) +          if reph then +            break            end          end        end -      local features=resources.features -      if features then -        for _,what in next,glists do -          local list=features[what] -          if list then -            for feature,spec in next,list do -              list[feature]=pack_normal(spec) +      local current=start +      local last=getnext(stop) +      while current~=last do +        if current~=stop then +          local c=locl[current] or getchar(current) +          local found=lookupcache[c] +          if found then +            local next=getnext(current) +            local n=locl[next] or getchar(next) +            if found[n] then   +              local afternext=next~=stop and getnext(next) +              if afternext and zw_char[getchar(afternext)] then  +                current=next +                current=getnext(current) +              elseif current==start then +                setprop(current,a_state,s_rphf) +                current=next +              else +                current=next +              end              end            end          end +        current=getnext(current)        end -      if not success(1,pass) then -        return -      end -    end -    if nt>0 then -      for pass=1,2 do -        if trace_packing then -          report_otf("start packing: stage 2, pass %s",pass) -        end -        local pack_normal,pack_indexed,pack_flat,pack_boolean,pack_mixed=packers(pass) -        for unicode,description in next,data.descriptions do -          local kerns=description.kerns -          if kerns then -            description.kerns=pack_normal(kerns) -          end -          local math=description.math -          if math then -            local kerns=math.kerns -            if kerns then -              math.kerns=pack_normal(kerns) +    elseif kind=="pref" then +      local current=start +      local last=getnext(stop) +      while current~=last do +        if current~=stop then +          local c=locl[current] or getchar(current) +          local found=lookupcache[c] +          if found then +            local next=getnext(current) +            local n=locl[next] or getchar(next) +            if found[n] then +              setprop(current,a_state,s_pref) +              setprop(next,a_state,s_pref) +              current=next              end            end -          local anchors=description.anchors -          if anchors then -            description.anchors=pack_normal(anchors) +        end +        current=getnext(current) +      end +    elseif kind=="half" then  +      local current=start +      local last=getnext(stop) +      while current~=last do +        if current~=stop then +          local c=locl[current] or getchar(current) +          local found=lookupcache[c] +          if found then +            local next=getnext(current) +            local n=locl[next] or getchar(next) +            if found[n] then +              if next~=stop and getchar(getnext(next))==c_zwnj then   +                current=next +              else +                setprop(current,a_state,s_half) +                if not halfpos then +                  halfpos=current +                end +              end +              current=getnext(current) +            end            end -          local mlookups=description.mlookups -          if mlookups then -            for tag,mlookup in next,mlookups do -              mlookups[tag]=pack_normal(mlookup) +        end +        current=getnext(current) +      end +    elseif kind=="blwf" then  +      local current=start +      local last=getnext(stop) +      while current~=last do +        if current~=stop then +          local c=locl[current] or getchar(current) +          local found=lookupcache[c] +          if found then +            local next=getnext(current) +            local n=locl[next] or getchar(next) +            if found[n] then +              setprop(current,a_state,s_blwf) +              setprop(next,a_state,s_blwf) +              current=next +              subpos=current              end            end -          local altuni=description.altuni -          if altuni then -            description.altuni=pack_normal(altuni) +        end +        current=getnext(current) +      end +    elseif kind=="pstf" then  +      local current=start +      local last=getnext(stop) +      while current~=last do +        if current~=stop then +          local c=locl[current] or getchar(current) +          local found=lookupcache[c] +          if found then +            local next=getnext(current) +            local n=locl[next] or getchar(next) +            if found[n] then +              setprop(current,a_state,s_pstf) +              setprop(next,a_state,s_pstf) +              current=next +              postpos=current +            end            end          end -        local lookups=data.lookups -        if lookups then -          for _,lookup in next,lookups do -            local rules=lookup.rules -            if rules then -              for i=1,#rules do  -                local rule=rules[i] -                local r=rule.before if r then rule.before=pack_normal(r) end -                local r=rule.after  if r then rule.after=pack_normal(r) end -                local r=rule.current if r then rule.current=pack_normal(r) end +        current=getnext(current) +      end +    end +  end +  reorderreph.coverage={ [reph]=true } +  local current,base,firstcons=start,nil,nil +  if getprop(start,a_state)==s_rphf then +    current=getnext(getnext(start)) +  end +  if current~=getnext(stop) and getchar(current)==c_nbsp then +    if current==stop then +      stop=getprev(stop) +      head=remove_node(head,current) +      free_node(current) +      return head,stop,nbspaces +    else +      nbspaces=nbspaces+1 +      base=current +      current=getnext(current) +      if current~=stop then +        local char=getchar(current) +        if nukta[char] then +          current=getnext(current) +          char=getchar(current) +        end +        if char==c_zwj then +          local next=getnext(current) +          if current~=stop and next~=stop and halant[getchar(next)] then +            current=next +            next=getnext(current) +            local tmp=getnext(next) +            local changestop=next==stop +            setnext(next,nil) +            setprop(current,a_state,s_pref) +            current=processcharacters(current,font) +            setprop(current,a_state,s_blwf) +            current=processcharacters(current,font) +            setprop(current,a_state,s_pstf) +            current=processcharacters(current,font) +            setprop(current,a_state,unsetvalue) +            if halant[getchar(current)] then +              setnext(getnext(current),tmp) +              local nc=copy_node(current) +							copyinjection(nc,current) +              setchar(current,dotted_circle) +              head=insert_node_after(head,current,nc) +            else +              setnext(current,tmp)  +              if changestop then +                stop=current                end              end            end          end -        local sequences=resources.sequences -        if sequences then -          for feature,sequence in next,sequences do -            sequence.features=pack_normal(sequence.features) +      end +    end +  else  +    local last=getnext(stop) +    while current~=last do   +      local next=getnext(current) +      if consonant[getchar(current)] then +        if not (current~=stop and next~=stop and halant[getchar(next)] and getchar(getnext(next))==c_zwj) then +          if not firstcons then +            firstcons=current +          end +          local a=getprop(current,a_state) +          if not (a==s_pref or a==s_blwf or a==s_pstf) then +            base=current            end -        end -        if not success(2,pass) then          end        end -      for pass=1,2 do -        local pack_normal,pack_indexed,pack_flat,pack_boolean,pack_mixed=packers(pass) -        for unicode,description in next,data.descriptions do -          local slookups=description.slookups -          if slookups then -            description.slookups=pack_normal(slookups) +      current=next +    end +    if not base then +      base=firstcons +    end +  end +  if not base then +    if getprop(start,a_state)==s_rphf then +      setprop(start,a_state,unsetvalue) +    end +    return head,stop,nbspaces +  else +    if getprop(base,a_state) then +      setprop(base,a_state,unsetvalue) +    end +    basepos=base +  end +  if not halfpos then +    halfpos=base +  end +  if not subpos then +    subpos=base +  end +  if not postpos then +    postpos=subpos or base +  end +  local moved={} +  local current=start +  local last=getnext(stop) +  while current~=last do +    local char,target,cn=locl[current] or getchar(current),nil,getnext(current) +    local tpm=twopart_mark[char] +    if tpm then +      local extra=copy_node(current) +      copyinjection(extra,current) +      char=tpm[1] +      setchar(current,char) +      setchar(extra,tpm[2]) +      head=insert_node_after(head,current,extra) +    end +    if not moved[current] and dependent_vowel[char] then +      if pre_mark[char] then       +        moved[current]=true +        local prev,next=getboth(current) +        setlink(prev,next) +        if current==stop then +          stop=getprev(current) +        end +        if halfpos==start then +          if head==start then +            head=current            end -          local mlookups=description.mlookups -          if mlookups then -            description.mlookups=pack_normal(mlookups) +          start=current +        end +        local prev=getprev(halfpos) +        setlink(prev,current) +        setlink(current,halfpos) +        halfpos=current +      elseif above_mark[char] then   +        target=basepos +        if subpos==basepos then +          subpos=current +        end +        if postpos==basepos then +          postpos=current +        end +        basepos=current +      elseif below_mark[char] then   +        target=subpos +        if postpos==subpos then +          postpos=current +        end +        subpos=current +      elseif post_mark[char] then   +        target=postpos +        postpos=current +      end +      if mark_above_below_post[char] then +        local prev=getprev(current) +        if prev~=target then +          local next=getnext(current) +          setlink(next,prev) +          if current==stop then +            stop=prev            end +          local next=getnext(target) +          setlink(current,next) +          setlink(target,current)          end        end      end +    current=cn    end -end -local unpacked_mt={ -  __index=function(t,k) -      t[k]=false -      return k  +  local current,c=start,nil +  while current~=stop do +    local char=getchar(current) +    if halant[char] or stress_tone_mark[char] then +      if not c then +        c=current +      end +    else +      c=nil      end -} -local function unpackdata(data) -  if data then -    local tables=data.tables -    if tables then -      local resources=data.resources -      local lookuptypes=resources.lookuptypes -      local unpacked={} -      setmetatable(unpacked,unpacked_mt) -      for unicode,description in next,data.descriptions do -        local tv=tables[description.boundingbox] -        if tv then -          description.boundingbox=tv -        end -        local slookups=description.slookups -        if slookups then -          local tv=tables[slookups] -          if tv then -            description.slookups=tv -            slookups=unpacked[tv] -          end -          if slookups then -            for tag,lookup in next,slookups do -              local what=lookuptypes[tag] -              if what=="pair" then -                local tv=tables[lookup[2]] -                if tv then -                  lookup[2]=tv -                end -                local tv=tables[lookup[3]] -                if tv then -                  lookup[3]=tv -                end -              elseif what~="substitution" then -                local tv=tables[lookup] -                if tv then -                  slookups[tag]=tv +    local next=getnext(current) +    if c and nukta[getchar(next)] then +      if head==c then +        head=next +      end +      if stop==next then +        stop=current +      end +      local prev=getprev(c) +      setlink(next,prev) +      local nextnext=getnext(next) +      setnext(current,nextnext) +      local nextnextnext=getnext(nextnext) +      if nextnextnext then +        setprev(nextnextnext,current) +      end +      setlink(nextnext,c) +    end +    if stop==current then break end +    current=getnext(current) +  end +  if getchar(base)==c_nbsp then +    nbspaces=nbspaces-1 +    head=remove_node(head,base) +    free_node(base) +  end +  return head,stop,nbspaces +end +local separator={} +imerge(separator,consonant) +imerge(separator,independent_vowel) +imerge(separator,dependent_vowel) +imerge(separator,vowel_modifier) +imerge(separator,stress_tone_mark) +for k,v in next,nukta do separator[k]=true end +for k,v in next,halant do separator[k]=true end +local function analyze_next_chars_one(c,font,variant) +  local n=getnext(c) +  if not n then +    return c +  end +  if variant==1 then +    local v=ischar(n,font) +    if v and nukta[v] then +      n=getnext(n) +      if n then +        v=ischar(n,font) +      end +    end +    if n and v then +      local nn=getnext(n) +      if nn then +        local vv=ischar(nn,font) +        if vv then +          local nnn=getnext(nn) +          if nnn then +            local vvv=ischar(nnn,font) +            if vvv then +              if vv==c_zwj and consonant[vvv] then +                c=nnn +              elseif (vv==c_zwnj or vv==c_zwj) and halant[vvv] then +                local nnnn=getnext(nnn) +                if nnnn then +                  local vvvv=ischar(nnnn) +                  if vvvv and consonant[vvvv] then +                    c=nnnn +                  end                  end                end              end            end          end -        local mlookups=description.mlookups -        if mlookups then -          local tv=tables[mlookups] -          if tv then -            description.mlookups=tv -            mlookups=unpacked[tv] +      end +    end +  elseif variant==2 then +    local v=ischar(n,font) +    if v and nukta[v] then +      c=n +    end +    n=getnext(c) +    if n then +      v=ischar(n,font) +      if v then +        local nn=getnext(n) +        if nn then +          local vv=ischar(nn,font) +          if vv and zw_char[vv] then +            n=nn +            v=vv +            nn=getnext(nn) +            vv=nn and ischar(nn,font)            end -          if mlookups then -            for tag,list in next,mlookups do -              local tv=tables[list] -              if tv then -                mlookups[tag]=tv -                list=unpacked[tv] -              end -              if list then -                local what=lookuptypes[tag] -                if what=="pair" then -                  for i=1,#list do -                    local lookup=list[i] -                    local tv=tables[lookup[2]] -                    if tv then -                      lookup[2]=tv -                    end -                    local tv=tables[lookup[3]] -                    if tv then -                      lookup[3]=tv -                    end -                  end -                elseif what~="substitution" then -                  for i=1,#list do -                    local tv=tables[list[i]] -                    if tv then -                      list[i]=tv -                    end -                  end -                end -              end -            end +          if vv and halant[v] and consonant[vv] then +            c=nn            end          end -        local kerns=description.kerns -        if kerns then -          local tm=tables[kerns] -          if tm then -            description.kerns=tm -            kerns=unpacked[tm] +      end +    end +  end +  local n=getnext(c) +  if not n then +    return c +  end +  local v=ischar(n,font) +  if not v then +    return c +  end +  if dependent_vowel[v] then +    c=getnext(c) +    n=getnext(c) +    if not n then +      return c +    end +    v=ischar(n,font) +    if not v then +      return c +    end +  end +  if nukta[v] then +    c=getnext(c) +    n=getnext(c) +    if not n then +      return c +    end +    v=ischar(n,font) +    if not v then +      return c +    end +  end +  if halant[v] then +    c=getnext(c) +    n=getnext(c) +    if not n then +      return c +    end +    v=ischar(n,font) +    if not v then +      return c +    end +  end +  if vowel_modifier[v] then +    c=getnext(c) +    n=getnext(c) +    if not n then +      return c +    end +    v=ischar(n,font) +    if not v then +      return c +    end +  end +  if stress_tone_mark[v] then +    c=getnext(c) +    n=getnext(c) +    if not n then +      return c +    end +    v=ischar(n,font) +    if not v then +      return c +    end +  end +  if stress_tone_mark[v] then +    return n +  else +    return c +  end +end +local function analyze_next_chars_two(c,font) +  local n=getnext(c) +  if not n then +    return c +  end +  local v=ischar(n,font) +  if v and nukta[v] then +    c=n +  end +  n=c +  while true do +    local nn=getnext(n) +    if nn then +      local vv=ischar(nn,font) +      if vv then +        if halant[vv] then +          n=nn +          local nnn=getnext(nn) +          if nnn then +            local vvv=ischar(nnn,font) +            if vvv and zw_char[vvv] then +              n=nnn +            end            end -          if kerns then -            for k,kern in next,kerns do -              local tv=tables[kern] -              if tv then -                kerns[k]=tv -              end +        elseif vv==c_zwnj or vv==c_zwj then +          local nnn=getnext(nn) +          if nnn then +            local vvv=ischar(nnn,font) +            if vvv and halant[vvv] then +              n=nnn              end            end +        else +          break          end -        local math=description.math -        if math then -          local kerns=math.kerns -          if kerns then -            local tm=tables[kerns] -            if tm then -              math.kerns=tm -              kerns=unpacked[tm] -            end -            if kerns then -              for k,kern in next,kerns do -                local tv=tables[kern] -                if tv then -                  kerns[k]=tv -                end +        local nn=getnext(n) +        if nn then +          local vv=ischar(nn,font) +          if vv and consonant[vv] then +            n=nn +            local nnn=getnext(nn) +            if nnn then +              local vvv=ischar(nnn,font) +              if vvv and nukta[vvv] then +                n=nnn                end              end +            c=n +          else +            break            end +        else +          break          end -        local anchors=description.anchors -        if anchors then -          local ta=tables[anchors] -          if ta then -            description.anchors=ta -            anchors=unpacked[ta] -          end -          if anchors then -            for tag,anchor in next,anchors do -              if tag=="baselig" then -                for _,list in next,anchor do -                  for i=1,#list do -                    local tv=tables[list[i]] -                    if tv then -                      list[i]=tv -                    end -                  end -                end -              else -                for a,data in next,anchor do -                  local tv=tables[data] -                  if tv then -                    anchor[a]=tv -                  end -                end -              end +      else +        break +      end +    else +      break +    end +  end +  if not c then +    return +  end +  local n=getnext(c) +  if not n then +    return c +  end +  local v=ischar(n,font) +  if not v then +    return c +  end +  if v==c_anudatta then +    c=n +    n=getnext(c) +    if not n then +      return c +    end +    v=ischar(n,font) +    if not v then +      return c +    end +  end +  if halant[v] then +    c=n +    n=getnext(c) +    if not n then +      return c +    end +    v=ischar(n,font) +    if not v then +      return c +    end +    if v==c_zwnj or v==c_zwj then +      c=n +      n=getnext(c) +      if not n then +        return c +      end +      v=ischar(n,font) +      if not v then +        return c +      end +    end +  else +    if dependent_vowel[v] then +      c=n +      n=getnext(c) +      if not n then +        return c +      end +      v=ischar(n,font) +      if not v then +        return c +      end +    end +    if nukta[v] then +      c=n +      n=getnext(c) +      if not n then +        return c +      end +      v=ischar(n,font) +      if not v then +        return c +      end +    end +    if halant[v] then +      c=n +      n=getnext(c) +      if not n then +        return c +      end +      v=ischar(n,font) +      if not v then +        return c +      end +    end +  end +  if vowel_modifier[v] then +    c=n +    n=getnext(c) +    if not n then +      return c +    end +    v=ischar(n,font) +    if not v then +      return c +    end +  end +  if stress_tone_mark[v] then +    c=n +    n=getnext(c) +    if not n then +      return c +    end +    v=ischar(n,font) +    if not v then +      return c +    end +  end +  if stress_tone_mark[v] then +    return n +  else +    return c +  end +end +local function inject_syntax_error(head,current,mark) +  local signal=copy_node(current) +	copyinjection(signal,current) +  if mark==pre_mark then  +    setchar(signal,dotted_circle) +  else +    setchar(current,dotted_circle) +  end +  return insert_node_after(head,current,signal) +end +function methods.deva(head,font,attr) +  head=tonut(head) +  local current=head +  local start=true +  local done=false +  local nbspaces=0 +  while current do +		local char=ischar(current,font) +    if char then +      done=true +      local syllablestart=current +      local syllableend=nil +      local c=current +      local n=getnext(c) +	    local first=char +      if n and ra[first] then +        local second=ischar(n,font) +        if second and halant[second] then +          local n=getnext(n) +          if n then +            local third=ischar(n,font) +            if third then +              c=n +              first=third              end            end          end -        local altuni=description.altuni -        if altuni then -          local altuni=tables[altuni] -          if altuni then -            description.altuni=altuni -            for i=1,#altuni do -              local tv=tables[altuni[i]] -              if tv then -                altuni[i]=tv -              end -            end +      end +      local standalone=first==c_nbsp +      if standalone then +        local prev=getprev(current) +        if prev then +          local prevchar=ischar(prev,font) +          if not prevchar then +          elseif not separator[prevchar] then +          else +            standalone=false            end +        else          end        end -      local lookups=data.lookups -      if lookups then -        for _,lookup in next,lookups do -          local rules=lookup.rules -          if rules then -            for i=1,#rules do  -              local rule=rules[i] -              local before=rule.before -              if before then -                local tv=tables[before] -                if tv then -                  rule.before=tv -                  before=unpacked[tv] -                end -                if before then -                  for i=1,#before do -                    local tv=tables[before[i]] -                    if tv then -                      before[i]=tv -                    end -                  end -                end +      if standalone then +				local syllableend=analyze_next_chars_one(c,font,2) +				current=getnext(syllableend) +        if syllablestart~=syllableend then +          head,current,nbspaces=deva_reorder(head,syllablestart,syllableend,font,attr,nbspaces) +          current=getnext(current) +        end +      else +        if consonant[char] then +          local prevc=true +          while prevc do +            prevc=false +            local n=getnext(current) +            if not n then +              break +            end +            local v=ischar(n,font) +            if not v then +              break +            end +            if nukta[v] then +              n=getnext(n) +              if not n then +                break                end -              local after=rule.after -              if after then -                local tv=tables[after] -                if tv then -                  rule.after=tv -                  after=unpacked[tv] -                end -                if after then -                  for i=1,#after do -                    local tv=tables[after[i]] -                    if tv then -                      after[i]=tv -                    end -                  end -                end +              v=ischar(n,font) +              if not v then +                break                end -              local current=rule.current -              if current then -                local tv=tables[current] -                if tv then -                  rule.current=tv -                  current=unpacked[tv] -                end -                if current then -                  for i=1,#current do -                    local tv=tables[current[i]] -                    if tv then -                      current[i]=tv -                    end -                  end -                end +            end +            if halant[v] then +              n=getnext(n) +              if not n then +                break                end -              local replacements=rule.replacements -              if replacements then -                local tv=tables[replacements] -                if tv then -                  rule.replacements=tv -                end +              v=ischar(n,font) +              if not v then +                break                end -              local lookups=rule.lookups -              if lookups then -                local tv=tables[lookups] -                if tv then -                  rule.lookups=tv +              if v==c_zwnj or v==c_zwj then +                n=getnext(n) +                if not n then +                  break +                end +                v=ischar(n,font) +                if not v then +                  break                  end                end +              if consonant[v] then +                prevc=true +                current=n +              end              end            end -        end -      end -      local anchor_to_lookup=resources.anchor_to_lookup -      if anchor_to_lookup then -        for anchor,lookup in next,anchor_to_lookup do -          local tv=tables[lookup] -          if tv then -            anchor_to_lookup[anchor]=tv -          end -        end -      end -      local lookup_to_anchor=resources.lookup_to_anchor -      if lookup_to_anchor then -        for lookup,anchor in next,lookup_to_anchor do -          local tv=tables[anchor] -          if tv then -            lookup_to_anchor[lookup]=tv -          end -        end -      end -      local ls=resources.sequences -      if ls then -        for _,feature in next,ls do -          local flags=feature.flags -          if flags then -            local tv=tables[flags] -            if tv then -              feature.flags=tv -            end -          end -          local subtables=feature.subtables -          if subtables then -            local tv=tables[subtables] -            if tv then -              feature.subtables=tv +          local n=getnext(current) +          if n then +            local v=ischar(n,font) +            if v and nukta[v] then +              current=n +              n=getnext(current)              end            end -          local features=feature.features -          if features then -            local tv=tables[features] -            if tv then -              feature.features=tv -              features=unpacked[tv] -            end -            if features then -              for script,data in next,features do -                local tv=tables[data] -                if tv then -                  features[script]=tv +          syllableend=current +          current=n +          if current then +            local v=ischar(current,font) +            if not v then +            elseif halant[v] then +              local n=getnext(current) +              if n then +                local v=ischar(n,font) +                if v and zw_char[v] then +                  syllableend=n +                  current=getnext(n) +                else +                  syllableend=current +                  current=n                  end +              else +                syllableend=current +                current=n +              end +            else +              if dependent_vowel[v] then +                syllableend=current +                current=getnext(current) +                v=ischar(current,font) +              end +              if v and vowel_modifier[v] then +                syllableend=current +                current=getnext(current) +                v=ischar(current,font) +              end +              if v and stress_tone_mark[v] then +                syllableend=current +                current=getnext(current)                end              end            end -          local order=feature.order -          if order then -            local tv=tables[order] -            if tv then -              feature.order=tv -            end +          if syllablestart~=syllableend then +            head,current,nbspaces=deva_reorder(head,syllablestart,syllableend,font,attr,nbspaces) +            current=getnext(current)            end -          local markclass=feature.markclass -          if markclass then -            local tv=tables[markclass] -            if tv then -              feature.markclass=tv +        elseif independent_vowel[char] then +          syllableend=current +          current=getnext(current) +          if current then +            local v=ischar(current,font) +            if v then +              if vowel_modifier[v] then +                syllableend=current +                current=getnext(current) +                v=ischar(current,font) +              end +              if v and stress_tone_mark[v] then +                syllableend=current +                current=getnext(current) +              end              end            end +        else +          local mark=mark_four[char] +          if mark then +            head,current=inject_syntax_error(head,current,mark) +          end +          current=getnext(current)          end        end -      local lookups=resources.lookups -      if lookups then -        for _,lookup in next,lookups do -          local flags=lookup.flags -          if flags then -            local tv=tables[flags] -            if tv then -              lookup.flags=tv -            end -          end -          local subtables=lookup.subtables -          if subtables then -            local tv=tables[subtables] -            if tv then -              lookup.subtables=tv +    else +      current=getnext(current) +    end +    start=false +  end +  if nbspaces>0 then +    head=replace_all_nbsp(head) +  end +  head=tonode(head) +  return head,done +end +function methods.dev2(head,font,attr) +  head=tonut(head) +  local current=head +  local start=true +  local done=false +  local syllabe=0 +  local nbspaces=0 +  while current do +    local syllablestart=nil +    local syllableend=nil +    local char=ischar(current,font) +    if char then +      done=true +      syllablestart=current +      local c=current +      local n=getnext(current) +      if n and ra[char] then +        local nextchar=ischar(n,font) +        if nextchar and halant[nextchar] then +          local n=getnext(n) +          if n then +            local nextnextchar=ischar(n,font) +            if nextnextchar then +              c=n +							char=nextnextchar              end            end          end        end -      local features=resources.features -      if features then -        for _,what in next,glists do -          local feature=features[what] -          if feature then -            for tag,spec in next,feature do -              local tv=tables[spec] -              if tv then -                feature[tag]=tv -              end -            end +      if independent_vowel[char] then +        current=analyze_next_chars_one(c,font,1) +        syllableend=current +      else +        local standalone=char==c_nbsp +        if standalone then +          nbspaces=nbspaces+1 +          local p=getprev(current) +          if not p then +          elseif ischar(p,font) then +          elseif not separator[getchar(p)] then +          else +            standalone=false            end          end +        if standalone then +          current=analyze_next_chars_one(c,font,2) +          syllableend=current +        elseif consonant[getchar(current)] then +          current=analyze_next_chars_two(current,font)  +          syllableend=current +        end        end -      data.tables=nil      end +    if syllableend then +      syllabe=syllabe+1 +      local c=syllablestart +      local n=getnext(syllableend) +      while c~=n do +        setprop(c,a_syllabe,syllabe) +        c=getnext(c) +      end +    end +    if syllableend and syllablestart~=syllableend then +      head,current,nbspaces=dev2_reorder(head,syllablestart,syllableend,font,attr,nbspaces) +    end +    if not syllableend then +      local char=ischar(current,font) +      if char and not getprop(current,a_state) then +        local mark=mark_four[char] +        if mark then +          head,current=inject_syntax_error(head,current,mark) +        end +      end +    end +    start=false +    current=getnext(current)    end +  if nbspaces>0 then +    head=replace_all_nbsp(head) +  end +  head=tonode(head) +  return head,done  end -if otf.enhancers.register then -  otf.enhancers.register("pack",packdata) -  otf.enhancers.register("unpack",unpackdata) -end -otf.enhancers.unpack=unpackdata  -otf.enhancers.pack=packdata   +methods.mlym=methods.deva +methods.mlm2=methods.dev2  end -- closure  do -- begin closure to overcome local limits and interference -if not modules then modules={} end modules ['luatex-fonts-lua']={ +if not modules then modules={} end modules ['font-lua']={    version=1.001, -  comment="companion to luatex-*.tex", +  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"  } -if context then -  texio.write_nl("fatal error: this module is not for context") -  os.exit() -end +local trace_defining=false trackers.register("fonts.defining",function(v) trace_defining=v end) +local report_lua=logs.reporter("fonts","lua loading")  local fonts=fonts +local readers=fonts.readers  fonts.formats.lua="lua" -function fonts.readers.lua(specification) +local function check_lua(specification,fullname) +  local fullname=resolvers.findfile(fullname) or "" +  if fullname~="" then +    local loader=loadfile(fullname) +    loader=loader and loader() +    return loader and loader(specification) +  end +end +readers.check_lua=check_lua +function readers.lua(specification) +  local original=specification.specification +  if trace_defining then +    report_lua("using lua reader for %a",original) +  end    local fullname=specification.filename or ""    if fullname=="" then      local forced=specification.forced or "" @@ -15810,12 +23010,7 @@ function fonts.readers.lua(specification)        fullname=specification.name      end    end -  local fullname=resolvers.findfile(fullname) or "" -  if fullname~="" then -    local loader=loadfile(fullname) -    loader=loader and loader() -    return loader and loader(specification) -  end +  return check_lua(specification,fullname)  end  end -- closure @@ -16189,7 +23384,7 @@ local filename_1=P("file:")/isfile*(namespec/thename)  local filename_2=P("[")*P(true)/isname*(((1-P("]"))^0)/thename)*P("]")  local fontname_1=P("name:")/isname*(namespec/thename)  local fontname_2=P(true)/issome*(namespec/thename) -local sometext=(R("az","AZ","09")+S("+-."))^1 +local sometext=(R("az","AZ","09")+S("+-.{}"))^1  local truevalue=P("+")*spaces*(sometext/istrue)  local falsevalue=P("-")*spaces*(sometext/isfalse)  local keyvalue=(C(sometext)*spaces*P("=")*spaces*C(sometext))/iskey @@ -16456,7 +23651,7 @@ end -- closure  do -- begin closure to overcome local limits and interference -if not modules then modules={} end modules ['luatex-fonts-cbk']={ +if not modules then modules={} end modules ['font-gbn']={    version=1.001,    comment="companion to luatex-*.tex",    author="Hans Hagen, PRAGMA-ADE, Hasselt NL", @@ -16469,45 +23664,59 @@ if context then  end  local fonts=fonts  local nodes=nodes -local traverse_id=node.traverse_id -local free_node=node.free -local remove_node=node.remove +local nuts=nodes.nuts  +local traverse_id=nuts.traverse_id +local free_node=nuts.free +local remove_node=nuts.remove  local glyph_code=nodes.nodecodes.glyph  local disc_code=nodes.nodecodes.disc -local ligaturing=node.ligaturing -local kerning=node.kerning -local basepass=true +local tonode=nuts.tonode +local tonut=nuts.tonut +local getfont=nuts.getfont +local getchar=nuts.getchar +local getid=nuts.getid +local getprev=nuts.getprev +local getnext=nuts.getnext +local getdisc=nuts.getdisc +local setchar=nuts.setchar +local setlink=nuts.setlink +local n_ligaturing=node.ligaturing +local n_kerning=node.kerning +local ligaturing=nuts.ligaturing +local kerning=nuts.kerning +local basemodepass=true  local function l_warning() texio.write_nl("warning: node.ligaturing called directly") l_warning=nil end  local function k_warning() texio.write_nl("warning: node.kerning called directly")  k_warning=nil end  function node.ligaturing(...) -  if basepass and l_warning then +  if basemodepass and l_warning then      l_warning()    end -  return ligaturing(...) +  return n_ligaturing(...)  end  function node.kerning(...) -  if basepass and k_warning then +  if basemodepass and k_warning then      k_warning()    end -  return kerning(...) +  return n_kerning(...)  end -function nodes.handlers.setbasepass(v) -  basepass=v +function nodes.handlers.setbasemodepass(v) +  basemodepass=v  end  function nodes.handlers.nodepass(head)    local fontdata=fonts.hashes.identifiers    if fontdata then +    local nuthead=tonut(head)      local usedfonts={}      local basefonts={}      local prevfont=nil      local basefont=nil      local variants=nil      local redundant=nil -    for n in traverse_id(glyph_code,head) do -      local font=n.font +    for n in traverse_id(glyph_code,nuthead) do +      local font=getfont(n)        if font~=prevfont then          if basefont then -          basefont[2]=n.prev +          basefont[2]=getprev(n)          end          prevfont=font          local used=usedfonts[font] @@ -16519,7 +23728,7 @@ function nodes.handlers.nodepass(head)                local processors=shared.processes                if processors and #processors>0 then                  usedfonts[font]=processors -              elseif basepass then +              elseif basemodepass then                  basefont={ n,nil }                  basefonts[#basefonts+1]=basefont                end @@ -16538,15 +23747,15 @@ function nodes.handlers.nodepass(head)          end        end        if variants then -        local char=n.char +        local char=getchar(n)          if char>=0xFE00 and (char<=0xFE0F or (char>=0xE0100 and char<=0xE01EF)) then            local hash=variants[char]            if hash then -            local p=n.prev -            if p and p.id==glyph_code then -              local variant=hash[p.char] +            local p=getprev(n) +            if p and getid(p)==glyph_code then +              local variant=hash[getchar(p)]                if variant then -                p.char=variant +                setchar(p,variant)                  if not redundant then                    redundant={ n }                  else @@ -16561,15 +23770,15 @@ function nodes.handlers.nodepass(head)      if redundant then        for i=1,#redundant do          local n=redundant[i] -        remove_node(head,n) +        remove_node(nuthead,n)          free_node(n)        end      end -    for d in traverse_id(disc_code,head) do -      local r=d.replace +    for d in traverse_id(disc_code,nuthead) do +      local _,_,r=getdisc(d)        if r then          for n in traverse_id(glyph_code,r) do -          local font=n.font +          local font=getfont(n)            if font~=prevfont then              prevfont=font              local used=usedfonts[font] @@ -16596,34 +23805,31 @@ function nodes.handlers.nodepass(head)          end        end      end -    if basepass and #basefonts>0 then +    if basemodepass and #basefonts>0 then        for i=1,#basefonts do          local range=basefonts[i]          local start=range[1]          local stop=range[2] -        if start or stop then -          local prev=nil -          local next=nil -          local front=start==head +        if start then +          local front=nuthead==start +          local prev,next            if stop then -            next=stop.next +            next=getnext(stop)              start,stop=ligaturing(start,stop)              start,stop=kerning(start,stop) -          elseif start then -            prev=start.prev +          else +            prev=getprev(start)              start=ligaturing(start)              start=kerning(start)            end            if prev then -            start.prev=prev -            prev.next=start +            setlink(prev,start)            end            if next then -            stop.next=next -            next.prev=stop +            setlink(stop,next)            end -          if front then -            head=start +          if front and nuthead~=start then +            head=tonode(start)            end          end        end @@ -16634,9 +23840,9 @@ function nodes.handlers.nodepass(head)    end  end  function nodes.handlers.basepass(head) -  if basepass then -    head=ligaturing(head) -    head=kerning(head) +  if not basemodepass then +    head=n_ligaturing(head) +    head=n_kerning(head)    end    return head,true  end @@ -16648,7 +23854,9 @@ function nodes.simple_font_handler(head)    if head then      head=nodepass(head)      head=injectpass(head) -    head=basepass(head) +    if not basemodepass then +      head=basepass(head) +    end      protectpass(head)      return head,true    else  | 
