diff options
author | Philipp Gesang <phg@phi-gamma.net> | 2016-06-15 00:02:42 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-06-15 00:02:42 +0200 |
commit | 36cc5c9c567e24916f254203fc362bf124e26d02 (patch) | |
tree | 56cdd0a401ffbb99e8702f47a7865677b0971a8e | |
parent | 17fbf1d1c26047f1e0e80fc6e5f3331f6183a795 (diff) | |
parent | ba744a4bce3ed03eefbf2b4746fa24e6d388d9ff (diff) | |
download | luaotfload-36cc5c9c567e24916f254203fc362bf124e26d02.tar.gz |
Merge pull request #364 from phi-gamma/master
fixes, 3rd edition
35 files changed, 4790 insertions, 2685 deletions
diff --git a/doc/filegraph.dot b/doc/filegraph.dot index b274461..34c462a 100644 --- a/doc/filegraph.dot +++ b/doc/filegraph.dot @@ -356,7 +356,7 @@ strict digraph luaotfload_files { //looks weird with circo ... <td>font-tfm.lua</td> </tr> <tr> - <td>font-afm.lua</td> + <td>font-one.lua</td> <td>font-afk.lua</td> <td>font-oti.lua</td> </tr> @@ -385,6 +385,9 @@ strict digraph luaotfload_files { //looks weird with circo ... <td>font-xtx.lua</td> <td>font-gbn.lua</td> </tr> + <tr> + <td>font-ocl.lua</td> + </tr> </table> >, ] diff --git a/doc/luaotfload-main.tex b/doc/luaotfload-main.tex index 05c3670..e238901 100644 --- a/doc/luaotfload-main.tex +++ b/doc/luaotfload-main.tex @@ -1279,7 +1279,7 @@ grouped twofold as below: \beginaltitem{font-cid.lua} \endaltitem \beginaltitem{font-map.lua} \endaltitem \beginaltitem{font-tfm.lua} \endaltitem - \beginaltitem{font-afm.lua} \endaltitem + \beginaltitem{font-one.lua} \endaltitem \beginaltitem{font-afk.lua} \endaltitem \beginaltitem{font-oti.lua} \endaltitem \beginaltitem{font-otr.lua} \endaltitem @@ -1293,6 +1293,7 @@ grouped twofold as below: \beginaltitem{font-ota.lua} \endaltitem \beginaltitem{font-ots.lua} \endaltitem \beginaltitem{font-osd.lua} \endaltitem + \beginaltitem{font-ocl.lua} \endaltitem \beginaltitem{font-lua.lua} \endaltitem \beginaltitem{font-def.lua} \endaltitem \beginaltitem{font-xtx.lua} \endaltitem diff --git a/doc/luaotfload.conf.rst b/doc/luaotfload.conf.rst index 7b7f342..16f055b 100644 --- a/doc/luaotfload.conf.rst +++ b/doc/luaotfload.conf.rst @@ -188,10 +188,8 @@ If ``update-live`` is set, Luaotfload will reload the database if it cannot find a requested font. Those who prefer to update manually using **luaotfload-tool** should unset this flag. -The option ``use-fontforge`` switches name scanning back to the old -method that relies on the builtin ``fontloader`` library. This is -interesting for reference until the Fontforge code will be removed -eventually. +The option ``use-fontforge`` had a meaning during the transition to the +Lua-only Opentype reader. At present it is ignored. Section ``default-features`` ----------------------------------------------------------------------- diff --git a/scripts/mkimport b/scripts/mkimport index 25d7560..1dc0a29 100755 --- a/scripts/mkimport +++ b/scripts/mkimport @@ -228,7 +228,6 @@ local imports = { context = { --=> all merged { name = "data-con" , ours = "data-con" , kind = kind_merged }, { name = "font-afk" , ours = "font-afk" , kind = kind_merged }, - { name = "font-afm" , ours = "font-afm" , kind = kind_merged }, { name = "font-cff" , ours = "font-cff" , kind = kind_merged }, { name = "font-cid" , ours = "font-cid" , kind = kind_merged }, { name = "font-con" , ours = "font-con" , kind = kind_merged }, @@ -238,6 +237,9 @@ local imports = { { name = "font-ini" , ours = "font-ini" , kind = kind_merged }, { name = "font-lua" , ours = "font-lua" , kind = kind_merged }, { name = "font-map" , ours = "font-map" , kind = kind_merged }, + { name = "font-ocl" , ours = "font-ocl" , kind = kind_merged }, + { name = "font-onr" , ours = "font-onr" , kind = kind_merged }, + { name = "font-one" , ours = "font-one" , kind = kind_merged }, { name = "font-osd" , ours = "font-osd" , kind = kind_merged }, { name = "font-ota" , ours = "font-ota" , kind = kind_merged }, { name = "font-otd" , ours = "font-otd" , kind = kind_merged }, @@ -294,25 +296,27 @@ local package = { --- [20] font-map.lua --- [21] luatex-fonts-syn.lua --- [22] font-tfm.lua ---- [23] font-afm.lua ---- [24] font-afk.lua ---- [25] font-oti.lua ---- [26] font-otr.lua ---- [27] font-cff.lua ---- [28] font-ttf.lua ---- [29] font-dsp.lua ---- [30] font-oup.lua ---- [31] font-otl.lua ---- [32] font-oto.lua ---- [33] font-otj.lua ---- [34] font-ota.lua ---- [35] font-ots.lua ---- [36] font-osd.lua ---- [37] font-lua.lua ---- [38] font-def.lua ---- [39] font-xtx.lua ---- [40] luatex-fonts-ext.lua ---- [41] font-gbn.lua +--- [23] font-oti.lua +--- [24] font-otr.lua +--- [25] font-cff.lua +--- [26] font-ttf.lua +--- [27] font-dsp.lua +--- [28] font-oup.lua +--- [29] font-otl.lua +--- [30] font-oto.lua +--- [31] font-otj.lua +--- [32] font-ota.lua +--- [33] font-ots.lua +--- [34] font-osd.lua +--- [35] font-ocl.lua +--- [36] font-onr.lua +--- [37] font-one.lua +--- [38] font-afk.lua +--- [39] font-lua.lua +--- [40] font-def.lua +--- [41] font-xtx.lua +--- [42] luatex-fonts-ext.lua +--- [43] font-gbn.lua --- --- Of these, nos. 01--11 are provided by the Lualibs. Keeping them --- around in the Luaotfload fontloader is therefore unnecessary. @@ -358,8 +362,6 @@ local package = { "font-map", "fonts-syn", "font-tfm", - "font-afm", - "font-afk", "font-oti", "font-otr", "font-cff", @@ -372,6 +374,10 @@ local package = { "font-ota", "font-ots", "font-osd", + "font-ocl", + "font-onr", + "font-one", + "font-afk", "font-lua", "font-def", "fonts-ext", diff --git a/scripts/mkstatus b/scripts/mkstatus index f9ff7eb..10f6680 100755 --- a/scripts/mkstatus +++ b/scripts/mkstatus @@ -84,7 +84,8 @@ local names = { { miscdir, "fontloader-basics-nod.lua", }, { miscdir, "fontloader-data-con.lua", }, { miscdir, "fontloader-font-afk.lua", }, - { miscdir, "fontloader-font-afm.lua", }, + { miscdir, "fontloader-font-onr.lua", }, + { miscdir, "fontloader-font-one.lua", }, { miscdir, "fontloader-font-cid.lua", }, { miscdir, "fontloader-font-con.lua", }, { miscdir, "fontloader-font-def.lua", }, @@ -112,6 +113,7 @@ local names = { { miscdir, "fontloader-font-ota.lua", }, { miscdir, "fontloader-font-ots.lua", }, { miscdir, "fontloader-font-osd.lua", }, + { miscdir, "fontloader-font-ocl.lua", }, --- lua libraries { miscdir, "fontloader-languages.lua", }, diff --git a/src/fontloader/misc/fontloader-font-con.lua b/src/fontloader/misc/fontloader-font-con.lua index b118535..1a0daff 100644 --- a/src/fontloader/misc/fontloader-font-con.lua +++ b/src/fontloader/misc/fontloader-font-con.lua @@ -10,7 +10,6 @@ if not modules then modules = { } end modules ['font-con'] = { local next, tostring, rawget = next, tostring, rawget local format, match, lower, gsub = string.format, string.match, string.lower, string.gsub -local utfbyte = utf.byte local sort, insert, concat, sortedkeys, serialize, fastcopy = table.sort, table.insert, table.concat, table.sortedkeys, table.serialize, table.fastcopy local derivetable = table.derive @@ -338,6 +337,20 @@ function constructors.enhanceparameters(parameters) } end +local function mathkerns(v,vdelta) + local k = { } + for i=1,#v do + local entry = v[i] + local height = entry.height + local kern = entry.kern + k[i] = { + height = height and vdelta*height or 0, + kern = kern and vdelta*kern or 0, + } + end + return k +end + function constructors.scale(tfmdata,specification) local target = { } -- the new table -- @@ -749,22 +762,15 @@ function constructors.scale(tfmdata,specification) chr.top_accent = vdelta*va end if stackmath then - local mk = character.mathkerns -- not in math ? + local mk = character.mathkerns if mk then - local kerns = { } - local v = mk.top_right if v then local k = { } for i=1,#v do local vi = v[i] - k[i] = { height = vdelta*vi.height, kern = vdelta*vi.kern } - end kerns.top_right = k end - local v = mk.top_left if v then local k = { } for i=1,#v do local vi = v[i] - k[i] = { height = vdelta*vi.height, kern = vdelta*vi.kern } - end kerns.top_left = k end - local v = mk.bottom_left if v then local k = { } for i=1,#v do local vi = v[i] - k[i] = { height = vdelta*vi.height, kern = vdelta*vi.kern } - end kerns.bottom_left = k end - local v = mk.bottom_right if v then local k = { } for i=1,#v do local vi = v[i] - k[i] = { height = vdelta*vi.height, kern = vdelta*vi.kern } - end kerns.bottom_right = k end - chr.mathkern = kerns -- singular -> should be patched in luatex ! + local tr, tl, br, bl = mk.topright, mk.topleft, mk.bottomright, mk.bottomleft + chr.mathkern = { -- singular -> should be patched in luatex ! + top_right = tr and mathkerns(tr,vdelta) or nil, + top_left = tl and mathkerns(tl,vdelta) or nil, + bottom_right = br and mathkerns(br,vdelta) or nil, + bottom_left = bl and mathkerns(bl,vdelta) or nil, + } end end if hasitalics then @@ -1043,6 +1049,29 @@ function constructors.hashfeatures(specification) -- will be overloaded return "unknown" end +-- hashmethods.normal = function(list) +-- local s = { } +-- local n = 0 +-- for k, v in next, list do +-- if not k then +-- -- no need to add to hash +-- elseif k == "number" or k == "features" then +-- -- no need to add to hash (maybe we need a skip list) +-- else +-- n = n + 1 +-- s[n] = k +-- end +-- end +-- if n > 0 then +-- sort(s) +-- for i=1,n do +-- local k = s[i] +-- s[i] = k .. '=' .. tostring(list[k]) +-- end +-- return concat(s,"+") +-- end +-- end + hashmethods.normal = function(list) local s = { } local n = 0 @@ -1053,15 +1082,11 @@ hashmethods.normal = function(list) -- no need to add to hash (maybe we need a skip list) else n = n + 1 - s[n] = k + s[n] = k .. '=' .. tostring(v) end end if n > 0 then sort(s) - for i=1,n do - local k = s[i] - s[i] = k .. '=' .. tostring(list[k]) - end return concat(s,"+") end end @@ -1230,7 +1255,11 @@ function constructors.getfeatureaction(what,where,mode,name) end end -function constructors.newhandler(what) -- could be a metatable newindex +local newhandler = { } +constructors.handlers = newhandler -- downward compatible +constructors.newhandler = newhandler + +local function setnewhandler(what) -- could be a metatable newindex local handler = handlers[what] if not handler then handler = { } @@ -1239,7 +1268,16 @@ function constructors.newhandler(what) -- could be a metatable newindex return handler end -function constructors.newfeatures(what) -- could be a metatable newindex +setmetatable(newhandler, { + __call = function(t,k) local v = t[k] return v end, + __index = function(t,k) local v = setnewhandler(k) t[k] = v return v end, +}) + +local newfeatures = { } +constructors.newfeatures = newfeatures -- downward compatible +constructors.features = newfeatures + +local function setnewfeatures(what) local handler = handlers[what] local features = handler.features if not features then @@ -1259,6 +1297,11 @@ function constructors.newfeatures(what) -- could be a metatable newindex return features end +setmetatable(newfeatures, { + __call = function(t,k) local v = t[k] return v end, + __index = function(t,k) local v = setnewfeatures(k) t[k] = v return v end, +}) + --[[ldx-- <p>We need to check for default features. For this we provide a helper function.</p> @@ -1286,7 +1329,6 @@ function constructors.initializefeatures(what,tfmdata,features,trace,report) local properties = tfmdata.properties or { } -- brrr local whathandler = handlers[what] local whatfeatures = whathandler.features - local whatinitializers = whatfeatures.initializers local whatmodechecker = whatfeatures.modechecker -- properties.mode can be enforces (for instance in font-otd) local mode = properties.mode or (whatmodechecker and whatmodechecker(tfmdata,features,features.mode)) or features.mode or "base" diff --git a/src/fontloader/misc/fontloader-font-dsp.lua b/src/fontloader/misc/fontloader-font-dsp.lua index 330a940..a1ae17f 100644 --- a/src/fontloader/misc/fontloader-font-dsp.lua +++ b/src/fontloader/misc/fontloader-font-dsp.lua @@ -69,7 +69,6 @@ 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 -- 16-bit unsigned integer local readulong = streamreader.readcardinal4 -- 24-bit unsigned integer @@ -77,6 +76,7 @@ local readshort = streamreader.readinteger2 -- 16-bit signed integer local readfword = readshort local readstring = streamreader.readstring local readtag = streamreader.readtag +local readbytes = streamreader.readbytes local gsubhandlers = { } local gposhandlers = { } @@ -716,6 +716,8 @@ function gsubhandlers.single(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofg end end +-- we see coverage format 0x300 in some old ms fonts + local function sethandler(f,fontdata,lookupid,lookupoffset,offset,glyphs,nofglyphs,what) local tableoffset = lookupoffset + offset setposition(f,tableoffset) @@ -1599,6 +1601,14 @@ do local reported = { } + local function report_issue(i,what,sequence,kind) + local name = sequence.name + if not reported[name] then + report("rule %i in %s lookup %a has %s lookups",i,what,name,kind) + reported[name] = true + end + end + for i=lastsequence+1,nofsequences do local sequence = sequences[i] local steps = sequence.steps @@ -1610,18 +1620,10 @@ 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 + report_issue(i,what,sequence,"no") elseif not next(rlookups) then - local name = sequence.name - if not reported[name] then - -- can be ok as it aborts a chain sequence - report("rule %i in %s lookup %a has %s lookups",i,what,name,"empty") - reported[name] = true - end + -- can be ok as it aborts a chain sequence + report_issue(i,what,sequence,"empty") rule.lookups = nil else for index, lookupid in sortedhash(rlookups) do -- nicer @@ -1629,23 +1631,36 @@ do if not h then -- here we have a lookup that is used independent as well -- as in another one - nofsublookups = nofsublookups + 1 - -- report("registering %i as sublookup %i",lookupid,nofsublookups) - local d = lookups[lookupid].done - h = { - index = nofsublookups, -- handy for tracing - name = f_lookupname(lookupprefix,"d",lookupid+lookupidoffset), - derived = true, -- handy for tracing - steps = d.steps, - nofsteps = d.nofsteps, - type = d.lookuptype, - markclass = d.markclass or nil, - flags = d.flags, - -- chain = d.chain, - } - sublookuplist[nofsublookups] = h - sublookuphash[lookupid] = nofsublookups - sublookupcheck[lookupid] = 1 + local lookup = lookups[lookupid] + if lookup then + local d = lookup.done + if d then + nofsublookups = nofsublookups + 1 + -- report("registering %i as sublookup %i",lookupid,nofsublookups) + h = { + index = nofsublookups, -- handy for tracing + name = f_lookupname(lookupprefix,"d",lookupid+lookupidoffset), + derived = true, -- handy for tracing + steps = d.steps, + nofsteps = d.nofsteps, + type = d.lookuptype, + markclass = d.markclass or nil, + flags = d.flags, + -- chain = d.chain, + } + sublookuplist[nofsublookups] = h + sublookuphash[lookupid] = nofsublookups + sublookupcheck[lookupid] = 1 + else + report_issue(i,what,sequence,"missing") + rule.lookups = nil + break + end + else + report_issue(i,what,sequence,"bad") + rule.lookups = nil + break + end else sublookupcheck[lookupid] = sublookupcheck[lookupid] + 1 end @@ -2015,16 +2030,15 @@ local function readmathglyphinfo(f,fontdata,offset) local function get(offset) setposition(f,kernoffset+offset) local n = readushort(f) - if n > 0 then + if n == 0 then + local k = readmathvalue(f) + if k == 0 then + -- no need for it (happens sometimes) + else + return { { kern = k } } + end + else local l = { } - -- for i=1,n do - -- l[i] = { readushort(f), 0 } -- height, kern - -- skipshort(f) - -- end - -- for i=1,n do - -- l[i][2] = readushort(f) - -- skipshort(f) - -- end for i=1,n do l[i] = { height = readmathvalue(f) } end @@ -2059,10 +2073,10 @@ local function readmathglyphinfo(f,fontdata,offset) if next(kernset) then local glyph = glyphs[coverage[i]] local math = glyph.math - if not math then - glyph.math = { kerns = kernset } - else + if math then math.kerns = kernset + else + glyph.math = { kerns = kernset } end end end @@ -2178,7 +2192,7 @@ function readers.math(f,fontdata,specification) 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) + report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"math",fontdata.filename) return end local constants = readushort(f) @@ -2198,3 +2212,146 @@ function readers.math(f,fontdata,specification) end end end + +function readers.colr(f,fontdata,specification) + local datatable = fontdata.tables.colr + if datatable then + if specification.glyphs then + local tableoffset = datatable.offset + setposition(f,tableoffset) + local version = readushort(f) + if version ~= 0 then + report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"colr",fontdata.filename) + return + end + if not fontdata.tables.cpal then + report("color table %a in font %a has no mandate %a table","colr",fontdata.filename,"cpal") + fontdata.colorpalettes = { } + end + local glyphs = fontdata.glyphs + local nofglyphs = readushort(f) + local baseoffset = readulong(f) + local layeroffset = readulong(f) + local noflayers = readushort(f) + local layerrecords = { } + local maxclass = 0 + -- The special value 0xFFFF is foreground (but we index from 1). It + -- more looks like indices into a palette so 'class' is a better name + -- than 'palette'. + setposition(f,tableoffset + layeroffset) + for i=1,noflayers do + local slot = readushort(f) + local class = readushort(f) + if class < 0xFFFF then + class = class + 1 + if class > maxclass then + maxclass = class + end + end + layerrecords[i] = { + slot = slot, + class = class, + } + end + fontdata.maxcolorclass = maxclass + setposition(f,tableoffset + baseoffset) + for i=0,nofglyphs-1 do + local glyphindex = readushort(f) + local firstlayer = readushort(f) + local noflayers = readushort(f) + local t = { } + for i=1,noflayers do + t[i] = layerrecords[firstlayer+i] + end + glyphs[glyphindex].colors = t + end + end + fontdata.hascolor = true + end +end + +function readers.cpal(f,fontdata,specification) + if specification.glyphs then + local datatable = fontdata.tables.cpal + if datatable then + local tableoffset = datatable.offset + setposition(f,tableoffset) + local version = readushort(f) + if version > 1 then + report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"cpal",fontdata.filename) + return + end + local nofpaletteentries = readushort(f) + local nofpalettes = readushort(f) + local nofcolorrecords = readushort(f) + local firstcoloroffset = readulong(f) + local colorrecords = { } + local palettes = { } + for i=1,nofpalettes do + palettes[i] = readushort(f) + end + if version == 1 then + -- used for guis + local palettettypesoffset = readulong(f) + local palettelabelsoffset = readulong(f) + local paletteentryoffset = readulong(f) + end + setposition(f,tableoffset+firstcoloroffset) + for i=1,nofcolorrecords do + local b, g, r, a = readbytes(f,4) + colorrecords[i] = { + r, g, b, a ~= 255 and a or nil, + } + end + for i=1,nofpalettes do + local p = { } + local o = palettes[i] + for j=1,nofpaletteentries do + p[j] = colorrecords[o+j] + end + palettes[i] = p + end + fontdata.colorpalettes = palettes + end + end +end + +function readers.svg(f,fontdata,specification) + local datatable = fontdata.tables.svg + if datatable then + if specification.glyphs then + local tableoffset = datatable.offset + setposition(f,tableoffset) + local version = readushort(f) + if version ~= 0 then + report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"svg",fontdata.filename) + return + end + local glyphs = fontdata.glyphs + local indexoffset = tableoffset + readulong(f) + local reserved = readulong(f) + setposition(f,indexoffset) + local nofentries = readushort(f) + local entries = { } + for i=1,nofentries do + entries[i] = { + first = readushort(f), + last = readushort(f), + offset = indexoffset + readulong(f), + length = readulong(f), + } + end + for i=1,nofentries do + local entry = entries[i] + setposition(f,entry.offset) + entries[i] = { + first = entry.first, + last = entry.last, + data = readstring(f,entry.length) + } + end + fontdata.svgshapes = entries + end + fontdata.hascolor = true + end +end diff --git a/src/fontloader/misc/fontloader-font-gbn.lua b/src/fontloader/misc/fontloader-font-gbn.lua index daa072b..1ae817d 100644 --- a/src/fontloader/misc/fontloader-font-gbn.lua +++ b/src/fontloader/misc/fontloader-font-gbn.lua @@ -19,7 +19,6 @@ local nodes = nodes local nuts = nodes.nuts -- context abstraction of direct nodes local traverse_id = nuts.traverse_id -local remove_node = nuts.remove local free_node = nuts.free local glyph_code = nodes.nodecodes.glyph @@ -126,17 +125,19 @@ function nodes.handlers.nodepass(head) local variant = hash[getchar(p)] if variant then setchar(p,variant) - if not redundant then - redundant = { n } - else - redundant[#redundant+1] = n - end end end end + -- per generic user request we always remove selectors + if not redundant then + redundant = { n } + else + redundant[#redundant+1] = n + end end end end + local nofbasefonts = #basefonts if redundant then for i=1,#redundant do local r = redundant[i] @@ -147,8 +148,8 @@ function nodes.handlers.nodepass(head) else setlink(p,n) end - if b > 0 then - for i=1,b do + if nofbasefonts > 0 then + for i=1,nofbasefonts do local bi = basefonts[i] if r == bi[1] then bi[1] = n @@ -192,8 +193,8 @@ function nodes.handlers.nodepass(head) end end end - if basemodepass and #basefonts > 0 then - for i=1,#basefonts do + if basemodepass and nofbasefonts > 0 then + for i=1,nofbasefonts do local range = basefonts[i] local start = range[1] local stop = range[2] diff --git a/src/fontloader/misc/fontloader-font-ini.lua b/src/fontloader/misc/fontloader-font-ini.lua index c547f89..abc3194 100644 --- a/src/fontloader/misc/fontloader-font-ini.lua +++ b/src/fontloader/misc/fontloader-font-ini.lua @@ -12,8 +12,6 @@ if not modules then modules = { } end modules ['font-ini'] = { local allocate = utilities.storage.allocate -local report_defining = logs.reporter("fonts","defining") - fonts = fonts or { } local fonts = fonts diff --git a/src/fontloader/misc/fontloader-font-map.lua b/src/fontloader/misc/fontloader-font-map.lua index 509e751..6151b37 100644 --- a/src/fontloader/misc/fontloader-font-map.lua +++ b/src/fontloader/misc/fontloader-font-map.lua @@ -10,7 +10,6 @@ local tonumber, next, type = tonumber, next, type local match, format, find, concat, gsub, lower = string.match, string.format, string.find, table.concat, string.gsub, string.lower local P, R, S, C, Ct, Cc, lpegmatch = lpeg.P, lpeg.R, lpeg.S, lpeg.C, lpeg.Ct, lpeg.Cc, lpeg.match -local utfbyte = utf.byte local floor = math.floor local formatters = string.formatters @@ -45,7 +44,7 @@ of obsolete. Some code may move to runtime or auxiliary modules.</p> -- end -- end -local hex = R("AF","09") +local hex = R("AF","af","09") ----- hexfour = (hex*hex*hex*hex) / function(s) return tonumber(s,16) end ----- hexsix = (hex*hex*hex*hex*hex*hex) / function(s) return tonumber(s,16) end local hexfour = (hex*hex*hex^-2) / function(s) return tonumber(s,16) end @@ -140,7 +139,7 @@ local f_double = formatters["%04X%04X"] -- end -- end -local function tounicode16(unicode,name) +local function tounicode16(unicode) if unicode < 0xD7FF or (unicode > 0xDFFF and unicode <= 0xFFFF) then return f_single(unicode) else @@ -149,7 +148,7 @@ local function tounicode16(unicode,name) end end -local function tounicode16sequence(unicodes,name) +local function tounicode16sequence(unicodes) local t = { } for l=1,#unicodes do local u = unicodes[l] @@ -295,145 +294,145 @@ function mappings.addtounicode(data,filename,checklookups) local ns = 0 local nl = 0 -- - for unic, glyph in next, descriptions do + for du, glyph in next, descriptions do local name = glyph.name if name then - local index = glyph.index - local r = overloads[name] - if r then + local overload = overloads[name] + if overload then -- get rid of weird ligatures - -- glyph.name = r.name - glyph.unicode = r.unicode - elseif not unic or unic == -1 or unic >= private or (unic >= 0xE000 and unic <= 0xF8FF) or unic == 0xFFFE or unic == 0xFFFF then - local unicode = unicodevector[name] or contextvector[name] - if unicode then - glyph.unicode = unicode - ns = ns + 1 - end - -- cidmap heuristics, beware, there is no guarantee for a match unless - -- the chain resolves - if (not unicode) and usedmap then - local foundindex = lpegmatch(oparser,name) - if foundindex then - unicode = cidcodes[foundindex] -- name to number - if unicode then - glyph.unicode = unicode - ns = ns + 1 - else - local reference = cidnames[foundindex] -- number to name - if reference then - local foundindex = lpegmatch(oparser,reference) - if foundindex then - unicode = cidcodes[foundindex] - if unicode then - glyph.unicode = unicode - ns = ns + 1 + -- glyph.name = overload.name + glyph.unicode = overload.unicode + else + local gu = glyph.unicode -- can already be set (number or table) + if not gu or gu == -1 or du >= private or (du >= 0xE000 and du <= 0xF8FF) or du == 0xFFFE or du == 0xFFFF then + local unicode = unicodevector[name] or contextvector[name] + if unicode then + glyph.unicode = unicode + ns = ns + 1 + end + -- cidmap heuristics, beware, there is no guarantee for a match unless + -- the chain resolves + if (not unicode) and usedmap then + local foundindex = lpegmatch(oparser,name) + if foundindex then + unicode = cidcodes[foundindex] -- name to number + if unicode then + glyph.unicode = unicode + ns = ns + 1 + else + local reference = cidnames[foundindex] -- number to name + if reference then + local foundindex = lpegmatch(oparser,reference) + if foundindex then + unicode = cidcodes[foundindex] + if unicode then + glyph.unicode = unicode + ns = ns + 1 + end end - end - if not unicode or unicode == "" then - local foundcodes, multiple = lpegmatch(uparser,reference) - if foundcodes then - glyph.unicode = foundcodes - if multiple then - nl = nl + 1 - unicode = true - else - ns = ns + 1 - unicode = foundcodes + if not unicode or unicode == "" then + local foundcodes, multiple = lpegmatch(uparser,reference) + if foundcodes then + glyph.unicode = foundcodes + if multiple then + nl = nl + 1 + unicode = true + else + ns = ns + 1 + unicode = foundcodes + end end end end end end end - end - -- a.whatever or a_b_c.whatever or a_b_c (no numbers) a.b_ - -- - -- It is not trivial to find a solution that suits all fonts. We tried several alternatives - -- and this one seems to work reasonable also with fonts that use less standardized naming - -- schemes. The extra private test is tested by KE and seems to work okay with non-typical - -- fonts as well. - -- - if not unicode or unicode == "" then - local split = lpegmatch(namesplitter,name) - local nsplit = split and #split or 0 -- add if - if nsplit == 0 then - -- skip - elseif nsplit == 1 then - local base = split[1] - local u = unicodes[base] or unicodevector[base] or contextvector[name] - if not u then + -- a.whatever or a_b_c.whatever or a_b_c (no numbers) a.b_ + -- + -- It is not trivial to find a solution that suits all fonts. We tried several alternatives + -- and this one seems to work reasonable also with fonts that use less standardized naming + -- schemes. The extra private test is tested by KE and seems to work okay with non-typical + -- fonts as well. + -- + if not unicode or unicode == "" then + local split = lpegmatch(namesplitter,name) + local nsplit = split and #split or 0 -- add if + if nsplit == 0 then -- skip - elseif type(u) == "table" then - -- unlikely - if u[1] < private then - unicode = u - glyph.unicode = unicode - end - elseif u < private then - unicode = u - glyph.unicode = unicode - end - else - local t, n = { }, 0 - for l=1,nsplit do - local base = split[l] + elseif nsplit == 1 then + local base = split[1] local u = unicodes[base] or unicodevector[base] or contextvector[name] if not u then - break + -- skip elseif type(u) == "table" then - if u[1] >= private then - break + -- unlikely + if u[1] < private then + unicode = u + glyph.unicode = unicode end - n = n + 1 - t[n] = u[1] - else - if u >= private then + elseif u < private then + unicode = u + glyph.unicode = unicode + end + else + local t, n = { }, 0 + for l=1,nsplit do + local base = split[l] + local u = unicodes[base] or unicodevector[base] or contextvector[name] + if not u then break + elseif type(u) == "table" then + if u[1] >= private then + break + end + n = n + 1 + t[n] = u[1] + else + if u >= private then + break + end + n = n + 1 + t[n] = u + end + end + if n > 0 then + if n == 1 then + unicode = t[1] + else + unicode = t end - n = n + 1 - t[n] = u + glyph.unicode = unicode end end - if n > 0 then - if n == 1 then - unicode = t[1] + nl = nl + 1 + end + -- last resort (we might need to catch private here as well) + if not unicode or unicode == "" then + local foundcodes, multiple = lpegmatch(uparser,name) + if foundcodes then + glyph.unicode = foundcodes + if multiple then + nl = nl + 1 + unicode = true else - unicode = t + ns = ns + 1 + unicode = foundcodes end - glyph.unicode = unicode end end - nl = nl + 1 - end - -- last resort (we might need to catch private here as well) - if not unicode or unicode == "" then - local foundcodes, multiple = lpegmatch(uparser,name) - if foundcodes then - glyph.unicode = foundcodes - if multiple then - nl = nl + 1 - unicode = true - else - ns = ns + 1 - unicode = foundcodes - end + -- check using substitutes and alternates + local r = overloads[unicode] + if r then + unicode = r.unicode + glyph.unicode = unicode + end + -- + if not unicode then + missing[du] = true + nofmissing = nofmissing + 1 end - end - -- check using substitutes and alternates - local r = overloads[unicode] - if r then - unicode = r.unicode - glyph.unicode = unicode - end - -- - if not unicode then - missing[unic] = true - nofmissing = nofmissing + 1 end end - else - -- no name end end if type(checklookups) == "function" then diff --git a/src/fontloader/misc/fontloader-font-ocl.lua b/src/fontloader/misc/fontloader-font-ocl.lua new file mode 100644 index 0000000..9661083 --- /dev/null +++ b/src/fontloader/misc/fontloader-font-ocl.lua @@ -0,0 +1,297 @@ +if not modules then modules = { } end modules ['font-ocl'] = { + version = 1.001, + comment = "companion to font-otf.lua (context)", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} + +-- todo : user list of colors + +local formatters = string.formatters + +local otf = fonts.handlers.otf + +local f_color_start = formatters["pdf:direct: %f %f %f rg"] +local s_color_stop = "pdf:direct:" + +if context then + + local startactualtext = nil + local stopactualtext = nil + + function otf.getactualtext(n) + if not startactualtext then + startactualtext = backends.codeinjections.startunicodetoactualtext + stopactualtext = backends.codeinjections.stopunicodetoactualtext + end + return startactualtext(n), stopactualtext() + end + +else + + local tounicode = fonts.mappings.tounicode16 + + function otf.getactualtext(n) + return "/Span << /ActualText <feff" .. tounicode(n) .. "> >> BDC", "EMC" + end + +end + +local function initializecolr(tfmdata,kind,value) -- hm, always value + if value then + local palettes = tfmdata.resources.colorpalettes + if palettes then + -- + local palette = palettes[tonumber(value) or 1] or palettes[1] or { } + local classes = #palette + if classes == 0 then + return + end + -- + local characters = tfmdata.characters + local descriptions = tfmdata.descriptions + local properties = tfmdata.properties + local colorvalues = { } + -- + properties.virtualized = true + tfmdata.fonts = { + { id = 0 } + } + -- + for i=1,classes do + local p = palette[i] + colorvalues[i] = { "special", f_color_start(p[1]/255,p[2]/255,p[3]/255) } + end + -- + local getactualtext = otf.getactualtext + -- + for unicode, character in next, characters do + local description = descriptions[unicode] + if description then + local colorlist = description.colors + if colorlist then + local b, e = getactualtext(unicode) + local w = character.width or 0 + local s = #colorlist + local n = 1 + local t = { + { "special", "pdf:direct: q " .. b } + } + for i=1,s do + local entry = colorlist[i] + n = n + 1 t[n] = colorvalues[entry.class] + n = n + 1 t[n] = { "char", entry.slot } + if s > 1 and i < s and w ~= 0 then + n = n + 1 t[n] = { "right", -w } + end + end + n = n + 1 t[n] = { "special", "pdf:direct:" .. e .. " Q" } + character.commands = t + end + end + end + end + end +end + +fonts.handlers.otf.features.register { + name = "colr", + description = "color glyphs", + manipulators = { + base = initializecolr, + node = initializecolr, + } +} + +local otfsvg = otf.svg or { } +otf.svg = otfsvg +otf.svgenabled = true + +do + + local nofstreams = 0 + + -- local f_setstream = formatters[ [[io.savedata("svg-glyph-%05i",%q)]] ] + -- local f_getstream = formatters[ [[svg-glyph-%05i]] ] + + -- function otfsvg.storepdfdata(pdf) + -- nofstreams = nofstreams + 1 + -- storepdfdata = function(pdf) + -- nofstreams = nofstreams + 1 + -- return f_setstream(nofstreams,pdf), f_getstream(nofstreams) + -- end + -- end + + local f_name = formatters[ [[svg-glyph-%05i]] ] + local f_used = context and formatters[ [[original:///%s]] ] or formatters[ [[%s]] ] + + local cache = { } + + function otfsvg.storepdfdata(pdf) + nofstreams = nofstreams + 1 + local o, n = epdf.openMemStream(pdf,#pdf,f_name(nofstreams)) + cache[n] = o -- we need to keep in mem + return nil, f_used(n), nil + end + + if context then + + local storepdfdata = otfsvg.storepdfdata + local initialized = false + + function otfsvg.storepdfdata(pdf) + if not initialized then + if resolvers.setmemstream then + local f_setstream = formatters[ [[resolvers.setmemstream("svg-glyph-%05i",%q,true)]] ] + local f_getstream = formatters[ [[memstream:///svg-glyph-%05i]] ] + local f_nilstream = formatters[ [[resolvers.resetmemstream("svg-glyph-%05i",true)]] ] + storepdfdata = function(pdf) + nofstreams = nofstreams + 1 + return + f_setstream(nofstreams,pdf), + f_getstream(nofstreams), + f_nilstream(nofstreams) + end + otfsvg.storepdfdata = storepdfdata + end + initialized = true + end + return storepdfdata(pdf) + end + + end + +end + +if context and xml.convert then + + local report_svg = logs.reporter("fonts","svg conversion") + + function otfsvg.topdf(svgshapes) + local svgfile = "temp-otf-svg-shape.svg" + local pdffile = "temp-otf-svg-shape.pdf" + local command = "inkscape " .. svgfile .. " --export-pdf=" .. pdffile + -- local command = [[python "c:\Users\Hans Hagen\AppData\Roaming\Python\Scripts\cairosvg" -f pdf ]] .. svgfile .. " -o " .. pdffile + local testrun = false + local pdfshapes = { } + local nofshapes = #svgshapes + report_svg("processing %i svg containers",nofshapes) + for i=1,nofshapes do + local entry = svgshapes[i] + for j=entry.first,entry.last do + local svg = xml.convert(entry.data) + local data = xml.first(svg,"/svg[@id='glyph"..j.."']") + io.savedata(svgfile,tostring(data)) + report_svg("processing svg shape of glyph %i in container %i",j,i) + os.execute(command) + pdfshapes[j] = io.loaddata(pdffile) + end + if testrun and i > testrun then + report_svg("quiting test run") + break + end + end + os.remove(svgfile) + return pdfshapes + end + +else + + function otfsvg.topdf(svgshapes) + local svgfile = "temp-otf-svg-shape.svg" + local pdffile = "temp-otf-svg-shape.pdf" + local command = "inkscape " .. svgfile .. " --export-pdf=" .. pdffile + local pdfshapes = { } + local nofshapes = #svgshapes + texio.write(formatters["[converting %i svg glyphs to pdf using command %q : "](nofshapes,command)) + for i=1,nofshapes do + local entry = svgshapes[i] + for j=entry.first,entry.last do + -- cross our fingers .. some, day i will filter + texio.write(formatters["%i "](j)) + io.savedata(svgfile,tostring(entry.data)) + os.execute(command) + pdfshapes[j] = io.loaddata(pdffile) + end + end + os.remove(svgfile) + texio.write("done]") + return pdfshapes + end + +end + +local function initializesvg(tfmdata,kind,value) -- hm, always value + if value and otf.svgenabled then + local characters = tfmdata.characters + local descriptions = tfmdata.descriptions + local properties = tfmdata.properties + -- + local svg = properties.svg + local hash = svg and svg.hash + local timestamp = svg and svg.timestamp + if not hash then + return + end + -- + local pdffile = containers.read(otf.pdfcache,hash) + local pdfshapes = pdffile and pdffile.pdfshapes + if not pdfshapes or pdffile.timestamp ~= timestamp then + local svgfile = containers.read(otf.svgcache,hash) + local svgshapes = svgfile and svgfile.svgshapes + pdfshapes = svgshapes and otfsvg.topdf(svgshapes) or { } + containers.write(otf.pdfcache, hash, { + pdfshapes = pdfshapes, + timestamp = timestamp, + }) + end + if not pdfshapes or not next(pdfshapes) then + return + end + -- + properties.virtualized = true + tfmdata.fonts = { + { id = 0 } + } + -- + local getactualtext = otf.getactualtext + local storepdfdata = otfsvg.storepdfdata + -- + local nop = { "nop" } + -- + for unicode, character in next, characters do + local index = character.index + if index then + local pdf = pdfshapes[index] + if pdf then + local setcode, name, nilcode = storepdfdata(pdf) + if name then + local bt, et = getactualtext(unicode) + local wd = character.width or 0 + local ht = character.height or 0 + local dp = character.depth or 0 + character.commands = { + { "special", "pdf:direct:" .. bt }, + { "down", dp }, + setcode and { "lua", setcode } or nop, + { "image", { filename = name, width = wd, height = ht, depth = dp } }, + nilcode and { "lua", nilcode } or nop, + { "special", "pdf:direct:" .. et }, + } + character.svg = true + end + end + end + end + end +end + +fonts.handlers.otf.features.register { + name = "svg", + description = "svg glyphs", + manipulators = { + base = initializesvg, + node = initializesvg, + } +} diff --git a/src/fontloader/misc/fontloader-font-afm.lua b/src/fontloader/misc/fontloader-font-one.lua index 0d6b7cb..a6f47e8 100644 --- a/src/fontloader/misc/fontloader-font-afm.lua +++ b/src/fontloader/misc/fontloader-font-one.lua @@ -1,4 +1,4 @@ -if not modules then modules = { } end modules ['font-afm'] = { +if not modules then modules = { } end modules ['font-one'] = { version = 1.001, comment = "companion to font-ini.mkiv", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", @@ -7,33 +7,27 @@ if not modules then modules = { } end modules ['font-afm'] = { } --[[ldx-- -<p>Some code may look a bit obscure but this has to do with the -fact that we also use this code for testing and much code evolved -in the transition from <l n='tfm'/> to <l n='afm'/> to <l -n='otf'/>.</p> +<p>Some code may look a bit obscure but this has to do with the fact that we also use +this code for testing and much code evolved in the transition from <l n='tfm'/> to +<l n='afm'/> to <l n='otf'/>.</p> -<p>The following code still has traces of intermediate font support -where we handles font encodings. Eventually font encoding goes -away.</p> - -<p>The embedding of a font involves creating temporary files and -depending on your system setup that can fail. It took more than a -day to figure out why sometimes embedding failed in mingw luatex -where running on a real path like c:\... failed while running on -say e:\... being a link worked well. The native windows binaries -don't have this issue.</p> +<p>The following code still has traces of intermediate font support where we handles +font encodings. Eventually font encoding went away but we kept some code around in +other modules.</p> +<p>This version implements a node mode approach so that users can also more easily +add features.</p> --ldx]]-- local fonts, logs, trackers, containers, resolvers = fonts, logs, trackers, containers, resolvers -local next, type, tonumber = next, type, tonumber +local next, type, tonumber, rawget = next, type, tonumber, rawget local match, gmatch, lower, gsub, strip, find = string.match, string.gmatch, string.lower, string.gsub, string.strip, string.find local char, byte, sub = string.char, string.byte, string.sub local abs = math.abs local bxor, rshift = bit32.bxor, bit32.rshift -local P, S, R, Cmt, C, Ct, Cs, lpegmatch, patterns = lpeg.P, lpeg.S, lpeg.R, lpeg.Cmt, lpeg.C, lpeg.Ct, lpeg.Cs, lpeg.match, lpeg.patterns -local derivetable = table.derive +local P, S, R, Cmt, C, Ct, Cs, Carg = lpeg.P, lpeg.S, lpeg.R, lpeg.Cmt, lpeg.C, lpeg.Ct, lpeg.Cs, lpeg.Carg +local lpegmatch, patterns = lpeg.match, lpeg.patterns local trace_features = false trackers.register("afm.features", function(v) trace_features = v end) local trace_indexing = false trackers.register("afm.indexing", function(v) trace_indexing = v end) @@ -43,6 +37,7 @@ local trace_defining = false trackers.register("fonts.defining", function(v local report_afm = logs.reporter("fonts","afm loading") local setmetatableindex = table.setmetatableindex +local derivetable = table.derive local findbinfile = resolvers.findbinfile @@ -50,434 +45,67 @@ local definers = fonts.definers local readers = fonts.readers local constructors = fonts.constructors -local afm = constructors.newhandler("afm") -local pfb = constructors.newhandler("pfb") +local afm = constructors.handlers.afm +local pfb = constructors.handlers.pfb +local otf = fonts.handlers.otf + +local otfreaders = otf.readers +local otfenhancers = otf.enhancers -local afmfeatures = constructors.newfeatures("afm") +local afmfeatures = constructors.features.afm local registerafmfeature = afmfeatures.register -afm.version = 1.501 -- incrementing this number one up will force a re-cache +afm.version = 1.512 -- incrementing this number one up will force a re-cache afm.cache = containers.define("fonts", "afm", afm.version, true) afm.autoprefixed = true -- this will become false some day (catches texnansi-blabla.*) afm.helpdata = { } -- set later on so no local for this afm.syncspace = true -- when true, nicer stretch values -afm.addligatures = true -- best leave this set to true -afm.addtexligatures = true -- best leave this set to true -afm.addkerns = true -- best leave this set to true local overloads = fonts.mappings.overloads local applyruntimefixes = fonts.treatments and fonts.treatments.applyfixes -local function setmode(tfmdata,value) - if value then - tfmdata.properties.mode = lower(value) - end -end - -registerafmfeature { - name = "mode", - description = "mode", - initializers = { - base = setmode, - node = setmode, - } -} - --[[ldx-- -<p>We start with the basic reader which we give a name similar to the -built in <l n='tfm'/> and <l n='otf'/> reader.</p> ---ldx]]-- - ---~ Comment FONTIDENTIFIER LMMATHSYMBOLS10 ---~ Comment CODINGSCHEME TEX MATH SYMBOLS ---~ Comment DESIGNSIZE 10.0 pt ---~ Comment CHECKSUM O 4261307036 ---~ Comment SPACE 0 plus 0 minus 0 ---~ Comment QUAD 1000 ---~ Comment EXTRASPACE 0 ---~ Comment NUM 676.508 393.732 443.731 ---~ Comment DENOM 685.951 344.841 ---~ Comment SUP 412.892 362.892 288.889 ---~ Comment SUB 150 247.217 ---~ Comment SUPDROP 386.108 ---~ Comment SUBDROP 50 ---~ Comment DELIM 2390 1010 ---~ Comment AXISHEIGHT 250 - -local comment = P("Comment") -local spacing = patterns.spacer -- S(" \t")^1 -local lineend = patterns.newline -- S("\n\r") -local words = C((1 - lineend)^1) -local number = C((R("09") + S("."))^1) / tonumber * spacing^0 -local data = lpeg.Carg(1) - -local pattern = ( -- needs testing ... not used anyway as we no longer need math afm's - comment * spacing * - ( - data * ( - ("CODINGSCHEME" * spacing * words ) / function(fd,a) end + - ("DESIGNSIZE" * spacing * number * words ) / function(fd,a) fd[ 1] = a end + - ("CHECKSUM" * spacing * number * words ) / function(fd,a) fd[ 2] = a end + - ("SPACE" * spacing * number * "plus" * number * "minus" * number) / function(fd,a,b,c) fd[ 3], fd[ 4], fd[ 5] = a, b, c end + - ("QUAD" * spacing * number ) / function(fd,a) fd[ 6] = a end + - ("EXTRASPACE" * spacing * number ) / function(fd,a) fd[ 7] = a end + - ("NUM" * spacing * number * number * number ) / function(fd,a,b,c) fd[ 8], fd[ 9], fd[10] = a, b, c end + - ("DENOM" * spacing * number * number ) / function(fd,a,b ) fd[11], fd[12] = a, b end + - ("SUP" * spacing * number * number * number ) / function(fd,a,b,c) fd[13], fd[14], fd[15] = a, b, c end + - ("SUB" * spacing * number * number ) / function(fd,a,b) fd[16], fd[17] = a, b end + - ("SUPDROP" * spacing * number ) / function(fd,a) fd[18] = a end + - ("SUBDROP" * spacing * number ) / function(fd,a) fd[19] = a end + - ("DELIM" * spacing * number * number ) / function(fd,a,b) fd[20], fd[21] = a, b end + - ("AXISHEIGHT" * spacing * number ) / function(fd,a) fd[22] = a end - ) - + (1-lineend)^0 - ) - + (1-comment)^1 -)^0 - -local function scan_comment(str) - local fd = { } - lpegmatch(pattern,str,1,fd) - return fd -end - --- On a rainy day I will rewrite this in lpeg ... or we can use the (slower) fontloader --- as in now supports afm/pfb loading but it's not too bad to have different methods --- for testing approaches. - -local keys = { } - -function keys.FontName (data,line) data.metadata.fontname = strip (line) -- get rid of spaces - data.metadata.fullname = strip (line) end -function keys.ItalicAngle (data,line) data.metadata.italicangle = tonumber (line) end -function keys.IsFixedPitch(data,line) data.metadata.monospaced = toboolean(line,true) end -function keys.CharWidth (data,line) data.metadata.charwidth = tonumber (line) end -function keys.XHeight (data,line) data.metadata.xheight = tonumber (line) end -function keys.Descender (data,line) data.metadata.descender = tonumber (line) end -function keys.Ascender (data,line) data.metadata.ascender = tonumber (line) end -function keys.Comment (data,line) - -- Comment DesignSize 12 (pts) - -- Comment TFM designsize: 12 (in points) - line = lower(line) - local designsize = match(line,"designsize[^%d]*(%d+)") - if designsize then data.metadata.designsize = tonumber(designsize) end -end - -local function get_charmetrics(data,charmetrics,vector) - local characters = data.characters - local chr, ind = { }, 0 - for k, v in gmatch(charmetrics,"([%a]+) +(.-) *;") do - if k == 'C' then - v = tonumber(v) - if v < 0 then - ind = ind + 1 -- ? - else - ind = v - end - chr = { - index = ind - } - elseif k == 'WX' then - chr.width = tonumber(v) - elseif k == 'N' then - characters[v] = chr - elseif k == 'B' then - local llx, lly, urx, ury = match(v,"^ *(.-) +(.-) +(.-) +(.-)$") - chr.boundingbox = { tonumber(llx), tonumber(lly), tonumber(urx), tonumber(ury) } - elseif k == 'L' then - local plus, becomes = match(v,"^(.-) +(.-)$") - local ligatures = chr.ligatures - if ligatures then - ligatures[plus] = becomes - else - chr.ligatures = { [plus] = becomes } - end - end - end -end - -local function get_kernpairs(data,kernpairs) - local characters = data.characters - for one, two, value in gmatch(kernpairs,"KPX +(.-) +(.-) +(.-)\n") do - local chr = characters[one] - if chr then - local kerns = chr.kerns - if kerns then - kerns[two] = tonumber(value) - else - chr.kerns = { [two] = tonumber(value) } - end - end - end -end - -local function get_variables(data,fontmetrics) - for key, rest in gmatch(fontmetrics,"(%a+) *(.-)[\n\r]") do - local keyhandler = keys[key] - if keyhandler then - keyhandler(data,rest) - end - end -end - -local get_indexes - -do - - -- old font loader - - local fontloader = fontloader - local get_indexes_old = false - - if fontloader then - - local font_to_table = fontloader.to_table - local open_font = fontloader.open - local close_font = fontloader.close - - get_indexes_old = function(data,pfbname) - local pfbblob = open_font(pfbname) - if pfbblob then - local characters = data.characters - local pfbdata = font_to_table(pfbblob) - if pfbdata then - local glyphs = pfbdata.glyphs - if glyphs then - if trace_loading then - report_afm("getting index data from %a",pfbname) - end - for index, glyph in next, glyphs do - local name = glyph.name - if name then - local char = characters[name] - if char then - if trace_indexing then - report_afm("glyph %a has index %a",name,index) - end - char.index = index - end - end - end - elseif trace_loading then - report_afm("no glyph data in pfb file %a",pfbname) - end - elseif trace_loading then - report_afm("no data in pfb file %a",pfbname) - end - close_font(pfbblob) - elseif trace_loading then - report_afm("invalid pfb file %a",pfbname) - end - end - - end - - -- new (unfinished) font loader but i see no differences between - -- old and new (one bad vector with old) - - local n, m - - local progress = function(str,position,name,size) - local forward = position + tonumber(size) + 3 + 2 - n = n + 1 - if n >= m then - return #str, name - elseif forward < #str then - return forward, name - else - return #str, name - end - end - - local initialize = function(str,position,size) - n = 0 - m = tonumber(size) - return position + 1 - end - - local charstrings = P("/CharStrings") - local name = P("/") * C((R("az")+R("AZ")+R("09")+S("-_."))^1) - local size = C(R("09")^1) - local spaces = P(" ")^1 - - local p_filternames = Ct ( - (1-charstrings)^0 * charstrings * spaces * Cmt(size,initialize) - * (Cmt(name * P(" ")^1 * C(R("09")^1), progress) + P(1))^1 - ) - - -- if one of first 4 not 0-9A-F then binary else hex - - local decrypt - - do - - local r, c1, c2, n = 0, 0, 0, 0 - - local function step(c) - local cipher = byte(c) - local plain = bxor(cipher,rshift(r,8)) - r = ((cipher + r) * c1 + c2) % 65536 - return char(plain) - end - - decrypt = function(binary) - r, c1, c2, n = 55665, 52845, 22719, 4 - binary = gsub(binary,".",step) - return sub(binary,n+1) - end - - -- local pattern = Cs((P(1) / step)^1) - -- - -- decrypt = function(binary) - -- r, c1, c2, n = 55665, 52845, 22719, 4 - -- binary = lpegmatch(pattern,binary) - -- return sub(binary,n+1) - -- end - - end - - local function loadpfbvector(filename) - -- for the moment limited to encoding only - - local data = io.loaddata(resolvers.findfile(filename)) - - if not find(data,"!PS%-AdobeFont%-") then - print("no font",filename) - return - end - - if not data then - print("no data",filename) - return - end - - local ascii, binary = match(data,"(.*)eexec%s+......(.*)") - - if not binary then - print("no binary",filename) - return - end - - binary = decrypt(binary,4) - - local vector = lpegmatch(p_filternames,binary) - - vector[0] = table.remove(vector,1) - - if not vector then - print("no vector",filename) - return - end - - return vector - - end - - get_indexes = function(data,pfbname) - local vector = loadpfbvector(pfbname) - if vector then - local characters = data.characters - if trace_loading then - report_afm("getting index data from %a",pfbname) - end - for index=1,#vector do - local name = vector[index] - local char = characters[name] - if char then - if trace_indexing then - report_afm("glyph %a has index %a",name,index) - end - char.index = index - end - end - end - end +<p>We cache files. Caching is taken care of in the loader. We cheat a bit by adding +ligatures and kern information to the afm derived data. That way we can set them faster +when defining a font.</p> - if get_indexes_old then - - afm.use_new_indexer = true - get_indexes_new = get_indexes - - get_indexes = function(data,pfbname) - if afm.use_new_indexer then - return get_indexes_new(data,pfbname) - else - return get_indexes_old(data,pfbname) - end - end +<p>We still keep the loading two phased: first we load the data in a traditional +fashion and later we transform it to sequences. Then we apply some methods also +used in opentype fonts (like <t>tlig</t>).</p> +--ldx]]-- - end +local enhancers = { + -- It's cleaner to implement them after we've seen what we are + -- dealing with. +} -end +local steps = { + "unify names", + "add ligatures", + "add extra kerns", + "normalize features", + "fix names", +-- "add tounicode data", +} -local function readafm(filename) - local ok, afmblob, size = resolvers.loadbinfile(filename) -- has logging - if ok and afmblob then - local data = { - resources = { - filename = resolvers.unresolve(filename), - version = afm.version, - creator = "context mkiv", - }, - properties = { - hasitalics = false, - }, - goodies = { - }, - metadata = { - filename = file.removesuffix(file.basename(filename)) - }, - characters = { - -- a temporary store - }, - descriptions = { - -- the final store - }, - } - afmblob = gsub(afmblob,"StartCharMetrics(.-)EndCharMetrics", function(charmetrics) +local function applyenhancers(data,filename) + for i=1,#steps do + local step = steps[i] + local enhancer = enhancers[step] + if enhancer then if trace_loading then - report_afm("loading char metrics") + report_afm("applying enhancer %a",step) end - get_charmetrics(data,charmetrics,vector) - return "" - end) - afmblob = gsub(afmblob,"StartKernPairs(.-)EndKernPairs", function(kernpairs) - if trace_loading then - report_afm("loading kern pairs") - end - get_kernpairs(data,kernpairs) - return "" - end) - afmblob = gsub(afmblob,"StartFontMetrics%s+([%d%.]+)(.-)EndFontMetrics", function(version,fontmetrics) - if trace_loading then - report_afm("loading variables") - end - data.afmversion = version - get_variables(data,fontmetrics) - data.fontdimens = scan_comment(fontmetrics) -- todo: all lpeg, no time now - return "" - end) - return data - else - if trace_loading then - report_afm("no valid afm file %a",filename) + enhancer(data,filename) + else + report_afm("invalid enhancer %a",step) end - return nil end end ---[[ldx-- -<p>We cache files. Caching is taken care of in the loader. We cheat a bit -by adding ligatures and kern information to the afm derived data. That -way we can set them faster when defining a font.</p> ---ldx]]-- - -local addkerns, addligatures, addtexligatures, unify, normalize, fixnames -- we will implement these later - function afm.load(filename) - -- hm, for some reasons not resolved yet filename = resolvers.findfile(filename,'afm') or "" if filename ~= "" and not fonts.names.ignoredfile(filename) then local name = file.removesuffix(file.basename(filename)) @@ -498,55 +126,42 @@ function afm.load(filename) end if not data or data.size ~= size or data.time ~= time or data.pfbsize ~= pfbsize or data.pfbtime ~= pfbtime then report_afm("reading %a",filename) - data = readafm(filename) + data = afm.readers.loadfont(filename,pfbname) if data then - if pfbname ~= "" then - data.resources.filename = resolvers.unresolve(pfbname) - get_indexes(data,pfbname) - elseif trace_loading then - report_afm("no pfb file for %a",filename) - -- data.resources.filename = "unset" -- better than loading the afm file - end - report_afm("unifying %a",filename) - unify(data,filename) - if afm.addligatures then - report_afm("add ligatures") - addligatures(data) - end - if afm.addtexligatures then - report_afm("add tex ligatures") - addtexligatures(data) - end - if afm.addkerns then - report_afm("add extra kerns") - addkerns(data) - end - normalize(data) - fixnames(data) - report_afm("add tounicode data") + applyenhancers(data,filename) + -- otfreaders.addunicodetable(data) -- only when not done yet fonts.mappings.addtounicode(data,filename) + -- otfreaders.extend(data) + otfreaders.pack(data) data.size = size data.time = time data.pfbsize = pfbsize data.pfbtime = pfbtime report_afm("saving %a in cache",name) - data.resources.unicodes = nil -- consistent with otf but here we save not much + -- data.resources.unicodes = nil -- consistent with otf but here we save not much data = containers.write(afm.cache, name, data) data = containers.read(afm.cache,name) end - if applyruntimefixes and data then + end + if data then + -- constructors.addcoreunicodes(unicodes) + otfreaders.unpack(data) + otfreaders.expand(data) -- inline tables + otfreaders.addunicodetable(data) -- only when not done yet + otfenhancers.apply(data,filename,data) + if applyruntimefixes then applyruntimefixes(filename,data) end end return data - else - return nil end end -local uparser = fonts.mappings.makenameparser() +-- we run a more advanced analyzer later on anyway + +local uparser = fonts.mappings.makenameparser() -- each time -unify = function(data, filename) +enhancers["unify names"] = function(data, filename) local unicodevector = fonts.encodings.agl.unicodes -- loaded runtime in context local unicodes = { } local names = { } @@ -556,7 +171,7 @@ unify = function(data, filename) local code = unicodevector[name] -- or characters.name_to_unicode[name] if not code then code = lpegmatch(uparser,name) - if not code then + if type(code) ~= "number" then code = private private = private + 1 report_afm("assigning private slot %U for unknown glyph name %a",code,name) @@ -602,15 +217,108 @@ end local everywhere = { ["*"] = { ["*"] = true } } -- or: { ["*"] = { "*" } } local noflags = { false, false, false, false } -afm.experimental_normalize = false - -normalize = function(data) - if type(afm.experimental_normalize) == "function" then - afm.experimental_normalize(data) +enhancers["normalize features"] = function(data) + local ligatures = setmetatableindex("table") + local kerns = setmetatableindex("table") + local extrakerns = setmetatableindex("table") + for u, c in next, data.descriptions do + local l = c.ligatures + local k = c.kerns + local e = c.extrakerns + if l then + ligatures[u] = l + for u, v in next, l do + l[u] = { ligature = v } + end + c.ligatures = nil + end + if k then + kerns[u] = k + for u, v in next, k do + k[u] = v -- { v, 0 } + end + c.kerns = nil + end + if e then + extrakerns[u] = e + for u, v in next, e do + e[u] = v -- { v, 0 } + end + c.extrakerns = nil + end end + local features = { + gpos = { }, + gsub = { }, + } + local sequences = { + -- only filled ones + } + if next(ligatures) then + features.gsub.liga = everywhere + data.properties.hasligatures = true + sequences[#sequences+1] = { + features = { + liga = everywhere, + }, + flags = noflags, + name = "s_s_0", + nofsteps = 1, + order = { "liga" }, + type = "gsub_ligature", + steps = { + { + coverage = ligatures, + }, + }, + } + end + if next(kerns) then + features.gpos.kern = everywhere + data.properties.haskerns = true + sequences[#sequences+1] = { + features = { + kern = everywhere, + }, + flags = noflags, + name = "p_s_0", + nofsteps = 1, + order = { "kern" }, + type = "gpos_pair", + steps = { + { + format = "kern", + coverage = kerns, + }, + }, + } + end + if next(extrakerns) then + features.gpos.extrakerns = everywhere + data.properties.haskerns = true + sequences[#sequences+1] = { + features = { + extrakerns = everywhere, + }, + flags = noflags, + name = "p_s_1", + nofsteps = 1, + order = { "extrakerns" }, + type = "gpos_pair", + steps = { + { + format = "kern", + coverage = extrakerns, + }, + }, + } + end + -- todo: compress kerns + data.resources.features = features + data.resources.sequences = sequences end -fixnames = function(data) +enhancers["fix names"] = function(data) for k, v in next, data.descriptions do local n = v.name local r = overloads[n] @@ -657,8 +365,13 @@ local addthem = function(rawdata,ligatures) end end -addligatures = function(rawdata) addthem(rawdata,afm.helpdata.ligatures ) end -addtexligatures = function(rawdata) addthem(rawdata,afm.helpdata.texligatures) end +enhancers["add ligatures"] = function(rawdata) + addthem(rawdata,afm.helpdata.ligatures) +end + +-- enhancers["add tex ligatures"] = function(rawdata) +-- addthem(rawdata,afm.helpdata.texligatures) +-- end --[[ldx-- <p>We keep the extra kerns in separate kerning tables so that we can use @@ -672,7 +385,7 @@ them selectively.</p> -- we don't use the character database. (Ok, we can have a context specific -- variant). -addkerns = function(rawdata) -- using shcodes is not robust here +enhancers["add extra kerns"] = function(rawdata) -- using shcodes is not robust here local descriptions = rawdata.descriptions local resources = rawdata.resources local unicodes = resources.unicodes @@ -864,11 +577,31 @@ local function copytotfm(data) end -- end - local fd = data.fontdimens - if fd and fd[8] and fd[9] and fd[10] then -- math - for k,v in next, fd do - parameters[k] = v - end + -- + if metadata.sup then + local dummy = { 0, 0, 0 } + parameters[ 1] = metadata.designsize or 0 + parameters[ 2] = metadata.checksum or 0 + parameters[ 3], + parameters[ 4], + parameters[ 5] = unpack(metadata.space or dummy) + parameters[ 6] = metadata.quad or 0 + parameters[ 7] = metadata.extraspace or 0 + parameters[ 8], + parameters[ 9], + parameters[10] = unpack(metadata.num or dummy) + parameters[11], + parameters[12] = unpack(metadata.denom or dummy) + parameters[13], + parameters[14], + parameters[15] = unpack(metadata.sup or dummy) + parameters[16], + parameters[17] = unpack(metadata.sub or dummy) + parameters[18] = metadata.supdrop or 0 + parameters[19] = metadata.subdrop or 0 + parameters[20], + parameters[21] = unpack(metadata.delim or dummy) + parameters[22] = metadata.axisheight or 0 end -- parameters.designsize = (metadata.designsize or 10)*65536 @@ -900,10 +633,9 @@ local function copytotfm(data) end --[[ldx-- -<p>Originally we had features kind of hard coded for <l n='afm'/> -files but since I expect to support more font formats, I decided -to treat this fontformat like any other and handle features in a -more configurable way.</p> +<p>Originally we had features kind of hard coded for <l n='afm'/> files but since I +expect to support more font formats, I decided to treat this fontformat like any +other and handle features in a more configurable way.</p> --ldx]]-- function afm.setfeatures(tfmdata,features) @@ -982,6 +714,8 @@ local function afmtotfm(specification) tfmdata.shared = shared end shared.rawdata = rawdata + shared.dynamics = { } + tfmdata.changed = { } shared.features = features shared.processes = afm.setfeatures(tfmdata,features) end @@ -1015,127 +749,34 @@ local function read_from_afm(specification) end --[[ldx-- -<p>Here comes the implementation of a few features. We only implement -those that make sense for this format.</p> +<p>We have the usual two modes and related features initializers and processors.</p> --ldx]]-- -local function prepareligatures(tfmdata,ligatures,value) - if value then - local descriptions = tfmdata.descriptions - local hasligatures = false - for unicode, character in next, tfmdata.characters do - local description = descriptions[unicode] - local dligatures = description.ligatures - if dligatures then - local cligatures = character.ligatures - if not cligatures then - cligatures = { } - character.ligatures = cligatures - end - for unicode, ligature in next, dligatures do - cligatures[unicode] = { - char = ligature, - type = 0 - } - end - hasligatures = true - end - end - tfmdata.properties.hasligatures = hasligatures - end -end - -local function preparekerns(tfmdata,kerns,value) +local function setmode(tfmdata,value) if value then - local rawdata = tfmdata.shared.rawdata - local resources = rawdata.resources - local unicodes = resources.unicodes - local descriptions = tfmdata.descriptions - local haskerns = false - for u, chr in next, tfmdata.characters do - local d = descriptions[u] - local newkerns = d[kerns] - if newkerns then - local kerns = chr.kerns - if not kerns then - kerns = { } - chr.kerns = kerns - end - for k,v in next, newkerns do - local uk = unicodes[k] - if uk then - kerns[uk] = v - end - end - haskerns = true - end - end - tfmdata.properties.haskerns = haskerns - end -end - -local list = { - -- [0x0022] = 0x201D, - [0x0027] = 0x2019, - -- [0x0060] = 0x2018, -} - -local function texreplacements(tfmdata,value) - local descriptions = tfmdata.descriptions - local characters = tfmdata.characters - for k, v in next, list do - characters [k] = characters [v] -- we forget about kerns - descriptions[k] = descriptions[v] -- we forget about kerns + tfmdata.properties.mode = lower(value) end end -local function ligatures (tfmdata,value) prepareligatures(tfmdata,'ligatures', value) end -local function texligatures(tfmdata,value) prepareligatures(tfmdata,'texligatures',value) end -local function kerns (tfmdata,value) preparekerns (tfmdata,'kerns', value) end -local function extrakerns (tfmdata,value) preparekerns (tfmdata,'extrakerns', value) end - -registerafmfeature { - name = "liga", - description = "traditional ligatures", - initializers = { - base = ligatures, - node = ligatures, - } -} - -registerafmfeature { - name = "kern", - description = "intercharacter kerning", - initializers = { - base = kerns, - node = kerns, - } -} - -registerafmfeature { - name = "extrakerns", - description = "additional intercharacter kerning", - initializers = { - base = extrakerns, - node = extrakerns, - } -} - registerafmfeature { - name = 'tlig', - description = 'tex ligatures', + name = "mode", + description = "mode", initializers = { - base = texligatures, - node = texligatures, + base = setmode, + node = setmode, } } registerafmfeature { - name = 'trep', - description = 'tex replacements', + name = "features", + description = "features", + default = true, initializers = { - base = texreplacements, - node = texreplacements, + node = otf.nodemodeinitializer, + base = otf.basemodeinitializer, + }, + processors = { + node = otf.featuresprocessor, } } @@ -1171,7 +812,8 @@ local function check_afm(specification,fullname) end function readers.afm(specification,method) - local fullname, tfmdata = specification.filename or "", nil + local fullname = specification.filename or "" + local tfmdata = nil if fullname == "" then local forced = specification.forced or "" if forced ~= "" then @@ -1200,7 +842,16 @@ function readers.pfb(specification,method) -- only called when forced if trace_defining then report_afm("using afm reader for %a",original) end - specification.specification = gsub(original,"%.pfb",".afm") specification.forced = "afm" + local function swap(name) + local value = specification[swap] + if value then + specification[swap] = gsub("%.pfb",".afm",1) + end + end + swap("filename") + swap("fullname") + swap("forcedname") + swap("specification") return readers.afm(specification,method) end diff --git a/src/fontloader/misc/fontloader-font-onr.lua b/src/fontloader/misc/fontloader-font-onr.lua new file mode 100644 index 0000000..a4969ad --- /dev/null +++ b/src/fontloader/misc/fontloader-font-onr.lua @@ -0,0 +1,411 @@ +if not modules then modules = { } end modules ['font-onr'] = { + 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" +} + +--[[ldx-- +<p>Some code may look a bit obscure but this has to do with the fact that we also use +this code for testing and much code evolved in the transition from <l n='tfm'/> to +<l n='afm'/> to <l n='otf'/>.</p> + +<p>The following code still has traces of intermediate font support where we handles +font encodings. Eventually font encoding went away but we kept some code around in +other modules.</p> + +<p>This version implements a node mode approach so that users can also more easily +add features.</p> +--ldx]]-- + +local fonts, logs, trackers, resolvers = fonts, logs, trackers, resolvers + +local next, type, tonumber, rawget = next, type, tonumber, rawget +local match, lower, gsub, strip, find = string.match, string.lower, string.gsub, string.strip, string.find +local char, byte, sub = string.char, string.byte, string.sub +local abs = math.abs +local bxor, rshift = bit32.bxor, bit32.rshift +local P, S, R, Cmt, C, Ct, Cs, Carg = lpeg.P, lpeg.S, lpeg.R, lpeg.Cmt, lpeg.C, lpeg.Ct, lpeg.Cs, lpeg.Carg +local lpegmatch, patterns = lpeg.match, lpeg.patterns + +local trace_indexing = false trackers.register("afm.indexing", function(v) trace_indexing = v end) +local trace_loading = false trackers.register("afm.loading", function(v) trace_loading = v end) + +local report_afm = logs.reporter("fonts","afm loading") +local report_afm = logs.reporter("fonts","pfb loading") + +fonts = fonts or { } +local handlers = fonts.handlers or { } +fonts.handlers = handlers +local afm = handlers.afm or { } +handlers.afm = afm +local readers = afm.readers or { } +afm.readers = readers + +afm.version = 1.512 -- incrementing this number one up will force a re-cache + +--[[ldx-- +<p>We start with the basic reader which we give a name similar to the built in <l n='tfm'/> +and <l n='otf'/> reader.</p> +<p>We use a new (unfinished) pfb loader but I see no differences between the old +and new vectors (we actually had one bad vector with the old loader).</p> +--ldx]]-- + +local get_indexes + +do + + local n, m + + local progress = function(str,position,name,size) + local forward = position + tonumber(size) + 3 + 2 + n = n + 1 + if n >= m then + return #str, name + elseif forward < #str then + return forward, name + else + return #str, name + end + end + + local initialize = function(str,position,size) + n = 0 + m = tonumber(size) + return position + 1 + end + + local charstrings = P("/CharStrings") + local name = P("/") * C((R("az")+R("AZ")+R("09")+S("-_."))^1) + local size = C(R("09")^1) + local spaces = P(" ")^1 + + local p_filternames = Ct ( + (1-charstrings)^0 * charstrings * spaces * Cmt(size,initialize) + * (Cmt(name * P(" ")^1 * C(R("09")^1), progress) + P(1))^1 + ) + + -- if one of first 4 not 0-9A-F then binary else hex + + local decrypt + + do + + local r, c1, c2, n = 0, 0, 0, 0 + + local function step(c) + local cipher = byte(c) + local plain = bxor(cipher,rshift(r,8)) + r = ((cipher + r) * c1 + c2) % 65536 + return char(plain) + end + + decrypt = function(binary) + r, c1, c2, n = 55665, 52845, 22719, 4 + binary = gsub(binary,".",step) + return sub(binary,n+1) + end + + -- local pattern = Cs((P(1) / step)^1) + -- + -- decrypt = function(binary) + -- r, c1, c2, n = 55665, 52845, 22719, 4 + -- binary = lpegmatch(pattern,binary) + -- return sub(binary,n+1) + -- end + + end + + local function loadpfbvector(filename) + -- for the moment limited to encoding only + + local data = io.loaddata(resolvers.findfile(filename)) + + if not data then + report_pfb("no data in %a",filename) + return + end + + if not (find(data,"!PS%-AdobeFont%-") or find(data,"%%!FontType1")) then + report_pfb("no font in %a",filename) + return + end + + local ascii, binary = match(data,"(.*)eexec%s+......(.*)") + + if not binary then + report_pfb("no binary data in %a",filename) + return + end + + binary = decrypt(binary,4) + + local vector = lpegmatch(p_filternames,binary) + + if vector[1] == ".notdef" then + -- tricky + vector[0] = table.remove(vector,1) + end + + if not vector then + report_pfb("no vector in %a",filename) + return + end + + return vector + + end + + get_indexes = function(data,pfbname) + local vector = loadpfbvector(pfbname) + if vector then + local characters = data.characters + if trace_loading then + report_afm("getting index data from %a",pfbname) + end + for index=1,#vector do + local name = vector[index] + local char = characters[name] + if char then + if trace_indexing then + report_afm("glyph %a has index %a",name,index) + end + char.index = index + end + end + end + end + +end + +--[[ldx-- +<p>We start with the basic reader which we give a name similar to the built in <l n='tfm'/> +and <l n='otf'/> reader. We only need data that is relevant for our use. We don't support +more complex arrangements like multiple master (obsolete), direction specific kerning, etc.</p> +--ldx]]-- + +local spacer = patterns.spacer +local whitespace = patterns.whitespace +local lineend = patterns.newline +local spacing = spacer^0 +local number = spacing * S("+-")^-1 * (R("09") + S("."))^1 / tonumber +local name = spacing * C((1 - whitespace)^1) +local words = spacing * ((1 - lineend)^1 / strip) +local rest = (1 - lineend)^0 +local fontdata = Carg(1) +local semicolon = spacing * P(";") +local plus = spacing * P("plus") * number +local minus = spacing * P("minus") * number + +-- kern pairs + +local function addkernpair(data,one,two,value) + local chr = data.characters[one] + if chr then + local kerns = chr.kerns + if kerns then + kerns[two] = tonumber(value) + else + chr.kerns = { [two] = tonumber(value) } + end + end +end + +local p_kernpair = (fontdata * P("KPX") * name * name * number) / addkernpair + +-- char metrics + +local chr = false +local ind = 0 + +local function start(data,version) + data.metadata.afmversion = version + ind = 0 + chr = { } +end + +local function stop() + ind = 0 + chr = false +end + +local function setindex(i) + if i < 0 then + ind = ind + 1 -- ? + else + ind = i + end + chr = { + index = ind + } +end + +local function setwidth(width) + chr.width = width +end + +local function setname(data,name) + data.characters[name] = chr +end + +local function setboundingbox(boundingbox) + chr.boundingbox = boundingbox +end + +local function setligature(plus,becomes) + local ligatures = chr.ligatures + if ligatures then + ligatures[plus] = becomes + else + chr.ligatures = { [plus] = becomes } + end +end + +local p_charmetric = ( ( + P("C") * number / setindex + + P("WX") * number / setwidth + + P("N") * fontdata * name / setname + + P("B") * Ct((number)^4) / setboundingbox + + P("L") * (name)^2 / setligature + ) * semicolon )^1 + +local p_charmetrics = P("StartCharMetrics") * number * (p_charmetric + (1-P("EndCharMetrics")))^0 * P("EndCharMetrics") +local p_kernpairs = P("StartKernPairs") * number * (p_kernpair + (1-P("EndKernPairs" )))^0 * P("EndKernPairs" ) + +local function set_1(data,key,a) data.metadata[lower(key)] = a end +local function set_2(data,key,a,b) data.metadata[lower(key)] = { a, b } end +local function set_3(data,key,a,b,c) data.metadata[lower(key)] = { a, b, c } end + +-- Notice string +-- EncodingScheme string +-- MappingScheme integer +-- EscChar integer +-- CharacterSet string +-- Characters integer +-- IsBaseFont boolean +-- VVector number number +-- IsFixedV boolean + +local p_parameters = P(false) + + fontdata + * ((P("FontName") + P("FullName") + P("FamilyName"))/lower) + * words / function(data,key,value) + data.metadata[key] = value + end + + fontdata + * ((P("Weight") + P("Version"))/lower) + * name / function(data,key,value) + data.metadata[key] = value + end + + fontdata + * P("IsFixedPitch") + * name / function(data,pitch) + data.metadata.monospaced = toboolean(pitch,true) + end + + fontdata + * P("FontBBox") + * Ct(number^4) / function(data,boundingbox) + data.metadata.boundingbox = boundingbox + end + + fontdata + * ((P("CharWidth") + P("CapHeight") + P("XHeight") + P("Descender") + P("Ascender") + P("ItalicAngle"))/lower) + * number / function(data,key,value) + data.metadata[key] = value + end + + P("Comment") * spacing * ( P(false) + + (fontdata * C("DESIGNSIZE") * number * rest) / set_1 -- 1 + + (fontdata * C("TFM designsize") * number * rest) / set_1 + + (fontdata * C("DesignSize") * number * rest) / set_1 + + (fontdata * C("CODINGSCHEME") * words * rest) / set_1 -- + + (fontdata * C("CHECKSUM") * number * words * rest) / set_1 -- 2 + + (fontdata * C("SPACE") * number * plus * minus * rest) / set_3 -- 3 4 5 + + (fontdata * C("QUAD") * number * rest) / set_1 -- 6 + + (fontdata * C("EXTRASPACE") * number * rest) / set_1 -- 7 + + (fontdata * C("NUM") * number * number * number * rest) / set_3 -- 8 9 10 + + (fontdata * C("DENOM") * number * number * rest) / set_2 -- 11 12 + + (fontdata * C("SUP") * number * number * number * rest) / set_3 -- 13 14 15 + + (fontdata * C("SUB") * number * number * rest) / set_2 -- 16 17 + + (fontdata * C("SUPDROP") * number * rest) / set_1 -- 18 + + (fontdata * C("SUBDROP") * number * rest) / set_1 -- 19 + + (fontdata * C("DELIM") * number * number * rest) / set_2 -- 20 21 + + (fontdata * C("AXISHEIGHT") * number * rest) / set_1 -- 22 + ) + +local fullparser = ( P("StartFontMetrics") * fontdata * name / start ) + * ( p_charmetrics + p_kernpairs + p_parameters + (1-P("EndFontMetrics")) )^0 + * ( P("EndFontMetrics") / stop ) + +local fullparser = ( P("StartFontMetrics") * fontdata * name / start ) + * ( p_charmetrics + p_kernpairs + p_parameters + (1-P("EndFontMetrics")) )^0 + * ( P("EndFontMetrics") / stop ) + +local infoparser = ( P("StartFontMetrics") * fontdata * name / start ) + * ( p_parameters + (1-P("EndFontMetrics")) )^0 + * ( P("EndFontMetrics") / stop ) + +-- infoparser = ( P("StartFontMetrics") * fontdata * name / start ) +-- * ( p_parameters + (1-P("EndFontMetrics") - P("StartCharMetrics")) )^0 +-- * ( (P("EndFontMetrics") + P("StartCharMetrics")) / stop ) + +local function read(filename,parser) + local afmblob = io.loaddata(filename) + if afmblob then + local data = { + resources = { + filename = resolvers.unresolve(filename), + version = afm.version, + creator = "context mkiv", + }, + properties = { + hasitalics = false, + }, + goodies = { + }, + metadata = { + filename = file.removesuffix(file.basename(filename)) + }, + characters = { + -- a temporary store + }, + descriptions = { + -- the final store + }, + } + if trace_loading then + report_afm("parsing afm file %a",filename) + end + lpegmatch(parser,afmblob,1,data) + return data + else + if trace_loading then + report_afm("no valid afm file %a",filename) + end + return nil + end +end + +function readers.loadfont(afmname,pfbname) + local data = read(resolvers.findfile(afmname),fullparser) + if data then + if not pfbname or pfbname == "" then + pfbname = file.replacesuffix(file.nameonly(afmname),"pfb") + pfbname = resolvers.findfile(pfbname) + end + if pfbname and pfbname ~= "" then + data.resources.filename = resolvers.unresolve(pfbname) + get_indexes(data,pfbname) + elseif trace_loading then + report_afm("no pfb file for %a",afmname) + -- data.resources.filename = "unset" -- better than loading the afm file + end + return data + end +end + +function readers.getinfo(filename) + local data = read(resolvers.findfile(filename),infoparser) + if data then + return data.metadata + end +end diff --git a/src/fontloader/misc/fontloader-font-osd.lua b/src/fontloader/misc/fontloader-font-osd.lua index 6ff2e38..a3dda67 100644 --- a/src/fontloader/misc/fontloader-font-osd.lua +++ b/src/fontloader/misc/fontloader-font-osd.lua @@ -79,13 +79,10 @@ 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 otffeatures = fonts.constructors.features.otf local registerotffeature = otffeatures.register local nuts = nodes.nuts diff --git a/src/fontloader/misc/fontloader-font-ota.lua b/src/fontloader/misc/fontloader-font-ota.lua index 6a3804a..4ddb831 100644 --- a/src/fontloader/misc/fontloader-font-ota.lua +++ b/src/fontloader/misc/fontloader-font-ota.lua @@ -44,7 +44,6 @@ 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 @@ -56,7 +55,7 @@ local fontdata = fonts.hashes.identifiers local categories = characters and characters.categories or { } -- sorry, only in context local chardata = characters and characters.data -local otffeatures = fonts.constructors.newfeatures("otf") +local otffeatures = fonts.constructors.features.otf local registerotffeature = otffeatures.register --[[ldx-- diff --git a/src/fontloader/misc/fontloader-font-otd.lua b/src/fontloader/misc/fontloader-font-otd.lua index 2257caa..64cb1bc 100644 --- a/src/fontloader/misc/fontloader-font-otd.lua +++ b/src/fontloader/misc/fontloader-font-otd.lua @@ -36,9 +36,6 @@ local contextmerged = specifiers.contextmerged local setmetatableindex = table.setmetatableindex -local otffeatures = fonts.constructors.newfeatures("otf") -local registerotffeature = otffeatures.register - local a_to_script = { } local a_to_language = { } @@ -135,6 +132,10 @@ local wildcard = "*" -- needs checking: some added features can pass twice +local P, C, Cc, lpegmatch = lpeg.P, lpeg.C, lpeg.Cc, lpeg.match + +local pattern = P("always") * (P(-1) * Cc(true) + P(":") * C((1-P(-1))^1)) + local function initialize(sequence,script,language,s_enabled,a_enabled,font,attr,dynamic,ra,autoscript,autolanguage) local features = sequence.features if features then @@ -151,21 +152,34 @@ local function initialize(sequence,script,language,s_enabled,a_enabled,font,attr e_e = s_enabled and s_enabled[kind] -- the value (font) end if e_e then - local scripts = features[kind] -- - local languages = scripts[script] or scripts[wildcard] - if not languages and autoscript then - langages = defaultscript(featuretype,autoscript,scripts) - end - if languages then - -- we need detailed control over default becase we want to trace - -- only first attribute match check, so we assume simple fina's - local valid = false - if languages[language] then - valid = e_e - elseif languages[wildcard] then - valid = e_e - elseif autolanguage and defaultlanguage(featuretype,autolanguage,languages) then - valid = e_e + local valid = type(e_e) == "string" and lpegmatch(pattern,e_e) + if valid then + -- we have hit always + local attribute = autofeatures[kind] or false + if trace_applied then + report_process( + "font %s, dynamic %a (%a), feature %a, script %a, language %a, lookup %a, value %a", + font,attr or 0,dynamic,kind,"*","*",sequence.name,valid) + end + ra[#ra+1] = { valid, attribute, sequence, kind } + else + -- we already checked for e_e + local scripts = features[kind] -- + local languages = scripts[script] or scripts[wildcard] + if not languages and autoscript then + langages = defaultscript(featuretype,autoscript,scripts) + end + if languages then + -- we need detailed control over default becase we want to trace + -- only first attribute match check, so we assume simple fina's + -- local valid = false + if languages[language] then + valid = e_e + elseif languages[wildcard] then + valid = e_e + elseif autolanguage and defaultlanguage(featuretype,autolanguage,languages) then + valid = e_e + end end if valid then local attribute = autofeatures[kind] or false @@ -244,6 +258,7 @@ function otf.dataset(tfmdata,font,attr) -- attr only when explicit (as in specia local autoscript = (s_enabled and s_enabled.autoscript ) or (a_enabled and a_enabled.autoscript ) local autolanguage = (s_enabled and s_enabled.autolanguage) or (a_enabled and a_enabled.autolanguage) for s=1,#sequences do + -- just return nil or ra step initialize(sequences[s],script,language,s_enabled,a_enabled,font,attr,dynamic,ra,autoscript,autolanguage) end end diff --git a/src/fontloader/misc/fontloader-font-oti.lua b/src/fontloader/misc/fontloader-font-oti.lua index bacd001..d74d2d5 100644 --- a/src/fontloader/misc/fontloader-font-oti.lua +++ b/src/fontloader/misc/fontloader-font-oti.lua @@ -11,8 +11,8 @@ local lower = string.lower local fonts = fonts local constructors = fonts.constructors -local otf = constructors.newhandler("otf") -local otffeatures = constructors.newfeatures("otf") +local otf = constructors.handlers.otf +local otffeatures = constructors.features.otf local registerotffeature = otffeatures.register local otftables = otf.tables or { } diff --git a/src/fontloader/misc/fontloader-font-otj.lua b/src/fontloader/misc/fontloader-font-otj.lua index 6ff80d8..d1408fd 100644 --- a/src/fontloader/misc/fontloader-font-otj.lua +++ b/src/fontloader/misc/fontloader-font-otj.lua @@ -24,10 +24,11 @@ if not modules then modules = { } end modules ['font-otj'] = { -- The use_advance code is just a test and is meant for testing and manuals. There is no -- performance (or whatever) gain and using kerns is somewhat cleaner (at least for now). +-- Maybe: subtype fontkern when pure kerns. + if not nodes.properties then return end local next, rawget = next, rawget -local utfchar = utf.char local fastcopy = table.fastcopy local registertracker = trackers.register @@ -92,7 +93,6 @@ 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 local properties = nodes.properties.data @@ -755,7 +755,7 @@ local function inject_pairs_only(head,where) end local leftkern = i.leftkern if leftkern and leftkern ~= 0 then - insert_node_before(head,current,newkern(leftkern)) + head = insert_node_before(head,current,newkern(leftkern)) end local rightkern = i.rightkern if rightkern and rightkern ~= 0 then @@ -1255,11 +1255,11 @@ local function inject_everything(head,where) 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) + if hasmarks then + local pm = i.markbasenode + if pm then + processmark(pm,current,i) + end end end end @@ -1287,11 +1287,11 @@ local function inject_everything(head,where) 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) + if hasmarks then + local pm = i.markbasenode + if pm then + processmark(pm,current,i) + end end end end @@ -1319,11 +1319,11 @@ local function inject_everything(head,where) 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) + if hasmarks then + local pm = i.markbasenode + if pm then + processmark(pm,current,i) + end end end end @@ -1523,10 +1523,19 @@ function injections.handler(head,where) head = injectspaces(head) end if nofregisteredmarks > 0 or nofregisteredcursives > 0 then + if trace_injections then + report_injections("injection variant %a","everything") + end return inject_everything(head,where) elseif nofregisteredpairs > 0 then + if trace_injections then + report_injections("injection variant %a","pairs") + end return inject_pairs_only(head,where) elseif nofregisteredkerns > 0 then + if trace_injections then + report_injections("injection variant %a","kerns") + end return inject_kerns_only(head,where) else return head, false diff --git a/src/fontloader/misc/fontloader-font-otl.lua b/src/fontloader/misc/fontloader-font-otl.lua index f7b6eb5..c7c278a 100644 --- a/src/fontloader/misc/fontloader-font-otl.lua +++ b/src/fontloader/misc/fontloader-font-otl.lua @@ -53,8 +53,12 @@ local report_otf = logs.reporter("fonts","otf loading") local fonts = fonts local otf = fonts.handlers.otf -otf.version = 3.019 -- beware: also sync font-mis.lua and in mtx-fonts +otf.version = 3.022 -- beware: also sync font-mis.lua and in mtx-fonts otf.cache = containers.define("fonts", "otl", otf.version, true) +otf.svgcache = containers.define("fonts", "svg", otf.version, true) +otf.pdfcache = containers.define("fonts", "pdf", otf.version, true) + +otf.svgenabled = false local otfreaders = otf.readers @@ -63,7 +67,7 @@ local definers = fonts.definers local readers = fonts.readers local constructors = fonts.constructors -local otffeatures = constructors.newfeatures("otf") +local otffeatures = constructors.features.otf local registerotffeature = otffeatures.register local enhancers = allocate() @@ -101,6 +105,12 @@ registerdirective("fonts.otf.loader.forcenotdef", function(v) forcenotdef = -- end -- end +-- Enhancers are used to apply fixes and extensions to fonts. For instance, we use them +-- to implement tlig and trep features. They are not neccessarily bound to opentype +-- fonts but can also apply to type one fonts, given that they obey the structure of an +-- opentype font. They are not to be confused with format specific features but maybe +-- some are so generic that they might eventually move to this mechanism. + local ordered_enhancers = { "check extra features", } @@ -264,6 +274,25 @@ function otf.load(filename,sub,featurefile) -- second argument (format) is gone -- -- if data then + -- + local resources = data.resources + local svgshapes = resources.svgshapes + if svgshapes then + resources.svgshapes = nil + if otf.svgenabled then + local timestamp = os.date() + -- work in progress ... a bit boring to do + containers.write(otf.svgcache,hash, { + svgshapes = svgshapes, + timestamp = timestamp, + }) + data.properties.svg = { + hash = hash, + timestamp = timestamp, + } + end + end + -- otfreaders.compact(data) otfreaders.rehash(data,"unicodes") otfreaders.addunicodetable(data) @@ -302,7 +331,7 @@ function otf.load(filename,sub,featurefile) -- second argument (format) is gone -- enhancers.apply(data,filename,data) -- - constructors.addcoreunicodes(unicodes) + -- constructors.addcoreunicodes(data.resources.unicodes) -- still needed ? -- if applyruntimefixes then applyruntimefixes(filename,data) @@ -340,7 +369,6 @@ end local function copytotfm(data,cache_id) if data then local metadata = data.metadata - local resources = data.resources local properties = derivetable(data.properties) local descriptions = derivetable(data.descriptions) local goodies = derivetable(data.goodies) diff --git a/src/fontloader/misc/fontloader-font-oto.lua b/src/fontloader/misc/fontloader-font-oto.lua index b7ee717..1199778 100644 --- a/src/fontloader/misc/fontloader-font-oto.lua +++ b/src/fontloader/misc/fontloader-font-oto.lua @@ -14,8 +14,6 @@ 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 -local utfchar = utf.char local trace_baseinit = false trackers.register("otf.baseinit", function(v) trace_baseinit = v end) local trace_singles = false trackers.register("otf.singles", function(v) trace_singles = v end) @@ -450,3 +448,5 @@ registerotffeature { base = featuresinitializer, } } + +otf.basemodeinitializer = featuresinitializer diff --git a/src/fontloader/misc/fontloader-font-otr.lua b/src/fontloader/misc/fontloader-font-otr.lua index 24f6854..7d0bf04 100644 --- a/src/fontloader/misc/fontloader-font-otr.lua +++ b/src/fontloader/misc/fontloader-font-otr.lua @@ -99,7 +99,7 @@ readers.streamreader = streamreader local openfile = streamreader.open local closefile = streamreader.close -local skipbytes = streamreader.skip +----- skipbytes = streamreader.skip local setposition = streamreader.setposition local skipshort = streamreader.skipshort local readbytes = streamreader.readbytes @@ -108,7 +108,7 @@ local readbyte = streamreader.readcardinal1 -- 8-bit unsigned integer local readushort = streamreader.readcardinal2 -- 16-bit unsigned integer local readuint = streamreader.readcardinal3 -- 24-bit unsigned integer local readulong = streamreader.readcardinal4 -- 24-bit unsigned integer -local readchar = streamreader.readinteger1 -- 8-bit signed integer +----- readchar = streamreader.readinteger1 -- 8-bit signed integer local readshort = streamreader.readinteger2 -- 16-bit signed integer local readlong = streamreader.readinteger4 -- 24-bit unsigned integer local readfixed = streamreader.readfixed4 @@ -129,12 +129,11 @@ local function readlongdatetime(f) 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 -- 0x10FFFF +local tableversion = 0.004 +readers.tableversion = tableversion +local privateoffset = fonts.constructors and fonts.constructors.privateoffset or 0xF0000 -- 0x10FFFF +local reportedskipped = { } -readers.tableversion = tableversion - -local reportedskipped = { } local function reportskippedtable(tag) if not reportedskipped[tag] then @@ -1657,6 +1656,26 @@ function readers.glyf(f,fontdata,specification) -- part goes to cff module end end +-- Experimental (we need fonts). + +function readers.colr(f,fontdata,specification) + if specification.glyphs then + reportskippedtable("colr") + end +end + +function readers.cpal(f,fontdata,specification) + if specification.glyphs then + reportskippedtable("cpal") + end +end + +function readers.svg(f,fontdata,specification) + if specification.glyphs then + reportskippedtable("svg") + end +end + -- Here we have a table that we really need for later processing although a more advanced gpos table -- can also be available. Todo: we need a 'fake' lookup for this (analogue to ff). @@ -1874,6 +1893,7 @@ local function getinfo(maindata,sub,platformnames,rawfamilynames) -- format = fontdata.format, fontname = fontname, fullname = fullname, + -- cfffullname = cff.fullname, family = family, subfamily = subfamily, familyname = familyname, @@ -1996,6 +2016,9 @@ local function readdata(f,offset,specification) readers["cmap"](f,fontdata,specification) readers["loca"](f,fontdata,specification) readers["glyf"](f,fontdata,specification) + readers["colr"](f,fontdata,specification) + readers["cpal"](f,fontdata,specification) + readers["svg" ](f,fontdata,specification) readers["kern"](f,fontdata,specification) readers["gdef"](f,fontdata,specification) readers["gsub"](f,fontdata,specification) @@ -2164,7 +2187,9 @@ function readers.loadfont(filename,n) goodies = { }, metadata = getinfo(fontdata,n), -- no platformnames here ! properties = { - hasitalics = fontdata.hasitalics or false, + hasitalics = fontdata.hasitalics or false, + maxcolorclass = fontdata.maxcolorclass, + hascolor = fontdata.hascolor or false, }, resources = { -- filename = fontdata.filename, @@ -2181,6 +2206,8 @@ function readers.loadfont(filename,n) version = getname(fontdata,"version"), cidinfo = fontdata.cidinfo, mathconstants = fontdata.mathconstants, + colorpalettes = fontdata.colorpalettes, + svgshapes = fontdata.svgshapes, }, } end diff --git a/src/fontloader/misc/fontloader-font-ots.lua b/src/fontloader/misc/fontloader-font-ots.lua index 21225c2..51704bf 100644 --- a/src/fontloader/misc/fontloader-font-ots.lua +++ b/src/fontloader/misc/fontloader-font-ots.lua @@ -124,7 +124,7 @@ local trace_cursive = false registertracker("otf.cursive", function(v local trace_preparing = false registertracker("otf.preparing", function(v) trace_preparing = v end) local trace_bugs = false registertracker("otf.bugs", function(v) trace_bugs = v end) local trace_details = false registertracker("otf.details", function(v) trace_details = v end) -local trace_applied = false registertracker("otf.applied", function(v) trace_applied = v end) +----- trace_applied = false registertracker("otf.applied", function(v) trace_applied = v end) local trace_steps = false registertracker("otf.steps", function(v) trace_steps = v end) local trace_skips = false registertracker("otf.skips", function(v) trace_skips = v end) local trace_directions = false registertracker("otf.directions", function(v) trace_directions = v end) @@ -145,10 +145,8 @@ 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") ------ report_prepare = logs.reporter("fonts","otf prepare") local report_warning = logs.reporter("fonts","otf warning") local report_run = logs.reporter("fonts","otf run") -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") @@ -185,10 +183,7 @@ 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 -local remove_node = nuts.remove local copy_node = nuts.copy local copy_node_list = nuts.copy_list local find_node_tail = nuts.tail @@ -244,7 +239,7 @@ local cursonce = true local fonthashes = fonts.hashes local fontdata = fonthashes.identifiers -local otffeatures = fonts.constructors.newfeatures("otf") +local otffeatures = fonts.constructors.features.otf local registerotffeature = otffeatures.register local onetimemessage = fonts.loggers.onetimemessage or function() end @@ -3064,7 +3059,7 @@ local function c_run_single(head,font,attr,lookupcache,step,dataset,sequence,rlm while start do local char = ischar(start,font) if char then - local a = getattr(start,0) + local a = attr and getattr(start,0) if not a or (a == attr) then local lookupmatch = lookupcache[char] if lookupmatch then @@ -3097,7 +3092,7 @@ 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) + local a = attr and getattr(start,0) if not a or (a == attr) then local lookupmatch = lookupcache[char] if lookupmatch then -- hm, hyphens can match (tlig) so we need to really check @@ -3132,7 +3127,7 @@ local function t_run_single(start,stop,font,attr,lookupcache) end -- local function d_run_single(prev,font,attr,lookupcache,step,dataset,sequence,rlmode,handler) --- local a = getattr(prev,0) +-- local a = attr and getattr(prev,0) -- if not a or (a == attr) then -- local char = ischar(prev) -- can be disc -- if char then @@ -3149,7 +3144,7 @@ end -- end local function k_run_single(sub,injection,last,font,attr,lookupcache,step,dataset,sequence,rlmode,handler) - local a = getattr(sub,0) + local a = attr and getattr(sub,0) if not a or (a == attr) then for n in traverse_nodes(sub) do -- only gpos if n == last then @@ -3181,7 +3176,7 @@ local function c_run_multiple(head,font,attr,steps,nofsteps,dataset,sequence,rlm while start do local char = ischar(start,font) if char then - local a = getattr(start,0) + local a = attr and getattr(start,0) if not a or (a == attr) then for i=1,nofsteps do local step = steps[i] @@ -3228,7 +3223,7 @@ 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) + local a = attr and getattr(start,0) if not a or (a == attr) then for i=1,nofsteps do local step = steps[i] @@ -3271,7 +3266,7 @@ local function t_run_multiple(start,stop,font,attr,steps,nofsteps) end -- local function d_run_multiple(prev,attr,steps,nofsteps,dataset,sequence,rlmode,handler) --- local a = getattr(prev,0) +-- local a = attr and getattr(prev,0) -- if not a or (a == attr) then -- local char = ischar(prev) -- can be disc -- if char then @@ -3297,7 +3292,7 @@ end -- end local function k_run_multiple(sub,injection,last,font,attr,steps,nofsteps,dataset,sequence,rlmode,handler) - local a = getattr(sub,0) + local a = attr and getattr(sub,0) if not a or (a == attr) then for n in traverse_nodes(sub) do -- only gpos if n == last then @@ -3394,6 +3389,14 @@ local function featuresprocessor(head,font,attr) end + -- some 10% faster when no dynamics but hardly measureable on real runs .. but: it only + -- works when we have no other dynamics as otherwise the zero run will be applied to the + -- whole stream for which we then need to pass another variable which we won't + + -- if attr == 0 then + -- attr = false + -- end + head = tonut(head) if trace_steps then @@ -3405,7 +3408,7 @@ local function featuresprocessor(head,font,attr) local done = false local datasets = otf.dataset(tfmdata,font,attr) - local dirstack = { } -- could move outside function btu we can have local runss + local dirstack = { } -- could move outside function but we can have local runs sweephead = { } @@ -3451,7 +3454,7 @@ local function featuresprocessor(head,font,attr) while start do local char = ischar(start,font) if char then - local a = getattr(start,0) + local a = attr and getattr(start,0) if not a or (a == attr) then for i=1,nofsteps do local step = steps[i] @@ -3485,18 +3488,15 @@ local function featuresprocessor(head,font,attr) local start = head -- local ? rlmode = 0 -- to be checked ? if nofsteps == 1 then -- happens often - local step = steps[1] local lookupcache = step.coverage if not lookupcache then - -- can't happen, no check in loop either report_missing_coverage(dataset,sequence) else - while start do local char, id = ischar(start,font) if char then - local a = getattr(start,0) + local a = attr and getattr(start,0) if a then a = (a == attr) and (not attribute or getprop(start,a_state) == attribute) else @@ -3553,7 +3553,7 @@ local function featuresprocessor(head,font,attr) while start do local char, id = ischar(start,font) if char then - local a = getattr(start,0) + local a = attr and getattr(start,0) if a then a = (a == attr) and (not attribute or getprop(start,a_state) == attribute) else @@ -3652,6 +3652,9 @@ registerotffeature { } } +otf.nodemodeinitializer = featuresinitializer +otf.featuresprocessor = featuresprocessor + -- This can be used for extra handlers, but should be used with care! otf.handlers = handlers -- used in devanagari diff --git a/src/fontloader/misc/fontloader-font-oup.lua b/src/fontloader/misc/fontloader-font-oup.lua index 3b6d8ea..e2d209a 100644 --- a/src/fontloader/misc/fontloader-font-oup.lua +++ b/src/fontloader/misc/fontloader-font-oup.lua @@ -586,7 +586,8 @@ local function checklookups(fontdata,missing,nofmissing) local done = { } for i, r in next, missing do if r then - local name = descriptions[i].name or f_index(i) + local data = descriptions[i] + local name = data and data.name or f_index(i) if not ignore[name] then done[name] = true end @@ -706,6 +707,19 @@ local function unifyglyphs(fontdata,usenames) end end -- + local colorpalettes = resources.colorpalettes + if colorpalettes then + for index=1,#glyphs do + local colors = glyphs[index].colors + if colors then + for i=1,#colors do + local c = colors[i] + c.slot = indices[c.slot] + end + end + end + end + -- fontdata.private = private fontdata.glyphs = nil fontdata.names = names @@ -1158,6 +1172,7 @@ function readers.pack(data) local sequences = resources.sequences local sublookups = resources.sublookups local features = resources.features + local palettes = resources.colorpalettes local chardata = characters and characters.data local descriptions = data.descriptions or data.glyphs @@ -1190,6 +1205,14 @@ function readers.pack(data) end end end + -- if palettes then + -- local color = description.color + -- if color then + -- for i=1,#color do + -- color[i] = pack_normal(color[i]) + -- end + -- end + -- end end local function packthem(sequences) @@ -1314,6 +1337,16 @@ function readers.pack(data) end end + if palettes then + for i=1,#palettes do + local p = palettes[i] + for j=1,#p do + p[j] = pack_indexed(p[j]) + end + end + + end + if not success(1,pass) then return end @@ -1461,6 +1494,7 @@ function readers.unpack(data) local sequences = resources.sequences local sublookups = resources.sublookups local features = resources.features + local palettes = resources.colorpalettes local unpacked = { } setmetatable(unpacked,unpacked_mt) for unicode, description in next, descriptions do @@ -1487,6 +1521,17 @@ function readers.unpack(data) end end end + -- if palettes then + -- local color = description.color + -- if color then + -- for i=1,#color do + -- local tv = tables[color[i]] + -- if tv then + -- color[i] = tv + -- end + -- end + -- end + -- end end local function unpackthem(sequences) @@ -1716,6 +1761,18 @@ function readers.unpack(data) end end + if palettes then + for i=1,#palettes do + local p = palettes[i] + for j=1,#p do + local tv = tables[p[j]] + if tv then + p[j] = tv + end + end + end + end + data.tables = nil end end diff --git a/src/fontloader/misc/fontloader-font-tfm.lua b/src/fontloader/misc/fontloader-font-tfm.lua index 8e92c48..ab6d795 100644 --- a/src/fontloader/misc/fontloader-font-tfm.lua +++ b/src/fontloader/misc/fontloader-font-tfm.lua @@ -23,13 +23,13 @@ local readers = fonts.readers local constructors = fonts.constructors local encodings = fonts.encodings -local tfm = constructors.newhandler("tfm") +local tfm = constructors.handlers.tfm tfm.version = 1.000 tfm.maxnestingdepth = 5 tfm.maxnestingsize = 65536*1024 -local tfmfeatures = constructors.newfeatures("tfm") -local registertfmfeature = tfmfeatures.register +local tfmfeatures = constructors.features.tfm +----- registertfmfeature = tfmfeatures.register constructors.resolvevirtualtoo = false -- wil be set in font-ctx.lua diff --git a/src/fontloader/misc/fontloader-fonts-ext.lua b/src/fontloader/misc/fontloader-fonts-ext.lua index b60d045..9d8d307 100644 --- a/src/fontloader/misc/fontloader-fonts-ext.lua +++ b/src/fontloader/misc/fontloader-fonts-ext.lua @@ -12,7 +12,7 @@ if context then end local fonts = fonts -local otffeatures = fonts.constructors.newfeatures("otf") +local otffeatures = fonts.constructors.features.otf -- A few generic extensions. diff --git a/src/fontloader/misc/fontloader-fonts.lua b/src/fontloader/misc/fontloader-fonts.lua index e1ec376..83d52d9 100644 --- a/src/fontloader/misc/fontloader-fonts.lua +++ b/src/fontloader/misc/fontloader-fonts.lua @@ -186,6 +186,7 @@ if non_generic_context.luatex_fonts.skip_loading ~= true then loadmodule("l-file.lua") loadmodule("l-boolean.lua") loadmodule("l-math.lua") + loadmodule("l-unicode.lua") -- A few slightly higher level support modules: @@ -230,8 +231,6 @@ if non_generic_context.luatex_fonts.skip_loading ~= true then loadmodule('luatex-fonts-syn.lua') loadmodule('font-tfm.lua') - loadmodule('font-afm.lua') - loadmodule('font-afk.lua') loadmodule('font-oti.lua') -- These are the old loader and processing modules. These use the built-in font loader and @@ -259,6 +258,13 @@ if non_generic_context.luatex_fonts.skip_loading ~= true then loadmodule('font-ota.lua') loadmodule('font-ots.lua') loadmodule('font-osd.lua') + loadmodule('font-ocl.lua') -- svg needs 0.97 (for fix in memstreams) + + -- type one code + + loadmodule('font-onr.lua') -- was font-afm.lua + loadmodule('font-one.lua') -- was font-afm.lua + loadmodule('font-afk.lua') -- common code diff --git a/src/fontloader/misc/fontloader-math.tex b/src/fontloader/misc/fontloader-math.tex index 604b4a1..b249021 100644 --- a/src/fontloader/misc/fontloader-math.tex +++ b/src/fontloader/misc/fontloader-math.tex @@ -40,9 +40,9 @@ \font\tenbf = file:lmroman10-bold.otf:+liga;+kern;+tlig;+trep at 10pt \font\tenbi = file:lmroman10-bolditalic.otf:+liga;+kern;+tlig;+trep at 10pt % - \font\mathfonttextupright = file:latinmodern-math.otf:ssty=0;fixmath=yes at 10pt - \font\mathfontscriptupright = file:latinmodern-math.otf:ssty=1;fixmath=yes at 7pt - \font\mathfontscriptscriptupright = file:latinmodern-math.otf:ssty=2;fixmath=yes at 5pt + \font\mathfonttextupright = file:latinmodern-math.otf:script=math;ssty=0;mathsize=yes at 10pt + \font\mathfontscriptupright = file:latinmodern-math.otf:script=math;ssty=1;mathsize=yes at 7pt + \font\mathfontscriptscriptupright = file:latinmodern-math.otf:script=math;ssty=2;mathsize=yes at 5pt % \textfont 0 = \mathfonttextupright \scriptfont 0 = \mathfontscriptupright @@ -61,9 +61,9 @@ \font\tenbf = file:lucidabrightot-demi.otf:+liga;+kern;+tlig;+trep at 10pt \font\tenbi = file:lucidabrightot-demiitalic.otf:+liga;+kern;+tlig;+trep at 10pt % - \font\mathfonttextupright = file:lucidabrightmathot.otf:ssty=0;fixmath=yes at 10pt - \font\mathfontscriptupright = file:lucidabrightmathot.otf:ssty=1;fixmath=yes at 7pt - \font\mathfontscriptscriptupright = file:lucidabrightmathot.otf:ssty=2;fixmath=yes at 5pt + \font\mathfonttextupright = file:lucidabrightmathot.otf:script=math;ssty=0;mathsize=yes at 10pt + \font\mathfontscriptupright = file:lucidabrightmathot.otf:script=math;ssty=1;mathsize=yes at 7pt + \font\mathfontscriptscriptupright = file:lucidabrightmathot.otf:script=math;ssty=2;mathsize=yes at 5pt % \textfont 0 = \mathfonttextupright \scriptfont 0 = \mathfontscriptupright diff --git a/src/fontloader/runtime/fontloader-reference.lua b/src/fontloader/runtime/fontloader-reference.lua index 5f35ded..6f96d97 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 : 05/01/16 09:52:32 +-- merge date : 06/13/16 17:00:29 do -- begin closure to overcome local limits and interference @@ -2785,6 +2785,620 @@ end -- closure do -- begin closure to overcome local limits and interference +if not modules then modules={} end modules ['l-unicode']={ + 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" +} +utf=utf or (unicode and unicode.utf8) or {} +utf.characters=utf.characters or string.utfcharacters +utf.values=utf.values or string.utfvalues +local type=type +local char,byte,format,sub,gmatch=string.char,string.byte,string.format,string.sub,string.gmatch +local concat=table.concat +local P,C,R,Cs,Ct,Cmt,Cc,Carg,Cp=lpeg.P,lpeg.C,lpeg.R,lpeg.Cs,lpeg.Ct,lpeg.Cmt,lpeg.Cc,lpeg.Carg,lpeg.Cp +local lpegmatch=lpeg.match +local patterns=lpeg.patterns +local tabletopattern=lpeg.utfchartabletopattern +local bytepairs=string.bytepairs +local finder=lpeg.finder +local replacer=lpeg.replacer +local utfvalues=utf.values +local utfgmatch=utf.gmatch +local p_utftype=patterns.utftype +local p_utfstricttype=patterns.utfstricttype +local p_utfoffset=patterns.utfoffset +local p_utf8char=patterns.utf8character +local p_utf8byte=patterns.utf8byte +local p_utfbom=patterns.utfbom +local p_newline=patterns.newline +local p_whitespace=patterns.whitespace +if not unicode then + unicode={ utf=utf } +end +if not utf.char then + local floor,char=math.floor,string.char + function utf.char(n) + if n<0x80 then + return char(n) + elseif n<0x800 then + return char( + 0xC0+floor(n/0x40), + 0x80+(n%0x40) + ) + elseif n<0x10000 then + return char( + 0xE0+floor(n/0x1000), + 0x80+(floor(n/0x40)%0x40), + 0x80+(n%0x40) + ) + elseif n<0x200000 then + return char( + 0xF0+floor(n/0x40000), + 0x80+(floor(n/0x1000)%0x40), + 0x80+(floor(n/0x40)%0x40), + 0x80+(n%0x40) + ) + else + return "" + end + end +end +if not utf.byte then + local utf8byte=patterns.utf8byte + function utf.byte(c) + return lpegmatch(utf8byte,c) + end +end +local utfchar,utfbyte=utf.char,utf.byte +function utf.filetype(data) + return data and lpegmatch(p_utftype,data) or "unknown" +end +local toentities=Cs ( + ( + patterns.utf8one+( + patterns.utf8two+patterns.utf8three+patterns.utf8four + )/function(s) local b=utfbyte(s) if b<127 then return s else return format("&#%X;",b) end end + )^0 +) +patterns.toentities=toentities +function utf.toentities(str) + return lpegmatch(toentities,str) +end +local one=P(1) +local two=C(1)*C(1) +local four=C(R(utfchar(0xD8),utfchar(0xFF)))*C(1)*C(1)*C(1) +local pattern=P("\254\255")*Cs(( + four/function(a,b,c,d) + local ab=0xFF*byte(a)+byte(b) + local cd=0xFF*byte(c)+byte(d) + return utfchar((ab-0xD800)*0x400+(cd-0xDC00)+0x10000) + end+two/function(a,b) + return utfchar(byte(a)*256+byte(b)) + end+one + )^1 )+P("\255\254")*Cs(( + four/function(b,a,d,c) + local ab=0xFF*byte(a)+byte(b) + local cd=0xFF*byte(c)+byte(d) + return utfchar((ab-0xD800)*0x400+(cd-0xDC00)+0x10000) + end+two/function(b,a) + return utfchar(byte(a)*256+byte(b)) + end+one + )^1 ) +function string.toutf(s) + return lpegmatch(pattern,s) or s +end +local validatedutf=Cs ( + ( + patterns.utf8one+patterns.utf8two+patterns.utf8three+patterns.utf8four+P(1)/"�" + )^0 +) +patterns.validatedutf=validatedutf +function utf.is_valid(str) + return type(str)=="string" and lpegmatch(validatedutf,str) or false +end +if not utf.len then + local n,f=0,1 + local utfcharcounter=patterns.utfbom^-1*Cmt ( + Cc(1)*patterns.utf8one^1+Cc(2)*patterns.utf8two^1+Cc(3)*patterns.utf8three^1+Cc(4)*patterns.utf8four^1, + function(_,t,d) + n=n+(t-f)/d + f=t + return true + end + )^0 + function utf.len(str) + n,f=0,1 + lpegmatch(utfcharcounter,str or "") + return n + end +end +utf.length=utf.len +if not utf.sub then + local utflength=utf.length + local b,e,n,first,last=0,0,0,0,0 + local function slide_zero(s,p) + n=n+1 + if n>=last then + e=p-1 + else + return p + end + end + local function slide_one(s,p) + n=n+1 + if n==first then + b=p + end + if n>=last then + e=p-1 + else + return p + end + end + local function slide_two(s,p) + n=n+1 + if n==first then + b=p + else + return true + end + end + local pattern_zero=Cmt(p_utf8char,slide_zero)^0 + local pattern_one=Cmt(p_utf8char,slide_one )^0 + local pattern_two=Cmt(p_utf8char,slide_two )^0 + local pattern_first=C(patterns.utf8character) + function utf.sub(str,start,stop) + if not start then + return str + end + if start==0 then + start=1 + end + if not stop then + if start<0 then + local l=utflength(str) + start=l+start + else + start=start-1 + end + b,n,first=0,0,start + lpegmatch(pattern_two,str) + if n>=first then + return sub(str,b) + else + return "" + end + end + if start<0 or stop<0 then + local l=utf.length(str) + if start<0 then + start=l+start + if start<=0 then + start=1 + else + start=start+1 + end + end + if stop<0 then + stop=l+stop + if stop==0 then + stop=1 + else + stop=stop+1 + end + end + end + if start==1 and stop==1 then + return lpegmatch(pattern_first,str) or "" + elseif start>stop then + return "" + elseif start>1 then + b,e,n,first,last=0,0,0,start-1,stop + lpegmatch(pattern_one,str) + if n>=first and e==0 then + e=#str + end + return sub(str,b,e) + else + b,e,n,last=1,0,0,stop + lpegmatch(pattern_zero,str) + if e==0 then + e=#str + end + return sub(str,b,e) + end + end +end +function utf.remapper(mapping,option,action) + local variant=type(mapping) + if variant=="table" then + action=action or mapping + if option=="dynamic" then + local pattern=false + table.setmetatablenewindex(mapping,function(t,k,v) rawset(t,k,v) pattern=false end) + return function(str) + if not str or str=="" then + return "" + else + if not pattern then + pattern=Cs((tabletopattern(mapping)/action+p_utf8char)^0) + end + return lpegmatch(pattern,str) + end + end + elseif option=="pattern" then + return Cs((tabletopattern(mapping)/action+p_utf8char)^0) + else + local pattern=Cs((tabletopattern(mapping)/action+p_utf8char)^0) + return function(str) + if not str or str=="" then + return "" + else + return lpegmatch(pattern,str) + end + end,pattern + end + elseif variant=="function" then + if option=="pattern" then + return Cs((p_utf8char/mapping+p_utf8char)^0) + else + local pattern=Cs((p_utf8char/mapping+p_utf8char)^0) + return function(str) + if not str or str=="" then + return "" + else + return lpegmatch(pattern,str) + end + end,pattern + end + else + return function(str) + return str or "" + end + end +end +function utf.replacer(t) + local r=replacer(t,false,false,true) + return function(str) + return lpegmatch(r,str) + end +end +function utf.subtituter(t) + local f=finder (t) + local r=replacer(t,false,false,true) + return function(str) + local i=lpegmatch(f,str) + if not i then + return str + elseif i>#str then + return str + else + return lpegmatch(r,str) + end + end +end +local utflinesplitter=p_utfbom^-1*lpeg.tsplitat(p_newline) +local utfcharsplitter_ows=p_utfbom^-1*Ct(C(p_utf8char)^0) +local utfcharsplitter_iws=p_utfbom^-1*Ct((p_whitespace^1+C(p_utf8char))^0) +local utfcharsplitter_raw=Ct(C(p_utf8char)^0) +patterns.utflinesplitter=utflinesplitter +function utf.splitlines(str) + return lpegmatch(utflinesplitter,str or "") +end +function utf.split(str,ignorewhitespace) + if ignorewhitespace then + return lpegmatch(utfcharsplitter_iws,str or "") + else + return lpegmatch(utfcharsplitter_ows,str or "") + end +end +function utf.totable(str) + return lpegmatch(utfcharsplitter_raw,str) +end +function utf.magic(f) + local str=f:read(4) or "" + local off=lpegmatch(p_utfoffset,str) + if off<4 then + f:seek('set',off) + end + return lpegmatch(p_utftype,str) +end +local utf16_to_utf8_be,utf16_to_utf8_le +local utf32_to_utf8_be,utf32_to_utf8_le +local utf_16_be_getbom=patterns.utfbom_16_be^-1 +local utf_16_le_getbom=patterns.utfbom_16_le^-1 +local utf_32_be_getbom=patterns.utfbom_32_be^-1 +local utf_32_le_getbom=patterns.utfbom_32_le^-1 +local utf_16_be_linesplitter=utf_16_be_getbom*lpeg.tsplitat(patterns.utf_16_be_nl) +local utf_16_le_linesplitter=utf_16_le_getbom*lpeg.tsplitat(patterns.utf_16_le_nl) +local utf_32_be_linesplitter=utf_32_be_getbom*lpeg.tsplitat(patterns.utf_32_be_nl) +local utf_32_le_linesplitter=utf_32_le_getbom*lpeg.tsplitat(patterns.utf_32_le_nl) +local more=0 +local p_utf16_to_utf8_be=C(1)*C(1)/function(left,right) + local now=256*byte(left)+byte(right) + if more>0 then + now=(more-0xD800)*0x400+(now-0xDC00)+0x10000 + more=0 + return utfchar(now) + elseif now>=0xD800 and now<=0xDBFF then + more=now + return "" + else + return utfchar(now) + end +end +local p_utf16_to_utf8_le=C(1)*C(1)/function(right,left) + local now=256*byte(left)+byte(right) + if more>0 then + now=(more-0xD800)*0x400+(now-0xDC00)+0x10000 + more=0 + return utfchar(now) + elseif now>=0xD800 and now<=0xDBFF then + more=now + return "" + else + return utfchar(now) + end +end +local p_utf32_to_utf8_be=C(1)*C(1)*C(1)*C(1)/function(a,b,c,d) + return utfchar(256*256*256*byte(a)+256*256*byte(b)+256*byte(c)+byte(d)) +end +local p_utf32_to_utf8_le=C(1)*C(1)*C(1)*C(1)/function(a,b,c,d) + return utfchar(256*256*256*byte(d)+256*256*byte(c)+256*byte(b)+byte(a)) +end +p_utf16_to_utf8_be=P(true)/function() more=0 end*utf_16_be_getbom*Cs(p_utf16_to_utf8_be^0) +p_utf16_to_utf8_le=P(true)/function() more=0 end*utf_16_le_getbom*Cs(p_utf16_to_utf8_le^0) +p_utf32_to_utf8_be=P(true)/function() more=0 end*utf_32_be_getbom*Cs(p_utf32_to_utf8_be^0) +p_utf32_to_utf8_le=P(true)/function() more=0 end*utf_32_le_getbom*Cs(p_utf32_to_utf8_le^0) +patterns.utf16_to_utf8_be=p_utf16_to_utf8_be +patterns.utf16_to_utf8_le=p_utf16_to_utf8_le +patterns.utf32_to_utf8_be=p_utf32_to_utf8_be +patterns.utf32_to_utf8_le=p_utf32_to_utf8_le +utf16_to_utf8_be=function(s) + if s and s~="" then + return lpegmatch(p_utf16_to_utf8_be,s) + else + return s + end +end +local utf16_to_utf8_be_t=function(t) + if not t then + return nil + elseif type(t)=="string" then + t=lpegmatch(utf_16_be_linesplitter,t) + end + for i=1,#t do + local s=t[i] + if s~="" then + t[i]=lpegmatch(p_utf16_to_utf8_be,s) + end + end + return t +end +utf16_to_utf8_le=function(s) + if s and s~="" then + return lpegmatch(p_utf16_to_utf8_le,s) + else + return s + end +end +local utf16_to_utf8_le_t=function(t) + if not t then + return nil + elseif type(t)=="string" then + t=lpegmatch(utf_16_le_linesplitter,t) + end + for i=1,#t do + local s=t[i] + if s~="" then + t[i]=lpegmatch(p_utf16_to_utf8_le,s) + end + end + return t +end +utf32_to_utf8_be=function(s) + if s and s~="" then + return lpegmatch(p_utf32_to_utf8_be,s) + else + return s + end +end +local utf32_to_utf8_be_t=function(t) + if not t then + return nil + elseif type(t)=="string" then + t=lpegmatch(utf_32_be_linesplitter,t) + end + for i=1,#t do + local s=t[i] + if s~="" then + t[i]=lpegmatch(p_utf32_to_utf8_be,s) + end + end + return t +end +utf32_to_utf8_le=function(s) + if s and s~="" then + return lpegmatch(p_utf32_to_utf8_le,s) + else + return s + end +end +local utf32_to_utf8_le_t=function(t) + if not t then + return nil + elseif type(t)=="string" then + t=lpegmatch(utf_32_le_linesplitter,t) + end + for i=1,#t do + local s=t[i] + if s~="" then + t[i]=lpegmatch(p_utf32_to_utf8_le,s) + end + end + return t +end +utf.utf16_to_utf8_le_t=utf16_to_utf8_le_t +utf.utf16_to_utf8_be_t=utf16_to_utf8_be_t +utf.utf32_to_utf8_le_t=utf32_to_utf8_le_t +utf.utf32_to_utf8_be_t=utf32_to_utf8_be_t +utf.utf16_to_utf8_le=utf16_to_utf8_le +utf.utf16_to_utf8_be=utf16_to_utf8_be +utf.utf32_to_utf8_le=utf32_to_utf8_le +utf.utf32_to_utf8_be=utf32_to_utf8_be +function utf.utf8_to_utf8_t(t) + return type(t)=="string" and lpegmatch(utflinesplitter,t) or t +end +function utf.utf16_to_utf8_t(t,endian) + return endian and utf16_to_utf8_be_t(t) or utf16_to_utf8_le_t(t) or t +end +function utf.utf32_to_utf8_t(t,endian) + return endian and utf32_to_utf8_be_t(t) or utf32_to_utf8_le_t(t) or t +end +local function little(b) + if b<0x10000 then + return char(b%256,b/256) + else + b=b-0x10000 + local b1,b2=b/1024+0xD800,b%1024+0xDC00 + return char(b1%256,b1/256,b2%256,b2/256) + end +end +local function big(b) + if b<0x10000 then + return char(b/256,b%256) + else + b=b-0x10000 + local b1,b2=b/1024+0xD800,b%1024+0xDC00 + return char(b1/256,b1%256,b2/256,b2%256) + end +end +local l_remap=Cs((p_utf8byte/little+P(1)/"")^0) +local b_remap=Cs((p_utf8byte/big+P(1)/"")^0) +local function utf8_to_utf16_be(str,nobom) + if nobom then + return lpegmatch(b_remap,str) + else + return char(254,255)..lpegmatch(b_remap,str) + end +end +local function utf8_to_utf16_le(str,nobom) + if nobom then + return lpegmatch(l_remap,str) + else + return char(255,254)..lpegmatch(l_remap,str) + end +end +utf.utf8_to_utf16_be=utf8_to_utf16_be +utf.utf8_to_utf16_le=utf8_to_utf16_le +function utf.utf8_to_utf16(str,littleendian,nobom) + if littleendian then + return utf8_to_utf16_le(str,nobom) + else + return utf8_to_utf16_be(str,nobom) + end +end +local pattern=Cs ( + (p_utf8byte/function(unicode ) return format("0x%04X",unicode) end)*(p_utf8byte*Carg(1)/function(unicode,separator) return format("%s0x%04X",separator,unicode) end)^0 +) +function utf.tocodes(str,separator) + return lpegmatch(pattern,str,1,separator or " ") +end +function utf.ustring(s) + return format("U+%05X",type(s)=="number" and s or utfbyte(s)) +end +function utf.xstring(s) + return format("0x%05X",type(s)=="number" and s or utfbyte(s)) +end +function utf.toeight(str) + if not str or str=="" then + return nil + end + local utftype=lpegmatch(p_utfstricttype,str) + if utftype=="utf-8" then + return sub(str,4) + elseif utftype=="utf-16-be" then + return utf16_to_utf8_be(str) + elseif utftype=="utf-16-le" then + return utf16_to_utf8_le(str) + else + return str + end +end +local p_nany=p_utf8char/"" +if utfgmatch then + function utf.count(str,what) + if type(what)=="string" then + local n=0 + for _ in utfgmatch(str,what) do + n=n+1 + end + return n + else + return #lpegmatch(Cs((P(what)/" "+p_nany)^0),str) + end + end +else + local cache={} + function utf.count(str,what) + if type(what)=="string" then + local p=cache[what] + if not p then + p=Cs((P(what)/" "+p_nany)^0) + cache[p]=p + end + return #lpegmatch(p,str) + else + return #lpegmatch(Cs((P(what)/" "+p_nany)^0),str) + end + end +end +if not utf.characters then + function utf.characters(str) + return gmatch(str,".[\128-\191]*") + end + string.utfcharacters=utf.characters +end +if not utf.values then + local find=string.find + local dummy=function() + end + function utf.values(str) + local n=#str + if n==0 then + return dummy + elseif n==1 then + return function() return utfbyte(str) end + else + local p=1 + return function() + local b,e=find(str,".[\128-\191]*",p) + if b then + p=e+1 + return utfbyte(sub(str,b,e)) + end + end + end + end + string.utfvalues=utf.values +end +function utf.chrlen(u) + return + (u<0x80 and 1) or + (u<0xE0 and 2) or + (u<0xF0 and 3) or + (u<0xF8 and 4) or + (u<0xFC and 5) or + (u<0xFE and 6) or 0 +end + +end -- closure + +do -- begin closure to overcome local limits and interference + if not modules then modules={} end modules ['util-str']={ version=1.001, comment="companion to luat-lib.mkiv", @@ -5034,7 +5648,6 @@ if not modules then modules={} end modules ['font-ini']={ license="see context related readme files" } local allocate=utilities.storage.allocate -local report_defining=logs.reporter("fonts","defining") fonts=fonts or {} local fonts=fonts fonts.hashes={ identifiers=allocate() } @@ -5061,7 +5674,6 @@ if not modules then modules={} end modules ['font-con']={ } local next,tostring,rawget=next,tostring,rawget local format,match,lower,gsub=string.format,string.match,string.lower,string.gsub -local utfbyte=utf.byte local sort,insert,concat,sortedkeys,serialize,fastcopy=table.sort,table.insert,table.concat,table.sortedkeys,table.serialize,table.fastcopy local derivetable=table.derive local trace_defining=false trackers.register("fonts.defining",function(v) trace_defining=v end) @@ -5288,6 +5900,19 @@ function constructors.enhanceparameters(parameters) extra=extra, } end +local function mathkerns(v,vdelta) + local k={} + for i=1,#v do + local entry=v[i] + local height=entry.height + local kern=entry.kern + k[i]={ + height=height and vdelta*height or 0, + kern=kern and vdelta*kern or 0, + } + end + return k +end function constructors.scale(tfmdata,specification) local target={} if tonumber(specification) then @@ -5626,22 +6251,15 @@ function constructors.scale(tfmdata,specification) chr.top_accent=vdelta*va end if stackmath then - local mk=character.mathkerns + local mk=character.mathkerns if mk then - local kerns={} - local v=mk.top_right if v then local k={} for i=1,#v do local vi=v[i] - k[i]={ height=vdelta*vi.height,kern=vdelta*vi.kern } - end kerns.top_right=k end - local v=mk.top_left if v then local k={} for i=1,#v do local vi=v[i] - k[i]={ height=vdelta*vi.height,kern=vdelta*vi.kern } - end kerns.top_left=k end - local v=mk.bottom_left if v then local k={} for i=1,#v do local vi=v[i] - k[i]={ height=vdelta*vi.height,kern=vdelta*vi.kern } - end kerns.bottom_left=k end - local v=mk.bottom_right if v then local k={} for i=1,#v do local vi=v[i] - k[i]={ height=vdelta*vi.height,kern=vdelta*vi.kern } - end kerns.bottom_right=k end - chr.mathkern=kerns + local tr,tl,br,bl=mk.topright,mk.topleft,mk.bottomright,mk.bottomleft + chr.mathkern={ + top_right=tr and mathkerns(tr,vdelta) or nil, + top_left=tl and mathkerns(tl,vdelta) or nil, + bottom_right=br and mathkerns(br,vdelta) or nil, + bottom_left=bl and mathkerns(bl,vdelta) or nil, + } end end if hasitalics then @@ -5886,15 +6504,11 @@ hashmethods.normal=function(list) elseif k=="number" or k=="features" then else n=n+1 - s[n]=k + s[n]=k..'='..tostring(v) end end if n>0 then sort(s) - for i=1,n do - local k=s[i] - s[i]=k..'='..tostring(list[k]) - end return concat(s,"+") end end @@ -6041,7 +6655,10 @@ function constructors.getfeatureaction(what,where,mode,name) end end end -function constructors.newhandler(what) +local newhandler={} +constructors.handlers=newhandler +constructors.newhandler=newhandler +local function setnewhandler(what) local handler=handlers[what] if not handler then handler={} @@ -6049,7 +6666,14 @@ function constructors.newhandler(what) end return handler end -function constructors.newfeatures(what) +setmetatable(newhandler,{ + __call=function(t,k) local v=t[k] return v end, + __index=function(t,k) local v=setnewhandler(k) t[k]=v return v end, +}) +local newfeatures={} +constructors.newfeatures=newfeatures +constructors.features=newfeatures +local function setnewfeatures(what) local handler=handlers[what] local features=handler.features if not features then @@ -6068,6 +6692,10 @@ function constructors.newfeatures(what) end return features end +setmetatable(newfeatures,{ + __call=function(t,k) local v=t[k] return v end, + __index=function(t,k) local v=setnewfeatures(k) t[k]=v return v end, +}) function constructors.checkedfeatures(what,features) local defaults=handlers[what].features.defaults if features and next(features) then @@ -6087,7 +6715,6 @@ function constructors.initializefeatures(what,tfmdata,features,trace,report) local properties=tfmdata.properties or {} local whathandler=handlers[what] local whatfeatures=whathandler.features - local whatinitializers=whatfeatures.initializers local whatmodechecker=whatfeatures.modechecker local mode=properties.mode or (whatmodechecker and whatmodechecker(tfmdata,features,features.mode)) or features.mode or "base" properties.mode=mode @@ -6403,7 +7030,6 @@ if not modules then modules={} end modules ['font-map']={ local tonumber,next,type=tonumber,next,type local match,format,find,concat,gsub,lower=string.match,string.format,string.find,table.concat,string.gsub,string.lower local P,R,S,C,Ct,Cc,lpegmatch=lpeg.P,lpeg.R,lpeg.S,lpeg.C,lpeg.Ct,lpeg.Cc,lpeg.match -local utfbyte=utf.byte local floor=math.floor local formatters=string.formatters local trace_loading=false trackers.register("fonts.loading",function(v) trace_loading=v end) @@ -6414,7 +7040,7 @@ local fonts=fonts or {} local mappings=fonts.mappings or {} fonts.mappings=mappings local allocate=utilities.storage.allocate -local hex=R("AF","09") +local hex=R("AF","af","09") local hexfour=(hex*hex*hex^-2)/function(s) return tonumber(s,16) end local hexsix=(hex*hex*hex^-4)/function(s) return tonumber(s,16) end local dec=(R("09")^1)/tonumber @@ -6438,7 +7064,7 @@ local function makenameparser(str) end local f_single=formatters["%04X"] local f_double=formatters["%04X%04X"] -local function tounicode16(unicode,name) +local function tounicode16(unicode) if unicode<0xD7FF or (unicode>0xDFFF and unicode<=0xFFFF) then return f_single(unicode) else @@ -6446,7 +7072,7 @@ local function tounicode16(unicode,name) return f_double(floor(unicode/1024)+0xD800,unicode%1024+0xDC00) end end -local function tounicode16sequence(unicodes,name) +local function tounicode16sequence(unicodes) local t={} for l=1,#unicodes do local u=unicodes[l] @@ -6549,127 +7175,128 @@ function mappings.addtounicode(data,filename,checklookups) end local ns=0 local nl=0 - for unic,glyph in next,descriptions do + for du,glyph in next,descriptions do local name=glyph.name if name then - local index=glyph.index - local r=overloads[name] - if r then - glyph.unicode=r.unicode - elseif not unic or unic==-1 or unic>=private or (unic>=0xE000 and unic<=0xF8FF) or unic==0xFFFE or unic==0xFFFF then - local unicode=unicodevector[name] or contextvector[name] - if unicode then - glyph.unicode=unicode - ns=ns+1 - end - if (not unicode) and usedmap then - local foundindex=lpegmatch(oparser,name) - if foundindex then - unicode=cidcodes[foundindex] - if unicode then - glyph.unicode=unicode - ns=ns+1 - else - local reference=cidnames[foundindex] - if reference then - local foundindex=lpegmatch(oparser,reference) - if foundindex then - unicode=cidcodes[foundindex] - if unicode then - glyph.unicode=unicode - ns=ns+1 - end - end - if not unicode or unicode=="" then - local foundcodes,multiple=lpegmatch(uparser,reference) - if foundcodes then - glyph.unicode=foundcodes - if multiple then - nl=nl+1 - unicode=true - else + local overload=overloads[name] + if overload then + glyph.unicode=overload.unicode + else + local gu=glyph.unicode + if not gu or gu==-1 or du>=private or (du>=0xE000 and du<=0xF8FF) or du==0xFFFE or du==0xFFFF then + local unicode=unicodevector[name] or contextvector[name] + if unicode then + glyph.unicode=unicode + ns=ns+1 + end + if (not unicode) and usedmap then + local foundindex=lpegmatch(oparser,name) + if foundindex then + unicode=cidcodes[foundindex] + if unicode then + glyph.unicode=unicode + ns=ns+1 + else + local reference=cidnames[foundindex] + if reference then + local foundindex=lpegmatch(oparser,reference) + if foundindex then + unicode=cidcodes[foundindex] + if unicode then + glyph.unicode=unicode ns=ns+1 - unicode=foundcodes + end + end + if not unicode or unicode=="" then + local foundcodes,multiple=lpegmatch(uparser,reference) + if foundcodes then + glyph.unicode=foundcodes + if multiple then + nl=nl+1 + unicode=true + else + ns=ns+1 + unicode=foundcodes + end end end end end end end - end - if not unicode or unicode=="" then - local split=lpegmatch(namesplitter,name) - local nsplit=split and #split or 0 - if nsplit==0 then - elseif nsplit==1 then - local base=split[1] - local u=unicodes[base] or unicodevector[base] or contextvector[name] - if not u then - elseif type(u)=="table" then - if u[1]<private then - unicode=u - glyph.unicode=unicode - end - elseif u<private then - unicode=u - glyph.unicode=unicode - end - else - local t,n={},0 - for l=1,nsplit do - local base=split[l] + if not unicode or unicode=="" then + local split=lpegmatch(namesplitter,name) + local nsplit=split and #split or 0 + if nsplit==0 then + elseif nsplit==1 then + local base=split[1] local u=unicodes[base] or unicodevector[base] or contextvector[name] if not u then - break elseif type(u)=="table" then - if u[1]>=private then - break + if u[1]<private then + unicode=u + glyph.unicode=unicode end - n=n+1 - t[n]=u[1] - else - if u>=private then + elseif u<private then + unicode=u + glyph.unicode=unicode + end + else + local t,n={},0 + for l=1,nsplit do + local base=split[l] + local u=unicodes[base] or unicodevector[base] or contextvector[name] + if not u then break + elseif type(u)=="table" then + if u[1]>=private then + break + end + n=n+1 + t[n]=u[1] + else + if u>=private then + break + end + n=n+1 + t[n]=u end - n=n+1 - t[n]=u + end + if n>0 then + if n==1 then + unicode=t[1] + else + unicode=t + end + glyph.unicode=unicode end end - if n>0 then - if n==1 then - unicode=t[1] + nl=nl+1 + end + if not unicode or unicode=="" then + local foundcodes,multiple=lpegmatch(uparser,name) + if foundcodes then + glyph.unicode=foundcodes + if multiple then + nl=nl+1 + unicode=true else - unicode=t + ns=ns+1 + unicode=foundcodes end - glyph.unicode=unicode end end - nl=nl+1 - end - if not unicode or unicode=="" then - local foundcodes,multiple=lpegmatch(uparser,name) - if foundcodes then - glyph.unicode=foundcodes - if multiple then - nl=nl+1 - unicode=true - else - ns=ns+1 - unicode=foundcodes - end + local r=overloads[unicode] + if r then + unicode=r.unicode + glyph.unicode=unicode + end + if not unicode then + missing[du]=true + nofmissing=nofmissing+1 end - end - local r=overloads[unicode] - if r then - unicode=r.unicode - glyph.unicode=unicode - end - if not unicode then - missing[unic]=true - nofmissing=nofmissing+1 end end - else end end if type(checklookups)=="function" then @@ -6833,12 +7460,11 @@ local handlers=fonts.handlers local readers=fonts.readers local constructors=fonts.constructors local encodings=fonts.encodings -local tfm=constructors.newhandler("tfm") +local tfm=constructors.handlers.tfm tfm.version=1.000 tfm.maxnestingdepth=5 tfm.maxnestingsize=65536*1024 -local tfmfeatures=constructors.newfeatures("tfm") -local registertfmfeature=tfmfeatures.register +local tfmfeatures=constructors.features.tfm constructors.resolvevirtualtoo=false fonts.formats.tfm="type1" fonts.formats.ofm="type1" @@ -6973,1137 +7599,6 @@ end -- closure do -- begin closure to overcome local limits and interference -if not modules then modules={} end modules ['font-afm']={ - version=1.001, - comment="companion to font-ini.mkiv", - author="Hans Hagen, PRAGMA-ADE, Hasselt NL", - copyright="PRAGMA ADE / ConTeXt Development Team", - license="see context related readme files" -} -local fonts,logs,trackers,containers,resolvers=fonts,logs,trackers,containers,resolvers -local next,type,tonumber=next,type,tonumber -local match,gmatch,lower,gsub,strip,find=string.match,string.gmatch,string.lower,string.gsub,string.strip,string.find -local char,byte,sub=string.char,string.byte,string.sub -local abs=math.abs -local bxor,rshift=bit32.bxor,bit32.rshift -local P,S,R,Cmt,C,Ct,Cs,lpegmatch,patterns=lpeg.P,lpeg.S,lpeg.R,lpeg.Cmt,lpeg.C,lpeg.Ct,lpeg.Cs,lpeg.match,lpeg.patterns -local derivetable=table.derive -local trace_features=false trackers.register("afm.features",function(v) trace_features=v end) -local trace_indexing=false trackers.register("afm.indexing",function(v) trace_indexing=v end) -local trace_loading=false trackers.register("afm.loading",function(v) trace_loading=v end) -local trace_defining=false trackers.register("fonts.defining",function(v) trace_defining=v end) -local report_afm=logs.reporter("fonts","afm loading") -local setmetatableindex=table.setmetatableindex -local findbinfile=resolvers.findbinfile -local definers=fonts.definers -local readers=fonts.readers -local constructors=fonts.constructors -local afm=constructors.newhandler("afm") -local pfb=constructors.newhandler("pfb") -local afmfeatures=constructors.newfeatures("afm") -local registerafmfeature=afmfeatures.register -afm.version=1.501 -afm.cache=containers.define("fonts","afm",afm.version,true) -afm.autoprefixed=true -afm.helpdata={} -afm.syncspace=true -afm.addligatures=true -afm.addtexligatures=true -afm.addkerns=true -local overloads=fonts.mappings.overloads -local applyruntimefixes=fonts.treatments and fonts.treatments.applyfixes -local function setmode(tfmdata,value) - if value then - tfmdata.properties.mode=lower(value) - end -end -registerafmfeature { - name="mode", - description="mode", - initializers={ - base=setmode, - node=setmode, - } -} -local comment=P("Comment") -local spacing=patterns.spacer -local lineend=patterns.newline -local words=C((1-lineend)^1) -local number=C((R("09")+S("."))^1)/tonumber*spacing^0 -local data=lpeg.Carg(1) -local pattern=( - comment*spacing*( - data*( - ("CODINGSCHEME"*spacing*words )/function(fd,a) end+("DESIGNSIZE"*spacing*number*words )/function(fd,a) fd[ 1]=a end+("CHECKSUM"*spacing*number*words )/function(fd,a) fd[ 2]=a end+("SPACE"*spacing*number*"plus"*number*"minus"*number)/function(fd,a,b,c) fd[ 3],fd[ 4],fd[ 5]=a,b,c end+("QUAD"*spacing*number )/function(fd,a) fd[ 6]=a end+("EXTRASPACE"*spacing*number )/function(fd,a) fd[ 7]=a end+("NUM"*spacing*number*number*number )/function(fd,a,b,c) fd[ 8],fd[ 9],fd[10]=a,b,c end+("DENOM"*spacing*number*number )/function(fd,a,b ) fd[11],fd[12]=a,b end+("SUP"*spacing*number*number*number )/function(fd,a,b,c) fd[13],fd[14],fd[15]=a,b,c end+("SUB"*spacing*number*number )/function(fd,a,b) fd[16],fd[17]=a,b end+("SUPDROP"*spacing*number )/function(fd,a) fd[18]=a end+("SUBDROP"*spacing*number )/function(fd,a) fd[19]=a end+("DELIM"*spacing*number*number )/function(fd,a,b) fd[20],fd[21]=a,b end+("AXISHEIGHT"*spacing*number )/function(fd,a) fd[22]=a end - )+(1-lineend)^0 - )+(1-comment)^1 -)^0 -local function scan_comment(str) - local fd={} - lpegmatch(pattern,str,1,fd) - return fd -end -local keys={} -function keys.FontName (data,line) data.metadata.fontname=strip (line) - data.metadata.fullname=strip (line) end -function keys.ItalicAngle (data,line) data.metadata.italicangle=tonumber (line) end -function keys.IsFixedPitch(data,line) data.metadata.monospaced=toboolean(line,true) end -function keys.CharWidth (data,line) data.metadata.charwidth=tonumber (line) end -function keys.XHeight (data,line) data.metadata.xheight=tonumber (line) end -function keys.Descender (data,line) data.metadata.descender=tonumber (line) end -function keys.Ascender (data,line) data.metadata.ascender=tonumber (line) end -function keys.Comment (data,line) - line=lower(line) - local designsize=match(line,"designsize[^%d]*(%d+)") - if designsize then data.metadata.designsize=tonumber(designsize) end -end -local function get_charmetrics(data,charmetrics,vector) - local characters=data.characters - local chr,ind={},0 - for k,v in gmatch(charmetrics,"([%a]+) +(.-) *;") do - if k=='C' then - v=tonumber(v) - if v<0 then - ind=ind+1 - else - ind=v - end - chr={ - index=ind - } - elseif k=='WX' then - chr.width=tonumber(v) - elseif k=='N' then - characters[v]=chr - elseif k=='B' then - local llx,lly,urx,ury=match(v,"^ *(.-) +(.-) +(.-) +(.-)$") - chr.boundingbox={ tonumber(llx),tonumber(lly),tonumber(urx),tonumber(ury) } - elseif k=='L' then - local plus,becomes=match(v,"^(.-) +(.-)$") - local ligatures=chr.ligatures - if ligatures then - ligatures[plus]=becomes - else - chr.ligatures={ [plus]=becomes } - end - end - end -end -local function get_kernpairs(data,kernpairs) - local characters=data.characters - for one,two,value in gmatch(kernpairs,"KPX +(.-) +(.-) +(.-)\n") do - local chr=characters[one] - if chr then - local kerns=chr.kerns - if kerns then - kerns[two]=tonumber(value) - else - chr.kerns={ [two]=tonumber(value) } - end - end - end -end -local function get_variables(data,fontmetrics) - for key,rest in gmatch(fontmetrics,"(%a+) *(.-)[\n\r]") do - local keyhandler=keys[key] - if keyhandler then - keyhandler(data,rest) - end - end -end -local get_indexes -do - local fontloader=fontloader - local get_indexes_old=false - if fontloader then - local font_to_table=fontloader.to_table - local open_font=fontloader.open - local close_font=fontloader.close - get_indexes_old=function(data,pfbname) - local pfbblob=open_font(pfbname) - if pfbblob then - local characters=data.characters - local pfbdata=font_to_table(pfbblob) - if pfbdata then - local glyphs=pfbdata.glyphs - if glyphs then - if trace_loading then - report_afm("getting index data from %a",pfbname) - end - for index,glyph in next,glyphs do - local name=glyph.name - if name then - local char=characters[name] - if char then - if trace_indexing then - report_afm("glyph %a has index %a",name,index) - end - char.index=index - end - end - end - elseif trace_loading then - report_afm("no glyph data in pfb file %a",pfbname) - end - elseif trace_loading then - report_afm("no data in pfb file %a",pfbname) - end - close_font(pfbblob) - elseif trace_loading then - report_afm("invalid pfb file %a",pfbname) - end - end - end - local n,m - local progress=function(str,position,name,size) - local forward=position+tonumber(size)+3+2 - n=n+1 - if n>=m then - return #str,name - elseif forward<#str then - return forward,name - else - return #str,name - end - end - local initialize=function(str,position,size) - n=0 - m=tonumber(size) - return position+1 - end - local charstrings=P("/CharStrings") - local name=P("/")*C((R("az")+R("AZ")+R("09")+S("-_."))^1) - local size=C(R("09")^1) - local spaces=P(" ")^1 - local p_filternames=Ct ( - (1-charstrings)^0*charstrings*spaces*Cmt(size,initialize)*(Cmt(name*P(" ")^1*C(R("09")^1),progress)+P(1))^1 - ) - local decrypt - do - local r,c1,c2,n=0,0,0,0 - local function step(c) - local cipher=byte(c) - local plain=bxor(cipher,rshift(r,8)) - r=((cipher+r)*c1+c2)%65536 - return char(plain) - end - decrypt=function(binary) - r,c1,c2,n=55665,52845,22719,4 - binary=gsub(binary,".",step) - return sub(binary,n+1) - end - end - local function loadpfbvector(filename) - local data=io.loaddata(resolvers.findfile(filename)) - if not find(data,"!PS%-AdobeFont%-") then - print("no font",filename) - return - end - if not data then - print("no data",filename) - return - end - local ascii,binary=match(data,"(.*)eexec%s+......(.*)") - if not binary then - print("no binary",filename) - return - end - binary=decrypt(binary,4) - local vector=lpegmatch(p_filternames,binary) - vector[0]=table.remove(vector,1) - if not vector then - print("no vector",filename) - return - end - return vector - end - get_indexes=function(data,pfbname) - local vector=loadpfbvector(pfbname) - if vector then - local characters=data.characters - if trace_loading then - report_afm("getting index data from %a",pfbname) - end - for index=1,#vector do - local name=vector[index] - local char=characters[name] - if char then - if trace_indexing then - report_afm("glyph %a has index %a",name,index) - end - char.index=index - end - end - end - end - if get_indexes_old then - afm.use_new_indexer=true - get_indexes_new=get_indexes - get_indexes=function(data,pfbname) - if afm.use_new_indexer then - return get_indexes_new(data,pfbname) - else - return get_indexes_old(data,pfbname) - end - end - end -end -local function readafm(filename) - local ok,afmblob,size=resolvers.loadbinfile(filename) - if ok and afmblob then - local data={ - resources={ - filename=resolvers.unresolve(filename), - version=afm.version, - creator="context mkiv", - }, - properties={ - hasitalics=false, - }, - goodies={}, - metadata={ - filename=file.removesuffix(file.basename(filename)) - }, - characters={ - }, - descriptions={ - }, - } - afmblob=gsub(afmblob,"StartCharMetrics(.-)EndCharMetrics",function(charmetrics) - if trace_loading then - report_afm("loading char metrics") - end - get_charmetrics(data,charmetrics,vector) - return "" - end) - afmblob=gsub(afmblob,"StartKernPairs(.-)EndKernPairs",function(kernpairs) - if trace_loading then - report_afm("loading kern pairs") - end - get_kernpairs(data,kernpairs) - return "" - end) - afmblob=gsub(afmblob,"StartFontMetrics%s+([%d%.]+)(.-)EndFontMetrics",function(version,fontmetrics) - if trace_loading then - report_afm("loading variables") - end - data.afmversion=version - get_variables(data,fontmetrics) - data.fontdimens=scan_comment(fontmetrics) - return "" - end) - return data - else - if trace_loading then - report_afm("no valid afm file %a",filename) - end - return nil - end -end -local addkerns,addligatures,addtexligatures,unify,normalize,fixnames -function afm.load(filename) - filename=resolvers.findfile(filename,'afm') or "" - if filename~="" and not fonts.names.ignoredfile(filename) then - local name=file.removesuffix(file.basename(filename)) - local data=containers.read(afm.cache,name) - local attr=lfs.attributes(filename) - local size,time=attr.size or 0,attr.modification or 0 - local pfbfile=file.replacesuffix(name,"pfb") - local pfbname=resolvers.findfile(pfbfile,"pfb") or "" - if pfbname=="" then - pfbname=resolvers.findfile(file.basename(pfbfile),"pfb") or "" - end - local pfbsize,pfbtime=0,0 - if pfbname~="" then - local attr=lfs.attributes(pfbname) - pfbsize=attr.size or 0 - pfbtime=attr.modification or 0 - end - if not data or data.size~=size or data.time~=time or data.pfbsize~=pfbsize or data.pfbtime~=pfbtime then - report_afm("reading %a",filename) - data=readafm(filename) - if data then - if pfbname~="" then - data.resources.filename=resolvers.unresolve(pfbname) - get_indexes(data,pfbname) - elseif trace_loading then - report_afm("no pfb file for %a",filename) - end - report_afm("unifying %a",filename) - unify(data,filename) - if afm.addligatures then - report_afm("add ligatures") - addligatures(data) - end - if afm.addtexligatures then - report_afm("add tex ligatures") - addtexligatures(data) - end - if afm.addkerns then - report_afm("add extra kerns") - addkerns(data) - end - normalize(data) - fixnames(data) - report_afm("add tounicode data") - fonts.mappings.addtounicode(data,filename) - data.size=size - data.time=time - data.pfbsize=pfbsize - data.pfbtime=pfbtime - report_afm("saving %a in cache",name) - data.resources.unicodes=nil - data=containers.write(afm.cache,name,data) - data=containers.read(afm.cache,name) - end - if applyruntimefixes and data then - applyruntimefixes(filename,data) - end - end - return data - else - return nil - end -end -local uparser=fonts.mappings.makenameparser() -unify=function(data,filename) - local unicodevector=fonts.encodings.agl.unicodes - local unicodes={} - local names={} - local private=constructors.privateoffset - local descriptions=data.descriptions - for name,blob in next,data.characters do - local code=unicodevector[name] - if not code then - code=lpegmatch(uparser,name) - if not code then - code=private - private=private+1 - report_afm("assigning private slot %U for unknown glyph name %a",code,name) - end - end - local index=blob.index - unicodes[name]=code - names[name]=index - blob.name=name - descriptions[code]={ - boundingbox=blob.boundingbox, - width=blob.width, - kerns=blob.kerns, - index=index, - name=name, - } - end - for unicode,description in next,descriptions do - local kerns=description.kerns - if kerns then - local krn={} - for name,kern in next,kerns do - local unicode=unicodes[name] - if unicode then - krn[unicode]=kern - else - end - end - description.kerns=krn - end - end - data.characters=nil - local resources=data.resources - local filename=resources.filename or file.removesuffix(file.basename(filename)) - resources.filename=resolvers.unresolve(filename) - resources.unicodes=unicodes - resources.marks={} - resources.private=private -end -local everywhere={ ["*"]={ ["*"]=true } } -local noflags={ false,false,false,false } -afm.experimental_normalize=false -normalize=function(data) - if type(afm.experimental_normalize)=="function" then - afm.experimental_normalize(data) - end -end -fixnames=function(data) - for k,v in next,data.descriptions do - local n=v.name - local r=overloads[n] - if r then - local name=r.name - if trace_indexing then - report_afm("renaming characters %a to %a",n,name) - end - v.name=name - v.unicode=r.unicode - end - end -end -local addthem=function(rawdata,ligatures) - if ligatures then - local descriptions=rawdata.descriptions - local resources=rawdata.resources - local unicodes=resources.unicodes - for ligname,ligdata in next,ligatures do - local one=descriptions[unicodes[ligname]] - if one then - for _,pair in next,ligdata do - local two,three=unicodes[pair[1]],unicodes[pair[2]] - if two and three then - local ol=one.ligatures - if ol then - if not ol[two] then - ol[two]=three - end - else - one.ligatures={ [two]=three } - end - end - end - end - end - end -end -addligatures=function(rawdata) addthem(rawdata,afm.helpdata.ligatures ) end -addtexligatures=function(rawdata) addthem(rawdata,afm.helpdata.texligatures) end -addkerns=function(rawdata) - local descriptions=rawdata.descriptions - local resources=rawdata.resources - local unicodes=resources.unicodes - local function do_it_left(what) - if what then - for unicode,description in next,descriptions do - local kerns=description.kerns - if kerns then - local extrakerns - for complex,simple in next,what do - complex=unicodes[complex] - simple=unicodes[simple] - if complex and simple then - local ks=kerns[simple] - if ks and not kerns[complex] then - if extrakerns then - extrakerns[complex]=ks - else - extrakerns={ [complex]=ks } - end - end - end - end - if extrakerns then - description.extrakerns=extrakerns - end - end - end - end - end - local function do_it_copy(what) - if what then - for complex,simple in next,what do - complex=unicodes[complex] - simple=unicodes[simple] - if complex and simple then - local complexdescription=descriptions[complex] - if complexdescription then - local simpledescription=descriptions[complex] - if simpledescription then - local extrakerns - local kerns=simpledescription.kerns - if kerns then - for unicode,kern in next,kerns do - if extrakerns then - extrakerns[unicode]=kern - else - extrakerns={ [unicode]=kern } - end - end - end - local extrakerns=simpledescription.extrakerns - if extrakerns then - for unicode,kern in next,extrakerns do - if extrakerns then - extrakerns[unicode]=kern - else - extrakerns={ [unicode]=kern } - end - end - end - if extrakerns then - complexdescription.extrakerns=extrakerns - end - end - end - end - end - end - end - do_it_left(afm.helpdata.leftkerned) - do_it_left(afm.helpdata.bothkerned) - do_it_copy(afm.helpdata.bothkerned) - do_it_copy(afm.helpdata.rightkerned) -end -local function adddimensions(data) - if data then - for unicode,description in next,data.descriptions do - local bb=description.boundingbox - if bb then - local ht,dp=bb[4],-bb[2] - if ht==0 or ht<0 then - else - description.height=ht - end - if dp==0 or dp<0 then - else - description.depth=dp - end - end - end - end -end -local function copytotfm(data) - if data and data.descriptions then - local metadata=data.metadata - local resources=data.resources - local properties=derivetable(data.properties) - local descriptions=derivetable(data.descriptions) - local goodies=derivetable(data.goodies) - local characters={} - local parameters={} - local unicodes=resources.unicodes - for unicode,description in next,data.descriptions do - characters[unicode]={} - end - local filename=constructors.checkedfilename(resources) - local fontname=metadata.fontname or metadata.fullname - local fullname=metadata.fullname or metadata.fontname - local endash=0x0020 - local emdash=0x2014 - local spacer="space" - local spaceunits=500 - local monospaced=metadata.monospaced - local charwidth=metadata.charwidth - local italicangle=metadata.italicangle - local charxheight=metadata.xheight and metadata.xheight>0 and metadata.xheight - properties.monospaced=monospaced - parameters.italicangle=italicangle - parameters.charwidth=charwidth - parameters.charxheight=charxheight - if properties.monospaced then - if descriptions[endash] then - spaceunits,spacer=descriptions[endash].width,"space" - end - if not spaceunits and descriptions[emdash] then - spaceunits,spacer=descriptions[emdash].width,"emdash" - end - if not spaceunits and charwidth then - spaceunits,spacer=charwidth,"charwidth" - end - else - if descriptions[endash] then - spaceunits,spacer=descriptions[endash].width,"space" - end - if not spaceunits and charwidth then - spaceunits,spacer=charwidth,"charwidth" - end - end - spaceunits=tonumber(spaceunits) - if spaceunits<200 then - end - parameters.slant=0 - parameters.space=spaceunits - parameters.space_stretch=500 - parameters.space_shrink=333 - parameters.x_height=400 - parameters.quad=1000 - if italicangle and italicangle~=0 then - parameters.italicangle=italicangle - parameters.italicfactor=math.cos(math.rad(90+italicangle)) - parameters.slant=- math.tan(italicangle*math.pi/180) - end - if monospaced then - parameters.space_stretch=0 - parameters.space_shrink=0 - elseif afm.syncspace then - parameters.space_stretch=spaceunits/2 - parameters.space_shrink=spaceunits/3 - end - parameters.extra_space=parameters.space_shrink - if charxheight then - parameters.x_height=charxheight - else - local x=0x0078 - if x then - local x=descriptions[x] - if x then - parameters.x_height=x.height - end - end - end - local fd=data.fontdimens - if fd and fd[8] and fd[9] and fd[10] then - for k,v in next,fd do - parameters[k]=v - end - end - parameters.designsize=(metadata.designsize or 10)*65536 - parameters.ascender=abs(metadata.ascender or 0) - parameters.descender=abs(metadata.descender or 0) - parameters.units=1000 - properties.spacer=spacer - properties.encodingbytes=2 - properties.format=fonts.formats[filename] or "type1" - properties.filename=filename - properties.fontname=fontname - properties.fullname=fullname - properties.psname=fullname - properties.name=filename or fullname or fontname - if next(characters) then - return { - characters=characters, - descriptions=descriptions, - parameters=parameters, - resources=resources, - properties=properties, - goodies=goodies, - } - end - end - return nil -end -function afm.setfeatures(tfmdata,features) - local okay=constructors.initializefeatures("afm",tfmdata,features,trace_features,report_afm) - if okay then - return constructors.collectprocessors("afm",tfmdata,features,trace_features,report_afm) - else - return {} - end -end -local function addtables(data) - local resources=data.resources - local lookuptags=resources.lookuptags - local unicodes=resources.unicodes - if not lookuptags then - lookuptags={} - resources.lookuptags=lookuptags - 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 - end - end - return rawget(t,k) - end) - end - constructors.addcoreunicodes(unicodes) -end -local function afmtotfm(specification) - local afmname=specification.filename or specification.name - if specification.forced=="afm" or specification.format=="afm" then - if trace_loading then - report_afm("forcing afm format for %a",afmname) - end - else - local tfmname=findbinfile(afmname,"ofm") or "" - if tfmname~="" then - if trace_loading then - report_afm("fallback from afm to tfm for %a",afmname) - end - return - end - end - if afmname~="" then - local features=constructors.checkedfeatures("afm",specification.features.normal) - specification.features.normal=features - constructors.hashinstance(specification,true) - specification=definers.resolve(specification) - local cache_id=specification.hash - local tfmdata=containers.read(constructors.cache,cache_id) - if not tfmdata then - local rawdata=afm.load(afmname) - if rawdata and next(rawdata) then - addtables(rawdata) - adddimensions(rawdata) - tfmdata=copytotfm(rawdata) - if tfmdata and next(tfmdata) then - local shared=tfmdata.shared - if not shared then - shared={} - tfmdata.shared=shared - end - shared.rawdata=rawdata - shared.features=features - shared.processes=afm.setfeatures(tfmdata,features) - end - elseif trace_loading then - report_afm("no (valid) afm file found with name %a",afmname) - end - tfmdata=containers.write(constructors.cache,cache_id,tfmdata) - end - return tfmdata - end -end -local function read_from_afm(specification) - local tfmdata=afmtotfm(specification) - if tfmdata then - tfmdata.properties.name=specification.name - tfmdata=constructors.scale(tfmdata,specification) - local allfeatures=tfmdata.shared.features or specification.features.normal - constructors.applymanipulators("afm",tfmdata,allfeatures,trace_features,report_afm) - fonts.loggers.register(tfmdata,'afm',specification) - end - return tfmdata -end -local function prepareligatures(tfmdata,ligatures,value) - if value then - local descriptions=tfmdata.descriptions - local hasligatures=false - for unicode,character in next,tfmdata.characters do - local description=descriptions[unicode] - local dligatures=description.ligatures - if dligatures then - local cligatures=character.ligatures - if not cligatures then - cligatures={} - character.ligatures=cligatures - end - for unicode,ligature in next,dligatures do - cligatures[unicode]={ - char=ligature, - type=0 - } - end - hasligatures=true - end - end - tfmdata.properties.hasligatures=hasligatures - end -end -local function preparekerns(tfmdata,kerns,value) - if value then - local rawdata=tfmdata.shared.rawdata - local resources=rawdata.resources - local unicodes=resources.unicodes - local descriptions=tfmdata.descriptions - local haskerns=false - for u,chr in next,tfmdata.characters do - local d=descriptions[u] - local newkerns=d[kerns] - if newkerns then - local kerns=chr.kerns - if not kerns then - kerns={} - chr.kerns=kerns - end - for k,v in next,newkerns do - local uk=unicodes[k] - if uk then - kerns[uk]=v - end - end - haskerns=true - end - end - tfmdata.properties.haskerns=haskerns - end -end -local list={ - [0x0027]=0x2019, -} -local function texreplacements(tfmdata,value) - local descriptions=tfmdata.descriptions - local characters=tfmdata.characters - for k,v in next,list do - characters [k]=characters [v] - descriptions[k]=descriptions[v] - end -end -local function ligatures (tfmdata,value) prepareligatures(tfmdata,'ligatures',value) end -local function texligatures(tfmdata,value) prepareligatures(tfmdata,'texligatures',value) end -local function kerns (tfmdata,value) preparekerns (tfmdata,'kerns',value) end -local function extrakerns (tfmdata,value) preparekerns (tfmdata,'extrakerns',value) end -registerafmfeature { - name="liga", - description="traditional ligatures", - initializers={ - base=ligatures, - node=ligatures, - } -} -registerafmfeature { - name="kern", - description="intercharacter kerning", - initializers={ - base=kerns, - node=kerns, - } -} -registerafmfeature { - name="extrakerns", - description="additional intercharacter kerning", - initializers={ - base=extrakerns, - node=extrakerns, - } -} -registerafmfeature { - name='tlig', - description='tex ligatures', - initializers={ - base=texligatures, - node=texligatures, - } -} -registerafmfeature { - name='trep', - description='tex replacements', - initializers={ - base=texreplacements, - node=texreplacements, - } -} -local check_tfm=readers.check_tfm -fonts.formats.afm="type1" -fonts.formats.pfb="type1" -local function check_afm(specification,fullname) - local foundname=findbinfile(fullname,'afm') or "" - if foundname=="" then - foundname=fonts.names.getfilename(fullname,"afm") or "" - end - if foundname=="" and afm.autoprefixed then - local encoding,shortname=match(fullname,"^(.-)%-(.*)$") - if encoding and shortname and fonts.encodings.known[encoding] then - shortname=findbinfile(shortname,'afm') or "" - if shortname~="" then - foundname=shortname - if trace_defining then - report_afm("stripping encoding prefix from filename %a",afmname) - end - end - end - end - if foundname~="" then - specification.filename=foundname - specification.format="afm" - return read_from_afm(specification) - end -end -function readers.afm(specification,method) - local fullname,tfmdata=specification.filename or "",nil - if fullname=="" then - local forced=specification.forced or "" - if forced~="" then - tfmdata=check_afm(specification,specification.name.."."..forced) - end - if not tfmdata then - method=method or definers.method or "afm or tfm" - if method=="tfm" then - tfmdata=check_tfm(specification,specification.name) - elseif method=="afm" then - tfmdata=check_afm(specification,specification.name) - elseif method=="tfm or afm" then - tfmdata=check_tfm(specification,specification.name) or check_afm(specification,specification.name) - else - tfmdata=check_afm(specification,specification.name) or check_tfm(specification,specification.name) - end - end - else - tfmdata=check_afm(specification,fullname) - end - return tfmdata -end -function readers.pfb(specification,method) - local original=specification.specification - if trace_defining then - report_afm("using afm reader for %a",original) - end - specification.specification=gsub(original,"%.pfb",".afm") - specification.forced="afm" - return readers.afm(specification,method) -end - -end -- closure - -do -- begin closure to overcome local limits and interference - -if not modules then modules={} end modules ['font-afk']={ - version=1.001, - comment="companion to font-afm.lua", - author="Hans Hagen, PRAGMA-ADE, Hasselt NL", - copyright="PRAGMA ADE / ConTeXt Development Team", - license="see context related readme files", - dataonly=true, -} -local allocate=utilities.storage.allocate -fonts.handlers.afm.helpdata={ - ligatures=allocate { - ['f']={ - { 'f','ff' }, - { 'i','fi' }, - { 'l','fl' }, - }, - ['ff']={ - { 'i','ffi' } - }, - ['fi']={ - { 'i','fii' } - }, - ['fl']={ - { 'i','fli' } - }, - ['s']={ - { 't','st' } - }, - ['i']={ - { 'j','ij' } - }, - }, - texligatures=allocate { - ['quoteleft']={ - { 'quoteleft','quotedblleft' } - }, - ['quoteright']={ - { 'quoteright','quotedblright' } - }, - ['hyphen']={ - { 'hyphen','endash' } - }, - ['endash']={ - { 'hyphen','emdash' } - } - }, - leftkerned=allocate { - AEligature="A",aeligature="a", - OEligature="O",oeligature="o", - IJligature="I",ijligature="i", - AE="A",ae="a", - OE="O",oe="o", - IJ="I",ij="i", - Ssharp="S",ssharp="s", - }, - rightkerned=allocate { - AEligature="E",aeligature="e", - OEligature="E",oeligature="e", - IJligature="J",ijligature="j", - AE="E",ae="e", - OE="E",oe="e", - IJ="J",ij="j", - Ssharp="S",ssharp="s", - }, - bothkerned=allocate { - Acircumflex="A",acircumflex="a", - Ccircumflex="C",ccircumflex="c", - Ecircumflex="E",ecircumflex="e", - Gcircumflex="G",gcircumflex="g", - Hcircumflex="H",hcircumflex="h", - Icircumflex="I",icircumflex="i", - Jcircumflex="J",jcircumflex="j", - Ocircumflex="O",ocircumflex="o", - Scircumflex="S",scircumflex="s", - Ucircumflex="U",ucircumflex="u", - Wcircumflex="W",wcircumflex="w", - Ycircumflex="Y",ycircumflex="y", - Agrave="A",agrave="a", - Egrave="E",egrave="e", - Igrave="I",igrave="i", - Ograve="O",ograve="o", - Ugrave="U",ugrave="u", - Ygrave="Y",ygrave="y", - Atilde="A",atilde="a", - Itilde="I",itilde="i", - Otilde="O",otilde="o", - Utilde="U",utilde="u", - Ntilde="N",ntilde="n", - Adiaeresis="A",adiaeresis="a",Adieresis="A",adieresis="a", - Ediaeresis="E",ediaeresis="e",Edieresis="E",edieresis="e", - Idiaeresis="I",idiaeresis="i",Idieresis="I",idieresis="i", - Odiaeresis="O",odiaeresis="o",Odieresis="O",odieresis="o", - Udiaeresis="U",udiaeresis="u",Udieresis="U",udieresis="u", - Ydiaeresis="Y",ydiaeresis="y",Ydieresis="Y",ydieresis="y", - Aacute="A",aacute="a", - Cacute="C",cacute="c", - Eacute="E",eacute="e", - Iacute="I",iacute="i", - Lacute="L",lacute="l", - Nacute="N",nacute="n", - Oacute="O",oacute="o", - Racute="R",racute="r", - Sacute="S",sacute="s", - Uacute="U",uacute="u", - Yacute="Y",yacute="y", - Zacute="Z",zacute="z", - Dstroke="D",dstroke="d", - Hstroke="H",hstroke="h", - Tstroke="T",tstroke="t", - Cdotaccent="C",cdotaccent="c", - Edotaccent="E",edotaccent="e", - Gdotaccent="G",gdotaccent="g", - Idotaccent="I",idotaccent="i", - Zdotaccent="Z",zdotaccent="z", - Amacron="A",amacron="a", - Emacron="E",emacron="e", - Imacron="I",imacron="i", - Omacron="O",omacron="o", - Umacron="U",umacron="u", - Ccedilla="C",ccedilla="c", - Kcedilla="K",kcedilla="k", - Lcedilla="L",lcedilla="l", - Ncedilla="N",ncedilla="n", - Rcedilla="R",rcedilla="r", - Scedilla="S",scedilla="s", - Tcedilla="T",tcedilla="t", - Ohungarumlaut="O",ohungarumlaut="o", - Uhungarumlaut="U",uhungarumlaut="u", - Aogonek="A",aogonek="a", - Eogonek="E",eogonek="e", - Iogonek="I",iogonek="i", - Uogonek="U",uogonek="u", - Aring="A",aring="a", - Uring="U",uring="u", - Abreve="A",abreve="a", - Ebreve="E",ebreve="e", - Gbreve="G",gbreve="g", - Ibreve="I",ibreve="i", - Obreve="O",obreve="o", - Ubreve="U",ubreve="u", - Ccaron="C",ccaron="c", - Dcaron="D",dcaron="d", - Ecaron="E",ecaron="e", - Lcaron="L",lcaron="l", - Ncaron="N",ncaron="n", - Rcaron="R",rcaron="r", - Scaron="S",scaron="s", - Tcaron="T",tcaron="t", - Zcaron="Z",zcaron="z", - dotlessI="I",dotlessi="i", - dotlessJ="J",dotlessj="j", - AEligature="AE",aeligature="ae",AE="AE",ae="ae", - OEligature="OE",oeligature="oe",OE="OE",oe="oe", - IJligature="IJ",ijligature="ij",IJ="IJ",ij="ij", - Lstroke="L",lstroke="l",Lslash="L",lslash="l", - Ostroke="O",ostroke="o",Oslash="O",oslash="o", - Ssharp="SS",ssharp="ss", - Aumlaut="A",aumlaut="a", - Eumlaut="E",eumlaut="e", - Iumlaut="I",iumlaut="i", - Oumlaut="O",oumlaut="o", - Uumlaut="U",uumlaut="u", - } -} - -end -- closure - -do -- begin closure to overcome local limits and interference - if not modules then modules={} end modules ['font-oti']={ version=1.001, comment="companion to font-ini.mkiv", @@ -8114,8 +7609,8 @@ if not modules then modules={} end modules ['font-oti']={ local lower=string.lower local fonts=fonts local constructors=fonts.constructors -local otf=constructors.newhandler("otf") -local otffeatures=constructors.newfeatures("otf") +local otf=constructors.handlers.otf +local otffeatures=constructors.features.otf local registerotffeature=otffeatures.register local otftables=otf.tables or {} otf.tables=otftables @@ -8278,7 +7773,6 @@ 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 @@ -8286,8 +7780,7 @@ 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 readulong=streamreader.readcardinal4 local readshort=streamreader.readinteger2 local readlong=streamreader.readinteger4 local readfixed=streamreader.readfixed4 @@ -8303,8 +7796,8 @@ local function readlongdatetime(f) 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 privateoffset=fonts.constructors and fonts.constructors.privateoffset or 0xF0000 local reportedskipped={} local function reportskippedtable(tag) if not reportedskipped[tag] then @@ -9308,6 +8801,21 @@ function readers.glyf(f,fontdata,specification) reportskippedtable("glyf") end end +function readers.colr(f,fontdata,specification) + if specification.glyphs then + reportskippedtable("colr") + end +end +function readers.cpal(f,fontdata,specification) + if specification.glyphs then + reportskippedtable("cpal") + end +end +function readers.svg(f,fontdata,specification) + if specification.glyphs then + reportskippedtable("svg") + end +end function readers.kern(f,fontdata,specification) if specification.kerns then local datatable=fontdata.tables.kern @@ -9616,6 +9124,9 @@ local function readdata(f,offset,specification) readers["cmap"](f,fontdata,specification) readers["loca"](f,fontdata,specification) readers["glyf"](f,fontdata,specification) + readers["colr"](f,fontdata,specification) + readers["cpal"](f,fontdata,specification) + readers["svg" ](f,fontdata,specification) readers["kern"](f,fontdata,specification) readers["gdef"](f,fontdata,specification) readers["gsub"](f,fontdata,specification) @@ -9771,6 +9282,8 @@ function readers.loadfont(filename,n) metadata=getinfo(fontdata,n), properties={ hasitalics=fontdata.hasitalics or false, + maxcolorclass=fontdata.maxcolorclass, + hascolor=fontdata.hascolor or false, }, resources={ filename=filename, @@ -9786,6 +9299,8 @@ function readers.loadfont(filename,n) version=getname(fontdata,"version"), cidinfo=fontdata.cidinfo, mathconstants=fontdata.mathconstants, + colorpalettes=fontdata.colorpalettes, + svgshapes=fontdata.svgshapes, }, } end @@ -11727,7 +11242,6 @@ 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 @@ -11735,6 +11249,7 @@ local readshort=streamreader.readinteger2 local readfword=readshort local readstring=streamreader.readstring local readtag=streamreader.readtag +local readbytes=streamreader.readbytes local gsubhandlers={} local gposhandlers={} local lookupidoffset=-1 @@ -13091,6 +12606,13 @@ do end end local reported={} + local function report_issue(i,what,sequence,kind) + local name=sequence.name + if not reported[name] then + report("rule %i in %s lookup %a has %s lookups",i,what,name,kind) + reported[name]=true + end + end for i=lastsequence+1,nofsequences do local sequence=sequences[i] local steps=sequence.steps @@ -13102,37 +12624,42 @@ 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 + report_issue(i,what,sequence,"no") 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 + report_issue(i,what,sequence,"empty") 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 + local lookup=lookups[lookupid] + if lookup then + local d=lookup.done + if d then + nofsublookups=nofsublookups+1 + 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 + report_issue(i,what,sequence,"missing") + rule.lookups=nil + break + end + else + report_issue(i,what,sequence,"bad") + rule.lookups=nil + break + end else sublookupcheck[lookupid]=sublookupcheck[lookupid]+1 end @@ -13473,7 +13000,13 @@ local function readmathglyphinfo(f,fontdata,offset) local function get(offset) setposition(f,kernoffset+offset) local n=readushort(f) - if n>0 then + if n==0 then + local k=readmathvalue(f) + if k==0 then + else + return { { kern=k } } + end + else local l={} for i=1,n do l[i]={ height=readmathvalue(f) } @@ -13509,10 +13042,10 @@ local function readmathglyphinfo(f,fontdata,offset) if next(kernset) then local glyph=glyphs[coverage[i]] local math=glyph.math - if not math then - glyph.math={ kerns=kernset } - else + if math then math.kerns=kernset + else + glyph.math={ kerns=kernset } end end end @@ -13616,7 +13149,7 @@ function readers.math(f,fontdata,specification) 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) + report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"math",fontdata.filename) return end local constants=readushort(f) @@ -13636,6 +13169,142 @@ function readers.math(f,fontdata,specification) end end end +function readers.colr(f,fontdata,specification) + local datatable=fontdata.tables.colr + if datatable then + if specification.glyphs then + local tableoffset=datatable.offset + setposition(f,tableoffset) + local version=readushort(f) + if version~=0 then + report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"colr",fontdata.filename) + return + end + if not fontdata.tables.cpal then + report("color table %a in font %a has no mandate %a table","colr",fontdata.filename,"cpal") + fontdata.colorpalettes={} + end + local glyphs=fontdata.glyphs + local nofglyphs=readushort(f) + local baseoffset=readulong(f) + local layeroffset=readulong(f) + local noflayers=readushort(f) + local layerrecords={} + local maxclass=0 + setposition(f,tableoffset+layeroffset) + for i=1,noflayers do + local slot=readushort(f) + local class=readushort(f) + if class<0xFFFF then + class=class+1 + if class>maxclass then + maxclass=class + end + end + layerrecords[i]={ + slot=slot, + class=class, + } + end + fontdata.maxcolorclass=maxclass + setposition(f,tableoffset+baseoffset) + for i=0,nofglyphs-1 do + local glyphindex=readushort(f) + local firstlayer=readushort(f) + local noflayers=readushort(f) + local t={} + for i=1,noflayers do + t[i]=layerrecords[firstlayer+i] + end + glyphs[glyphindex].colors=t + end + end + fontdata.hascolor=true + end +end +function readers.cpal(f,fontdata,specification) + if specification.glyphs then + local datatable=fontdata.tables.cpal + if datatable then + local tableoffset=datatable.offset + setposition(f,tableoffset) + local version=readushort(f) + if version>1 then + report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"cpal",fontdata.filename) + return + end + local nofpaletteentries=readushort(f) + local nofpalettes=readushort(f) + local nofcolorrecords=readushort(f) + local firstcoloroffset=readulong(f) + local colorrecords={} + local palettes={} + for i=1,nofpalettes do + palettes[i]=readushort(f) + end + if version==1 then + local palettettypesoffset=readulong(f) + local palettelabelsoffset=readulong(f) + local paletteentryoffset=readulong(f) + end + setposition(f,tableoffset+firstcoloroffset) + for i=1,nofcolorrecords do + local b,g,r,a=readbytes(f,4) + colorrecords[i]={ + r,g,b,a~=255 and a or nil, + } + end + for i=1,nofpalettes do + local p={} + local o=palettes[i] + for j=1,nofpaletteentries do + p[j]=colorrecords[o+j] + end + palettes[i]=p + end + fontdata.colorpalettes=palettes + end + end +end +function readers.svg(f,fontdata,specification) + local datatable=fontdata.tables.svg + if datatable then + if specification.glyphs then + local tableoffset=datatable.offset + setposition(f,tableoffset) + local version=readushort(f) + if version~=0 then + report("table version %a of %a is not supported (yet), maybe font %s is bad",version,"svg",fontdata.filename) + return + end + local glyphs=fontdata.glyphs + local indexoffset=tableoffset+readulong(f) + local reserved=readulong(f) + setposition(f,indexoffset) + local nofentries=readushort(f) + local entries={} + for i=1,nofentries do + entries[i]={ + first=readushort(f), + last=readushort(f), + offset=indexoffset+readulong(f), + length=readulong(f), + } + end + for i=1,nofentries do + local entry=entries[i] + setposition(f,entry.offset) + entries[i]={ + first=entry.first, + last=entry.last, + data=readstring(f,entry.length) + } + end + fontdata.svgshapes=entries + end + fontdata.hascolor=true + end +end end -- closure @@ -14183,7 +13852,8 @@ local function checklookups(fontdata,missing,nofmissing) local done={} for i,r in next,missing do if r then - local name=descriptions[i].name or f_index(i) + local data=descriptions[i] + local name=data and data.name or f_index(i) if not ignore[name] then done[name]=true end @@ -14292,6 +13962,18 @@ local function unifyglyphs(fontdata,usenames) end end end + local colorpalettes=resources.colorpalettes + if colorpalettes then + for index=1,#glyphs do + local colors=glyphs[index].colors + if colors then + for i=1,#colors do + local c=colors[i] + c.slot=indices[c.slot] + end + end + end + end fontdata.private=private fontdata.glyphs=nil fontdata.names=names @@ -14692,6 +14374,7 @@ function readers.pack(data) local sequences=resources.sequences local sublookups=resources.sublookups local features=resources.features + local palettes=resources.colorpalettes local chardata=characters and characters.data local descriptions=data.descriptions or data.glyphs if not descriptions then @@ -14834,6 +14517,14 @@ function readers.pack(data) end end end + if palettes then + for i=1,#palettes do + local p=palettes[i] + for j=1,#p do + p[j]=pack_indexed(p[j]) + end + end + end if not success(1,pass) then return end @@ -14954,6 +14645,7 @@ function readers.unpack(data) local sequences=resources.sequences local sublookups=resources.sublookups local features=resources.features + local palettes=resources.colorpalettes local unpacked={} setmetatable(unpacked,unpacked_mt) for unicode,description in next,descriptions do @@ -15192,6 +14884,17 @@ function readers.unpack(data) end end end + if palettes then + for i=1,#palettes do + local p=palettes[i] + for j=1,#p do + local tv=tables[p[j]] + if tv then + p[j]=tv + end + end + end + end data.tables=nil end end @@ -15645,14 +15348,17 @@ local trace_defining=false registertracker("fonts.defining",function(v) trace_de local report_otf=logs.reporter("fonts","otf loading") local fonts=fonts local otf=fonts.handlers.otf -otf.version=3.019 +otf.version=3.022 otf.cache=containers.define("fonts","otl",otf.version,true) +otf.svgcache=containers.define("fonts","svg",otf.version,true) +otf.pdfcache=containers.define("fonts","pdf",otf.version,true) +otf.svgenabled=false 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 otffeatures=constructors.features.otf local registerotffeature=otffeatures.register local enhancers=allocate() otf.enhancers=enhancers @@ -15792,6 +15498,22 @@ function otf.load(filename,sub,featurefile) starttiming(otfreaders) data=otfreaders.loadfont(filename,sub or 1) if data then + local resources=data.resources + local svgshapes=resources.svgshapes + if svgshapes then + resources.svgshapes=nil + if otf.svgenabled then + local timestamp=os.date() + containers.write(otf.svgcache,hash,{ + svgshapes=svgshapes, + timestamp=timestamp, + }) + data.properties.svg={ + hash=hash, + timestamp=timestamp, + } + end + end otfreaders.compact(data) otfreaders.rehash(data,"unicodes") otfreaders.addunicodetable(data) @@ -15827,7 +15549,6 @@ function otf.load(filename,sub,featurefile) otfreaders.expand(data) otfreaders.addunicodetable(data) enhancers.apply(data,filename,data) - constructors.addcoreunicodes(unicodes) if applyruntimefixes then applyruntimefixes(filename,data) end @@ -15846,7 +15567,6 @@ end local function copytotfm(data,cache_id) if data then local metadata=data.metadata - local resources=data.resources local properties=derivetable(data.properties) local descriptions=derivetable(data.descriptions) local goodies=derivetable(data.goodies) @@ -16300,8 +16020,6 @@ 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 -local utfchar=utf.char local trace_baseinit=false trackers.register("otf.baseinit",function(v) trace_baseinit=v end) local trace_singles=false trackers.register("otf.singles",function(v) trace_singles=v end) local trace_multiples=false trackers.register("otf.multiples",function(v) trace_multiples=v end) @@ -16658,6 +16376,7 @@ registerotffeature { base=featuresinitializer, } } +otf.basemodeinitializer=featuresinitializer end -- closure @@ -16672,7 +16391,6 @@ if not modules then modules={} end modules ['font-otj']={ } if not nodes.properties then return end local next,rawget=next,rawget -local utfchar=utf.char local fastcopy=table.fastcopy local registertracker=trackers.register local trace_injections=false registertracker("fonts.injections",function(v) trace_injections=v end) @@ -16717,7 +16435,6 @@ 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 local properties=nodes.properties.data function injections.installnewkern(nk) newkern=nk or newkern @@ -17288,7 +17005,7 @@ local function inject_pairs_only(head,where) end local leftkern=i.leftkern if leftkern and leftkern~=0 then - insert_node_before(head,current,newkern(leftkern)) + head=insert_node_before(head,current,newkern(leftkern)) end local rightkern=i.rightkern if rightkern and rightkern~=0 then @@ -17713,11 +17430,11 @@ local function inject_everything(head,where) 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) + if hasmarks then + local pm=i.markbasenode + if pm then + processmark(pm,current,i) + end end end end @@ -17743,11 +17460,11 @@ local function inject_everything(head,where) 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) + if hasmarks then + local pm=i.markbasenode + if pm then + processmark(pm,current,i) + end end end end @@ -17773,11 +17490,11 @@ local function inject_everything(head,where) 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) + if hasmarks then + local pm=i.markbasenode + if pm then + processmark(pm,current,i) + end end end end @@ -17949,10 +17666,19 @@ function injections.handler(head,where) head=injectspaces(head) end if nofregisteredmarks>0 or nofregisteredcursives>0 then + if trace_injections then + report_injections("injection variant %a","everything") + end return inject_everything(head,where) elseif nofregisteredpairs>0 then + if trace_injections then + report_injections("injection variant %a","pairs") + end return inject_pairs_only(head,where) elseif nofregisteredkerns>0 then + if trace_injections then + report_injections("injection variant %a","kerns") + end return inject_kerns_only(head,where) else return head,false @@ -17994,7 +17720,6 @@ 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 disc_code=nodecodes.disc @@ -18002,7 +17727,7 @@ 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 otffeatures=fonts.constructors.features.otf local registerotffeature=otffeatures.register local s_init=1 local s_rphf=7 local s_medi=2 local s_half=8 @@ -18367,7 +18092,6 @@ local trace_cursive=false registertracker("otf.cursive",function(v) trace_cursiv local trace_preparing=false registertracker("otf.preparing",function(v) trace_preparing=v end) local trace_bugs=false registertracker("otf.bugs",function(v) trace_bugs=v end) local trace_details=false registertracker("otf.details",function(v) trace_details=v end) -local trace_applied=false registertracker("otf.applied",function(v) trace_applied=v end) local trace_steps=false registertracker("otf.steps",function(v) trace_steps=v end) local trace_skips=false registertracker("otf.skips",function(v) trace_skips=v end) local trace_directions=false registertracker("otf.directions",function(v) trace_directions=v end) @@ -18386,7 +18110,6 @@ local report_chain=logs.reporter("fonts","otf chain") local report_process=logs.reporter("fonts","otf process") local report_warning=logs.reporter("fonts","otf warning") local report_run=logs.reporter("fonts","otf run") -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") @@ -18417,10 +18140,7 @@ 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 -local remove_node=nuts.remove local copy_node=nuts.copy local copy_node_list=nuts.copy_list local find_node_tail=nuts.tail @@ -18459,7 +18179,7 @@ local getligaindex=injections.getligaindex local cursonce=true local fonthashes=fonts.hashes local fontdata=fonthashes.identifiers -local otffeatures=fonts.constructors.newfeatures("otf") +local otffeatures=fonts.constructors.features.otf local registerotffeature=otffeatures.register local onetimemessage=fonts.loggers.onetimemessage or function() end otf.defaultnodealternate="none" @@ -20815,7 +20535,7 @@ local function c_run_single(head,font,attr,lookupcache,step,dataset,sequence,rlm while start do local char=ischar(start,font) if char then - local a=getattr(start,0) + local a=attr and getattr(start,0) if not a or (a==attr) then local lookupmatch=lookupcache[char] if lookupmatch then @@ -20845,7 +20565,7 @@ 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) + local a=attr and getattr(start,0) if not a or (a==attr) then local lookupmatch=lookupcache[char] if lookupmatch then @@ -20878,7 +20598,7 @@ local function t_run_single(start,stop,font,attr,lookupcache) end end local function k_run_single(sub,injection,last,font,attr,lookupcache,step,dataset,sequence,rlmode,handler) - local a=getattr(sub,0) + local a=attr and getattr(sub,0) if not a or (a==attr) then for n in traverse_nodes(sub) do if n==last then @@ -20909,7 +20629,7 @@ local function c_run_multiple(head,font,attr,steps,nofsteps,dataset,sequence,rlm while start do local char=ischar(start,font) if char then - local a=getattr(start,0) + local a=attr and getattr(start,0) if not a or (a==attr) then for i=1,nofsteps do local step=steps[i] @@ -20950,7 +20670,7 @@ 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) + local a=attr and getattr(start,0) if not a or (a==attr) then for i=1,nofsteps do local step=steps[i] @@ -20991,7 +20711,7 @@ local function t_run_multiple(start,stop,font,attr,steps,nofsteps) end end local function k_run_multiple(sub,injection,last,font,attr,steps,nofsteps,dataset,sequence,rlmode,handler) - local a=getattr(sub,0) + local a=attr and getattr(sub,0) if not a or (a==attr) then for n in traverse_nodes(sub) do if n==last then @@ -21110,7 +20830,7 @@ local function featuresprocessor(head,font,attr) while start do local char=ischar(start,font) if char then - local a=getattr(start,0) + local a=attr and getattr(start,0) if not a or (a==attr) then for i=1,nofsteps do local step=steps[i] @@ -21151,7 +20871,7 @@ local function featuresprocessor(head,font,attr) while start do local char,id=ischar(start,font) if char then - local a=getattr(start,0) + local a=attr and getattr(start,0) if a then a=(a==attr) and (not attribute or getprop(start,a_state)==attribute) else @@ -21201,7 +20921,7 @@ local function featuresprocessor(head,font,attr) while start do local char,id=ischar(start,font) if char then - local a=getattr(start,0) + local a=attr and getattr(start,0) if a then a=(a==attr) and (not attribute or getprop(start,a_state)==attribute) else @@ -21284,6 +21004,8 @@ registerotffeature { node=featuresprocessor, } } +otf.nodemodeinitializer=featuresinitializer +otf.featuresprocessor=featuresprocessor otf.handlers=handlers local setspacekerns=nodes.injections.setspacekerns if not setspacekerns then os.exit() end function otf.handlers.trigger_space_kerns(head,start,dataset,sequence,_,_,_,_,font,attr) @@ -21424,11 +21146,9 @@ 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 otffeatures=fonts.constructors.features.otf local registerotffeature=otffeatures.register local nuts=nodes.nuts local tonode=nuts.tonode @@ -23381,6 +23101,1411 @@ end -- closure do -- begin closure to overcome local limits and interference +if not modules then modules={} end modules ['font-ocl']={ + version=1.001, + comment="companion to font-otf.lua (context)", + author="Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright="PRAGMA ADE / ConTeXt Development Team", + license="see context related readme files" +} +local formatters=string.formatters +local otf=fonts.handlers.otf +local f_color_start=formatters["pdf:direct: %f %f %f rg"] +local s_color_stop="pdf:direct:" +if context then + local startactualtext=nil + local stopactualtext=nil + function otf.getactualtext(n) + if not startactualtext then + startactualtext=backends.codeinjections.startunicodetoactualtext + stopactualtext=backends.codeinjections.stopunicodetoactualtext + end + return startactualtext(n),stopactualtext() + end +else + local tounicode=fonts.mappings.tounicode16 + function otf.getactualtext(n) + return "/Span << /ActualText <feff"..tounicode(n).."> >> BDC","EMC" + end +end +local function initializecolr(tfmdata,kind,value) + if value then + local palettes=tfmdata.resources.colorpalettes + if palettes then + local palette=palettes[tonumber(value) or 1] or palettes[1] or {} + local classes=#palette + if classes==0 then + return + end + local characters=tfmdata.characters + local descriptions=tfmdata.descriptions + local properties=tfmdata.properties + local colorvalues={} + properties.virtualized=true + tfmdata.fonts={ + { id=0 } + } + for i=1,classes do + local p=palette[i] + colorvalues[i]={ "special",f_color_start(p[1]/255,p[2]/255,p[3]/255) } + end + local getactualtext=otf.getactualtext + for unicode,character in next,characters do + local description=descriptions[unicode] + if description then + local colorlist=description.colors + if colorlist then + local b,e=getactualtext(unicode) + local w=character.width or 0 + local s=#colorlist + local n=1 + local t={ + { "special","pdf:direct: q "..b } + } + for i=1,s do + local entry=colorlist[i] + n=n+1 t[n]=colorvalues[entry.class] + n=n+1 t[n]={ "char",entry.slot } + if s>1 and i<s and w~=0 then + n=n+1 t[n]={ "right",-w } + end + end + n=n+1 t[n]={ "special","pdf:direct:"..e.." Q" } + character.commands=t + end + end + end + end + end +end +fonts.handlers.otf.features.register { + name="colr", + description="color glyphs", + manipulators={ + base=initializecolr, + node=initializecolr, + } +} +local otfsvg=otf.svg or {} +otf.svg=otfsvg +otf.svgenabled=true +do + local nofstreams=0 + local f_name=formatters[ [[svg-glyph-%05i]] ] + local f_used=context and formatters[ [[original:///%s]] ] or formatters[ [[%s]] ] + local cache={} + function otfsvg.storepdfdata(pdf) + nofstreams=nofstreams+1 + local o,n=epdf.openMemStream(pdf,#pdf,f_name(nofstreams)) + cache[n]=o + return nil,f_used(n),nil + end + if context then + local storepdfdata=otfsvg.storepdfdata + local initialized=false + function otfsvg.storepdfdata(pdf) + if not initialized then + if resolvers.setmemstream then + local f_setstream=formatters[ [[resolvers.setmemstream("svg-glyph-%05i",%q,true)]] ] + local f_getstream=formatters[ [[memstream:///svg-glyph-%05i]] ] + local f_nilstream=formatters[ [[resolvers.resetmemstream("svg-glyph-%05i",true)]] ] + storepdfdata=function(pdf) + nofstreams=nofstreams+1 + return + f_setstream(nofstreams,pdf), + f_getstream(nofstreams), + f_nilstream(nofstreams) + end + otfsvg.storepdfdata=storepdfdata + end + initialized=true + end + return storepdfdata(pdf) + end + end +end +if context and xml.convert then + local report_svg=logs.reporter("fonts","svg conversion") + function otfsvg.topdf(svgshapes) + local svgfile="temp-otf-svg-shape.svg" + local pdffile="temp-otf-svg-shape.pdf" + local command="inkscape "..svgfile.." --export-pdf="..pdffile + local testrun=false + local pdfshapes={} + local nofshapes=#svgshapes + report_svg("processing %i svg containers",nofshapes) + for i=1,nofshapes do + local entry=svgshapes[i] + for j=entry.first,entry.last do + local svg=xml.convert(entry.data) + local data=xml.first(svg,"/svg[@id='glyph"..j.."']") + io.savedata(svgfile,tostring(data)) + report_svg("processing svg shape of glyph %i in container %i",j,i) + os.execute(command) + pdfshapes[j]=io.loaddata(pdffile) + end + if testrun and i>testrun then + report_svg("quiting test run") + break + end + end + os.remove(svgfile) + return pdfshapes + end +else + function otfsvg.topdf(svgshapes) + local svgfile="temp-otf-svg-shape.svg" + local pdffile="temp-otf-svg-shape.pdf" + local command="inkscape "..svgfile.." --export-pdf="..pdffile + local pdfshapes={} + local nofshapes=#svgshapes + texio.write(formatters["[converting %i svg glyphs to pdf using command %q : "](nofshapes,command)) + for i=1,nofshapes do + local entry=svgshapes[i] + for j=entry.first,entry.last do + texio.write(formatters["%i "](j)) + io.savedata(svgfile,tostring(entry.data)) + os.execute(command) + pdfshapes[j]=io.loaddata(pdffile) + end + end + os.remove(svgfile) + texio.write("done]") + return pdfshapes + end +end +local function initializesvg(tfmdata,kind,value) + if value and otf.svgenabled then + local characters=tfmdata.characters + local descriptions=tfmdata.descriptions + local properties=tfmdata.properties + local svg=properties.svg + local hash=svg and svg.hash + local timestamp=svg and svg.timestamp + if not hash then + return + end + local pdffile=containers.read(otf.pdfcache,hash) + local pdfshapes=pdffile and pdffile.pdfshapes + if not pdfshapes or pdffile.timestamp~=timestamp then + local svgfile=containers.read(otf.svgcache,hash) + local svgshapes=svgfile and svgfile.svgshapes + pdfshapes=svgshapes and otfsvg.topdf(svgshapes) or {} + containers.write(otf.pdfcache,hash,{ + pdfshapes=pdfshapes, + timestamp=timestamp, + }) + end + if not pdfshapes or not next(pdfshapes) then + return + end + properties.virtualized=true + tfmdata.fonts={ + { id=0 } + } + local getactualtext=otf.getactualtext + local storepdfdata=otfsvg.storepdfdata + local nop={ "nop" } + for unicode,character in next,characters do + local index=character.index + if index then + local pdf=pdfshapes[index] + if pdf then + local setcode,name,nilcode=storepdfdata(pdf) + if name then + local bt,et=getactualtext(unicode) + local wd=character.width or 0 + local ht=character.height or 0 + local dp=character.depth or 0 + character.commands={ + { "special","pdf:direct:"..bt }, + { "down",dp }, + setcode and { "lua",setcode } or nop, + { "image",{ filename=name,width=wd,height=ht,depth=dp } }, + nilcode and { "lua",nilcode } or nop, + { "special","pdf:direct:"..et }, + } + character.svg=true + end + end + end + end + end +end +fonts.handlers.otf.features.register { + name="svg", + description="svg glyphs", + manipulators={ + base=initializesvg, + node=initializesvg, + } +} + +end -- closure + +do -- begin closure to overcome local limits and interference + +if not modules then modules={} end modules ['font-onr']={ + version=1.001, + comment="companion to font-ini.mkiv", + author="Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright="PRAGMA ADE / ConTeXt Development Team", + license="see context related readme files" +} +local fonts,logs,trackers,resolvers=fonts,logs,trackers,resolvers +local next,type,tonumber,rawget=next,type,tonumber,rawget +local match,lower,gsub,strip,find=string.match,string.lower,string.gsub,string.strip,string.find +local char,byte,sub=string.char,string.byte,string.sub +local abs=math.abs +local bxor,rshift=bit32.bxor,bit32.rshift +local P,S,R,Cmt,C,Ct,Cs,Carg=lpeg.P,lpeg.S,lpeg.R,lpeg.Cmt,lpeg.C,lpeg.Ct,lpeg.Cs,lpeg.Carg +local lpegmatch,patterns=lpeg.match,lpeg.patterns +local trace_indexing=false trackers.register("afm.indexing",function(v) trace_indexing=v end) +local trace_loading=false trackers.register("afm.loading",function(v) trace_loading=v end) +local report_afm=logs.reporter("fonts","afm loading") +local report_afm=logs.reporter("fonts","pfb loading") +fonts=fonts or {} +local handlers=fonts.handlers or {} +fonts.handlers=handlers +local afm=handlers.afm or {} +handlers.afm=afm +local readers=afm.readers or {} +afm.readers=readers +afm.version=1.512 +local get_indexes +do + local n,m + local progress=function(str,position,name,size) + local forward=position+tonumber(size)+3+2 + n=n+1 + if n>=m then + return #str,name + elseif forward<#str then + return forward,name + else + return #str,name + end + end + local initialize=function(str,position,size) + n=0 + m=tonumber(size) + return position+1 + end + local charstrings=P("/CharStrings") + local name=P("/")*C((R("az")+R("AZ")+R("09")+S("-_."))^1) + local size=C(R("09")^1) + local spaces=P(" ")^1 + local p_filternames=Ct ( + (1-charstrings)^0*charstrings*spaces*Cmt(size,initialize)*(Cmt(name*P(" ")^1*C(R("09")^1),progress)+P(1))^1 + ) + local decrypt + do + local r,c1,c2,n=0,0,0,0 + local function step(c) + local cipher=byte(c) + local plain=bxor(cipher,rshift(r,8)) + r=((cipher+r)*c1+c2)%65536 + return char(plain) + end + decrypt=function(binary) + r,c1,c2,n=55665,52845,22719,4 + binary=gsub(binary,".",step) + return sub(binary,n+1) + end + end + local function loadpfbvector(filename) + local data=io.loaddata(resolvers.findfile(filename)) + if not data then + report_pfb("no data in %a",filename) + return + end + if not (find(data,"!PS%-AdobeFont%-") or find(data,"%%!FontType1")) then + report_pfb("no font in %a",filename) + return + end + local ascii,binary=match(data,"(.*)eexec%s+......(.*)") + if not binary then + report_pfb("no binary data in %a",filename) + return + end + binary=decrypt(binary,4) + local vector=lpegmatch(p_filternames,binary) + if vector[1]==".notdef" then + vector[0]=table.remove(vector,1) + end + if not vector then + report_pfb("no vector in %a",filename) + return + end + return vector + end + get_indexes=function(data,pfbname) + local vector=loadpfbvector(pfbname) + if vector then + local characters=data.characters + if trace_loading then + report_afm("getting index data from %a",pfbname) + end + for index=1,#vector do + local name=vector[index] + local char=characters[name] + if char then + if trace_indexing then + report_afm("glyph %a has index %a",name,index) + end + char.index=index + end + end + end + end +end +local spacer=patterns.spacer +local whitespace=patterns.whitespace +local lineend=patterns.newline +local spacing=spacer^0 +local number=spacing*S("+-")^-1*(R("09")+S("."))^1/tonumber +local name=spacing*C((1-whitespace)^1) +local words=spacing*((1-lineend)^1/strip) +local rest=(1-lineend)^0 +local fontdata=Carg(1) +local semicolon=spacing*P(";") +local plus=spacing*P("plus")*number +local minus=spacing*P("minus")*number +local function addkernpair(data,one,two,value) + local chr=data.characters[one] + if chr then + local kerns=chr.kerns + if kerns then + kerns[two]=tonumber(value) + else + chr.kerns={ [two]=tonumber(value) } + end + end +end +local p_kernpair=(fontdata*P("KPX")*name*name*number)/addkernpair +local chr=false +local ind=0 +local function start(data,version) + data.metadata.afmversion=version + ind=0 + chr={} +end +local function stop() + ind=0 + chr=false +end +local function setindex(i) + if i<0 then + ind=ind+1 + else + ind=i + end + chr={ + index=ind + } +end +local function setwidth(width) + chr.width=width +end +local function setname(data,name) + data.characters[name]=chr +end +local function setboundingbox(boundingbox) + chr.boundingbox=boundingbox +end +local function setligature(plus,becomes) + local ligatures=chr.ligatures + if ligatures then + ligatures[plus]=becomes + else + chr.ligatures={ [plus]=becomes } + end +end +local p_charmetric=(( + P("C")*number/setindex+P("WX")*number/setwidth+P("N")*fontdata*name/setname+P("B")*Ct((number)^4)/setboundingbox+P("L")*(name)^2/setligature + )*semicolon )^1 +local p_charmetrics=P("StartCharMetrics")*number*(p_charmetric+(1-P("EndCharMetrics")))^0*P("EndCharMetrics") +local p_kernpairs=P("StartKernPairs")*number*(p_kernpair+(1-P("EndKernPairs" )))^0*P("EndKernPairs" ) +local function set_1(data,key,a) data.metadata[lower(key)]=a end +local function set_2(data,key,a,b) data.metadata[lower(key)]={ a,b } end +local function set_3(data,key,a,b,c) data.metadata[lower(key)]={ a,b,c } end +local p_parameters=P(false)+fontdata*((P("FontName")+P("FullName")+P("FamilyName"))/lower)*words/function(data,key,value) + data.metadata[key]=value + end+fontdata*((P("Weight")+P("Version"))/lower)*name/function(data,key,value) + data.metadata[key]=value + end+fontdata*P("IsFixedPitch")*name/function(data,pitch) + data.metadata.monospaced=toboolean(pitch,true) + end+fontdata*P("FontBBox")*Ct(number^4)/function(data,boundingbox) + data.metadata.boundingbox=boundingbox + end+fontdata*((P("CharWidth")+P("CapHeight")+P("XHeight")+P("Descender")+P("Ascender")+P("ItalicAngle"))/lower)*number/function(data,key,value) + data.metadata[key]=value + end+P("Comment")*spacing*(P(false)+(fontdata*C("DESIGNSIZE")*number*rest)/set_1 ++(fontdata*C("TFM designsize")*number*rest)/set_1+(fontdata*C("DesignSize")*number*rest)/set_1+(fontdata*C("CODINGSCHEME")*words*rest)/set_1 ++(fontdata*C("CHECKSUM")*number*words*rest)/set_1 ++(fontdata*C("SPACE")*number*plus*minus*rest)/set_3 ++(fontdata*C("QUAD")*number*rest)/set_1 ++(fontdata*C("EXTRASPACE")*number*rest)/set_1 ++(fontdata*C("NUM")*number*number*number*rest)/set_3 ++(fontdata*C("DENOM")*number*number*rest)/set_2 ++(fontdata*C("SUP")*number*number*number*rest)/set_3 ++(fontdata*C("SUB")*number*number*rest)/set_2 ++(fontdata*C("SUPDROP")*number*rest)/set_1 ++(fontdata*C("SUBDROP")*number*rest)/set_1 ++(fontdata*C("DELIM")*number*number*rest)/set_2 ++(fontdata*C("AXISHEIGHT")*number*rest)/set_1 + ) +local fullparser=(P("StartFontMetrics")*fontdata*name/start )*(p_charmetrics+p_kernpairs+p_parameters+(1-P("EndFontMetrics")) )^0*(P("EndFontMetrics")/stop ) +local fullparser=(P("StartFontMetrics")*fontdata*name/start )*(p_charmetrics+p_kernpairs+p_parameters+(1-P("EndFontMetrics")) )^0*(P("EndFontMetrics")/stop ) +local infoparser=(P("StartFontMetrics")*fontdata*name/start )*(p_parameters+(1-P("EndFontMetrics")) )^0*(P("EndFontMetrics")/stop ) +local function read(filename,parser) + local afmblob=io.loaddata(filename) + if afmblob then + local data={ + resources={ + filename=resolvers.unresolve(filename), + version=afm.version, + creator="context mkiv", + }, + properties={ + hasitalics=false, + }, + goodies={}, + metadata={ + filename=file.removesuffix(file.basename(filename)) + }, + characters={ + }, + descriptions={ + }, + } + if trace_loading then + report_afm("parsing afm file %a",filename) + end + lpegmatch(parser,afmblob,1,data) + return data + else + if trace_loading then + report_afm("no valid afm file %a",filename) + end + return nil + end +end +function readers.loadfont(afmname,pfbname) + local data=read(resolvers.findfile(afmname),fullparser) + if data then + if not pfbname or pfbname=="" then + pfbname=file.replacesuffix(file.nameonly(afmname),"pfb") + pfbname=resolvers.findfile(pfbname) + end + if pfbname and pfbname~="" then + data.resources.filename=resolvers.unresolve(pfbname) + get_indexes(data,pfbname) + elseif trace_loading then + report_afm("no pfb file for %a",afmname) + end + return data + end +end +function readers.getinfo(filename) + local data=read(resolvers.findfile(filename),infoparser) + if data then + return data.metadata + end +end + +end -- closure + +do -- begin closure to overcome local limits and interference + +if not modules then modules={} end modules ['font-one']={ + version=1.001, + comment="companion to font-ini.mkiv", + author="Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright="PRAGMA ADE / ConTeXt Development Team", + license="see context related readme files" +} +local fonts,logs,trackers,containers,resolvers=fonts,logs,trackers,containers,resolvers +local next,type,tonumber,rawget=next,type,tonumber,rawget +local match,gmatch,lower,gsub,strip,find=string.match,string.gmatch,string.lower,string.gsub,string.strip,string.find +local char,byte,sub=string.char,string.byte,string.sub +local abs=math.abs +local bxor,rshift=bit32.bxor,bit32.rshift +local P,S,R,Cmt,C,Ct,Cs,Carg=lpeg.P,lpeg.S,lpeg.R,lpeg.Cmt,lpeg.C,lpeg.Ct,lpeg.Cs,lpeg.Carg +local lpegmatch,patterns=lpeg.match,lpeg.patterns +local trace_features=false trackers.register("afm.features",function(v) trace_features=v end) +local trace_indexing=false trackers.register("afm.indexing",function(v) trace_indexing=v end) +local trace_loading=false trackers.register("afm.loading",function(v) trace_loading=v end) +local trace_defining=false trackers.register("fonts.defining",function(v) trace_defining=v end) +local report_afm=logs.reporter("fonts","afm loading") +local setmetatableindex=table.setmetatableindex +local derivetable=table.derive +local findbinfile=resolvers.findbinfile +local definers=fonts.definers +local readers=fonts.readers +local constructors=fonts.constructors +local afm=constructors.handlers.afm +local pfb=constructors.handlers.pfb +local otf=fonts.handlers.otf +local otfreaders=otf.readers +local otfenhancers=otf.enhancers +local afmfeatures=constructors.features.afm +local registerafmfeature=afmfeatures.register +afm.version=1.512 +afm.cache=containers.define("fonts","afm",afm.version,true) +afm.autoprefixed=true +afm.helpdata={} +afm.syncspace=true +local overloads=fonts.mappings.overloads +local applyruntimefixes=fonts.treatments and fonts.treatments.applyfixes +local enhancers={ +} +local steps={ + "unify names", + "add ligatures", + "add extra kerns", + "normalize features", + "fix names", +} +local function applyenhancers(data,filename) + for i=1,#steps do + local step=steps[i] + local enhancer=enhancers[step] + if enhancer then + if trace_loading then + report_afm("applying enhancer %a",step) + end + enhancer(data,filename) + else + report_afm("invalid enhancer %a",step) + end + end +end +function afm.load(filename) + filename=resolvers.findfile(filename,'afm') or "" + if filename~="" and not fonts.names.ignoredfile(filename) then + local name=file.removesuffix(file.basename(filename)) + local data=containers.read(afm.cache,name) + local attr=lfs.attributes(filename) + local size,time=attr.size or 0,attr.modification or 0 + local pfbfile=file.replacesuffix(name,"pfb") + local pfbname=resolvers.findfile(pfbfile,"pfb") or "" + if pfbname=="" then + pfbname=resolvers.findfile(file.basename(pfbfile),"pfb") or "" + end + local pfbsize,pfbtime=0,0 + if pfbname~="" then + local attr=lfs.attributes(pfbname) + pfbsize=attr.size or 0 + pfbtime=attr.modification or 0 + end + if not data or data.size~=size or data.time~=time or data.pfbsize~=pfbsize or data.pfbtime~=pfbtime then + report_afm("reading %a",filename) + data=afm.readers.loadfont(filename,pfbname) + if data then + applyenhancers(data,filename) + fonts.mappings.addtounicode(data,filename) + otfreaders.pack(data) + data.size=size + data.time=time + data.pfbsize=pfbsize + data.pfbtime=pfbtime + report_afm("saving %a in cache",name) + data=containers.write(afm.cache,name,data) + data=containers.read(afm.cache,name) + end + end + if data then + otfreaders.unpack(data) + otfreaders.expand(data) + otfreaders.addunicodetable(data) + otfenhancers.apply(data,filename,data) + if applyruntimefixes then + applyruntimefixes(filename,data) + end + end + return data + end +end +local uparser=fonts.mappings.makenameparser() +enhancers["unify names"]=function(data,filename) + local unicodevector=fonts.encodings.agl.unicodes + local unicodes={} + local names={} + local private=constructors.privateoffset + local descriptions=data.descriptions + for name,blob in next,data.characters do + local code=unicodevector[name] + if not code then + code=lpegmatch(uparser,name) + if type(code)~="number" then + code=private + private=private+1 + report_afm("assigning private slot %U for unknown glyph name %a",code,name) + end + end + local index=blob.index + unicodes[name]=code + names[name]=index + blob.name=name + descriptions[code]={ + boundingbox=blob.boundingbox, + width=blob.width, + kerns=blob.kerns, + index=index, + name=name, + } + end + for unicode,description in next,descriptions do + local kerns=description.kerns + if kerns then + local krn={} + for name,kern in next,kerns do + local unicode=unicodes[name] + if unicode then + krn[unicode]=kern + else + end + end + description.kerns=krn + end + end + data.characters=nil + local resources=data.resources + local filename=resources.filename or file.removesuffix(file.basename(filename)) + resources.filename=resolvers.unresolve(filename) + resources.unicodes=unicodes + resources.marks={} + resources.private=private +end +local everywhere={ ["*"]={ ["*"]=true } } +local noflags={ false,false,false,false } +enhancers["normalize features"]=function(data) + local ligatures=setmetatableindex("table") + local kerns=setmetatableindex("table") + local extrakerns=setmetatableindex("table") + for u,c in next,data.descriptions do + local l=c.ligatures + local k=c.kerns + local e=c.extrakerns + if l then + ligatures[u]=l + for u,v in next,l do + l[u]={ ligature=v } + end + c.ligatures=nil + end + if k then + kerns[u]=k + for u,v in next,k do + k[u]=v + end + c.kerns=nil + end + if e then + extrakerns[u]=e + for u,v in next,e do + e[u]=v + end + c.extrakerns=nil + end + end + local features={ + gpos={}, + gsub={}, + } + local sequences={ + } + if next(ligatures) then + features.gsub.liga=everywhere + data.properties.hasligatures=true + sequences[#sequences+1]={ + features={ + liga=everywhere, + }, + flags=noflags, + name="s_s_0", + nofsteps=1, + order={ "liga" }, + type="gsub_ligature", + steps={ + { + coverage=ligatures, + }, + }, + } + end + if next(kerns) then + features.gpos.kern=everywhere + data.properties.haskerns=true + sequences[#sequences+1]={ + features={ + kern=everywhere, + }, + flags=noflags, + name="p_s_0", + nofsteps=1, + order={ "kern" }, + type="gpos_pair", + steps={ + { + format="kern", + coverage=kerns, + }, + }, + } + end + if next(extrakerns) then + features.gpos.extrakerns=everywhere + data.properties.haskerns=true + sequences[#sequences+1]={ + features={ + extrakerns=everywhere, + }, + flags=noflags, + name="p_s_1", + nofsteps=1, + order={ "extrakerns" }, + type="gpos_pair", + steps={ + { + format="kern", + coverage=extrakerns, + }, + }, + } + end + data.resources.features=features + data.resources.sequences=sequences +end +enhancers["fix names"]=function(data) + for k,v in next,data.descriptions do + local n=v.name + local r=overloads[n] + if r then + local name=r.name + if trace_indexing then + report_afm("renaming characters %a to %a",n,name) + end + v.name=name + v.unicode=r.unicode + end + end +end +local addthem=function(rawdata,ligatures) + if ligatures then + local descriptions=rawdata.descriptions + local resources=rawdata.resources + local unicodes=resources.unicodes + for ligname,ligdata in next,ligatures do + local one=descriptions[unicodes[ligname]] + if one then + for _,pair in next,ligdata do + local two,three=unicodes[pair[1]],unicodes[pair[2]] + if two and three then + local ol=one.ligatures + if ol then + if not ol[two] then + ol[two]=three + end + else + one.ligatures={ [two]=three } + end + end + end + end + end + end +end +enhancers["add ligatures"]=function(rawdata) + addthem(rawdata,afm.helpdata.ligatures) +end +enhancers["add extra kerns"]=function(rawdata) + local descriptions=rawdata.descriptions + local resources=rawdata.resources + local unicodes=resources.unicodes + local function do_it_left(what) + if what then + for unicode,description in next,descriptions do + local kerns=description.kerns + if kerns then + local extrakerns + for complex,simple in next,what do + complex=unicodes[complex] + simple=unicodes[simple] + if complex and simple then + local ks=kerns[simple] + if ks and not kerns[complex] then + if extrakerns then + extrakerns[complex]=ks + else + extrakerns={ [complex]=ks } + end + end + end + end + if extrakerns then + description.extrakerns=extrakerns + end + end + end + end + end + local function do_it_copy(what) + if what then + for complex,simple in next,what do + complex=unicodes[complex] + simple=unicodes[simple] + if complex and simple then + local complexdescription=descriptions[complex] + if complexdescription then + local simpledescription=descriptions[complex] + if simpledescription then + local extrakerns + local kerns=simpledescription.kerns + if kerns then + for unicode,kern in next,kerns do + if extrakerns then + extrakerns[unicode]=kern + else + extrakerns={ [unicode]=kern } + end + end + end + local extrakerns=simpledescription.extrakerns + if extrakerns then + for unicode,kern in next,extrakerns do + if extrakerns then + extrakerns[unicode]=kern + else + extrakerns={ [unicode]=kern } + end + end + end + if extrakerns then + complexdescription.extrakerns=extrakerns + end + end + end + end + end + end + end + do_it_left(afm.helpdata.leftkerned) + do_it_left(afm.helpdata.bothkerned) + do_it_copy(afm.helpdata.bothkerned) + do_it_copy(afm.helpdata.rightkerned) +end +local function adddimensions(data) + if data then + for unicode,description in next,data.descriptions do + local bb=description.boundingbox + if bb then + local ht,dp=bb[4],-bb[2] + if ht==0 or ht<0 then + else + description.height=ht + end + if dp==0 or dp<0 then + else + description.depth=dp + end + end + end + end +end +local function copytotfm(data) + if data and data.descriptions then + local metadata=data.metadata + local resources=data.resources + local properties=derivetable(data.properties) + local descriptions=derivetable(data.descriptions) + local goodies=derivetable(data.goodies) + local characters={} + local parameters={} + local unicodes=resources.unicodes + for unicode,description in next,data.descriptions do + characters[unicode]={} + end + local filename=constructors.checkedfilename(resources) + local fontname=metadata.fontname or metadata.fullname + local fullname=metadata.fullname or metadata.fontname + local endash=0x0020 + local emdash=0x2014 + local spacer="space" + local spaceunits=500 + local monospaced=metadata.monospaced + local charwidth=metadata.charwidth + local italicangle=metadata.italicangle + local charxheight=metadata.xheight and metadata.xheight>0 and metadata.xheight + properties.monospaced=monospaced + parameters.italicangle=italicangle + parameters.charwidth=charwidth + parameters.charxheight=charxheight + if properties.monospaced then + if descriptions[endash] then + spaceunits,spacer=descriptions[endash].width,"space" + end + if not spaceunits and descriptions[emdash] then + spaceunits,spacer=descriptions[emdash].width,"emdash" + end + if not spaceunits and charwidth then + spaceunits,spacer=charwidth,"charwidth" + end + else + if descriptions[endash] then + spaceunits,spacer=descriptions[endash].width,"space" + end + if not spaceunits and charwidth then + spaceunits,spacer=charwidth,"charwidth" + end + end + spaceunits=tonumber(spaceunits) + if spaceunits<200 then + end + parameters.slant=0 + parameters.space=spaceunits + parameters.space_stretch=500 + parameters.space_shrink=333 + parameters.x_height=400 + parameters.quad=1000 + if italicangle and italicangle~=0 then + parameters.italicangle=italicangle + parameters.italicfactor=math.cos(math.rad(90+italicangle)) + parameters.slant=- math.tan(italicangle*math.pi/180) + end + if monospaced then + parameters.space_stretch=0 + parameters.space_shrink=0 + elseif afm.syncspace then + parameters.space_stretch=spaceunits/2 + parameters.space_shrink=spaceunits/3 + end + parameters.extra_space=parameters.space_shrink + if charxheight then + parameters.x_height=charxheight + else + local x=0x0078 + if x then + local x=descriptions[x] + if x then + parameters.x_height=x.height + end + end + end + if metadata.sup then + local dummy={ 0,0,0 } + parameters[ 1]=metadata.designsize or 0 + parameters[ 2]=metadata.checksum or 0 + parameters[ 3], + parameters[ 4], + parameters[ 5]=unpack(metadata.space or dummy) + parameters[ 6]=metadata.quad or 0 + parameters[ 7]=metadata.extraspace or 0 + parameters[ 8], + parameters[ 9], + parameters[10]=unpack(metadata.num or dummy) + parameters[11], + parameters[12]=unpack(metadata.denom or dummy) + parameters[13], + parameters[14], + parameters[15]=unpack(metadata.sup or dummy) + parameters[16], + parameters[17]=unpack(metadata.sub or dummy) + parameters[18]=metadata.supdrop or 0 + parameters[19]=metadata.subdrop or 0 + parameters[20], + parameters[21]=unpack(metadata.delim or dummy) + parameters[22]=metadata.axisheight or 0 + end + parameters.designsize=(metadata.designsize or 10)*65536 + parameters.ascender=abs(metadata.ascender or 0) + parameters.descender=abs(metadata.descender or 0) + parameters.units=1000 + properties.spacer=spacer + properties.encodingbytes=2 + properties.format=fonts.formats[filename] or "type1" + properties.filename=filename + properties.fontname=fontname + properties.fullname=fullname + properties.psname=fullname + properties.name=filename or fullname or fontname + if next(characters) then + return { + characters=characters, + descriptions=descriptions, + parameters=parameters, + resources=resources, + properties=properties, + goodies=goodies, + } + end + end + return nil +end +function afm.setfeatures(tfmdata,features) + local okay=constructors.initializefeatures("afm",tfmdata,features,trace_features,report_afm) + if okay then + return constructors.collectprocessors("afm",tfmdata,features,trace_features,report_afm) + else + return {} + end +end +local function addtables(data) + local resources=data.resources + local lookuptags=resources.lookuptags + local unicodes=resources.unicodes + if not lookuptags then + lookuptags={} + resources.lookuptags=lookuptags + 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 + end + end + return rawget(t,k) + end) + end + constructors.addcoreunicodes(unicodes) +end +local function afmtotfm(specification) + local afmname=specification.filename or specification.name + if specification.forced=="afm" or specification.format=="afm" then + if trace_loading then + report_afm("forcing afm format for %a",afmname) + end + else + local tfmname=findbinfile(afmname,"ofm") or "" + if tfmname~="" then + if trace_loading then + report_afm("fallback from afm to tfm for %a",afmname) + end + return + end + end + if afmname~="" then + local features=constructors.checkedfeatures("afm",specification.features.normal) + specification.features.normal=features + constructors.hashinstance(specification,true) + specification=definers.resolve(specification) + local cache_id=specification.hash + local tfmdata=containers.read(constructors.cache,cache_id) + if not tfmdata then + local rawdata=afm.load(afmname) + if rawdata and next(rawdata) then + addtables(rawdata) + adddimensions(rawdata) + tfmdata=copytotfm(rawdata) + if tfmdata and next(tfmdata) then + local shared=tfmdata.shared + if not shared then + shared={} + tfmdata.shared=shared + end + shared.rawdata=rawdata + shared.dynamics={} + tfmdata.changed={} + shared.features=features + shared.processes=afm.setfeatures(tfmdata,features) + end + elseif trace_loading then + report_afm("no (valid) afm file found with name %a",afmname) + end + tfmdata=containers.write(constructors.cache,cache_id,tfmdata) + end + return tfmdata + end +end +local function read_from_afm(specification) + local tfmdata=afmtotfm(specification) + if tfmdata then + tfmdata.properties.name=specification.name + tfmdata=constructors.scale(tfmdata,specification) + local allfeatures=tfmdata.shared.features or specification.features.normal + constructors.applymanipulators("afm",tfmdata,allfeatures,trace_features,report_afm) + fonts.loggers.register(tfmdata,'afm',specification) + end + return tfmdata +end +local function setmode(tfmdata,value) + if value then + tfmdata.properties.mode=lower(value) + end +end +registerafmfeature { + name="mode", + description="mode", + initializers={ + base=setmode, + node=setmode, + } +} +registerafmfeature { + name="features", + description="features", + default=true, + initializers={ + node=otf.nodemodeinitializer, + base=otf.basemodeinitializer, + }, + processors={ + node=otf.featuresprocessor, + } +} +local check_tfm=readers.check_tfm +fonts.formats.afm="type1" +fonts.formats.pfb="type1" +local function check_afm(specification,fullname) + local foundname=findbinfile(fullname,'afm') or "" + if foundname=="" then + foundname=fonts.names.getfilename(fullname,"afm") or "" + end + if foundname=="" and afm.autoprefixed then + local encoding,shortname=match(fullname,"^(.-)%-(.*)$") + if encoding and shortname and fonts.encodings.known[encoding] then + shortname=findbinfile(shortname,'afm') or "" + if shortname~="" then + foundname=shortname + if trace_defining then + report_afm("stripping encoding prefix from filename %a",afmname) + end + end + end + end + if foundname~="" then + specification.filename=foundname + specification.format="afm" + return read_from_afm(specification) + end +end +function readers.afm(specification,method) + local fullname=specification.filename or "" + local tfmdata=nil + if fullname=="" then + local forced=specification.forced or "" + if forced~="" then + tfmdata=check_afm(specification,specification.name.."."..forced) + end + if not tfmdata then + method=method or definers.method or "afm or tfm" + if method=="tfm" then + tfmdata=check_tfm(specification,specification.name) + elseif method=="afm" then + tfmdata=check_afm(specification,specification.name) + elseif method=="tfm or afm" then + tfmdata=check_tfm(specification,specification.name) or check_afm(specification,specification.name) + else + tfmdata=check_afm(specification,specification.name) or check_tfm(specification,specification.name) + end + end + else + tfmdata=check_afm(specification,fullname) + end + return tfmdata +end +function readers.pfb(specification,method) + local original=specification.specification + if trace_defining then + report_afm("using afm reader for %a",original) + end + specification.forced="afm" + local function swap(name) + local value=specification[swap] + if value then + specification[swap]=gsub("%.pfb",".afm",1) + end + end + swap("filename") + swap("fullname") + swap("forcedname") + swap("specification") + return readers.afm(specification,method) +end + +end -- closure + +do -- begin closure to overcome local limits and interference + +if not modules then modules={} end modules ['font-afk']={ + version=1.001, + comment="companion to font-afm.lua", + author="Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright="PRAGMA ADE / ConTeXt Development Team", + license="see context related readme files", + dataonly=true, +} +local allocate=utilities.storage.allocate +fonts.handlers.afm.helpdata={ + ligatures=allocate { + ['f']={ + { 'f','ff' }, + { 'i','fi' }, + { 'l','fl' }, + }, + ['ff']={ + { 'i','ffi' } + }, + ['fi']={ + { 'i','fii' } + }, + ['fl']={ + { 'i','fli' } + }, + ['s']={ + { 't','st' } + }, + ['i']={ + { 'j','ij' } + }, + }, + texligatures=allocate { + ['quoteleft']={ + { 'quoteleft','quotedblleft' } + }, + ['quoteright']={ + { 'quoteright','quotedblright' } + }, + ['hyphen']={ + { 'hyphen','endash' } + }, + ['endash']={ + { 'hyphen','emdash' } + } + }, + leftkerned=allocate { + AEligature="A",aeligature="a", + OEligature="O",oeligature="o", + IJligature="I",ijligature="i", + AE="A",ae="a", + OE="O",oe="o", + IJ="I",ij="i", + Ssharp="S",ssharp="s", + }, + rightkerned=allocate { + AEligature="E",aeligature="e", + OEligature="E",oeligature="e", + IJligature="J",ijligature="j", + AE="E",ae="e", + OE="E",oe="e", + IJ="J",ij="j", + Ssharp="S",ssharp="s", + }, + bothkerned=allocate { + Acircumflex="A",acircumflex="a", + Ccircumflex="C",ccircumflex="c", + Ecircumflex="E",ecircumflex="e", + Gcircumflex="G",gcircumflex="g", + Hcircumflex="H",hcircumflex="h", + Icircumflex="I",icircumflex="i", + Jcircumflex="J",jcircumflex="j", + Ocircumflex="O",ocircumflex="o", + Scircumflex="S",scircumflex="s", + Ucircumflex="U",ucircumflex="u", + Wcircumflex="W",wcircumflex="w", + Ycircumflex="Y",ycircumflex="y", + Agrave="A",agrave="a", + Egrave="E",egrave="e", + Igrave="I",igrave="i", + Ograve="O",ograve="o", + Ugrave="U",ugrave="u", + Ygrave="Y",ygrave="y", + Atilde="A",atilde="a", + Itilde="I",itilde="i", + Otilde="O",otilde="o", + Utilde="U",utilde="u", + Ntilde="N",ntilde="n", + Adiaeresis="A",adiaeresis="a",Adieresis="A",adieresis="a", + Ediaeresis="E",ediaeresis="e",Edieresis="E",edieresis="e", + Idiaeresis="I",idiaeresis="i",Idieresis="I",idieresis="i", + Odiaeresis="O",odiaeresis="o",Odieresis="O",odieresis="o", + Udiaeresis="U",udiaeresis="u",Udieresis="U",udieresis="u", + Ydiaeresis="Y",ydiaeresis="y",Ydieresis="Y",ydieresis="y", + Aacute="A",aacute="a", + Cacute="C",cacute="c", + Eacute="E",eacute="e", + Iacute="I",iacute="i", + Lacute="L",lacute="l", + Nacute="N",nacute="n", + Oacute="O",oacute="o", + Racute="R",racute="r", + Sacute="S",sacute="s", + Uacute="U",uacute="u", + Yacute="Y",yacute="y", + Zacute="Z",zacute="z", + Dstroke="D",dstroke="d", + Hstroke="H",hstroke="h", + Tstroke="T",tstroke="t", + Cdotaccent="C",cdotaccent="c", + Edotaccent="E",edotaccent="e", + Gdotaccent="G",gdotaccent="g", + Idotaccent="I",idotaccent="i", + Zdotaccent="Z",zdotaccent="z", + Amacron="A",amacron="a", + Emacron="E",emacron="e", + Imacron="I",imacron="i", + Omacron="O",omacron="o", + Umacron="U",umacron="u", + Ccedilla="C",ccedilla="c", + Kcedilla="K",kcedilla="k", + Lcedilla="L",lcedilla="l", + Ncedilla="N",ncedilla="n", + Rcedilla="R",rcedilla="r", + Scedilla="S",scedilla="s", + Tcedilla="T",tcedilla="t", + Ohungarumlaut="O",ohungarumlaut="o", + Uhungarumlaut="U",uhungarumlaut="u", + Aogonek="A",aogonek="a", + Eogonek="E",eogonek="e", + Iogonek="I",iogonek="i", + Uogonek="U",uogonek="u", + Aring="A",aring="a", + Uring="U",uring="u", + Abreve="A",abreve="a", + Ebreve="E",ebreve="e", + Gbreve="G",gbreve="g", + Ibreve="I",ibreve="i", + Obreve="O",obreve="o", + Ubreve="U",ubreve="u", + Ccaron="C",ccaron="c", + Dcaron="D",dcaron="d", + Ecaron="E",ecaron="e", + Lcaron="L",lcaron="l", + Ncaron="N",ncaron="n", + Rcaron="R",rcaron="r", + Scaron="S",scaron="s", + Tcaron="T",tcaron="t", + Zcaron="Z",zcaron="z", + dotlessI="I",dotlessi="i", + dotlessJ="J",dotlessj="j", + AEligature="AE",aeligature="ae",AE="AE",ae="ae", + OEligature="OE",oeligature="oe",OE="OE",oe="oe", + IJligature="IJ",ijligature="ij",IJ="IJ",ij="ij", + Lstroke="L",lstroke="l",Lslash="L",lslash="l", + Ostroke="O",ostroke="o",Oslash="O",oslash="o", + Ssharp="SS",ssharp="ss", + Aumlaut="A",aumlaut="a", + Eumlaut="E",eumlaut="e", + Iumlaut="I",iumlaut="i", + Oumlaut="O",oumlaut="o", + Uumlaut="U",uumlaut="u", + } +} + +end -- closure + +do -- begin closure to overcome local limits and interference + if not modules then modules={} end modules ['font-lua']={ version=1.001, comment="companion to font-ini.mkiv", @@ -23850,7 +24975,7 @@ if context then os.exit() end local fonts=fonts -local otffeatures=fonts.constructors.newfeatures("otf") +local otffeatures=fonts.constructors.features.otf local function initializeitlc(tfmdata,value) if value then local parameters=tfmdata.parameters @@ -24072,7 +25197,6 @@ local fonts=fonts local nodes=nodes local nuts=nodes.nuts local traverse_id=nuts.traverse_id -local remove_node=nuts.remove local free_node=nuts.free local glyph_code=nodes.nodecodes.glyph local disc_code=nodes.nodecodes.disc @@ -24164,17 +25288,18 @@ function nodes.handlers.nodepass(head) local variant=hash[getchar(p)] if variant then setchar(p,variant) - if not redundant then - redundant={ n } - else - redundant[#redundant+1]=n - end end end end + if not redundant then + redundant={ n } + else + redundant[#redundant+1]=n + end end end end + local nofbasefonts=#basefonts if redundant then for i=1,#redundant do local r=redundant[i] @@ -24185,8 +25310,8 @@ function nodes.handlers.nodepass(head) else setlink(p,n) end - if b>0 then - for i=1,b do + if nofbasefonts>0 then + for i=1,nofbasefonts do local bi=basefonts[i] if r==bi[1] then bi[1]=n @@ -24230,8 +25355,8 @@ function nodes.handlers.nodepass(head) end end end - if basemodepass and #basefonts>0 then - for i=1,#basefonts do + if basemodepass and nofbasefonts>0 then + for i=1,nofbasefonts do local range=basefonts[i] local start=range[1] local stop=range[2] diff --git a/src/luaotfload-auxiliary.lua b/src/luaotfload-auxiliary.lua index e482aba..3c43eb9 100644 --- a/src/luaotfload-auxiliary.lua +++ b/src/luaotfload-auxiliary.lua @@ -212,15 +212,19 @@ end local query_ascender = function (fontdata) local parameters = fontdata.parameters if not parameters then return false end + local ascender = parameters.ascender + if ascender then + return ascender --- pre-scaled + end + local shared = fontdata.shared if not shared then return false end local rawdata = shared.rawdata if not rawdata then return false end local metadata = rawdata.metadata if not metadata then return false end - local ascender = parameters.ascender - or metadata.ascender if not ascender then return false end + ascender = metadata.ascender if not ascender then return false end local size = parameters.size if not size then return false end local units = lookup_units (fontdata) if not units or units == 0 then return false end - return ascender * size / units + return ascender * size / units --- scaled end local query_capheight = function (fontdata) diff --git a/src/luaotfload-configuration.lua b/src/luaotfload-configuration.lua index 8cdebe0..c707b63 100644 --- a/src/luaotfload-configuration.lua +++ b/src/luaotfload-configuration.lua @@ -387,17 +387,8 @@ local set_fontforge = function () end local use_ff = config.luaotfload.db.use_fontforge if use_ff == true then - if not _G.fontloader then logreport ("both", 0, "db", - "Fontforge loader was requested but the fontloader \z - library is missing.") - return false - end - logreport ("log", 0, "db", "Loading font data with FontForge.") - names.use_fontforge (true) - else - logreport ("log", 4, "db", "Loading font data with the Lua loader.") - names.use_fontforge (false) + "Fontforge loader was requested but not supported anymore.") end return true end diff --git a/src/luaotfload-database.lua b/src/luaotfload-database.lua index 437091f..7495ff2 100644 --- a/src/luaotfload-database.lua +++ b/src/luaotfload-database.lua @@ -132,11 +132,6 @@ local fontshandlers = fonts.handlers or { } local otfhandler = fonts.handlers.otf or { } fonts.handlers = fontshandlers -local read_font_file = otfhandler.readers.loadfont -local read_font_info = read_font_file -local close_font_file = function () end -local get_english_names - local gzipload = gzip.load local gzipsave = gzip.save local iolines = io.lines @@ -207,8 +202,7 @@ local make_luanames = function (path) end local format_precedence = { - "otf", "ttc", "ttf", "afm", - -- "pfb" --- may come back before Luatex 1.0 + "otf", "ttc", "ttf", "afm", "pfb" } local location_precedence = { @@ -355,25 +349,25 @@ This is a sketch of the luaotfload db: full : (int, string) hash; // idx -> full path } and fontentry = { // finalized by collect_families() - basename : string; // file name without path "foo.otf" - conflicts : { barename : int; basename : int }; // filename conflict with font at index; happens with subfonts - familyname : string; // sanitized name of the font family the font belongs to, usually from the names table - fontname : string; // sanitized name of the font - format : string; // "otf" | "ttf" | "afm" (* | "pfb" *) - fullname : string; // sanitized full name of the font including style modifiers - fullpath : string; // path to font in filesystem - index : int; // index in the mappings table - italicangle : float; // italic angle; non-zero with oblique faces - location : string; // "texmf" | "system" | "local" - metafamily : string; // alternative family identifier if appropriate, sanitized - plainname : string; // unsanitized font name - prefmodifiers : string; // sanitized preferred subfamily (names table 14) - psname : string; // PostScript name - size : (false | float * float * float); // if available, size info from the size table converted from decipoints - subfamily : string; // sanitized subfamily (names table 2) - subfont : (int | bool); // integer if font is part of a TrueType collection ("ttc") - version : string; // font version string - weight : int; // usWeightClass + basename : string; // file name without path "foo.otf" + conflicts : { barename : int; basename : int }; // filename conflict with font at index; happens with subfonts + familyname : string; // sanitized name of the font family the font belongs to, usually from the names table + fontname : string; // sanitized name of the font + format : string; // "otf" | "ttf" | "afm" (* | "pfb" *) + fullname : string; // sanitized full name of the font including style modifiers + fullpath : string; // path to font in filesystem + index : int; // index in the mappings table + italicangle : float; // italic angle; non-zero with oblique faces + location : string; // "texmf" | "system" | "local" + metafamily : string; // alternative family identifier if appropriate, sanitized + plainname : string; // unsanitized font name + typographicsubfamily : string; // sanitized preferred subfamily (names table 14) + psname : string; // PostScript name + size : (false | float * float * float); // if available, size info from the size table converted from decipoints + subfamily : string; // sanitized subfamily (names table 2) + subfont : (int | bool); // integer if font is part of a TrueType collection ("ttc") + version : string; // font version string + weight : int; // usWeightClass } and filestatus = (string, // fullname { index : int list; // pointer into mappings @@ -1016,7 +1010,7 @@ local lookup_fontname = function (specification, name, style) style = style_category [style] for i = 1, #mappings do local face = mappings [i] - local prefmodifiers = face.prefmodifiers + local typographicsubfamily = face.typographicsubfamily local subfamily = face.subfamily if face.fontname == name or face.fullname == name @@ -1024,17 +1018,17 @@ local lookup_fontname = function (specification, name, style) then return face.basename, face.subfont elseif face.familyname == name then - if prefmodifiers == style + if typographicsubfamily == style or subfamily == style then fallback = face - elseif regular_synonym [prefmodifiers] + elseif regular_synonym [typographicsubfamily] or regular_synonym [subfamily] then lastresort = face end elseif face.metafamily == name - and ( regular_synonym [prefmodifiers] + and ( regular_synonym [typographicsubfamily] or regular_synonym [subfamily]) then lastresort = face @@ -1082,12 +1076,12 @@ end size should that be? Xetex appears to pick the “normal” (unmarked) size: with Adobe fonts this would be the one that is neither “caption” nor “subhead” nor “display” &c ... For fonts by Adobe this - seems to be the one that does not receive a “prefmodifiers” field. - (IOW Adobe uses the “prefmodifiers” field to encode the design size - in more or less human readable format.) However, this is not true - of LM and EB Garamond. As this matters only where there are - multiple design sizes to a given font/style combination, we put a - workaround in place that chooses that unmarked version. + seems to be the one that does not receive a “typographicsubfamily” + field. (IOW Adobe uses the “typographicsubfamily” field to encode + the design size in more or less human readable format.) However, + this is not true of LM and EB Garamond. As this matters only where + there are multiple design sizes to a given font/style combination, + we put a workaround in place that chooses that unmarked version. The first return value of “lookup_font_name” is the file name of the requested font (string). It can be passed to the fullname resolver @@ -1304,13 +1298,29 @@ find_closest = function (name, limit) return false end --- find_closest() +--- string -> uint -> bool * (string | rawdata) +local read_font_file = function (filename, subfont) + local fontdata = otfhandler.readers.getinfo (filename, + { subfont = subfont + , details = false + , platformnames = true + , rawfamilynames = true + }) + local msg = fontdata.comment + if msg then + return false, msg + end + return true, fontdata +end + local load_font_file = function (filename, subfont) - local rawfont, _msg = read_font_file (filename, subfont, true) - if not rawfont then - logreport ("log", 1, "db", "ERROR: failed to open %s.", filename) + local err, ret = read_font_file (filename, subfont) + if err == false then + logreport ("both", 1, "db", "ERROR: failed to open %q: %q.", + tostring (filename), tostring (ret)) return end - return rawfont + return ret end --- rawdata -> (int * int * int | bool) @@ -1337,46 +1347,13 @@ local get_size_info = function (rawinfo) end --[[doc-- - get_english_names_from_ff -- For legacy Fontforge-style names - tables. Extracted from the actual names table, not the font item - itself. ---doc]]-- - -local get_english_names_from_ff = function (metadata) - local names = metadata.names - if names then - for _, raw_namedata in next, names do - if raw_namedata.lang == "English (US)" then - return raw_namedata.names - end - end - end - - -- no (English) names table, probably a broken font - logreport ("both", 3, "db", - "%s: missing or broken English names table.", basename) - return { fontname = metadata.fontname, - fullname = metadata.fullname, } -end - ---[[doc-- map_enlish_names -- Names-table for Lua fontloader objects. This may vanish eventually once we ditch Fontforge completely. Only subset of entries of that table are actually relevant so we’ll stick to that part. --doc]]-- -local names_items = { - compatfull = "compatiblefullname", - fullname = "fullname", - postscriptname = "postscriptname", - preffamily = "typographicfamily", - prefmodifiers = "typographicsubfamily", - family = "family", - subfamily = "subfamily", -} - -local map_english_names = function (metadata) +local get_english_names = function (metadata) local namesource local platformnames = metadata.platformnames --[[-- @@ -1412,16 +1389,9 @@ local map_english_names = function (metadata) --namesource = platformnames.macintosh or platformnames.windows namesource = platformnames.windows or platformnames.macintosh end - namesource = namesource or metadata - local nameinfo = { } - for ours, theirs in next, names_items do - nameinfo [ours] = namesource [theirs] - end - return nameinfo + return namesource or metadata end -get_english_names = map_english_names - --[[-- In case of broken PS names we set some dummies. @@ -1432,14 +1402,15 @@ local get_raw_info = function (metadata, basename) local fontname = metadata.fontname local fullname = metadata.fullname - local validation_state = metadata.validation_state if not fontname or not fullname then --- Broken names table, e.g. avkv.ttf with UTF-16 strings; --- we put some dummies in place like the fontloader --- (font-otf.lua) does. logreport ("both", 3, "db", - "invalid names table of font %s, using dummies.", - basename) + "Invalid names table of font %s, using dummies. \z + Reported: fontname=%q, fullname=%q.", + tostring (basename), tostring (fontname), + tostring (fullname)) fontname = "bad-fontname-" .. basename fullname = "bad-fullname-" .. basename end @@ -1462,46 +1433,46 @@ local organize_namedata = function (rawinfo, nametable, basename, info) - local default_name = nametable.compatfull + local default_name = nametable.compatiblefullname or nametable.fullname or nametable.postscriptname or rawinfo.fullname or rawinfo.fontname or info.fullname or info.fontname - local default_family = nametable.preffamily + local default_family = nametable.typographicfamily or nametable.family or rawinfo.familyname or info.familyname --- local default_modifier = nametable.prefmodifiers +-- local default_modifier = nametable.typographicsubfamily -- or nametable.subfamily local fontnames = { --- see --- https://developer.apple.com/fonts/TTRefMan/RM06/Chap6name.html --- http://www.microsoft.com/typography/OTSPEC/name.htm#NameIDs english = { - --- where a “compatfull” field is given, the value of “fullname” is - --- either identical or differs by separating the style - --- with a hyphen and omitting spaces. (According to the - --- spec, “compatfull” is “Macintosh only”.) - --- Of the three “fullname” fields, this one appears to be the one - --- with the entire name given in a legible, - --- non-abbreviated fashion, for most fonts at any rate. - --- However, in some fonts (e.g. CMU) all three fields are - --- identical. - fullname = --[[ 18 ]] nametable.compatfull + --- where a “compatiblefullname” field is given, the value + --- of “fullname” is either identical or differs by + --- separating the style with a hyphen and omitting spaces. + --- (According to the spec, “compatiblefullname” is + --- “Macintosh only”.) Of the three “fullname” fields, this + --- one appears to be the one with the entire name given in + --- a legible, non-abbreviated fashion, for most fonts at + --- any rate. However, in some fonts (e.g. CMU) all three + --- fields are identical. + fullname = --[[ 18 ]] nametable.compatiblefullname or --[[ 4 ]] nametable.fullname or default_name, --- we keep both the “preferred family” and the “family” --- values around since both are valid but can turn out --- quite differently, e.g. with Latin Modern: - --- preffamily: “Latin Modern Sans”, + --- typographicfamily: “Latin Modern Sans”, --- family: “LM Sans 10” - preffamily = --[[ 16 ]] nametable.preffamilyname, - family = --[[ 1 ]] nametable.family or default_family, - prefmodifiers = --[[ 17 ]] nametable.prefmodifiers, - subfamily = --[[ 2 ]] nametable.subfamily or rawinfo.subfamilyname, - psname = --[[ 6 ]] nametable.postscriptname, + family = --[[ 1 ]] nametable.family or default_family, + subfamily = --[[ 2 ]] nametable.subfamily or rawinfo.subfamilyname, + psname = --[[ 6 ]] nametable.postscriptname, + typographicfamily = --[[ 16 ]] nametable.typographicfamily, + typographicsubfamily = --[[ 17 ]] nametable.typographicsubfamily, }, metadata = { @@ -1596,7 +1567,6 @@ ot_fullinfo = function (filename, style = style, version = rawinfo.version, } - close_font_file (metadata) --> FF only return res end @@ -1623,12 +1593,12 @@ t1_fullinfo = function (filename, _subfont, location, basename, format) local weight sanitized = sanitize_fontnames ({ - fontname = fontname, - psname = fullname, - metafamily = familyname, - familyname = familyname, - weight = metadata.weight, --- string identifier - prefmodifiers = style, + fontname = fontname, + psname = fullname, + metafamily = familyname, + familyname = familyname, + weight = metadata.weight, --- string identifier + typographicsubfamily = style, }) weight = sanitized.weight @@ -1642,21 +1612,21 @@ t1_fullinfo = function (filename, _subfont, location, basename, format) end return { - basename = basename, - fullpath = filename, - subfont = false, - location = location or "system", - format = format, - fullname = sanitized.fullname, - fontname = sanitized.fontname, - familyname = sanitized.familyname, - plainname = fullname, - psname = sanitized.fontname, - version = metadata.version, - size = false, - prefmodifiers = style ~= "" and style or weight, - weight = metadata.pfminfo and pfminfo.weight or 400, - italicangle = italicangle, + basename = basename, + fullpath = filename, + subfont = false, + location = location or "system", + format = format, + fullname = sanitized.fullname, + fontname = sanitized.fontname, + familyname = sanitized.familyname, + plainname = fullname, + psname = sanitized.fontname, + version = metadata.version, + size = false, + typographicsubfamily = style ~= "" and style or weight, + weight = metadata.pfminfo and pfminfo.weight or 400, + italicangle = italicangle, } end @@ -1664,7 +1634,8 @@ local loaders = { otf = ot_fullinfo, ttc = ot_fullinfo, ttf = ot_fullinfo, ---- pfb = t1_fullinfo, + afm = t1_fullinfo, + pfb = t1_fullinfo, } --- not side-effect free! @@ -1808,11 +1779,12 @@ local read_font_names = function (fullname, --- 4) get basic info, abort if fontloader can’t read it - local info = read_font_file (fullname) + local err, info = read_font_file (fullname) - if not info then + if err == false then logreport ("log", 1, "db", - "Failed to read basic information from %q", basename) + "Failed to read basic information from %q: %q", + basename, tostring (info)) return false end @@ -2057,8 +2029,18 @@ do end end +local locate_matching_pfb = function (afmfile, dir) + local pfbname = filereplacesuffix (afmfile, "pfb") + local pfbpath = dir .. "/" .. pfbname + if lfsisfile (pfbpath) then + return pfbpath + end + --- Check for match in texmf too + return kpsefind_file (pfbname, "type1 fonts") +end + local process_dir_tree -process_dir_tree = function (acc, dirs) +process_dir_tree = function (acc, dirs, done) if not next (dirs) then --- done return acc end @@ -2067,48 +2049,56 @@ process_dir_tree = function (acc, dirs) local dir = dirs[#dirs] dirs[#dirs] = nil - if lfschdir (dir) then - lfschdir (pwd) - - local newfiles = { } - local blacklist = names.blacklist - for ent in lfsdir (dir) do - --- filter right away - if ent ~= "." and ent ~= ".." and not blacklist[ent] then - local fullpath = dir .. "/" .. ent - if lfsisdir (fullpath) - and not lpegmatch (p_blacklist, fullpath) - then - dirs[#dirs+1] = fullpath - elseif lfsisfile (fullpath) then - ent = stringlower (ent) - - if lpegmatch (p_font_filter, ent) then - if filesuffix (ent) == "afm" then - --- fontloader.open() will load the afm - --- iff both files are in the same directory - local pfbpath = filereplacesuffix - (fullpath, "pfb") - if lfsisfile (pfbpath) then - newfiles[#newfiles+1] = pfbpath - end - else - newfiles[#newfiles+1] = fullpath + if not lfschdir (dir) then + --- Cannot cd; skip. + return process_dir_tree (acc, dirs, done) + end + + dir = lfscurrentdir () --- resolve symlinks + lfschdir (pwd) + if tablecontains (done, dir) then + --- Already traversed. Note that it’d be unsafe to rely on the + --- hash part above due to Lua only processing up to 32 bytes + --- of string data. The lookup shouldn’t impact performance too + --- much but we could check the performance of alternative data + --- structures at some point. + return process_dir_tree (acc, dirs, done) + end + + local newfiles = { } + local blacklist = names.blacklist + for ent in lfsdir (dir) do + --- filter right away + if ent ~= "." and ent ~= ".." and not blacklist[ent] then + local fullpath = dir .. "/" .. ent + if lfsisdir (fullpath) + and not lpegmatch (p_blacklist, fullpath) + then + dirs[#dirs+1] = fullpath + elseif lfsisfile (fullpath) then + ent = stringlower (ent) + if lpegmatch (p_font_filter, ent) then + newfiles[#newfiles+1] = fullpath + if filesuffix (ent) == "afm" then + local pfbpath = locate_matching_pfb (ent, dir) + if pfbpath then + newfiles[#newfiles+1] = pfbpath end + else + newfiles[#newfiles+1] = fullpath end - end end end - return process_dir_tree (tableappend (acc, newfiles), dirs) end - --- cannot cd; skip - return process_dir_tree (acc, dirs) + done [#done + 1] = dir + return process_dir_tree (tableappend (acc, newfiles), dirs, done) end local process_dir = function (dir) local pwd = lfscurrentdir () if lfschdir (dir) then + dir = lfscurrentdir () --- resolve symlinks lfschdir (pwd) local files = { } @@ -2121,12 +2111,8 @@ local process_dir = function (dir) if lpegmatch (p_font_filter, ent) then if filesuffix (ent) == "afm" then - --- fontloader.open() will load the afm - --- iff both files are in the same - --- directory - local pfbpath = filereplacesuffix - (fullpath, "pfb") - if lfsisfile (pfbpath) then + local pfbpath = locate_matching_pfb (ent, dir) + if pfbpath then files[#files+1] = pfbpath end else @@ -2145,7 +2131,7 @@ end local find_font_files = function (root, recurse) if lfsisdir (root) then if recurse == true then - return process_dir_tree ({}, { root }) + return process_dir_tree ({}, { root }, {}) else --- kpathsea already delivered the necessary subdirs return process_dir (root) end @@ -2261,6 +2247,7 @@ local collect_font_filenames_texmf = function () fontdirs = kpseexpand_path "$OPENTYPEFONTS" fontdirs = fontdirs .. path_separator .. kpseexpand_path "$TTFONTS" fontdirs = fontdirs .. path_separator .. kpseexpand_path "$T1FONTS" + fontdirs = fontdirs .. path_separator .. kpseexpand_path "$AFMFONTS" if stringis_empty (fontdirs) then return { } @@ -2568,6 +2555,7 @@ end local bold_spectrum_low = 501 --- 500 is medium, 900 heavy/black local bold_weight = 700 +local normal_width = 5 local pick_style local pick_fallback_style @@ -2596,18 +2584,19 @@ do return false end - pick_style = function (prefmodifiers, - subfamily) + pick_style = function (typographicsubfamily, subfamily) local style - if prefmodifiers then - style = choose_exact (prefmodifiers) + if typographicsubfamily then + style = choose_exact (typographicsubfamily) + if style then return style end elseif subfamily then style = choose_exact (subfamily) + if style then return style end end - return style + return false end - pick_fallback_style = function (italicangle, weight, pfmweight) + pick_fallback_style = function (italicangle, pfmweight, width) --[[-- More aggressive, but only to determine bold faces. Note: Before you make this test more inclusive, ensure @@ -2618,7 +2607,8 @@ do treating weights > 500 as bold or allowing synonyms like “heavy”, “black”. --]]-- - if pfmweight == bold_weight then --- bold spectrum matches + if width == normal_width and pfmweight == bold_weight then + --- bold spectrum matches if italicangle == 0 then return "b" end @@ -2630,30 +2620,46 @@ do --- we use only exact matches here since there are constructs --- like “regularitalic” (Cabin, Bodoni Old Fashion) - check_regular = function (prefmodifiers, + check_regular = function (typographicsubfamily, subfamily, italicangle, weight, + width, pfmweight) local plausible_weight = false --[[-- This filters out undesirable candidates that specify their - prefmodifiers or subfamily as “regular” but are actually of + typographicsubfamily or subfamily as “regular” but are actually of “semibold” or other weight—another drawback of the oversimplifying classification into only three styles (r, i, b, bi). --]]-- - if italicangle == 0 then - if pfmweight == 400 then plausible_weight = true - elseif weight and regular_synonym [weight] then plausible_weight = true end + if pfmweight == 400 then + --[[-- + Some fonts like Dejavu advertise an undistinguished + regular and a “condensed” version with the same + weight whilst also providing the style info in the + typographic subfamily instead of the subfamily (i. e. + the converse of what Adobe’s doing). The only way to + weed out the undesired pseudo-regular shape is to + peek at its advertised width (4 vs. 5). + --]]-- + if width then + plausible_weight = width == normal_width + else + plausible_weight = true + end + elseif weight and regular_synonym [weight] then + plausible_weight = true + end end if plausible_weight then - if prefmodifiers and regular_synonym [prefmodifiers] - or subfamily and regular_synonym [subfamily] - then - return "r" + if subfamily then + if regular_synonym [subfamily] then return "r" end + elseif typographicsubfamily then + if regular_synonym [typographicsubfamily] then return "r" end end end return false @@ -2676,18 +2682,19 @@ local pull_values = function (entry) entry.subfont = file.subfont --- pull name info ... - entry.psname = english.psname - entry.fontname = info.fontname or metadata.fontname - entry.fullname = english.fullname or info.fullname - entry.prefmodifiers = english.prefmodifiers - entry.familyname = metadata.familyname or english.preffamily or english.family - entry.plainname = names.fullname - entry.subfamily = english.subfamily + entry.psname = english.psname + entry.fontname = info.fontname or metadata.fontname + entry.fullname = english.fullname or info.fullname + entry.typographicsubfamily = english.typographicsubfamily + entry.familyname = metadata.familyname or english.typographicfamily or english.family + entry.plainname = names.fullname + entry.subfamily = english.subfamily --- pull style info ... entry.italicangle = style.italicangle entry.size = style.size entry.weight = style.weight + entry.width = style.width entry.pfmweight = style.pfmweight if config.luaotfload.db.strip == true then @@ -2742,28 +2749,27 @@ local collect_families = function (mappings) pull_values (entry) end - local subtable = get_subtable (families, entry) - - local familyname = entry.familyname - local prefmodifiers = entry.prefmodifiers - local subfamily = entry.subfamily - - local weight = entry.weight - local pfmweight = entry.pfmweight - local italicangle = entry.italicangle - - local modifier = pick_style (prefmodifiers, subfamily) + local subtable = get_subtable (families, entry) + local familyname = entry.familyname + local typographicsubfamily = entry.typographicsubfamily + local subfamily = entry.subfamily + local weight = entry.weight + local width = entry.width + local pfmweight = entry.pfmweight + local italicangle = entry.italicangle + local modifier = pick_style (typographicsubfamily, subfamily) if not modifier then --- regular, exact only - modifier = check_regular (prefmodifiers, + modifier = check_regular (typographicsubfamily, subfamily, italicangle, weight, + width, pfmweight) end if not modifier then - modifier = pick_fallback_style (italicangle, weight, pfmweight) + modifier = pick_fallback_style (italicangle, pfmweight, width) end if modifier then @@ -2994,7 +3000,7 @@ local collect_statistics = function (mappings) local sum_dsnsize, n_dsnsize = 0, 0 local fullname, family, families = { }, { }, { } - local subfamily, prefmodifiers = { }, { } + local subfamily, typographicsubfamily = { }, { } local addtohash = function (hash, item) if item then @@ -3050,10 +3056,10 @@ local collect_statistics = function (mappings) local names = entry.names.sanitized local englishnames = names.english - addtohash (fullname, englishnames.fullname) - addtohash (family, englishnames.family) - addtohash (subfamily, englishnames.subfamily) - addtohash (prefmodifiers, englishnames.prefmodifiers) + addtohash (fullname, englishnames.fullname) + addtohash (family, englishnames.family) + addtohash (subfamily, englishnames.subfamily) + addtohash (typographicsubfamily, englishnames.typographicsubfamily) addtoset (families, englishnames.family, englishnames.fullname) @@ -3124,9 +3130,9 @@ local collect_statistics = function (mappings) pprint_top (subfamily, 4) logreport ("both", 0, "db", - " · %d different “prefmodifiers” kinds.", - setsize (prefmodifiers)) - pprint_top (prefmodifiers, 4) + " · %d different “typographicsubfamily” kinds.", + setsize (typographicsubfamily)) + pprint_top (typographicsubfamily, 4) end @@ -3143,7 +3149,7 @@ local collect_statistics = function (mappings) }, -- style = { -- subfamily = subfamily, --- prefmodifiers = prefmodifiers, +-- typographicsubfamily = typographicsubfamily, -- }, } end @@ -3500,29 +3506,6 @@ local show_cache = function ( ) return true end -local use_fontforge = function (val) - if val == true then - local fontloader = fontloader - read_font_info = fontloader.info - read_font_file = fontloader.open - close_font_file = fontloader.close - get_english_names = get_english_names_from_ff - else - local wrapper = function (filename, subfont) - return otfhandler.readers.getinfo (filename, - { subfont = subfont - , details = false - , platformnames = true - , rawfamilynames = true - }) - end - read_font_file = wrapper - read_font_info = wrapper - close_font_file = function () end - get_english_names = map_english_names - end -end - ----------------------------------------------------------------------- --- export functionality to the namespace “fonts.names” ----------------------------------------------------------------------- @@ -3554,7 +3537,7 @@ local export = { show_cache = show_cache, find_closest = find_closest, --- transitionary - use_fontforge = use_fontforge, + use_fontforge = false, } return { @@ -3570,7 +3553,7 @@ return { fonts.definers = fonts.definers or { resolvers = { } } names.blacklist = blacklist - names.version = 2.7 + names.version = 2.8 names.data = nil --- contains the loaded database names.lookups = nil --- contains the lookup cache diff --git a/src/luaotfload-features.lua b/src/luaotfload-features.lua index 5152fab..b6e889e 100644 --- a/src/luaotfload-features.lua +++ b/src/luaotfload-features.lua @@ -1122,7 +1122,6 @@ local import_values = { -- "style", "optsize",--> from slashed notation; handled otherwise { "lookup", false }, { "sub", false }, - { "mode", true }, } local lookup_types = { "anon" , "file", "kpse" @@ -1221,8 +1220,14 @@ local handle_request = function (specification) request.features = apply_default_features(request.features) if name then - specification.name = name - specification.lookup = lookup or specification.lookup + if lookup == "file" then + local suffix = file.suffix (name) + specification.forcedname = name + specification.forced = suffix + name = file.removesuffix (name) + end + specification.name = name + specification.lookup = lookup or specification.lookup end if request.modifiers then @@ -1245,7 +1250,12 @@ local handle_request = function (specification) --- The next line sets the “rand” feature to “random”; I haven’t --- investigated it any further (luatex-fonts-ext), so it will --- just stay here. - specification.features.normal = normalize (request.features) + local features = specification.features + if not features then + features = { } + specification.features = features + end + features.normal = normalize (request.features) local subfont = tonumber (specification.sub) if subfont and subfont >= 0 then specification.sub = subfont + 1 @@ -1253,8 +1263,9 @@ local handle_request = function (specification) return specification end +fonts.names.handle_request = handle_request + if as_script == true then --- skip the remainder of the file - fonts.names.handle_request = handle_request report ("log", 5, "features", "Exiting early from luaotfload-features.lua.") return @@ -1285,8 +1296,9 @@ local report_otf = logs.reporter("fonts","otf loading") --- start locals for addfeature() -local utfbyte = unicode.utf8.byte -local utfchar = unicode.utf8.char +local utf8 = unicode.utf8 +local utfbyte = utf8.byte +local utflen = utf8.len local otf = handlers and handlers.otf --- filled in later during initialization @@ -1330,15 +1342,15 @@ local function current_addfeature(data,feature,specifications) if not features or not sequences then return end - local gsubfeatures = features.gsub - if gsubfeatures and gsubfeatures[feature] then - return -- already present - end + -- feature has to be unique but the name entry wins eventually + local fontfeatures = resources.features or everywhere local unicodes = resources.unicodes local splitter = lpeg.splitter(" ",unicodes) local done = 0 local skip = 0 + local aglunicodes = false + if not specifications[1] then -- so we accept a one entry specification specifications = { specifications } @@ -1347,11 +1359,24 @@ local function current_addfeature(data,feature,specifications) local function tounicode(code) if not code then return - elseif type(code) == "number" then + end + if type(code) == "number" then return code - else - return unicodes[code] or utfbyte(code) end + local u = unicodes[code] + if u then + return u + end + if utflen(code) == 1 then + u = utfbyte(code) + if u then + return u + end + end + if not aglunicodes then + aglunicodes = fonts.encodings.agl.unicodes -- delayed + end + return aglunicodes[code] end local coverup = otf.coverup @@ -1359,9 +1384,281 @@ local function current_addfeature(data,feature,specifications) local stepkey = coverup.stepkey local register = coverup.register + local function prepare_substitution(list,featuretype) + local coverage = { } + local cover = coveractions[featuretype] + for code, replacement in next, list do + local unicode = tounicode(code) + local description = descriptions[unicode] + if description then + if type(replacement) == "table" then + replacement = replacement[1] + end + replacement = tounicode(replacement) + if replacement and descriptions[replacement] then + cover(coverage,unicode,replacement) + done = done + 1 + else + skip = skip + 1 + end + else + skip = skip + 1 + end + end + return coverage + end + + local function prepare_alternate(list,featuretype) + local coverage = { } + local cover = coveractions[featuretype] + for code, replacement in next, list do + local unicode = tounicode(code) + local description = descriptions[unicode] + if not description then + skip = skip + 1 + elseif type(replacement) == "table" then + local r = { } + for i=1,#replacement do + local u = tounicode(replacement[i]) + r[i] = descriptions[u] and u or unicode + end + cover(coverage,unicode,r) + done = done + 1 + else + local u = tounicode(replacement) + if u then + cover(coverage,unicode,{ u }) + done = done + 1 + else + skip = skip + 1 + end + end + end + return coverage + end + + local function prepare_multiple(list,featuretype) + local coverage = { } + local cover = coveractions[featuretype] + for code, replacement in next, list do + local unicode = tounicode(code) + local description = descriptions[unicode] + if not description then + skip = skip + 1 + elseif type(replacement) == "table" then + local r, n = { }, 0 + for i=1,#replacement do + local u = tounicode(replacement[i]) + if descriptions[u] then + n = n + 1 + r[n] = u + end + end + if n > 0 then + cover(coverage,unicode,r) + done = done + 1 + else + skip = skip + 1 + end + else + local u = tounicode(replacement) + if u then + cover(coverage,unicode,{ u }) + done = done + 1 + else + skip = skip + 1 + end + end + end + return coverage + end + + local function prepare_ligature(list,featuretype) + local coverage = { } + local cover = coveractions[featuretype] + for code, ligature in next, list do + local unicode = tounicode(code) + local description = descriptions[unicode] + if description then + if type(ligature) == "string" then + ligature = { lpegmatch(splitter,ligature) } + end + local present = true + for i=1,#ligature do + local l = ligature[i] + local u = tounicode(l) + if descriptions[u] then + ligature[i] = u + else + present = false + break + end + end + if present then + cover(coverage,unicode,ligature) + done = done + 1 + else + skip = skip + 1 + end + else + skip = skip + 1 + end + end + return coverage + end + + local function prepare_kern(list,featuretype) + local coverage = { } + local cover = coveractions[featuretype] + for code, replacement in next, list do + local unicode = tounicode(code) + local description = descriptions[unicode] + if description and type(replacement) == "table" then + local r = { } + for k, v in next, replacement do + local u = tounicode(k) + if u then + r[u] = v + end + end + if next(r) then + cover(coverage,unicode,r) + done = done + 1 + else + skip = skip + 1 + end + else + skip = skip + 1 + end + end + return coverage + end + + local function prepare_pair(list,featuretype) + local coverage = { } + local cover = coveractions[featuretype] + if cover then + for code, replacement in next, list do + local unicode = tounicode(code) + local description = descriptions[unicode] + if description and type(replacement) == "table" then + local r = { } + for k, v in next, replacement do + local u = tounicode(k) + if u then + r[u] = v + end + end + if next(r) then + cover(coverage,unicode,r) + done = done + 1 + else + skip = skip + 1 + end + else + skip = skip + 1 + end + end + else + report_otf("unknown cover type %a",featuretype) + end + return coverage + end + + local function prepare_chain(list,featuretype,sublookups) + -- todo: coveractions + local rules = list.rules + local coverage = { } + if rules then + local rulehash = { } + local rulesize = 0 + local sequence = { } + local nofsequences = 0 + local lookuptype = types[featuretype] + for nofrules=1,#rules do + local rule = rules[nofrules] + local current = rule.current + local before = rule.before + local after = rule.after + local replacements = rule.replacements or false + local sequence = { } + local nofsequences = 0 + if before then + for n=1,#before do + nofsequences = nofsequences + 1 + sequence[nofsequences] = before[n] + end + end + local start = nofsequences + 1 + for n=1,#current do + nofsequences = nofsequences + 1 + sequence[nofsequences] = current[n] + end + local stop = nofsequences + if after then + for n=1,#after do + nofsequences = nofsequences + 1 + sequence[nofsequences] = after[n] + end + end + local lookups = rule.lookups or false + local subtype = nil + if lookups and sublookups then + for k, v in next, lookups do + local lookup = sublookups[v] + if lookup then + lookups[k] = lookup + if not subtype then + subtype = lookup.type + end + else + -- already expanded + end + end + end + if nofsequences > 0 then -- we merge coverage into one + -- we copy as we can have different fonts + local hashed = { } + for i=1,nofsequences do + local t = { } + local s = sequence[i] + for i=1,#s do + local u = tounicode(s[i]) + if u then + t[u] = true + end + end + hashed[i] = t + end + sequence = hashed + -- now we create the rule + rulesize = rulesize + 1 + rulehash[rulesize] = { + nofrules, -- 1 + lookuptype, -- 2 + sequence, -- 3 + start, -- 4 + stop, -- 5 + lookups, -- 6 (6/7 also signal of what to do) + replacements, -- 7 + subtype, -- 8 + } + for unic in next, sequence[start] do + local cu = coverage[unic] + if not cu then + coverage[unic] = rulehash -- can now be done cleaner i think + end + end + end + end + end + return coverage + end + for s=1,#specifications do local specification = specifications[s] local valid = specification.valid + local feature = specification.name or feature if not valid or valid(data,specification,feature) then local initialize = specification.initialize if initialize then @@ -1369,185 +1666,133 @@ local function current_addfeature(data,feature,specifications) specification.initialize = initialize(specification,data) and initialize or nil end local askedfeatures = specification.features or everywhere - local askedsteps = specifications.steps or specification.subtables or { specification.data } or { } + local askedsteps = specification.steps or specification.subtables or { specification.data } or { } local featuretype = normalized[specification.type or "substitution"] or "substitution" local featureflags = specification.flags or noflags local featureorder = specification.order or { feature } - local added = false + local featurechain = (featuretype == "chainsubstitution" or featuretype == "chainposition") and 1 or 0 local nofsteps = 0 local steps = { } + local sublookups = specification.lookups + local category = nil + if sublookups then + local s = { } + for i=1,#sublookups do + local specification = sublookups[i] + local askedsteps = specification.steps or specification.subtables or { specification.data } or { } + local featuretype = normalized[specification.type or "substitution"] or "substitution" + local featureflags = specification.flags or noflags + local nofsteps = 0 + local steps = { } + for i=1,#askedsteps do + local list = askedsteps[i] + local coverage = nil + local format = nil + if featuretype == "substitution" then + coverage = prepare_substitution(list,featuretype) + elseif featuretype == "ligature" then + coverage = prepare_ligature(list,featuretype) + elseif featuretype == "alternate" then + coverage = prepare_alternate(list,featuretype) + elseif featuretype == "multiple" then + coverage = prepare_multiple(list,featuretype) + elseif featuretype == "kern" then + format = "kern" + coverage = prepare_kern(list,featuretype) + elseif featuretype == "pair" then + format = "pair" + coverage = prepare_pair(list,featuretype) + end + if coverage and next(coverage) then + nofsteps = nofsteps + 1 + steps[nofsteps] = register(coverage,featuretype,format,feature,nofsteps,descriptions,resources) + end + end + s[i] = { + [stepkey] = steps, + nofsteps = nofsteps, + type = types[featuretype], + } + end + sublookups = s + end for i=1,#askedsteps do local list = askedsteps[i] - local coverage = { } - local cover = coveractions[featuretype] + local coverage = nil local format = nil - if not cover then - -- unknown - elseif featuretype == "substitution" then - for code, replacement in next, list do - local unicode = tounicode(code) - local description = descriptions[unicode] - if description then - if type(replacement) == "table" then - replacement = replacement[1] - end - replacement = tounicode(replacement) - if replacement and descriptions[replacement] then - cover(coverage,unicode,replacement) - done = done + 1 - else - skip = skip + 1 - end - else - skip = skip + 1 - end - end + if featuretype == "substitution" then + category = "gsub" + coverage = prepare_substitution(list,featuretype) elseif featuretype == "ligature" then - for code, ligature in next, list do - local unicode = tounicode(code) - local description = descriptions[unicode] - if description then - if type(ligature) == "string" then - ligature = { lpegmatch(splitter,ligature) } - end - local present = true - for i=1,#ligature do - local l = ligature[i] - local u = tounicode(l) - if descriptions[u] then - ligature[i] = u - else - present = false - break - end - end - if present then - cover(coverage,unicode,ligature) - done = done + 1 - else - skip = skip + 1 - end - else - skip = skip + 1 - end - end + category = "gsub" + coverage = prepare_ligature(list,featuretype) elseif featuretype == "alternate" then - for code, replacement in next, list do - local unicode = tounicode(code) - local description = descriptions[unicode] - if not description then - skip = skip + 1 - elseif type(replacement) == "table" then - local r = { } - for i=1,#replacement do - local u = tounicode(replacement[i]) - r[i] = descriptions[u] and u or unicode - end - cover(coverage,unicode,r) - done = done + 1 - else - local u = tounicode(replacement) - if u then - cover(coverage,unicode,{ u }) - done = done + 1 - else - skip = skip + 1 - end - end - end - elseif featuretype == "multiple" then -- todo: unicode can be table - for code, replacement in next, list do - local unicode = tounicode(code) - local description = descriptions[unicode] - if not description then - skip = skip + 1 - elseif type(replacement) == "table" then - local r, n = { }, 0 - for i=1,#replacement do - local u = tounicode(replacement[i]) - if descriptions[u] then - n = n + 1 - r[n] = u - end - end - if n > 0 then - cover(coverage,unicode,r) - done = done + 1 - else - skip = skip + 1 - end - else - local u = tounicode(replacement) - if u then - cover(coverage,unicode,{ u }) - done = done + 1 - else - skip = skip + 1 - end - end - end + category = "gsub" + coverage = prepare_alternate(list,featuretype) + elseif featuretype == "multiple" then + category = "gsub" + coverage = prepare_multiple(list,featuretype) elseif featuretype == "kern" then - for code, replacement in next, list do - local unicode = tounicode(code) - local description = descriptions[unicode] - if description and type(replacement) == "table" then - local r = { } - for k, v in next, replacement do - local u = tounicode(k) - if u then - r[u] = v - end - end - if next(r) then - cover(coverage,unicode,r) - done = done + 1 - else - skip = skip + 1 - end - else - skip = skip + 1 - end - end - format = "kern" + category = "gpos" + format = "kern" + coverage = prepare_kern(list,featuretype) + elseif featuretype == "pair" then + category = "gpos" + format = "pair" + coverage = prepare_pair(list,featuretype) + elseif featuretype == "chainsubstitution" then + category = "gsub" + coverage = prepare_chain(list,featuretype,sublookups) + elseif featuretype == "chainposition" then + category = "gpos" + coverage = prepare_chain(list,featuretype,sublookups) + else + report_otf("not registering feature %a, unknown category",feature) + return end - if next(coverage) then - added = true + if coverage and next(coverage) then nofsteps = nofsteps + 1 steps[nofsteps] = register(coverage,featuretype,format,feature,nofsteps,descriptions,resources) end end - if added then + if nofsteps > 0 then -- script = { lang1, lang2, lang3 } or script = { lang1 = true, ... } for k, v in next, askedfeatures do if v[1] then askedfeatures[k] = tohash(v) end end + if featureflags[1] then featureflags[1] = "mark" end + if featureflags[2] then featureflags[2] = "ligature" end + if featureflags[3] then featureflags[3] = "base" end local sequence = { - chain = 0, + chain = featurechain, features = { [feature] = askedfeatures }, flags = featureflags, - name = feature, -- not needed + name = feature, -- redundant order = featureorder, [stepkey] = steps, nofsteps = nofsteps, type = types[featuretype], } + -- todo : before|after|index if specification.prepend then insert(sequences,1,sequence) else insert(sequences,sequence) end -- register in metadata (merge as there can be a few) - if not gsubfeatures then - gsubfeatures = { } - fontfeatures.gsub = gsubfeatures + local features = fontfeatures[category] + if not features then + features = { } + fontfeatures[category] = features end - local k = gsubfeatures[feature] + local k = features[feature] if not k then k = { } - gsubfeatures[feature] = k + features[feature] = k end + -- for script, languages in next, askedfeatures do local kk = k[script] if not kk then diff --git a/src/luaotfload-init.lua b/src/luaotfload-init.lua index bcc0e18..c977b49 100644 --- a/src/luaotfload-init.lua +++ b/src/luaotfload-init.lua @@ -9,6 +9,7 @@ local setmetatable = setmetatable local kpselookup = kpse.lookup +local lfsisdir = lfs.isdir --[[doc-- @@ -223,7 +224,7 @@ end --- below paths are relative to the texmf-context local ltx = "tex/generic/context/luatex" -local ctx = "tex/context/base" +local ctx = { "tex/context/base/mkiv", "tex/context/base" } local context_modules = { @@ -251,8 +252,6 @@ local context_modules = { { ctx, "font-map" }, { ltx, "luatex-fonts-syn" }, { ctx, "font-tfm" }, - { ctx, "font-afm" }, - { ctx, "font-afk" }, { ctx, "font-oti" }, { ctx, "font-otr" }, { ctx, "font-cff" }, @@ -265,6 +264,10 @@ local context_modules = { { ctx, "font-ota" }, { ctx, "font-ots" }, { ctx, "font-osd" }, + { ctx, "font-ocl" }, + { ctx, "font-onr" }, + { ctx, "font-one" }, + { ctx, "font-afk" }, { ctx, "font-lua" }, { ctx, "font-def" }, { ltx, "luatex-fonts-ext" }, @@ -284,17 +287,37 @@ local load_context_modules = function (pth) local sub, spec = unpack (context_modules [i]) if sub == false then ignore_module (spec) - elseif type (sub) == "string" then - if pth then + else + local tsub = type (sub) + if not pth then + load_module (spec) + elseif tsub == "string" then load_module (spec, file.join (pth, sub)) + elseif tsub == "table" then + local pfx + local nsub = #sub + for j = 1, nsub do + local full = file.join (pth, sub [j]) + if lfsisdir (full) then --- pick the first real one + pfx = full + break + end + end + if pfx then + load_module (spec, pfx) + else + logreport ("both", 0, "init", + "None of the %d search paths for module %q exist; \z + falling back to default path.", + nsub, tostring (spec)) + load_module (spec) --- maybe we’ll get by after all? + end else - load_module (spec) + logreport ("both", 0, "init", + "Internal error, please report. \z + This is not your fault.") + os.exit (-1) end - else - logreport ("both", 0, "init", - "Internal error, please report. \z - This is not your fault.") - os.exit (-1) end end @@ -509,8 +532,6 @@ local init_main = function () load_fontloader_module "font-map" load_fontloader_module "fonts-syn" load_fontloader_module "font-tfm" - load_fontloader_module "font-afm" - load_fontloader_module "font-afk" load_fontloader_module "font-oti" load_fontloader_module "font-otr" load_fontloader_module "font-cff" @@ -523,6 +544,10 @@ local init_main = function () load_fontloader_module "font-ota" load_fontloader_module "font-ots" load_fontloader_module "font-osd" + load_fontloader_module "font-ocl" + load_fontloader_module "font-onr" + load_fontloader_module "font-one" + load_fontloader_module "font-afk" load_fontloader_module "font-lua" load_fontloader_module "font-def" load_fontloader_module "fonts-ext" @@ -533,7 +558,7 @@ local init_main = function () "Loading Context modules in lookup path.") load_context_modules () - elseif lfs.isdir (fontloader) then + elseif lfsisdir (fontloader) then logreport ("log", 0, "init", "Loading Context files under prefix “%s”.", fontloader) diff --git a/src/luaotfload-loaders.lua b/src/luaotfload-loaders.lua index f6cb272..68cc50f 100644 --- a/src/luaotfload-loaders.lua +++ b/src/luaotfload-loaders.lua @@ -49,12 +49,22 @@ local unsupported_reader = function (format) end end -local afm_compat_message = function (specification, method) +local afm_reader = fonts.readers.afm + +local afm_loader = function (specification) + specification.forced = "afm" + specification.sub = false + specification.forcedname = file.addsuffix(specification.name, "afm") + inspect(specification) + return afm_reader (specification, "afm") +end + +local afm_compat_message = function (specification) logreport ("both", 4, "loaders", "PFB format only supported with matching \z - AFM; redirecting (“%s”, “%s”).", - tostring (specification.name), tostring (method)) - return fonts.readers.afm (specification, method) + AFM; redirecting (“%s”, “afm”).", + tostring (specification.name)) + return afm_loader (specification) end local install_formats = function () @@ -64,9 +74,8 @@ local install_formats = function () local readers = fonts.readers local sequence = readers.sequence local seqset = table.tohash (sequence) - local handlers = fonts.handlers local formats = fonts.formats - if not readers or not handlers or not formats then return false end + if not readers or not formats then return false end local aux = function (which, reader) if not which or type (which) ~= "string" @@ -76,7 +85,6 @@ local install_formats = function () end formats [which] = "type1" readers [which] = reader - handlers [which] = { } if not seqset [which] then logreport ("both", 3, "loaders", "Extending reader sequence for “%s”.", which) @@ -89,11 +97,28 @@ local install_formats = function () return aux ("evl", eval_reader) and aux ("lua", lua_reader) and aux ("pfa", unsupported_reader "pfa") + and aux ("afm", afm_loader) and aux ("pfb", afm_compat_message) --- pfb loader is incomplete and aux ("ofm", readers.tfm) and aux ("dfont", unsupported_reader "dfont") end +local not_found_msg = function (specification, size, id) + logreport ("both", 0, "loaders", "") + logreport ("both", 0, "loaders", + "--------------------------------------------------------") + logreport ("both", 0, "loaders", "") + logreport ("both", 0, "loaders", "Font definition failed for:") + logreport ("both", 0, "loaders", "") + logreport ("both", 0, "loaders", " > id : %d", id) + logreport ("both", 0, "loaders", " > specification : %q", specification) + if size > 0 then + logreport ("both", 0, "loaders", " > size : %.2f pt", size / 2^16) + end + logreport ("both", 0, "loaders", "") + logreport ("both", 0, "loaders", + "--------------------------------------------------------") +end --[[doc-- \subsection{\CONTEXT override} @@ -110,6 +135,7 @@ do local patch = function (specification, size, id) local fontdata = read (specification, size, id) +----if not fontdata then not_found_msg (specification, size, id) end if type (fontdata) == "table" and fontdata.shared then --- We need to test for the “shared” field here --- or else the fontspec capheight callback will @@ -129,10 +155,8 @@ do logreport ("both", 0, "loaders", " > spec %q", specification) logreport ("both", 0, "loaders", " > at size %.2f pt", size / 2^16) local result = definer (specification, size, id) - if not result then - logreport ("both", 0, "loaders", " > font definition failed") - return - elseif type (result) == "number" then + if not result then return not_found_msg (specification, size, id) end + if type (result) == "number" then logreport ("both", 0, "loaders", " > font definition yielded id %d", result) return result end diff --git a/src/luaotfload-tool.lua b/src/luaotfload-tool.lua index 35dc9b3..bc9e425 100755 --- a/src/luaotfload-tool.lua +++ b/src/luaotfload-tool.lua @@ -145,8 +145,6 @@ require "fontloader-fonts-enc" require "fontloader-font-cid" require "fontloader-font-map" require "fontloader-font-tfm" -require "fontloader-font-afm" -require "fontloader-font-afk" require "fontloader-font-oti" require "fontloader-font-otr" require "fontloader-font-cff" @@ -159,6 +157,9 @@ require "fontloader-font-oto" ------- "fontloader-font-ota" ------- "fontloader-font-ots" ------- "fontloader-font-osd" +require "fontloader-font-onr" +require "fontloader-font-one" +require "fontloader-font-afk" require "fontloader-font-lua" require "fontloader-font-def" require "fontloader-fonts-ext" @@ -797,7 +798,7 @@ actions.blacklist = function (job) end actions.generate = function (job) - local _ = fonts.names.update (fontnames, job.force_reload, job.dry_run) + local _ = fonts.names.update ({ }, job.force_reload, job.dry_run) local namedata = fonts.names.data () if namedata then logreport ("info", 2, "db", |