diff options
Diffstat (limited to 'tex/context/base/font-otr.lua')
-rw-r--r-- | tex/context/base/font-otr.lua | 655 |
1 files changed, 424 insertions, 231 deletions
diff --git a/tex/context/base/font-otr.lua b/tex/context/base/font-otr.lua index 3ff260d44..1b53601a9 100644 --- a/tex/context/base/font-otr.lua +++ b/tex/context/base/font-otr.lua @@ -6,35 +6,59 @@ if not modules then modules = { } end modules ['font-otr'] = { license = "see context related readme files" } --- this code is not yet ready for generic i.e. i want to be free to change the --- keys and values - --- we can optimize kern pairs (i.e. simple h only positioning) later if we want --- which is easier as then we know if we have clashes between features --- -- When looking into a cid font relates issue in the ff library I wondered if -- it made sense to use Lua to filter the information from the otf and ttf -- files. Quite some ff code relates to special fonts and in practice we only -- use rather normal opentype fonts. -- -- The code here is based on the documentation (and examples) at the microsoft --- website. The code will be extended and improved stepwise. We generate a table --- that is comparabel with the one luatex creates but also can create one for --- context directly. +-- website. The code will be extended and improved stepwise. After some experiments +-- I decided to convert to a format more suitable for the context font handler +-- because it makes no sense to rehash all those lookups again. +-- +-- Currently we can use this code for getting basic info about the font, loading +-- shapes and loading the extensive table. I'm not sure if I will provide a ff +-- compatible output as well (We're not that far from it as currently I can load +-- all data reasonable fast.) + +-- This code is not yet ready for generic i.e. I want to be free to change the +-- keys and values. Especially the gpos/gsub/gdef/math needs checking (this +-- is implemented in font-dsp.lua). + +-- We can omit redundant glyphs names i.e. ones that match the agl or +-- are just a unicode string but it doesn't save that much. It will be an option +-- some day. + +-- Optimizing the widths wil be done anyway as it save quite some on a cjk font +-- and the existing (old) code if okay. + +-- todo: duplicates +-- todo: markclasses : checking needed (see font-otf) +-- +-- todo: check all unsigned / signed (will be done last) +-- todo: more messages (only if really needed) +-- +-- todo (in old loader and new one) math: -- --- todo: add checks for versions --- todo: check all unsigned / signed --- todo: save mode for context font loader (also deal with unicode dups) +-- italic_correction -> italic +-- top_accent -> top accent +-- vert_parts -> vparts +-- horiz_parts -> hparts +-- vert_variants -> vvariants -> next in tex, so better 'sizes' +-- horiz_variants -> hvariants -> next in tex, so better 'sizes' -- --- widths and weights are kind of messy: for instance lmmonolt ias a pfmweight of --- 400 while it should be 300 +-- start -> first (so we can skip the first same-size one) +-- end -> last -- --- we can have a bit more in the info data if needed as it will nto really slow --- down identifying +-- We can optimize kern pairs (i.e. simple h only positioning) later if we want +-- which is easier as then we know if we have clashes between features. We can have +-- kerns as well as moves (smaller files) -- --- the main loader is not yet for production use (work in progress on the dsp file --- but as soon we we're done i will also adapt that table (as there is no need to --- be completely ff compatible) +-- Widths and weights are kind of messy: for instance lmmonolt has a pfmweight of +-- 400 while it should be 300. So, for now we mostly stick to the old compromis. + +-- We don't really need all those language tables so they might be dropped some +-- day. if not characters then require("char-def") @@ -42,11 +66,11 @@ if not characters then end local next, type, unpack = next, type, unpack -local byte, lower, char = string.byte, string.lower, string.char +local byte, lower, char, strip, gsub = string.byte, string.lower, string.char, string.strip, string.gsub local bittest = bit32.btest local concat, remove = table.concat, table.remove local floor, mod, abs, sqrt, round = math.floor, math.mod, math.abs, math.sqrt, math.round -local P, C, R, S, C, Cs, Cc, Ct, Carg, Cmt = lpeg.P, lpeg.C, lpeg.R, lpeg.S, lpeg.C, lpeg.Cs, lpeg.Cc, lpeg.Ct, lpeg.Carg, lpeg.Cmt +local P, R, S, C, Cs, Cc, Ct, Carg, Cmt = lpeg.P, lpeg.R, lpeg.S, lpeg.C, lpeg.Cs, lpeg.Cc, lpeg.Ct, lpeg.Carg, lpeg.Cmt local lpegmatch = lpeg.match local setmetatableindex = table.setmetatableindex @@ -66,25 +90,45 @@ handlers.otf = otf local readers = otf.readers or { } otf.readers = readers -local files = utilities.files - -local readbytes = files.readbytes -local readstring = files.readstring -local readbyte = files.readcardinal1 -- 8-bit unsigned integer -local readushort = files.readcardinal2 -- 16-bit unsigned integer -local readuint = files.readcardinal3 -- 24-bit unsigned integer -local readulong = files.readcardinal4 -- 24-bit unsigned integer -local readchar = files.readinteger1 -- 8-bit signed integer -local readshort = files.readinteger2 -- 16-bit signed integer -local readlong = files.readinteger4 -- 24-bit unsigned integer -local readfixed = files.readfixed4 -local readfword = readshort -- 16-bit signed integer that describes a quantity in FUnits -local readufword = readushort -- 16-bit unsigned integer that describes a quantity in FUnits +-- local streamreader = utilities.streams -- faster on big files +local streamreader = utilities.files -- faster on identify + +readers.streamreader = streamreader + +local openfile = streamreader.open +local closefile = streamreader.close +local skipbytes = streamreader.skip +local setposition = streamreader.setposition +local skipshort = streamreader.skipshort +local readbytes = streamreader.readbytes +local readstring = streamreader.readstring +local readbyte = streamreader.readcardinal1 -- 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 +local readshort = streamreader.readinteger2 -- 16-bit signed integer +local readlong = streamreader.readinteger4 -- 24-bit unsigned integer +local readfixed = streamreader.readfixed4 +local readfword = readshort -- 16-bit signed integer that describes a quantity in FUnits +local readufword = readushort -- 16-bit unsigned integer that describes a quantity in FUnits local readoffset = readushort -local read2dot14 = files.read2dot14 -- 16-bit signed fixed number with the low 14 bits of fraction (2.14) (F2DOT14) +local read2dot14 = streamreader.read2dot14 -- 16-bit signed fixed number with the low 14 bits of fraction (2.14) (F2DOT14) + +function streamreader.readtag(f) + return lower(strip(readstring(f,4))) +end -local readtag = function(f) return f:read(4) end -local skipshort = function(f,n) f:read(n and 2*n or 2) end +-- date represented in number of seconds since 12:00 midnight, January 1, 1904. The value is represented as a +-- signed 64-bit integer + +local function readlongdatetime(f) + local a, b, c, d, e, f, g, h = readbytes(f,8) + return 0x100000000 * d + 0x1000000 * e + 0x10000 * f + 0x100 * g + h +end + +local tableversion = 0.001 +local privateoffset = fonts.constructors and fonts.constructors.privateoffset or 0xF0000 -- 0x10FFFF local reportedskipped = { } @@ -94,13 +138,6 @@ local function reportskippedtable(tag) reportedskipped[tag] = true end end --- date represented in number of seconds since 12:00 midnight, January 1, 1904. The value is represented as a --- signed 64-bit integer - -local function readlongdatetime(f) - local a, b, c, d, e, f, g, h = byte(f:read(8),1,8) - return 0x100000000 * d + 0x1000000 * e + 0x10000 * f + 0x100 * g + h -end -- We have quite some data tables. We are somewhat ff compatible with names but as I used -- the information form the microsoft site there can be differences. Eventually I might end @@ -151,6 +188,7 @@ local platforms = { [0] = } local encodings = { + -- these stay: unicode = { [0] = "unicode 1.0 semantics", "unicode 1.1 semantics", @@ -160,6 +198,7 @@ local encodings = { "unicode variation sequences", -- cmap subtable format 14). "unicode full repertoire", -- cmap subtable formats 0, 4, 6, 10, 12, 13 }, + -- these can go: macintosh = { [0] = "roman", "japanese", "chinese (traditional)", "korean", "arabic", "hebrew", "greek", "russian", "rsymbol", "devanagari", "gurmukhi", "gujarati", "oriya", "bengali", "tamil", "telugu", "kannada", @@ -167,11 +206,13 @@ local encodings = { "chinese (simplified)", "tibetan", "mongolian", "geez", "slavic", "vietnamese", "sindhi", "uninterpreted", }, + -- these stay: iso = { [0] = "7-bit ascii", "iso 10646", "iso 8859-1", }, + -- these stay: windows = { [0] = "symbol", "unicode bmp", -- this is utf16 @@ -204,9 +245,11 @@ local decoders = { -- names (in that order). A font with no english name is probably a weird one anyway. local languages = { + -- these stay: unicode = { [ 0] = "english", }, + -- english can stay: macintosh = { [ 0] = "english", [ 1] = "french", @@ -327,8 +370,10 @@ local languages = { [149] = "greenlandic", [150] = "azerbaijani (roman script)", }, + -- these can stay: iso = { }, + -- english can stay: windows = { [0x0436] = "afrikaans - south africa", [0x041c] = "albanian - albania", @@ -652,7 +697,7 @@ local panosewidths = { function readers.name(f,fontdata) local datatable = fontdata.tables.name if datatable then - f:seek("set",datatable.offset) + setposition(f,datatable.offset) local format = readushort(f) local nofnames = readushort(f) local offset = readushort(f) @@ -730,7 +775,7 @@ function readers.name(f,fontdata) local encoding = name.encoding local language = name.language if (not e or encoding == e) and (not l or language == l) then - f:seek("set",start+name.offset) + setposition(f,start+name.offset) local content = readstring(f,name.length) local decoder = decoders[platform] if decoder then @@ -771,7 +816,7 @@ end readers["os/2"] = function(f,fontdata) local datatable = fontdata.tables["os/2"] if datatable then - f:seek("set",datatable.offset) + setposition(f,datatable.offset) local version = readushort(f) local windowsmetrics = { version = version, @@ -832,7 +877,7 @@ end readers.head = function(f,fontdata) local datatable = fontdata.tables.head if datatable then - f:seek("set",datatable.offset) + setposition(f,datatable.offset) local fontheader = { version = readfixed(f), revision = readfixed(f), @@ -867,7 +912,7 @@ readers.hhea = function(f,fontdata,specification) if specification.details then local datatable = fontdata.tables.hhea if datatable then - f:seek("set",datatable.offset) + setposition(f,datatable.offset) fontdata.horizontalheader = { version = readfixed(f), ascender = readfword(f), @@ -905,18 +950,20 @@ readers.maxp = function(f,fontdata,specification) if specification.details then local datatable = fontdata.tables.maxp if datatable then - f:seek("set",datatable.offset) - local version = readfixed(f) + setposition(f,datatable.offset) + local version = readfixed(f) + local nofglyphs = readushort(f) + fontdata.nofglyphs = nofglyphs if version == 0.5 then fontdata.maximumprofile = { version = version, - nofglyphs = readushort(f), + nofglyphs = nofglyphs, } return elseif version == 1.0 then fontdata.maximumprofile = { version = version, - nofglyphs = readushort(f), + nofglyphs = nofglyphs, points = readushort(f), contours = readushort(f), compositepoints = readushort(f), @@ -948,7 +995,7 @@ readers.hmtx = function(f,fontdata,specification) if specification.glyphs then local datatable = fontdata.tables.hmtx if datatable then - f:seek("set",datatable.offset) + setposition(f,datatable.offset) local nofmetrics = fontdata.horizontalheader.nofhmetrics local glyphs = fontdata.glyphs local nofglyphs = fontdata.nofglyphs @@ -962,9 +1009,9 @@ readers.hmtx = function(f,fontdata,specification) if advance ~= 0 then glyph.width = width end - if leftsidebearing ~= 0 then - glyph.lsb = leftsidebearing - end + -- if leftsidebearing ~= 0 then + -- glyph.lsb = leftsidebearing + -- end end -- The next can happen in for instance a monospace font or in a cjk font -- with fixed widths. @@ -973,9 +1020,9 @@ readers.hmtx = function(f,fontdata,specification) if width ~= 0 then glyph.width = width end - if leftsidebearing ~= 0 then - glyph.lsb = leftsidebearing - end + -- if leftsidebearing ~= 0 then + -- glyph.lsb = leftsidebearing + -- end end end end @@ -989,7 +1036,7 @@ end readers.post = function(f,fontdata,specification) local datatable = fontdata.tables.post if datatable then - f:seek("set",datatable.offset) + setposition(f,datatable.offset) local version = readfixed(f) fontdata.postscript = { version = version, @@ -1010,12 +1057,11 @@ readers.post = function(f,fontdata,specification) glyphs[index].name = standardromanencoding[index] end elseif version == 2.0 then - local glyphs = fontdata.glyphs - local nofglyphs = readushort(f) - local filesize = fontdata.filesize - local indices = { } - local names = { } - local maxnames = 0 + local glyphs = fontdata.glyphs + local nofglyphs = readushort(f) + local indices = { } + local names = { } + local maxnames = 0 for i=0,nofglyphs-1 do local nameindex = readushort(f) if nameindex >= 258 then @@ -1027,12 +1073,18 @@ readers.post = function(f,fontdata,specification) end end for i=1,maxnames do - local length = readbyte(f) - if length > 0 then - glyphs[indices[i]].name = readstring(f,length) - else - report("quit post name fetching at %a of %a",i,maxnames) + local mapping = indices[i] + if not mapping then + report("quit post name fetching at %a of %a: %s",i,maxnames,"no index") break + else + local length = readbyte(f) + if length > 0 then + glyphs[mapping].name = readstring(f,length) + else + report("quit post name fetching at %a of %a: %s",i,maxnames,"overflow") + break + end end end elseif version == 2.5 then @@ -1058,7 +1110,7 @@ end local formatreaders = { } formatreaders[4] = function(f,fontdata,offset) - f:seek("set",offset+2) -- skip format + setposition(f,offset+2) -- skip format -- local length = readushort(f) -- in bytes of subtable local language = readushort(f) @@ -1071,7 +1123,7 @@ formatreaders[4] = function(f,fontdata,offset) local deltas = { } local offsets = { } local indices = { } - local mapmap = fontdata.map.map + local mapping = fontdata.mapping local glyphs = fontdata.glyphs -- for i=1,nofsegments do @@ -1109,7 +1161,7 @@ formatreaders[4] = function(f,fontdata,offset) if not glyph.unicode then glyph.unicode = unicode end - mapmap[index] = unicode + mapping[index] = unicode -- report("%C %04i %05i %s",unicode,index,glyphs[index].name) end end @@ -1124,7 +1176,7 @@ formatreaders[4] = function(f,fontdata,offset) if not glyph.unicode then glyph.unicode = unicode end - mapmap[index] = unicode + mapping[index] = unicode -- report("%C %04i %05i %s",unicode,index,glyphs[index].name) end end @@ -1134,11 +1186,11 @@ formatreaders[4] = function(f,fontdata,offset) end formatreaders[6] = function(f,fontdata,offset) - f:seek("set",offset+2+2+2) -- skip format length language - local mapmap = fontdata.map.map - local glyphs = fontdata.glyphs - local start = readushort(f) - local count = readushort(f) + setposition(f,offset+2+2+2) -- skip format length language + local mapping = fontdata.mapping + local glyphs = fontdata.glyphs + local start = readushort(f) + local count = readushort(f) for unicode=start,start+count-1 do local index = readushort(f) if index > 0 then @@ -1146,15 +1198,15 @@ formatreaders[6] = function(f,fontdata,offset) if not glyph.unicode then glyph.unicode = unicode end - mapmap[unicode] = index + mapping[index] = unicode end end end formatreaders[12] = function(f,fontdata,offset) - f:seek("set",offset+2+2+4+4) -- skip format reserved length language - local mapmap = fontdata.map.map - local glyphs = fontdata.glyphs + setposition(f,offset+2+2+4+4) -- skip format reserved length language + local mapping = fontdata.mapping + local glyphs = fontdata.glyphs local nofgroups = readulong(f) for i=1,nofgroups do local first = readulong(f) @@ -1165,12 +1217,61 @@ formatreaders[12] = function(f,fontdata,offset) if not glyph.unicode then glyph.unicode = unicode end - mapmap[unicode] = index + mapping[index] = unicode index = index + 1 end end end +formatreaders[14] = function(f,fontdata,offset) + if offset and offset ~= 0 then + setposition(f,offset) + local format = readushort(f) + local length = readulong(f) + local nofrecords = readulong(f) + local records = { } + local variants = { } + fontdata.variants = variants + for i=1,nofrecords do + records[i] = { + selector = readuint(f), + default = readulong(f), -- default offset + other = readulong(f), -- non-default offset + } + end + for i=1,nofrecords do + local record = records[i] + local selector = record.selector + local default = record.default + local other = record.other + -- + -- there is no need to map the defaults to themselves + -- + -- if default ~= 0 then + -- setposition(f,offset+default) + -- local nofranges = readulong(f) + -- for i=1,nofranges do + -- local start = readuint(f) + -- local extra = readbyte(f) + -- for i=start,start+extra do + -- mapping[i] = i + -- end + -- end + -- end + local other = record.other + if other ~= 0 then + setposition(f,offset+other) + local mapping = { } + local count = readulong(f) + for i=1,count do + mapping[readuint(f)] = readushort(f) + end + variants[selector] = mapping + end + end + end +end + local function checkcmap(f,fontdata,records,platform,encoding,format) local data = records[platform] if not data then @@ -1198,12 +1299,13 @@ function readers.cmap(f,fontdata,specification) local datatable = fontdata.tables.cmap if datatable then local tableoffset = datatable.offset - f:seek("set",tableoffset) + setposition(f,tableoffset) local version = readushort(f) local noftables = readushort(f) local records = { } local unicodecid = false local variantcid = false + local variants = { } for i=1,noftables do local platform = readushort(f) local encoding = readushort(f) @@ -1235,7 +1337,7 @@ function readers.cmap(f,fontdata,specification) local formats = subtables.formats for i=1,#offsets do local offset = tableoffset + offsets[i] - f:seek("set",offset) + setposition(f,offset) formats[readushort(f)] = offset end record[encoding] = formats @@ -1246,8 +1348,11 @@ function readers.cmap(f,fontdata,specification) checkcmap(f,fontdata,records,3,10,12) -- checkcmap(f,fontdata,records,0, 3, 4) -- checkcmap(f,fontdata,records,1, 0, 6) - -- todo - variantcid = records[0] and records[0][5] + checkcmap(f,fontdata,records,0, 5,14) +-- variantcid = records[0] and records[0][5] +-- if variantcid then +-- formatreaders[14](f,fontdata,offset,variantcid[14]) +-- end -- fontdata.cidmaps = { version = version, @@ -1286,7 +1391,7 @@ function readers.kern(f,fontdata,specification) if specification.kerns then local datatable = fontdata.tables.kern if datatable then - f:seek("set",datatable.offset) + setposition(f,datatable.offset) local version = readushort(f) local noftables = readushort(f) for i=1,noftables do @@ -1345,14 +1450,7 @@ end function readers.math(f,fontdata,specification) if specification.glyphs then - local datatable = fontdata.tables.math - if datatable then - f:seek("set",datatable.offset) - local scriptlist = readulong(f) - local featurelist = readulong(f) - local lookuplist = readulong(f) - -- todo - end + reportskippedtable("math") end end @@ -1472,24 +1570,81 @@ otf.unpackoutlines = unpackoutlines -- some properties in order to read following tables. When details is true we also -- initialize the glyphs data. --- options: --- --- properties : common metrics, names, list of features --- glyphs : metrics, encoding --- shapes : sequences or segments --- kerns : global (ttf) kerns --- lookups : gsub and gpos lookups +----- validutf = lpeg.patterns.utf8character^0 * P(-1) +local validutf = lpeg.patterns.validutf8 -local function readdata(f,offset,specification) +local function getname(fontdata,key) + local names = fontdata.names + if names then + local value = names[key] + if value then + local content = value.content + return lpegmatch(validutf,content) and content or nil + end + end +end + +local function getinfo(maindata,sub) + local fontdata = sub and maindata.subfonts and maindata.subfonts[sub] or maindata + local names = fontdata.names + if names then + local metrics = fontdata.windowsmetrics or { } + local postscript = fontdata.postscript or { } + local fontheader = fontdata.fontheader or { } + local cffinfo = fontdata.cffinfo or { } + local filename = fontdata.filename + local weight = getname(fontdata,"weight") or cffinfo.weight or metrics.weight + local width = getname(fontdata,"width") or cffinfo.width or metrics.width + return { -- we inherit some inconsistencies/choices from ff + subfontindex = sub or 0, + -- filename = filename, + -- version = name("version"), + -- format = fontdata.format, + fontname = getname(fontdata,"postscriptname"), + fullname = getname(fontdata,"fullname"), -- or file.nameonly(filename) + familyname = getname(fontdata,"typographicfamily") or getname(fontdata,"family"), + subfamily = getname(fontdata,"subfamily"), + modifiers = getname(fontdata,"typographicsubfamily"), + weight = weight and lower(weight), + width = width and lower(width), + pfmweight = metrics.weightclass or 400, -- will become weightclass + pfmwidth = metrics.widthclass or 5, -- will become widthclass + panosewidth = metrics.panosewidth, + panoseweight = metrics.panoseweight, + italicangle = postscript.italicangle or 0, + units = fontheader.units or 0, + designsize = fontdata.designsize, + minsize = fontdata.minsize, + maxsize = fontdata.maxsize, + monospaced = (tonumber(postscript.monospaced or 0) > 0) or metrics.panosewidth == "monospaced", + averagewidth = metrics.averagewidth, + xheight = metrics.xheight, + } + elseif n then + return { + filename = fontdata.filename, + comment = "there is no info for subfont " .. n, + } + else + return { + filename = fontdata.filename, + comment = "there is no info", + } + end +end + +local function loadtables(f,specification,offset) if offset then - f:seek("set",offset) + setposition(f,offset) end - local tables = { } + local tables = { } local basename = file.basename(specification.filename) local filesize = specification.filesize + local filetime = specification.filetime local fontdata = { -- some can/will go filename = basename, filesize = filesize, + filetime = filetime, version = readstring(f,4), noftables = readushort(f), searchrange = readushort(f), -- not needed @@ -1511,23 +1666,44 @@ local function readdata(f,offset,specification) length = length, } end - if specification.glyphs then - local glyphs = setmetatableindex(function(t,k) - local v = { - -- maybe more defaults - index = k, - } - t[k] = v - return v - end) - local map = { - map = { }, - backmap = { }, + if tables.cff then + fontdata.format = "opentype" + else + fontdata.format = "truetype" + end + return fontdata +end + +local function prepareglyps(fontdata) + local glyphs = setmetatableindex(function(t,k) + local v = { + -- maybe more defaults + index = k, } - fontdata.glyphs = glyphs - fontdata.map = map + t[k] = v + return v + end) + fontdata.glyphs = glyphs + fontdata.mapping = { } +end + +local function readdata(f,offset,specification) + local fontdata = loadtables(f,specification,offset) + if specification.glyphs then + prepareglyps(fontdata) end + -- readers["name"](f,fontdata,specification) + -- + local askedname = specification.askedname + if askedname then + local cleanname = gsub(askedname,"[^azAZ09]","") + local foundname = gsub(getname(fontdata,"fullname") or "","[^azAZ09]","") + if lower(cleanname) ~= lower(foundname) then + return -- keep searching + end + end + -- readers["os/2"](f,fontdata,specification) readers["head"](f,fontdata,specification) readers["maxp"](f,fontdata,specification) @@ -1544,53 +1720,66 @@ local function readdata(f,offset,specification) readers["gpos"](f,fontdata,specification) readers["math"](f,fontdata,specification) -- - if readers.filterkerns then - readers.filterkerns(fontdata) - end - if readers.splitlookups then - readers.splitlookups(fontdata) - end - -- fontdata.locations = nil fontdata.tables = nil fontdata.cidmaps = nil fontdata.dictionaries = nil - -- - -- fontdata.cff = nil - -- + -- fontdata.cff = nil return fontdata end local function loadfontdata(specification) local filename = specification.filename - local filesize = file.size(filename) - local f = io.open(filename,"rb") - if f then - if filesize > 0 then - specification.filesize = filesize - local version = readstring(f,4) - local fontdata = nil - if version == "OTTO" or version == "true" or version == "\0\1\0\0" then - fontdata = readdata(f,0,specification) - elseif version == "ttcf" then - local subfont = tonumber(specification.subfont) - local offsets = { } - local ttcversion = readulong(f) - local nofsubfonts = readulong(f) - for i=1,nofsubfonts do - offsets[i] = readulong(f) + local fileattr = lfs.attributes(filename) + local filesize = fileattr and fileattr.size or 0 + local filetime = fileattr and fileattr.modification or 0 + local f = openfile(filename,true) -- zero based + if not f then + report("unable to open %a",filename) + elseif filesize == 0 then + report("empty file %a",filename) + closefile(f) + else + specification.filesize = filesize + specification.filetime = filetime + local version = readstring(f,4) + local fontdata = nil + if version == "OTTO" or version == "true" or version == "\0\1\0\0" then + fontdata = readdata(f,0,specification) + elseif version == "ttcf" then + local subfont = tonumber(specification.subfont) + local offsets = { } + local ttcversion = readulong(f) + local nofsubfonts = readulong(f) + for i=1,nofsubfonts do + offsets[i] = readulong(f) + end + if subfont then + if subfont >= 1 and subfont <= nofsubfonts then + fontdata = readdata(f,offsets[subfont],specification) + else + report("no subfont %a in file %a",subfont,filename) end - if subfont then - if subfont > 1 and subfont <= nofsubfonts then - fontdata = readdata(f,offsets[subfont],specification) - else - report("no subfont %a in file %a",subfont,filename) + else + subfont = specification.subfont + if type(subfont) == "string" and subfont ~= "" then + specification.askedname = subfont + for i=1,nofsubfonts do + fontdata = readdata(f,offsets[i],specification) + if fontdata then + report("subfont named %a has index %a",subfont,i) + break + end + end + if not fontdata then + report("no subfont named %a",subfont) end else local subfonts = { } fontdata = { filename = filename, filesize = filesize, + filetime = filetime, version = version, subfonts = subfonts, ttcversion = ttcversion, @@ -1600,21 +1789,16 @@ local function loadfontdata(specification) subfonts[i] = readdata(f,offsets[i],specification) end end - else - report("unknown version %a in file %a",version,filename) end - f:close() - return fontdata else - report("empty file %a",filename) - f:close() + report("unknown version %a in file %a",version,filename) end - else - report("unable to open %a",filename) + closefile(f) + return fontdata or { } end end -local function loadfont(specification) +local function loadfont(specification,n) if type(specification) == "string" then specification = { filename = specification, @@ -1625,7 +1809,8 @@ local function loadfont(specification) kerns = true, lookups = true, -- true or number: - subfont = true, + subfont = n or true, + tounicode = false, } end -- if shapes only then @@ -1639,7 +1824,7 @@ local function loadfont(specification) specification.info = true end local function message(str) - report("fatal error in file %a: %s",specification.filename,str) + report("fatal error in file %a: %s\n%s",specification.filename,str,debug.traceback()) end local ok, result = xpcall(loadfontdata,message,specification) if ok then @@ -1647,67 +1832,6 @@ local function loadfont(specification) end end -readers.loadfont = loadfont - ------ validutf = lpeg.patterns.utf8character^0 * P(-1) -local validutf = lpeg.patterns.validutf8 - -local function getinfo(maindata,sub) - local fontdata = sub and maindata.subfonts[sub] or maindata - local names = fontdata.names - if names then - local metrics = fontdata.windowsmetrics or { } - local postscript = fontdata.postscript or { } - local fontheader = fontdata.fontheader or { } - local cffinfo = fontdata.cffinfo or { } - local filename = fontdata.filename - -- - local function name(key) - local value = names[key] - if value then - local content = value.content - return lpegmatch(validutf,content) and content or nil - end - end - -- - local weight = name("weight") or cffinfo.weight or metrics.weight - local width = name("width") or cffinfo.width or metrics.width - local info = { -- we inherit some inconsistencies/choices from ff - subfontindex = sub or 0, - -- filename = filename, - -- version = name("version"), - fontname = name("postscriptname"), - fullname = name("fullname"), -- or file.nameonly(filename) - familyname = name("typographicfamily") or name("family"), - subfamily = name("subfamily"), - modifiers = name("typographicsubfamily"), - weight = weight and lower(weight), - width = width and lower(width), - pfmweight = metrics.weightclass or 400, -- will become weightclass - pfmwidth = metrics.widthclass or 5, -- will become widthclass - panosewidth = metrics.panosewidth, - panoseweight = metrics.panoseweight, - italicangle = postscript.italicangle or 0, - units = fontheader.units or 0, - designsize = fontdata.designsize, - minsize = fontdata.minsize, - maxsize = fontdata.maxsize, - monospaced = (tonumber(postscript.monospaced or 0) > 0) or metrics.panosewidth == "monospaced", - } - return info - elseif n then - return { - filename = fontdata.filename, - comment = "there is no info for subfont " .. n, - } - else - return { - filename = fontdata.filename, - comment = "there is no info", - } - end -end - -- we need even less, but we can have a 'detail' variant function readers.loadshapes(filename,n) @@ -1719,23 +1843,72 @@ function readers.loadshapes(filename,n) return fontdata and { -- version = 0.123 -- todo filename = filename, + format = fontdata.format, glyphs = fontdata.glyphs, units = fontdata.fontheader.units, } or { filename = filename, + format = "unknown", glyphs = { }, units = 0, } end +function readers.loadfont(filename,n) + local fontdata = loadfont { + filename = filename, + glyphs = true, + shapes = false, + lookups = true, + subfont = n, + } + if fontdata then + -- + return { + tableversion = tableversion, + -- cache_uuid = false, -- only when cached + -- cache_version = false, -- only when cached + size = fontdata.filesize, + time = fontdata.filetime, + -- warnings = { }, + glyphs = fontdata.glyphs, + descriptions = fontdata.descriptions, + format = fontdata.format, + goodies = { }, + -- lookups = { }, + metadata = getinfo(fontdata,n), + properties = { + hasitalics = fontdata.hasitalics or false, + }, + resources = { + -- anchor_to_lookup = fontdata.anchor_to_lookup or { }, + creator = "context mkiv", + duplicates = { }, -- todo + features = fontdata.features, + filename = fontdata.filename, + -- lookup_to_anchor = fontdata.lookup_to_anchor or { }, + sublookups = fontdata.sublookups, + subtables = fontdata.subtables, + -- lookuptags = { }, -- will be metatable using offsets: gsub-1, gpos-1 etc + lookuptypes = fontdata.lookuptypes or { }, + marks = fontdata.marks or { }, + markclasses = fontdata.markclasses or { }, + marksets = fontdata.marksets or { }, + private = privateoffset, + sequences = fontdata.sequences, + variants = fontdata.variants, -- variant -> unicode -> glyph + version = getname(fontdata,"version"), + cidinfo = fontdata.cidinfo, + }, + } + end +end + function readers.getinfo(filename,n,details) local fontdata = loadfont { filename = filename, details = true, } --- if string.find(filename,"ource") then --- inspect(fontdata) --- end if fontdata then local subfonts = fontdata.subfonts if not subfonts then @@ -1762,6 +1935,26 @@ function readers.getinfo(filename,n,details) end end +function readers.rehash(fontdata,hashmethod) + report("the %a helper is not yet implemented","rehash") +end + +function readers.checkhash(fontdata) + report("the %a helper is not yet implemented","checkhash") +end + +function readers.pack(fontdata,hashmethod) + report("the %a helper is not yet implemented","pack") +end + +function readers.unpack(fontdata) + report("the %a helper is not yet implemented","unpack") +end + +function readers.expand(fontdata) + report("the %a helper is not yet implemented","unpack") +end + -- if fonts.hashes then @@ -1792,7 +1985,7 @@ if fonts.hashes then data = loadshapes(filename,sub) if data then data.size = size - data.format = "opentype" + data.format = data.format or (kind == "otf" and "opentype") or "truetype" data.time = time packoutlines(data) containers.write(readers.cache,hash,data) |