You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2553 lines
93 KiB
2553 lines
93 KiB
if not modules then modules = { } end modules ['font-otr'] = {
|
|
version = 1.001,
|
|
comment = "companion to font-ini.mkiv",
|
|
author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
|
|
copyright = "PRAGMA ADE / ConTeXt Development Team",
|
|
license = "see context related readme files"
|
|
}
|
|
|
|
-- 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. 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.)
|
|
|
|
-- 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 will be done anyway as it save quite some on a cjk font
|
|
-- and the existing (old) code if okay.
|
|
|
|
-- todo: more messages (only if really needed)
|
|
--
|
|
-- considered, in math:
|
|
--
|
|
-- start -> first (so we can skip the first same-size one)
|
|
-- end -> last
|
|
--
|
|
-- 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.
|
|
|
|
-- The new reader is faster on some aspects and slower on other. The memory footprint
|
|
-- is lower. The string reader is a bit faster than the file reader. The new reader
|
|
-- gives more efficient tables and has bit more analysis. In practice these times are
|
|
-- not that relevant because we cache. The otf files take a it more time because we
|
|
-- need to calculate the boundingboxes. In theory the processing of text should be
|
|
-- somewhat faster especially for complex fonts with many lookups.
|
|
--
|
|
-- old new str reader
|
|
-- lmroman12-regular.otf 0.103 0.203 0.195
|
|
-- latinmodern-math.otf 0.454 0.768 0.712
|
|
-- husayni.ttf 1.142 1.526 1.259
|
|
--
|
|
-- If there is demand I will consider making a ff compatible table dumper but it's
|
|
-- probably more fun to provide a way to show features applied.
|
|
|
|
-- I experimented a bit with f:readbyte(n) and f:readshort() and so and it is indeed
|
|
-- faster but it might not be the real bottleneck as we still need to juggle data. It
|
|
-- is probably more memory efficient as no intermediate strings are involved.
|
|
|
|
-- if not characters then
|
|
-- require("char-def")
|
|
-- require("char-ini")
|
|
-- end
|
|
|
|
local next, type, tonumber, rawget = next, type, tonumber, rawget
|
|
local byte, lower, char, gsub = string.byte, string.lower, string.char, string.gsub
|
|
local fullstrip = string.fullstrip
|
|
local floor, round = math.floor, math.round
|
|
local P, R, S, C, Cs, Cc, Ct, Carg, Cmt = lpeg.P, lpeg.R, lpeg.S, lpeg.C, lpeg.Cs, lpeg.Cc, lpeg.Ct, lpeg.Carg, lpeg.Cmt
|
|
local lpegmatch = lpeg.match
|
|
local rshift = bit32.rshift
|
|
|
|
local setmetatableindex = table.setmetatableindex
|
|
local sortedkeys = table.sortedkeys
|
|
local sortedhash = table.sortedhash
|
|
local stripstring = string.nospaces
|
|
local utf16_to_utf8_be = utf.utf16_to_utf8_be
|
|
|
|
local report = logs.reporter("otf reader")
|
|
local report_cmap = logs.reporter("otf reader","cmap")
|
|
|
|
local trace_cmap = false trackers.register("otf.cmap", function(v) trace_cmap = v end)
|
|
local trace_cmap_details = false trackers.register("otf.cmap.details", function(v) trace_cmap_details = v end)
|
|
|
|
fonts = fonts or { }
|
|
local handlers = fonts.handlers or { }
|
|
fonts.handlers = handlers
|
|
local otf = handlers.otf or { }
|
|
handlers.otf = otf
|
|
local readers = otf.readers or { }
|
|
otf.readers = readers
|
|
|
|
----- streamreader = utilities.streams -- faster on big files (not true any longer)
|
|
local streamreader = utilities.files -- faster on identify (also uses less memory)
|
|
local streamwriter = utilities.files
|
|
|
|
readers.streamreader = streamreader
|
|
readers.streamwriter = streamwriter
|
|
|
|
local openfile = streamreader.open
|
|
local closefile = streamreader.close
|
|
----- 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 -- 32-bit unsigned integer
|
|
----- readchar = streamreader.readinteger1 -- 8-bit signed integer
|
|
local readshort = streamreader.readinteger2 -- 16-bit signed integer
|
|
local readlong = streamreader.readinteger4 -- 32-bit unsigned integer
|
|
local readfixed = streamreader.readfixed4
|
|
local read2dot14 = streamreader.read2dot14 -- 16-bit signed fixed number with the low 14 bits of fraction (2.14) (F2DOT14)
|
|
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 readcardinaltable = streamreader.readcardinaltable
|
|
local readintegertable = streamreader.readintegertable
|
|
|
|
function streamreader.readtag(f)
|
|
return lower(stripstring(readstring(f,4)))
|
|
end
|
|
|
|
local short = 2
|
|
local ushort = 2
|
|
local ulong = 4
|
|
|
|
directives.register("fonts.streamreader",function()
|
|
|
|
streamreader = utilities.streams
|
|
|
|
openfile = streamreader.open
|
|
closefile = streamreader.close
|
|
setposition = streamreader.setposition
|
|
skipshort = streamreader.skipshort
|
|
readbytes = streamreader.readbytes
|
|
readstring = streamreader.readstring
|
|
readbyte = streamreader.readcardinal1
|
|
readushort = streamreader.readcardinal2
|
|
readuint = streamreader.readcardinal3
|
|
readulong = streamreader.readcardinal4
|
|
readshort = streamreader.readinteger2
|
|
readlong = streamreader.readinteger4
|
|
readfixed = streamreader.readfixed4
|
|
read2dot14 = streamreader.read2dot14
|
|
readfword = readshort
|
|
readufword = readushort
|
|
readoffset = readushort
|
|
readcardinaltable = streamreader.readcardinaltable
|
|
readintegertable = streamreader.readintegertable
|
|
|
|
function streamreader.readtag(f)
|
|
return lower(stripstring(readstring(f,4)))
|
|
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 = readbytes(f,8)
|
|
return 0x100000000 * d + 0x1000000 * e + 0x10000 * f + 0x100 * g + h
|
|
end
|
|
|
|
local tableversion = 0.004
|
|
readers.tableversion = tableversion
|
|
local privateoffset = fonts.constructors and fonts.constructors.privateoffset or 0xF0000 -- 0x10FFFF
|
|
|
|
-- We have quite some data tables. We are somewhat ff compatible with names but as I used
|
|
-- the information from the microsoft site there can be differences. Eventually I might end
|
|
-- up with a different ordering and naming.
|
|
|
|
local reservednames = { [0] =
|
|
"copyright",
|
|
"family",
|
|
"subfamily",
|
|
"uniqueid",
|
|
"fullname",
|
|
"version",
|
|
"postscriptname",
|
|
"trademark",
|
|
"manufacturer",
|
|
"designer",
|
|
"description", -- descriptor in ff
|
|
"vendorurl",
|
|
"designerurl",
|
|
"license",
|
|
"licenseurl",
|
|
"reserved",
|
|
"typographicfamily", -- preffamilyname
|
|
"typographicsubfamily", -- prefmodifiers
|
|
"compatiblefullname", -- for mac
|
|
"sampletext",
|
|
"cidfindfontname",
|
|
"wwsfamily",
|
|
"wwssubfamily",
|
|
"lightbackgroundpalette",
|
|
"darkbackgroundpalette",
|
|
"variationspostscriptnameprefix",
|
|
}
|
|
|
|
-- more at: https://www.microsoft.com/typography/otspec/name.htm
|
|
|
|
-- setmetatableindex(reservednames,function(t,k)
|
|
-- local v = "name_" .. k
|
|
-- t[k] = v
|
|
-- return v
|
|
-- end)
|
|
|
|
local platforms = { [0] =
|
|
"unicode",
|
|
"macintosh",
|
|
"iso",
|
|
"windows",
|
|
"custom",
|
|
}
|
|
|
|
local encodings = {
|
|
-- these stay:
|
|
unicode = { [0] =
|
|
"unicode 1.0 semantics",
|
|
"unicode 1.1 semantics",
|
|
"iso/iec 10646",
|
|
"unicode 2.0 bmp", -- cmap subtable formats 0, 4, 6
|
|
"unicode 2.0 full", -- cmap subtable formats 0, 4, 6, 10, 12
|
|
"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",
|
|
"malayalam", "sinhalese", "burmese", "khmer", "thai", "laotian", "georgian", "armenian",
|
|
"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
|
|
"shiftjis",
|
|
"prc",
|
|
"big5",
|
|
"wansung",
|
|
"johab",
|
|
"reserved 7",
|
|
"reserved 8",
|
|
"reserved 9",
|
|
"unicode ucs-4",
|
|
},
|
|
custom = {
|
|
--custom: 0-255 : otf windows nt compatibility mapping
|
|
}
|
|
}
|
|
|
|
local decoders = {
|
|
unicode = { },
|
|
macintosh = { },
|
|
iso = { },
|
|
windows = {
|
|
-- maybe always utf16
|
|
["unicode semantics"] = utf16_to_utf8_be,
|
|
["unicode bmp"] = utf16_to_utf8_be,
|
|
["unicode full"] = utf16_to_utf8_be,
|
|
["unicode 1.0 semantics"] = utf16_to_utf8_be,
|
|
["unicode 1.1 semantics"] = utf16_to_utf8_be,
|
|
["unicode 2.0 bmp"] = utf16_to_utf8_be,
|
|
["unicode 2.0 full"] = utf16_to_utf8_be,
|
|
["unicode variation sequences"] = utf16_to_utf8_be,
|
|
["unicode full repertoire"] = utf16_to_utf8_be,
|
|
},
|
|
custom = { },
|
|
}
|
|
|
|
-- This is bit over the top as we can just look for either windows, unicode or macintosh
|
|
-- 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",
|
|
-- [ 2] = "german",
|
|
-- [ 3] = "italian",
|
|
-- [ 4] = "dutch",
|
|
-- [ 5] = "swedish",
|
|
-- [ 6] = "spanish",
|
|
-- [ 7] = "danish",
|
|
-- [ 8] = "portuguese",
|
|
-- [ 9] = "norwegian",
|
|
-- [ 10] = "hebrew",
|
|
-- [ 11] = "japanese",
|
|
-- [ 12] = "arabic",
|
|
-- [ 13] = "finnish",
|
|
-- [ 14] = "greek",
|
|
-- [ 15] = "icelandic",
|
|
-- [ 16] = "maltese",
|
|
-- [ 17] = "turkish",
|
|
-- [ 18] = "croatian",
|
|
-- [ 19] = "chinese (traditional)",
|
|
-- [ 20] = "urdu",
|
|
-- [ 21] = "hindi",
|
|
-- [ 22] = "thai",
|
|
-- [ 23] = "korean",
|
|
-- [ 24] = "lithuanian",
|
|
-- [ 25] = "polish",
|
|
-- [ 26] = "hungarian",
|
|
-- [ 27] = "estonian",
|
|
-- [ 28] = "latvian",
|
|
-- [ 29] = "sami",
|
|
-- [ 30] = "faroese",
|
|
-- [ 31] = "farsi/persian",
|
|
-- [ 32] = "russian",
|
|
-- [ 33] = "chinese (simplified)",
|
|
-- [ 34] = "flemish",
|
|
-- [ 35] = "irish gaelic",
|
|
-- [ 36] = "albanian",
|
|
-- [ 37] = "romanian",
|
|
-- [ 38] = "czech",
|
|
-- [ 39] = "slovak",
|
|
-- [ 40] = "slovenian",
|
|
-- [ 41] = "yiddish",
|
|
-- [ 42] = "serbian",
|
|
-- [ 43] = "macedonian",
|
|
-- [ 44] = "bulgarian",
|
|
-- [ 45] = "ukrainian",
|
|
-- [ 46] = "byelorussian",
|
|
-- [ 47] = "uzbek",
|
|
-- [ 48] = "kazakh",
|
|
-- [ 49] = "azerbaijani (cyrillic script)",
|
|
-- [ 50] = "azerbaijani (arabic script)",
|
|
-- [ 51] = "armenian",
|
|
-- [ 52] = "georgian",
|
|
-- [ 53] = "moldavian",
|
|
-- [ 54] = "kirghiz",
|
|
-- [ 55] = "tajiki",
|
|
-- [ 56] = "turkmen",
|
|
-- [ 57] = "mongolian (mongolian script)",
|
|
-- [ 58] = "mongolian (cyrillic script)",
|
|
-- [ 59] = "pashto",
|
|
-- [ 60] = "kurdish",
|
|
-- [ 61] = "kashmiri",
|
|
-- [ 62] = "sindhi",
|
|
-- [ 63] = "tibetan",
|
|
-- [ 64] = "nepali",
|
|
-- [ 65] = "sanskrit",
|
|
-- [ 66] = "marathi",
|
|
-- [ 67] = "bengali",
|
|
-- [ 68] = "assamese",
|
|
-- [ 69] = "gujarati",
|
|
-- [ 70] = "punjabi",
|
|
-- [ 71] = "oriya",
|
|
-- [ 72] = "malayalam",
|
|
-- [ 73] = "kannada",
|
|
-- [ 74] = "tamil",
|
|
-- [ 75] = "telugu",
|
|
-- [ 76] = "sinhalese",
|
|
-- [ 77] = "burmese",
|
|
-- [ 78] = "khmer",
|
|
-- [ 79] = "lao",
|
|
-- [ 80] = "vietnamese",
|
|
-- [ 81] = "indonesian",
|
|
-- [ 82] = "tagalong",
|
|
-- [ 83] = "malay (roman script)",
|
|
-- [ 84] = "malay (arabic script)",
|
|
-- [ 85] = "amharic",
|
|
-- [ 86] = "tigrinya",
|
|
-- [ 87] = "galla",
|
|
-- [ 88] = "somali",
|
|
-- [ 89] = "swahili",
|
|
-- [ 90] = "kinyarwanda/ruanda",
|
|
-- [ 91] = "rundi",
|
|
-- [ 92] = "nyanja/chewa",
|
|
-- [ 93] = "malagasy",
|
|
-- [ 94] = "esperanto",
|
|
-- [128] = "welsh",
|
|
-- [129] = "basque",
|
|
-- [130] = "catalan",
|
|
-- [131] = "latin",
|
|
-- [132] = "quenchua",
|
|
-- [133] = "guarani",
|
|
-- [134] = "aymara",
|
|
-- [135] = "tatar",
|
|
-- [136] = "uighur",
|
|
-- [137] = "dzongkha",
|
|
-- [138] = "javanese (roman script)",
|
|
-- [139] = "sundanese (roman script)",
|
|
-- [140] = "galician",
|
|
-- [141] = "afrikaans",
|
|
-- [142] = "breton",
|
|
-- [143] = "inuktitut",
|
|
-- [144] = "scottish gaelic",
|
|
-- [145] = "manx gaelic",
|
|
-- [146] = "irish gaelic (with dot above)",
|
|
-- [147] = "tongan",
|
|
-- [148] = "greek (polytonic)",
|
|
-- [149] = "greenlandic",
|
|
-- [150] = "azerbaijani (roman script)",
|
|
},
|
|
-- these can stay:
|
|
iso = {
|
|
},
|
|
-- english can stay:
|
|
windows = {
|
|
-- [0x0436] = "afrikaans - south africa",
|
|
-- [0x041c] = "albanian - albania",
|
|
-- [0x0484] = "alsatian - france",
|
|
-- [0x045e] = "amharic - ethiopia",
|
|
-- [0x1401] = "arabic - algeria",
|
|
-- [0x3c01] = "arabic - bahrain",
|
|
-- [0x0c01] = "arabic - egypt",
|
|
-- [0x0801] = "arabic - iraq",
|
|
-- [0x2c01] = "arabic - jordan",
|
|
-- [0x3401] = "arabic - kuwait",
|
|
-- [0x3001] = "arabic - lebanon",
|
|
-- [0x1001] = "arabic - libya",
|
|
-- [0x1801] = "arabic - morocco",
|
|
-- [0x2001] = "arabic - oman",
|
|
-- [0x4001] = "arabic - qatar",
|
|
-- [0x0401] = "arabic - saudi arabia",
|
|
-- [0x2801] = "arabic - syria",
|
|
-- [0x1c01] = "arabic - tunisia",
|
|
-- [0x3801] = "arabic - u.a.e.",
|
|
-- [0x2401] = "arabic - yemen",
|
|
-- [0x042b] = "armenian - armenia",
|
|
-- [0x044d] = "assamese - india",
|
|
-- [0x082c] = "azeri (cyrillic) - azerbaijan",
|
|
-- [0x042c] = "azeri (latin) - azerbaijan",
|
|
-- [0x046d] = "bashkir - russia",
|
|
-- [0x042d] = "basque - basque",
|
|
-- [0x0423] = "belarusian - belarus",
|
|
-- [0x0845] = "bengali - bangladesh",
|
|
-- [0x0445] = "bengali - india",
|
|
-- [0x201a] = "bosnian (cyrillic) - bosnia and herzegovina",
|
|
-- [0x141a] = "bosnian (latin) - bosnia and herzegovina",
|
|
-- [0x047e] = "breton - france",
|
|
-- [0x0402] = "bulgarian - bulgaria",
|
|
-- [0x0403] = "catalan - catalan",
|
|
-- [0x0c04] = "chinese - hong kong s.a.r.",
|
|
-- [0x1404] = "chinese - macao s.a.r.",
|
|
-- [0x0804] = "chinese - people's republic of china",
|
|
-- [0x1004] = "chinese - singapore",
|
|
-- [0x0404] = "chinese - taiwan",
|
|
-- [0x0483] = "corsican - france",
|
|
-- [0x041a] = "croatian - croatia",
|
|
-- [0x101a] = "croatian (latin) - bosnia and herzegovina",
|
|
-- [0x0405] = "czech - czech republic",
|
|
-- [0x0406] = "danish - denmark",
|
|
-- [0x048c] = "dari - afghanistan",
|
|
-- [0x0465] = "divehi - maldives",
|
|
-- [0x0813] = "dutch - belgium",
|
|
-- [0x0413] = "dutch - netherlands",
|
|
-- [0x0c09] = "english - australia",
|
|
-- [0x2809] = "english - belize",
|
|
-- [0x1009] = "english - canada",
|
|
-- [0x2409] = "english - caribbean",
|
|
-- [0x4009] = "english - india",
|
|
-- [0x1809] = "english - ireland",
|
|
-- [0x2009] = "english - jamaica",
|
|
-- [0x4409] = "english - malaysia",
|
|
-- [0x1409] = "english - new zealand",
|
|
-- [0x3409] = "english - republic of the philippines",
|
|
-- [0x4809] = "english - singapore",
|
|
-- [0x1c09] = "english - south africa",
|
|
-- [0x2c09] = "english - trinidad and tobago",
|
|
-- [0x0809] = "english - united kingdom",
|
|
[0x0409] = "english - united states",
|
|
-- [0x3009] = "english - zimbabwe",
|
|
-- [0x0425] = "estonian - estonia",
|
|
-- [0x0438] = "faroese - faroe islands",
|
|
-- [0x0464] = "filipino - philippines",
|
|
-- [0x040b] = "finnish - finland",
|
|
-- [0x080c] = "french - belgium",
|
|
-- [0x0c0c] = "french - canada",
|
|
-- [0x040c] = "french - france",
|
|
-- [0x140c] = "french - luxembourg",
|
|
-- [0x180c] = "french - principality of monoco",
|
|
-- [0x100c] = "french - switzerland",
|
|
-- [0x0462] = "frisian - netherlands",
|
|
-- [0x0456] = "galician - galician",
|
|
-- [0x0437] = "georgian -georgia",
|
|
-- [0x0c07] = "german - austria",
|
|
-- [0x0407] = "german - germany",
|
|
-- [0x1407] = "german - liechtenstein",
|
|
-- [0x1007] = "german - luxembourg",
|
|
-- [0x0807] = "german - switzerland",
|
|
-- [0x0408] = "greek - greece",
|
|
-- [0x046f] = "greenlandic - greenland",
|
|
-- [0x0447] = "gujarati - india",
|
|
-- [0x0468] = "hausa (latin) - nigeria",
|
|
-- [0x040d] = "hebrew - israel",
|
|
-- [0x0439] = "hindi - india",
|
|
-- [0x040e] = "hungarian - hungary",
|
|
-- [0x040f] = "icelandic - iceland",
|
|
-- [0x0470] = "igbo - nigeria",
|
|
-- [0x0421] = "indonesian - indonesia",
|
|
-- [0x045d] = "inuktitut - canada",
|
|
-- [0x085d] = "inuktitut (latin) - canada",
|
|
-- [0x083c] = "irish - ireland",
|
|
-- [0x0434] = "isixhosa - south africa",
|
|
-- [0x0435] = "isizulu - south africa",
|
|
-- [0x0410] = "italian - italy",
|
|
-- [0x0810] = "italian - switzerland",
|
|
-- [0x0411] = "japanese - japan",
|
|
-- [0x044b] = "kannada - india",
|
|
-- [0x043f] = "kazakh - kazakhstan",
|
|
-- [0x0453] = "khmer - cambodia",
|
|
-- [0x0486] = "k'iche - guatemala",
|
|
-- [0x0487] = "kinyarwanda - rwanda",
|
|
-- [0x0441] = "kiswahili - kenya",
|
|
-- [0x0457] = "konkani - india",
|
|
-- [0x0412] = "korean - korea",
|
|
-- [0x0440] = "kyrgyz - kyrgyzstan",
|
|
-- [0x0454] = "lao - lao p.d.r.",
|
|
-- [0x0426] = "latvian - latvia",
|
|
-- [0x0427] = "lithuanian - lithuania",
|
|
-- [0x082e] = "lower sorbian - germany",
|
|
-- [0x046e] = "luxembourgish - luxembourg",
|
|
-- [0x042f] = "macedonian (fyrom) - former yugoslav republic of macedonia",
|
|
-- [0x083e] = "malay - brunei darussalam",
|
|
-- [0x043e] = "malay - malaysia",
|
|
-- [0x044c] = "malayalam - india",
|
|
-- [0x043a] = "maltese - malta",
|
|
-- [0x0481] = "maori - new zealand",
|
|
-- [0x047a] = "mapudungun - chile",
|
|
-- [0x044e] = "marathi - india",
|
|
-- [0x047c] = "mohawk - mohawk",
|
|
-- [0x0450] = "mongolian (cyrillic) - mongolia",
|
|
-- [0x0850] = "mongolian (traditional) - people's republic of china",
|
|
-- [0x0461] = "nepali - nepal",
|
|
-- [0x0414] = "norwegian (bokmal) - norway",
|
|
-- [0x0814] = "norwegian (nynorsk) - norway",
|
|
-- [0x0482] = "occitan - france",
|
|
-- [0x0448] = "odia (formerly oriya) - india",
|
|
-- [0x0463] = "pashto - afghanistan",
|
|
-- [0x0415] = "polish - poland",
|
|
-- [0x0416] = "portuguese - brazil",
|
|
-- [0x0816] = "portuguese - portugal",
|
|
-- [0x0446] = "punjabi - india",
|
|
-- [0x046b] = "quechua - bolivia",
|
|
-- [0x086b] = "quechua - ecuador",
|
|
-- [0x0c6b] = "quechua - peru",
|
|
-- [0x0418] = "romanian - romania",
|
|
-- [0x0417] = "romansh - switzerland",
|
|
-- [0x0419] = "russian - russia",
|
|
-- [0x243b] = "sami (inari) - finland",
|
|
-- [0x103b] = "sami (lule) - norway",
|
|
-- [0x143b] = "sami (lule) - sweden",
|
|
-- [0x0c3b] = "sami (northern) - finland",
|
|
-- [0x043b] = "sami (northern) - norway",
|
|
-- [0x083b] = "sami (northern) - sweden",
|
|
-- [0x203b] = "sami (skolt) - finland",
|
|
-- [0x183b] = "sami (southern) - norway",
|
|
-- [0x1c3b] = "sami (southern) - sweden",
|
|
-- [0x044f] = "sanskrit - india",
|
|
-- [0x1c1a] = "serbian (cyrillic) - bosnia and herzegovina",
|
|
-- [0x0c1a] = "serbian (cyrillic) - serbia",
|
|
-- [0x181a] = "serbian (latin) - bosnia and herzegovina",
|
|
-- [0x081a] = "serbian (latin) - serbia",
|
|
-- [0x046c] = "sesotho sa leboa - south africa",
|
|
-- [0x0432] = "setswana - south africa",
|
|
-- [0x045b] = "sinhala - sri lanka",
|
|
-- [0x041b] = "slovak - slovakia",
|
|
-- [0x0424] = "slovenian - slovenia",
|
|
-- [0x2c0a] = "spanish - argentina",
|
|
-- [0x400a] = "spanish - bolivia",
|
|
-- [0x340a] = "spanish - chile",
|
|
-- [0x240a] = "spanish - colombia",
|
|
-- [0x140a] = "spanish - costa rica",
|
|
-- [0x1c0a] = "spanish - dominican republic",
|
|
-- [0x300a] = "spanish - ecuador",
|
|
-- [0x440a] = "spanish - el salvador",
|
|
-- [0x100a] = "spanish - guatemala",
|
|
-- [0x480a] = "spanish - honduras",
|
|
-- [0x080a] = "spanish - mexico",
|
|
-- [0x4c0a] = "spanish - nicaragua",
|
|
-- [0x180a] = "spanish - panama",
|
|
-- [0x3c0a] = "spanish - paraguay",
|
|
-- [0x280a] = "spanish - peru",
|
|
-- [0x500a] = "spanish - puerto rico",
|
|
-- [0x0c0a] = "spanish (modern sort) - spain",
|
|
-- [0x040a] = "spanish (traditional sort) - spain",
|
|
-- [0x540a] = "spanish - united states",
|
|
-- [0x380a] = "spanish - uruguay",
|
|
-- [0x200a] = "spanish - venezuela",
|
|
-- [0x081d] = "sweden - finland",
|
|
-- [0x041d] = "swedish - sweden",
|
|
-- [0x045a] = "syriac - syria",
|
|
-- [0x0428] = "tajik (cyrillic) - tajikistan",
|
|
-- [0x085f] = "tamazight (latin) - algeria",
|
|
-- [0x0449] = "tamil - india",
|
|
-- [0x0444] = "tatar - russia",
|
|
-- [0x044a] = "telugu - india",
|
|
-- [0x041e] = "thai - thailand",
|
|
-- [0x0451] = "tibetan - prc",
|
|
-- [0x041f] = "turkish - turkey",
|
|
-- [0x0442] = "turkmen - turkmenistan",
|
|
-- [0x0480] = "uighur - prc",
|
|
-- [0x0422] = "ukrainian - ukraine",
|
|
-- [0x042e] = "upper sorbian - germany",
|
|
-- [0x0420] = "urdu - islamic republic of pakistan",
|
|
-- [0x0843] = "uzbek (cyrillic) - uzbekistan",
|
|
-- [0x0443] = "uzbek (latin) - uzbekistan",
|
|
-- [0x042a] = "vietnamese - vietnam",
|
|
-- [0x0452] = "welsh - united kingdom",
|
|
-- [0x0488] = "wolof - senegal",
|
|
-- [0x0485] = "yakut - russia",
|
|
-- [0x0478] = "yi - prc",
|
|
-- [0x046a] = "yoruba - nigeria",
|
|
},
|
|
custom = {
|
|
},
|
|
}
|
|
|
|
local standardromanencoding = { [0] = -- taken from wikipedia
|
|
"notdef", ".null", "nonmarkingreturn", "space", "exclam", "quotedbl",
|
|
"numbersign", "dollar", "percent", "ampersand", "quotesingle", "parenleft",
|
|
"parenright", "asterisk", "plus", "comma", "hyphen", "period", "slash",
|
|
"zero", "one", "two", "three", "four", "five", "six", "seven", "eight",
|
|
"nine", "colon", "semicolon", "less", "equal", "greater", "question", "at",
|
|
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O",
|
|
"P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "bracketleft",
|
|
"backslash", "bracketright", "asciicircum", "underscore", "grave", "a", "b",
|
|
"c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q",
|
|
"r", "s", "t", "u", "v", "w", "x", "y", "z", "braceleft", "bar",
|
|
"braceright", "asciitilde", "Adieresis", "Aring", "Ccedilla", "Eacute",
|
|
"Ntilde", "Odieresis", "Udieresis", "aacute", "agrave", "acircumflex",
|
|
"adieresis", "atilde", "aring", "ccedilla", "eacute", "egrave",
|
|
"ecircumflex", "edieresis", "iacute", "igrave", "icircumflex", "idieresis",
|
|
"ntilde", "oacute", "ograve", "ocircumflex", "odieresis", "otilde", "uacute",
|
|
"ugrave", "ucircumflex", "udieresis", "dagger", "degree", "cent", "sterling",
|
|
"section", "bullet", "paragraph", "germandbls", "registered", "copyright",
|
|
"trademark", "acute", "dieresis", "notequal", "AE", "Oslash", "infinity",
|
|
"plusminus", "lessequal", "greaterequal", "yen", "mu", "partialdiff",
|
|
"summation", "product", "pi", "integral", "ordfeminine", "ordmasculine",
|
|
"Omega", "ae", "oslash", "questiondown", "exclamdown", "logicalnot",
|
|
"radical", "florin", "approxequal", "Delta", "guillemotleft",
|
|
"guillemotright", "ellipsis", "nonbreakingspace", "Agrave", "Atilde",
|
|
"Otilde", "OE", "oe", "endash", "emdash", "quotedblleft", "quotedblright",
|
|
"quoteleft", "quoteright", "divide", "lozenge", "ydieresis", "Ydieresis",
|
|
"fraction", "currency", "guilsinglleft", "guilsinglright", "fi", "fl",
|
|
"daggerdbl", "periodcentered", "quotesinglbase", "quotedblbase",
|
|
"perthousand", "Acircumflex", "Ecircumflex", "Aacute", "Edieresis", "Egrave",
|
|
"Iacute", "Icircumflex", "Idieresis", "Igrave", "Oacute", "Ocircumflex",
|
|
"apple", "Ograve", "Uacute", "Ucircumflex", "Ugrave", "dotlessi",
|
|
"circumflex", "tilde", "macron", "breve", "dotaccent", "ring", "cedilla",
|
|
"hungarumlaut", "ogonek", "caron", "Lslash", "lslash", "Scaron", "scaron",
|
|
"Zcaron", "zcaron", "brokenbar", "Eth", "eth", "Yacute", "yacute", "Thorn",
|
|
"thorn", "minus", "multiply", "onesuperior", "twosuperior", "threesuperior",
|
|
"onehalf", "onequarter", "threequarters", "franc", "Gbreve", "gbreve",
|
|
"Idotaccent", "Scedilla", "scedilla", "Cacute", "cacute", "Ccaron", "ccaron",
|
|
"dcroat",
|
|
}
|
|
|
|
local weights = {
|
|
[100] = "thin",
|
|
[200] = "extralight",
|
|
[300] = "light",
|
|
[400] = "normal",
|
|
[500] = "medium",
|
|
[600] = "semibold", -- demi demibold
|
|
[700] = "bold",
|
|
[800] = "extrabold",
|
|
[900] = "black",
|
|
}
|
|
|
|
local widths = {
|
|
[1] = "ultracondensed",
|
|
[2] = "extracondensed",
|
|
[3] = "condensed",
|
|
[4] = "semicondensed",
|
|
[5] = "normal",
|
|
[6] = "semiexpanded",
|
|
[7] = "expanded",
|
|
[8] = "extraexpanded",
|
|
[9] = "ultraexpanded",
|
|
}
|
|
|
|
setmetatableindex(weights, function(t,k)
|
|
local r = floor((k + 50) / 100) * 100
|
|
local v = (r > 900 and "black") or rawget(t,r) or "normal"
|
|
return v
|
|
end)
|
|
|
|
setmetatableindex(widths,function(t,k)
|
|
return "normal"
|
|
end)
|
|
|
|
local panoseweights = {
|
|
[ 0] = "normal",
|
|
[ 1] = "normal",
|
|
[ 2] = "verylight",
|
|
[ 3] = "light",
|
|
[ 4] = "thin",
|
|
[ 5] = "book",
|
|
[ 6] = "medium",
|
|
[ 7] = "demi",
|
|
[ 8] = "bold",
|
|
[ 9] = "heavy",
|
|
[10] = "black",
|
|
}
|
|
|
|
local panosewidths = {
|
|
[ 0] = "normal",
|
|
[ 1] = "normal",
|
|
[ 2] = "normal",
|
|
[ 3] = "normal",
|
|
[ 4] = "normal",
|
|
[ 5] = "expanded",
|
|
[ 6] = "condensed",
|
|
[ 7] = "veryexpanded",
|
|
[ 8] = "verycondensed",
|
|
[ 9] = "monospaced",
|
|
}
|
|
|
|
-- We implement a reader per table.
|
|
|
|
-- helper
|
|
|
|
local helpers = { }
|
|
readers.helpers = helpers
|
|
|
|
local function gotodatatable(f,fontdata,tag,criterium)
|
|
if criterium and f then
|
|
local tables = fontdata.tables
|
|
if tables then
|
|
local datatable = tables[tag]
|
|
if datatable then
|
|
local tableoffset = datatable.offset
|
|
setposition(f,tableoffset)
|
|
return tableoffset
|
|
end
|
|
else
|
|
report("no tables")
|
|
end
|
|
end
|
|
end
|
|
|
|
local function reportskippedtable(f,fontdata,tag,criterium)
|
|
if criterium and f then
|
|
local tables = fontdata.tables
|
|
if tables then
|
|
local datatable = tables[tag]
|
|
if datatable then
|
|
report("loading of table %a skipped",tag)
|
|
end
|
|
else
|
|
report("no tables")
|
|
end
|
|
end
|
|
end
|
|
|
|
local function setvariabledata(fontdata,tag,data)
|
|
local variabledata = fontdata.variabledata
|
|
if variabledata then
|
|
variabledata[tag] = data
|
|
else
|
|
fontdata.variabledata = { [tag] = data }
|
|
end
|
|
end
|
|
|
|
helpers.gotodatatable = gotodatatable
|
|
helpers.setvariabledata = setvariabledata
|
|
helpers.reportskippedtable = reportskippedtable
|
|
|
|
-- The name table is probably the first one to load. After all this one provides
|
|
-- useful information about what we deal with. The complication is that we need
|
|
-- to filter the best one available.
|
|
|
|
local platformnames = {
|
|
postscriptname = true,
|
|
fullname = true,
|
|
family = true,
|
|
subfamily = true,
|
|
typographicfamily = true,
|
|
typographicsubfamily = true,
|
|
compatiblefullname = true,
|
|
}
|
|
|
|
local platformextras = {
|
|
uniqueid = true,
|
|
version = true,
|
|
copyright = true,
|
|
license = true,
|
|
licenseurl = true,
|
|
manufacturer = true,
|
|
vendorurl = true,
|
|
}
|
|
|
|
function readers.name(f,fontdata,specification)
|
|
local tableoffset = gotodatatable(f,fontdata,"name",true)
|
|
if tableoffset then
|
|
local format = readushort(f)
|
|
local nofnames = readushort(f)
|
|
local offset = readushort(f)
|
|
-- we can also provide a raw list as extra, todo as option
|
|
local start = tableoffset + offset
|
|
local namelists = {
|
|
unicode = { },
|
|
windows = { },
|
|
macintosh = { },
|
|
-- iso = { },
|
|
-- windows = { },
|
|
}
|
|
for i=1,nofnames do
|
|
local platform = platforms[readushort(f)]
|
|
if platform then
|
|
local namelist = namelists[platform]
|
|
if namelist then
|
|
local encoding = readushort(f)
|
|
local language = readushort(f)
|
|
local encodings = encodings[platform]
|
|
local languages = languages[platform]
|
|
if encodings and languages then
|
|
local encoding = encodings[encoding]
|
|
local language = languages[language]
|
|
if encoding and language then
|
|
local index = readushort(f)
|
|
local name = reservednames[index]
|
|
namelist[#namelist+1] = {
|
|
platform = platform,
|
|
encoding = encoding,
|
|
language = language,
|
|
name = name,
|
|
index = index,
|
|
length = readushort(f),
|
|
offset = start + readushort(f),
|
|
}
|
|
else
|
|
skipshort(f,3)
|
|
end
|
|
else
|
|
skipshort(f,3)
|
|
end
|
|
else
|
|
skipshort(f,5)
|
|
end
|
|
else
|
|
skipshort(f,5)
|
|
end
|
|
end
|
|
-- if format == 1 then
|
|
-- local noftags = readushort(f)
|
|
-- for i=1,noftags do
|
|
-- local length = readushort(f)
|
|
-- local offset = readushort(f)
|
|
-- end
|
|
-- end
|
|
--
|
|
-- we need to choose one we like, for instance an unicode one
|
|
--
|
|
local names = { }
|
|
local done = { }
|
|
local extras = { }
|
|
--
|
|
-- there is quite some logic in ff ... hard to follow so we start simple
|
|
-- and extend when we run into it (todo: proper reverse hash) .. we're only
|
|
-- interested in english anyway
|
|
--
|
|
local function decoded(platform,encoding,content)
|
|
local decoder = decoders[platform]
|
|
if decoder then
|
|
decoder = decoder[encoding]
|
|
end
|
|
if decoder then
|
|
return decoder(content)
|
|
else
|
|
return content
|
|
end
|
|
end
|
|
--
|
|
local function filter(platform,e,l)
|
|
local namelist = namelists[platform]
|
|
for i=1,#namelist do
|
|
local name = namelist[i]
|
|
local nametag = name.name
|
|
local index = name.index
|
|
if not done[nametag or i] then
|
|
local encoding = name.encoding
|
|
local language = name.language
|
|
if (not e or encoding == e) and (not l or language == l) then
|
|
setposition(f,name.offset)
|
|
local content = decoded(platform,encoding,readstring(f,name.length))
|
|
if nametag then
|
|
names[nametag] = {
|
|
content = content,
|
|
platform = platform,
|
|
encoding = encoding,
|
|
language = language,
|
|
}
|
|
end
|
|
extras[index] = content
|
|
done[nametag or i] = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
--
|
|
filter("windows","unicode bmp","english - united states")
|
|
-- filter("unicode") -- which one ?
|
|
filter("macintosh","roman","english")
|
|
filter("windows")
|
|
filter("macintosh")
|
|
filter("unicode")
|
|
--
|
|
fontdata.names = names
|
|
fontdata.extras = extras
|
|
--
|
|
if specification.platformnames then
|
|
local collected = { }
|
|
local platformextras = specification.platformextras and platformextras
|
|
for platform, namelist in next, namelists do
|
|
local filtered = false
|
|
for i=1,#namelist do
|
|
local entry = namelist[i]
|
|
local name = entry.name
|
|
if platformnames[name] or (platformextras and platformextras[name]) then
|
|
setposition(f,entry.offset)
|
|
local content = decoded(platform,entry.encoding,readstring(f,entry.length))
|
|
if filtered then
|
|
filtered[name] = content
|
|
else
|
|
filtered = { [name] = content }
|
|
end
|
|
end
|
|
end
|
|
if filtered then
|
|
collected[platform] = filtered
|
|
end
|
|
end
|
|
fontdata.platformnames = collected
|
|
end
|
|
else
|
|
fontdata.names = { }
|
|
end
|
|
end
|
|
|
|
----- validutf = lpeg.patterns.utf8character^0 * P(-1)
|
|
local validutf = lpeg.patterns.validutf8
|
|
|
|
local function getname(fontdata,key)
|
|
local names = fontdata.names
|
|
if names then
|
|
local value = names[key]
|
|
if value then
|
|
local content = value.content
|
|
return lpegmatch(validutf,content) and content or nil
|
|
end
|
|
end
|
|
end
|
|
|
|
-- This table is an original windows (with its precursor os/2) table. In ff this one is
|
|
-- part of the pfminfo table but here we keep it separate (for now). We will create a
|
|
-- properties table afterwards.
|
|
|
|
readers["os/2"] = function(f,fontdata)
|
|
local tableoffset = gotodatatable(f,fontdata,"os/2",true)
|
|
if tableoffset then
|
|
local version = readushort(f)
|
|
local windowsmetrics = {
|
|
version = version,
|
|
averagewidth = readshort(f), -- ushort?
|
|
weightclass = readushort(f),
|
|
widthclass = readushort(f),
|
|
fstype = readushort(f),
|
|
subscriptxsize = readshort(f),
|
|
subscriptysize = readshort(f),
|
|
subscriptxoffset = readshort(f),
|
|
subscriptyoffset = readshort(f),
|
|
superscriptxsize = readshort(f),
|
|
superscriptysize = readshort(f),
|
|
superscriptxoffset = readshort(f),
|
|
superscriptyoffset = readshort(f),
|
|
strikeoutsize = readshort(f),
|
|
strikeoutpos = readshort(f),
|
|
familyclass = readshort(f),
|
|
panose = { readbytes(f,10) },
|
|
unicoderanges = { readulong(f), readulong(f), readulong(f), readulong(f) },
|
|
vendor = readstring(f,4),
|
|
fsselection = readushort(f),
|
|
firstcharindex = readushort(f),
|
|
lastcharindex = readushort(f),
|
|
typoascender = readshort(f),
|
|
typodescender = readshort(f),
|
|
typolinegap = readshort(f),
|
|
winascent = readushort(f),
|
|
windescent = readushort(f),
|
|
}
|
|
if version >= 1 then
|
|
windowsmetrics.codepageranges = { readulong(f), readulong(f) }
|
|
end
|
|
if version >= 2 then
|
|
windowsmetrics.xheight = readshort(f)
|
|
windowsmetrics.capheight = readshort(f)
|
|
windowsmetrics.defaultchar = readushort(f)
|
|
windowsmetrics.breakchar = readushort(f)
|
|
-- windowsmetrics.maxcontexts = readushort(f)
|
|
-- windowsmetrics.loweropticalpointsize = readushort(f)
|
|
-- windowsmetrics.upperopticalpointsize = readushort(f)
|
|
end
|
|
--
|
|
-- todo: unicoderanges
|
|
--
|
|
windowsmetrics.weight = windowsmetrics.weightclass and weights[windowsmetrics.weightclass]
|
|
windowsmetrics.width = windowsmetrics.widthclass and widths [windowsmetrics.widthclass]
|
|
--
|
|
windowsmetrics.panoseweight = panoseweights[windowsmetrics.panose[3]]
|
|
windowsmetrics.panosewidth = panosewidths [windowsmetrics.panose[4]]
|
|
--
|
|
fontdata.windowsmetrics = windowsmetrics
|
|
else
|
|
fontdata.windowsmetrics = { }
|
|
end
|
|
end
|
|
|
|
readers.head = function(f,fontdata)
|
|
local tableoffset = gotodatatable(f,fontdata,"head",true)
|
|
if tableoffset then
|
|
local version = readulong(f)
|
|
local fontversion = readulong(f)
|
|
local fontheader = {
|
|
version = version,
|
|
fontversion = number.to16dot16(fontversion),
|
|
fontversionnumber = fontversion,
|
|
-- checksum = readulong(f),
|
|
checksum = readushort(f) * 0x10000 + readushort(f),
|
|
magic = readulong(f),
|
|
flags = readushort(f),
|
|
units = readushort(f),
|
|
created = readlongdatetime(f),
|
|
modified = readlongdatetime(f),
|
|
xmin = readshort(f),
|
|
ymin = readshort(f),
|
|
xmax = readshort(f),
|
|
ymax = readshort(f),
|
|
macstyle = readushort(f),
|
|
smallpixels = readushort(f),
|
|
directionhint = readshort(f),
|
|
indextolocformat = readshort(f),
|
|
glyphformat = readshort(f),
|
|
}
|
|
fontdata.fontheader = fontheader
|
|
else
|
|
fontdata.fontheader = { }
|
|
end
|
|
fontdata.nofglyphs = 0
|
|
end
|
|
|
|
-- This table is a rather simple one. No treatment of values is needed here. Most
|
|
-- variables are not used but nofmetrics is quite important.
|
|
|
|
readers.hhea = function(f,fontdata,specification)
|
|
local tableoffset = gotodatatable(f,fontdata,"hhea",specification.details)
|
|
if tableoffset then
|
|
fontdata.horizontalheader = {
|
|
version = readulong(f),
|
|
ascender = readfword(f),
|
|
descender = readfword(f),
|
|
linegap = readfword(f),
|
|
maxadvancewidth = readufword(f),
|
|
minleftsidebearing = readfword(f),
|
|
minrightsidebearing = readfword(f),
|
|
maxextent = readfword(f),
|
|
caretsloperise = readshort(f),
|
|
caretsloperun = readshort(f),
|
|
caretoffset = readshort(f),
|
|
reserved_1 = readshort(f),
|
|
reserved_2 = readshort(f),
|
|
reserved_3 = readshort(f),
|
|
reserved_4 = readshort(f),
|
|
metricdataformat = readshort(f),
|
|
nofmetrics = readushort(f),
|
|
}
|
|
else
|
|
fontdata.horizontalheader = {
|
|
nofmetrics = 0,
|
|
}
|
|
end
|
|
end
|
|
|
|
readers.vhea = function(f,fontdata,specification)
|
|
local tableoffset = gotodatatable(f,fontdata,"vhea",specification.details)
|
|
if tableoffset then
|
|
fontdata.verticalheader = {
|
|
version = readulong(f),
|
|
ascender = readfword(f),
|
|
descender = readfword(f),
|
|
linegap = readfword(f),
|
|
maxadvanceheight = readufword(f),
|
|
mintopsidebearing = readfword(f),
|
|
minbottomsidebearing = readfword(f),
|
|
maxextent = readfword(f),
|
|
caretsloperise = readshort(f),
|
|
caretsloperun = readshort(f),
|
|
caretoffset = readshort(f),
|
|
reserved_1 = readshort(f),
|
|
reserved_2 = readshort(f),
|
|
reserved_3 = readshort(f),
|
|
reserved_4 = readshort(f),
|
|
metricdataformat = readshort(f),
|
|
nofmetrics = readushort(f),
|
|
}
|
|
else
|
|
fontdata.verticalheader = {
|
|
nofmetrics = 0,
|
|
}
|
|
end
|
|
end
|
|
|
|
-- We probably never need all these variables, but we do need the nofglyphs when loading other
|
|
-- tables. Again we use the microsoft names but see no reason to have "max" in each name.
|
|
|
|
-- fontdata.maximumprofile can be bad
|
|
|
|
readers.maxp = function(f,fontdata,specification)
|
|
local tableoffset = gotodatatable(f,fontdata,"maxp",specification.details)
|
|
if tableoffset then
|
|
local version = readulong(f)
|
|
local nofglyphs = readushort(f)
|
|
fontdata.nofglyphs = nofglyphs
|
|
if version == 0x00005000 then
|
|
fontdata.maximumprofile = {
|
|
version = version,
|
|
nofglyphs = nofglyphs,
|
|
}
|
|
elseif version == 0x00010000 then
|
|
fontdata.maximumprofile = {
|
|
version = version,
|
|
nofglyphs = nofglyphs,
|
|
points = readushort(f),
|
|
contours = readushort(f),
|
|
compositepoints = readushort(f),
|
|
compositecontours = readushort(f),
|
|
zones = readushort(f),
|
|
twilightpoints = readushort(f),
|
|
storage = readushort(f),
|
|
functiondefs = readushort(f),
|
|
instructiondefs = readushort(f),
|
|
stackelements = readushort(f),
|
|
sizeofinstructions = readushort(f),
|
|
componentelements = readushort(f),
|
|
componentdepth = readushort(f),
|
|
}
|
|
else
|
|
fontdata.maximumprofile = {
|
|
version = version,
|
|
nofglyphs = 0,
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Here we filter the (advance) widths (that can be different from the boundingbox width of
|
|
-- course).
|
|
|
|
readers.hmtx = function(f,fontdata,specification)
|
|
local tableoffset = gotodatatable(f,fontdata,"hmtx",specification.glyphs)
|
|
if tableoffset then
|
|
local horizontalheader = fontdata.horizontalheader
|
|
local nofmetrics = horizontalheader.nofmetrics
|
|
local glyphs = fontdata.glyphs
|
|
local nofglyphs = fontdata.nofglyphs
|
|
local width = 0 -- advance
|
|
local leftsidebearing = 0
|
|
for i=0,nofmetrics-1 do
|
|
local glyph = glyphs[i]
|
|
width = readshort(f) -- readushort
|
|
leftsidebearing = readshort(f)
|
|
if width ~= 0 then
|
|
glyph.width = width
|
|
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.
|
|
for i=nofmetrics,nofglyphs-1 do
|
|
local glyph = glyphs[i]
|
|
if width ~= 0 then
|
|
glyph.width = width
|
|
end
|
|
-- if leftsidebearing ~= 0 then
|
|
-- glyph.lsb = leftsidebearing
|
|
-- end
|
|
end
|
|
end
|
|
end
|
|
|
|
readers.vmtx = function(f,fontdata,specification)
|
|
local tableoffset = gotodatatable(f,fontdata,"vmtx",specification.glyphs)
|
|
if tableoffset then
|
|
local verticalheader = fontdata.verticalheader
|
|
local nofmetrics = verticalheader.nofmetrics
|
|
local glyphs = fontdata.glyphs
|
|
local nofglyphs = fontdata.nofglyphs
|
|
local vheight = 0
|
|
local vdefault = verticalheader.ascender - verticalheader.descender
|
|
local topsidebearing = 0
|
|
for i=0,nofmetrics-1 do
|
|
local glyph = glyphs[i]
|
|
vheight = readushort(f)
|
|
topsidebearing = readshort(f)
|
|
if vheight ~= 0 and vheight ~= vdefault then
|
|
glyph.vheight = vheight
|
|
end
|
|
if topsidebearing ~= 0 then
|
|
glyph.tsb = topsidebearing
|
|
end
|
|
end
|
|
-- The next can happen in for instance a monospace font or in a cjk font
|
|
-- with fixed heights.
|
|
for i=nofmetrics,nofglyphs-1 do
|
|
local glyph = glyphs[i]
|
|
if vheight ~= 0 and vheight ~= vdefault then
|
|
glyph.vheight = vheight
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
readers.vorg = function(f,fontdata,specification)
|
|
reportskippedtable(f,fontdata,"vorg",specification.glyphs)
|
|
end
|
|
|
|
-- The post table relates to postscript (printing) but has some relevant properties for other
|
|
-- usage as well. We just use the names from the microsoft specification. The version 2.0
|
|
-- description is somewhat fuzzy but it is a hybrid with overloads.
|
|
|
|
readers.post = function(f,fontdata,specification)
|
|
local tableoffset = gotodatatable(f,fontdata,"post",true)
|
|
if tableoffset then
|
|
local version = readulong(f)
|
|
fontdata.postscript = {
|
|
version = version,
|
|
italicangle = round(1000*readfixed(f))/1000,
|
|
underlineposition = readfword(f),
|
|
underlinethickness = readfword(f),
|
|
monospaced = readulong(f),
|
|
minmemtype42 = readulong(f),
|
|
maxmemtype42 = readulong(f),
|
|
minmemtype1 = readulong(f),
|
|
maxmemtype1 = readulong(f),
|
|
}
|
|
if not specification.glyphs then
|
|
-- enough done
|
|
elseif version == 0x00010000 then
|
|
-- mac encoding (258 glyphs)
|
|
for index=0,#standardromanencoding do
|
|
glyphs[index].name = standardromanencoding[index]
|
|
end
|
|
elseif version == 0x00020000 then
|
|
local glyphs = fontdata.glyphs
|
|
local nofglyphs = readushort(f)
|
|
local indices = { }
|
|
local names = { }
|
|
local maxnames = 0
|
|
for i=0,nofglyphs-1 do
|
|
local nameindex = readushort(f)
|
|
if nameindex >= 258 then
|
|
maxnames = maxnames + 1
|
|
nameindex = nameindex - 257
|
|
indices[nameindex] = i
|
|
else
|
|
glyphs[i].name = standardromanencoding[nameindex]
|
|
end
|
|
end
|
|
for i=1,maxnames do
|
|
local mapping = indices[i]
|
|
if not mapping then
|
|
report("quit post name fetching at %a of %a: %s",i,maxnames,"no index")
|
|
break
|
|
else
|
|
local length = readbyte(f)
|
|
if length > 0 then
|
|
glyphs[mapping].name = readstring(f,length)
|
|
else
|
|
report("quit post name fetching at %a of %a: %s",i,maxnames,"overflow")
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
else
|
|
fontdata.postscript = { }
|
|
end
|
|
end
|
|
|
|
readers.cff = function(f,fontdata,specification)
|
|
reportskippedtable(f,fontdata,"cff",specification.glyphs)
|
|
end
|
|
|
|
-- Not all cmaps make sense .. e.g. dfont is obsolete and probably more are not relevant. Let's see
|
|
-- what we run into. There is some weird calculation going on here because we offset in a table
|
|
-- being a blob of memory or file. Anyway, I can't stand lunatic formats like this esp when there
|
|
-- is no real gain.
|
|
|
|
local formatreaders = { }
|
|
local duplicatestoo = true
|
|
|
|
local sequence = {
|
|
-- these is some provision against redundant loading
|
|
{ 3, 1, 4 },
|
|
{ 3, 10, 12 },
|
|
{ 0, 3, 4 },
|
|
{ 0, 3, 12 },
|
|
{ 0, 1, 4 },
|
|
{ 0, 1, 12 }, -- for some old mac fonts
|
|
{ 0, 0, 6 },
|
|
{ 3, 0, 6 },
|
|
{ 3, 0, 4 }, -- for (likely) old crap
|
|
-- variants
|
|
{ 0, 5, 14 },
|
|
-- last resort ranges
|
|
{ 0, 4, 12 },
|
|
{ 3, 10, 13 },
|
|
}
|
|
|
|
local supported = { }
|
|
|
|
for i=1,#sequence do
|
|
local si = sequence[i]
|
|
local sp, se, sf = si[1], si[2], si[3]
|
|
local p = supported[sp]
|
|
if not p then
|
|
p = { }
|
|
supported[sp] = p
|
|
end
|
|
local e = p[se]
|
|
if not e then
|
|
e = { }
|
|
p[se] = e
|
|
end
|
|
e[sf] = true
|
|
end
|
|
|
|
formatreaders[4] = function(f,fontdata,offset)
|
|
setposition(f,offset+2) -- skip format
|
|
--
|
|
local length = readushort(f) -- in bytes of subtable
|
|
local language = readushort(f)
|
|
local nofsegments = readushort(f) / 2
|
|
--
|
|
skipshort(f,3) -- searchrange entryselector rangeshift
|
|
--
|
|
local mapping = fontdata.mapping
|
|
local glyphs = fontdata.glyphs
|
|
local duplicates = fontdata.duplicates
|
|
local nofdone = 0
|
|
local endchars = readcardinaltable(f,nofsegments,ushort)
|
|
local reserved = readushort(f) -- 0
|
|
local startchars = readcardinaltable(f,nofsegments,ushort)
|
|
local deltas = readcardinaltable(f,nofsegments,ushort)
|
|
local offsets = readcardinaltable(f,nofsegments,ushort)
|
|
-- format length language nofsegments searchrange entryselector rangeshift 4-tables
|
|
local size = (length - 2 * 2 - 5 * 2 - 4 * 2 * nofsegments) / 2
|
|
local indices = readcardinaltable(f,size-1,ushort)
|
|
--
|
|
for segment=1,nofsegments do
|
|
local startchar = startchars[segment]
|
|
local endchar = endchars[segment]
|
|
local offset = offsets[segment]
|
|
local delta = deltas[segment]
|
|
if startchar == 0xFFFF and endchar == 0xFFFF then
|
|
-- break
|
|
elseif startchar == 0xFFFF and offset == 0 then
|
|
-- break
|
|
elseif offset == 0xFFFF then
|
|
-- bad encoding
|
|
elseif offset == 0 then
|
|
if trace_cmap_details then
|
|
report("format 4.%i segment %2i from %C upto %C at index %H",1,segment,startchar,endchar,(startchar + delta) % 65536)
|
|
end
|
|
for unicode=startchar,endchar do
|
|
local index = (unicode + delta) % 65536
|
|
if index and index > 0 then
|
|
local glyph = glyphs[index]
|
|
if glyph then
|
|
local gu = glyph.unicode
|
|
if not gu then
|
|
glyph.unicode = unicode
|
|
nofdone = nofdone + 1
|
|
elseif gu ~= unicode then
|
|
if duplicatestoo then
|
|
local d = duplicates[gu]
|
|
if d then
|
|
d[unicode] = true
|
|
else
|
|
duplicates[gu] = { [unicode] = true }
|
|
end
|
|
else
|
|
-- no duplicates ... weird side effects in lm
|
|
report("duplicate case 1: %C %04i %s",unicode,index,glyphs[index].name)
|
|
end
|
|
end
|
|
if not mapping[index] then
|
|
mapping[index] = unicode
|
|
end
|
|
end
|
|
end
|
|
end
|
|
else
|
|
local shift = (segment-nofsegments+offset/2) - startchar
|
|
if trace_cmap_details then
|
|
report_cmap("format 4.%i segment %2i from %C upto %C at index %H",0,segment,startchar,endchar,(startchar + delta) % 65536)
|
|
end
|
|
for unicode=startchar,endchar do
|
|
local slot = shift + unicode
|
|
local index = indices[slot]
|
|
if index and index > 0 then
|
|
index = (index + delta) % 65536
|
|
local glyph = glyphs[index]
|
|
if glyph then
|
|
local gu = glyph.unicode
|
|
if not gu then
|
|
glyph.unicode = unicode
|
|
nofdone = nofdone + 1
|
|
elseif gu ~= unicode then
|
|
if duplicatestoo then
|
|
local d = duplicates[gu]
|
|
if d then
|
|
d[unicode] = true
|
|
else
|
|
duplicates[gu] = { [unicode] = true }
|
|
end
|
|
else
|
|
-- no duplicates ... weird side effects in lm
|
|
report("duplicate case 2: %C %04i %s",unicode,index,glyphs[index].name)
|
|
end
|
|
end
|
|
if not mapping[index] then
|
|
mapping[index] = unicode
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return nofdone
|
|
end
|
|
|
|
formatreaders[6] = function(f,fontdata,offset)
|
|
setposition(f,offset) -- + 2 + 2 + 2 -- skip format length language
|
|
local format = readushort(f)
|
|
local length = readushort(f)
|
|
local language = readushort(f)
|
|
local mapping = fontdata.mapping
|
|
local glyphs = fontdata.glyphs
|
|
local duplicates = fontdata.duplicates
|
|
local start = readushort(f)
|
|
local count = readushort(f)
|
|
local stop = start+count-1
|
|
local nofdone = 0
|
|
if trace_cmap_details then
|
|
report_cmap("format 6 from %C to %C",2,start,stop)
|
|
end
|
|
for unicode=start,stop do
|
|
local index = readushort(f)
|
|
if index > 0 then
|
|
local glyph = glyphs[index]
|
|
if glyph then
|
|
local gu = glyph.unicode
|
|
if not gu then
|
|
glyph.unicode = unicode
|
|
nofdone = nofdone + 1
|
|
elseif gu ~= unicode then
|
|
-- report("format 6 overloading %C to %C",gu,unicode)
|
|
-- glyph.unicode = unicode
|
|
-- no duplicates ... weird side effects in lm
|
|
end
|
|
if not mapping[index] then
|
|
mapping[index] = unicode
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return nofdone
|
|
end
|
|
|
|
formatreaders[12] = function(f,fontdata,offset)
|
|
setposition(f,offset+2+2+4+4) -- skip format reserved length language
|
|
local mapping = fontdata.mapping
|
|
local glyphs = fontdata.glyphs
|
|
local duplicates = fontdata.duplicates
|
|
local nofgroups = readulong(f)
|
|
local nofdone = 0
|
|
for i=1,nofgroups do
|
|
local first = readulong(f)
|
|
local last = readulong(f)
|
|
local index = readulong(f)
|
|
if trace_cmap_details then
|
|
report_cmap("format 12 from %C to %C starts at index %i",first,last,index)
|
|
end
|
|
for unicode=first,last do
|
|
local glyph = glyphs[index]
|
|
if glyph then
|
|
local gu = glyph.unicode
|
|
if not gu then
|
|
glyph.unicode = unicode
|
|
nofdone = nofdone + 1
|
|
elseif gu ~= unicode then
|
|
-- e.g. sourcehan fonts need this
|
|
local d = duplicates[gu]
|
|
if d then
|
|
d[unicode] = true
|
|
else
|
|
duplicates[gu] = { [unicode] = true }
|
|
end
|
|
end
|
|
if not mapping[index] then
|
|
mapping[index] = unicode
|
|
end
|
|
end
|
|
index = index + 1
|
|
end
|
|
end
|
|
return nofdone
|
|
end
|
|
|
|
formatreaders[13] = function(f,fontdata,offset)
|
|
--
|
|
-- this vector is only used for simple fallback fonts
|
|
--
|
|
setposition(f,offset+2+2+4+4) -- skip format reserved length language
|
|
local mapping = fontdata.mapping
|
|
local glyphs = fontdata.glyphs
|
|
local duplicates = fontdata.duplicates
|
|
local nofgroups = readulong(f)
|
|
local nofdone = 0
|
|
for i=1,nofgroups do
|
|
local first = readulong(f)
|
|
local last = readulong(f)
|
|
local index = readulong(f)
|
|
if first < privateoffset then
|
|
if trace_cmap_details then
|
|
report_cmap("format 13 from %C to %C get index %i",first,last,index)
|
|
end
|
|
local glyph = glyphs[index]
|
|
local unicode = glyph.unicode
|
|
if not unicode then
|
|
unicode = first
|
|
glyph.unicode = unicode
|
|
first = first + 1
|
|
end
|
|
local list = duplicates[unicode]
|
|
mapping[index] = unicode
|
|
if not list then
|
|
list = { }
|
|
duplicates[unicode] = list
|
|
end
|
|
if last >= privateoffset then
|
|
local limit = privateoffset - 1
|
|
report("format 13 from %C to %C pruned to %C",first,last,limit)
|
|
last = limit
|
|
end
|
|
for unicode=first,last do
|
|
list[unicode] = true
|
|
end
|
|
nofdone = nofdone + last - first + 1
|
|
else
|
|
report("format 13 from %C to %C ignored",first,last)
|
|
end
|
|
end
|
|
return nofdone
|
|
end
|
|
|
|
formatreaders[14] = function(f,fontdata,offset)
|
|
if offset and offset ~= 0 then
|
|
setposition(f,offset)
|
|
local format = readushort(f)
|
|
local length = readulong(f)
|
|
local nofrecords = readulong(f)
|
|
local records = { }
|
|
local variants = { }
|
|
local nofdone = 0
|
|
fontdata.variants = variants
|
|
for i=1,nofrecords do
|
|
records[i] = {
|
|
selector = readuint(f),
|
|
default = readulong(f), -- 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
|
|
nofdone = nofdone + count
|
|
variants[selector] = mapping
|
|
end
|
|
end
|
|
return nofdone
|
|
else
|
|
return 0
|
|
end
|
|
end
|
|
|
|
local function checkcmap(f,fontdata,records,platform,encoding,format)
|
|
local pdata = records[platform]
|
|
if not pdata then
|
|
if trace_cmap_details then
|
|
report_cmap("skipped, %s, p=%i e=%i f=%i","no platform",platform,encoding,format)
|
|
end
|
|
return 0
|
|
end
|
|
local edata = pdata[encoding]
|
|
if not edata then
|
|
if trace_cmap_details then
|
|
report_cmap("skipped, %s, p=%i e=%i f=%i","no encoding",platform,encoding,format)
|
|
end
|
|
return 0
|
|
end
|
|
local fdata = edata[format]
|
|
if not fdata then
|
|
if trace_cmap_details then
|
|
report_cmap("skipped, %s, p=%i e=%i f=%i","no format",platform,encoding,format)
|
|
end
|
|
return 0
|
|
elseif type(fdata) ~= "number" then
|
|
if trace_cmap_details then
|
|
report_cmap("skipped, %s, p=%i e=%i f=%i","already done",platform,encoding,format)
|
|
end
|
|
return 0
|
|
end
|
|
edata[format] = true -- done
|
|
local reader = formatreaders[format]
|
|
if not reader then
|
|
if trace_cmap_details then
|
|
report_cmap("skipped, %s, p=%i e=%i f=%i","unsupported format",platform,encoding,format)
|
|
end
|
|
return 0
|
|
end
|
|
local n = reader(f,fontdata,fdata) or 0
|
|
if trace_cmap_details or trace_cmap then
|
|
local p = platforms[platform]
|
|
local e = encodings[p]
|
|
report_cmap("checked, platform %i (%s), encoding %i (%s), format %i, new unicodes %i",
|
|
platform,p,encoding,e and e[encoding] or "?",format,n)
|
|
end
|
|
return n
|
|
end
|
|
|
|
function readers.cmap(f,fontdata,specification)
|
|
local tableoffset = gotodatatable(f,fontdata,"cmap",specification.glyphs)
|
|
if tableoffset then
|
|
local version = readushort(f)
|
|
local noftables = readushort(f)
|
|
local records = { }
|
|
local unicodecid = false
|
|
local variantcid = false
|
|
local variants = { }
|
|
local duplicates = fontdata.duplicates or { }
|
|
fontdata.duplicates = duplicates
|
|
for i=1,noftables do
|
|
local platform = readushort(f)
|
|
local encoding = readushort(f)
|
|
local offset = readulong(f)
|
|
local record = records[platform]
|
|
if not record then
|
|
records[platform] = {
|
|
[encoding] = {
|
|
offsets = { offset },
|
|
formats = { },
|
|
}
|
|
}
|
|
else
|
|
local subtables = record[encoding]
|
|
if not subtables then
|
|
record[encoding] = {
|
|
offsets = { offset },
|
|
formats = { },
|
|
}
|
|
else
|
|
local offsets = subtables.offsets
|
|
offsets[#offsets+1] = offset
|
|
end
|
|
end
|
|
end
|
|
if trace_cmap then
|
|
report("found cmaps:")
|
|
end
|
|
for platform, record in sortedhash(records) do
|
|
local p = platforms[platform]
|
|
local e = encodings[p]
|
|
local sp = supported[platform]
|
|
local ps = p or "?"
|
|
if trace_cmap then
|
|
if sp then
|
|
report(" platform %i: %s",platform,ps)
|
|
else
|
|
report(" platform %i: %s (unsupported)",platform,ps)
|
|
end
|
|
end
|
|
for encoding, subtables in sortedhash(record) do
|
|
local se = sp and sp[encoding]
|
|
local es = e and e[encoding] or "?"
|
|
if trace_cmap then
|
|
if se then
|
|
report(" encoding %i: %s",encoding,es)
|
|
else
|
|
report(" encoding %i: %s (unsupported)",encoding,es)
|
|
end
|
|
end
|
|
local offsets = subtables.offsets
|
|
local formats = subtables.formats
|
|
for i=1,#offsets do
|
|
local offset = tableoffset + offsets[i]
|
|
setposition(f,offset)
|
|
formats[readushort(f)] = offset
|
|
end
|
|
record[encoding] = formats
|
|
if trace_cmap then
|
|
local list = sortedkeys(formats)
|
|
for i=1,#list do
|
|
if not (se and se[list[i]]) then
|
|
list[i] = list[i] .. " (unsupported)"
|
|
end
|
|
end
|
|
report(" formats: % t",list)
|
|
end
|
|
end
|
|
end
|
|
--
|
|
local ok = false
|
|
for i=1,#sequence do
|
|
local si = sequence[i]
|
|
local sp, se, sf = si[1], si[2], si[3]
|
|
if checkcmap(f,fontdata,records,sp,se,sf) > 0 then
|
|
ok = true
|
|
end
|
|
end
|
|
if not ok then
|
|
report("no useable unicode cmap found")
|
|
end
|
|
--
|
|
fontdata.cidmaps = {
|
|
version = version,
|
|
noftables = noftables,
|
|
records = records,
|
|
}
|
|
else
|
|
fontdata.cidmaps = { }
|
|
end
|
|
end
|
|
|
|
-- The glyf table depends on the loca table. We have one entry to much in the locations table (the
|
|
-- last one is a dummy) because we need to calculate the size of a glyph blob from the delta,
|
|
-- although we not need it in our usage (yet). We can remove the locations table when we're done.
|
|
|
|
function readers.loca(f,fontdata,specification)
|
|
reportskippedtable(f,fontdata,"loca",specification.glyphs)
|
|
end
|
|
|
|
function readers.glyf(f,fontdata,specification) -- part goes to cff module
|
|
reportskippedtable(f,fontdata,"glyf",specification.glyphs)
|
|
end
|
|
|
|
-- The MicroSoft variant is pretty clean and is supported (implemented elsewhere)
|
|
-- just because I wanted to see how such a font looks like.
|
|
|
|
function readers.colr(f,fontdata,specification)
|
|
reportskippedtable(f,fontdata,"colr",specification.glyphs)
|
|
end
|
|
function readers.cpal(f,fontdata,specification)
|
|
reportskippedtable(f,fontdata,"cpal",specification.glyphs)
|
|
end
|
|
|
|
-- This one is also supported, if only because I could locate a proper font for
|
|
-- testing.
|
|
|
|
function readers.svg(f,fontdata,specification)
|
|
reportskippedtable(f,fontdata,"svg",specification.glyphs)
|
|
end
|
|
|
|
-- There is a font from apple to test the next one. Will there be more? Anyhow,
|
|
-- it's relatively easy to support, so I did it.
|
|
|
|
function readers.sbix(f,fontdata,specification)
|
|
reportskippedtable(f,fontdata,"sbix",specification.glyphs)
|
|
end
|
|
|
|
-- I'm only willing to look into the next variant if I see a decent and complete (!)
|
|
-- font and more can show up. It makes no sense to waste time on ideas. Okay, the
|
|
-- apple font also has these tables.
|
|
|
|
function readers.cbdt(f,fontdata,specification)
|
|
reportskippedtable(f,fontdata,"cbdt",specification.glyphs)
|
|
end
|
|
function readers.cblc(f,fontdata,specification)
|
|
reportskippedtable(f,fontdata,"cblc",specification.glyphs)
|
|
end
|
|
function readers.ebdt(f,fontdata,specification)
|
|
reportskippedtable(f,fontdata,"ebdt",specification.glyphs)
|
|
end
|
|
function readers.ebsc(f,fontdata,specification)
|
|
reportskippedtable(f,fontdata,"ebsc",specification.glyphs)
|
|
end
|
|
function readers.eblc(f,fontdata,specification)
|
|
reportskippedtable(f,fontdata,"eblc",specification.glyphs)
|
|
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).
|
|
|
|
function readers.kern(f,fontdata,specification)
|
|
local tableoffset = gotodatatable(f,fontdata,"kern",specification.kerns)
|
|
if tableoffset then
|
|
local version = readushort(f)
|
|
local noftables = readushort(f)
|
|
for i=1,noftables do
|
|
local version = readushort(f)
|
|
local length = readushort(f)
|
|
local coverage = readushort(f)
|
|
-- bit 8-15 of coverage: format 0 or 2
|
|
local format = rshift(coverage,8) -- is this ok?
|
|
if format == 0 then
|
|
local nofpairs = readushort(f)
|
|
local searchrange = readushort(f)
|
|
local entryselector = readushort(f)
|
|
local rangeshift = readushort(f)
|
|
local kerns = { }
|
|
local glyphs = fontdata.glyphs
|
|
for i=1,nofpairs do
|
|
local left = readushort(f)
|
|
local right = readushort(f)
|
|
local kern = readfword(f)
|
|
local glyph = glyphs[left]
|
|
local kerns = glyph.kerns
|
|
if kerns then
|
|
kerns[right] = kern
|
|
else
|
|
glyph.kerns = { [right] = kern }
|
|
end
|
|
end
|
|
elseif format == 2 then
|
|
report("todo: kern classes")
|
|
else
|
|
report("todo: kerns")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function readers.gdef(f,fontdata,specification)
|
|
reportskippedtable(f,fontdata,"gdef",specification.details)
|
|
end
|
|
|
|
function readers.gsub(f,fontdata,specification)
|
|
reportskippedtable(f,fontdata,"gsub",specification.details)
|
|
end
|
|
|
|
function readers.gpos(f,fontdata,specification)
|
|
reportskippedtable(f,fontdata,"gpos",specification.details)
|
|
end
|
|
|
|
function readers.math(f,fontdata,specification)
|
|
reportskippedtable(f,fontdata,"math",specification.details)
|
|
end
|
|
|
|
-- Now comes the loader. The order of reading these matters as we need to know
|
|
-- some properties in order to read following tables. When details is true we also
|
|
-- initialize the glyphs data.
|
|
|
|
local function getinfo(maindata,sub,platformnames,rawfamilynames,metricstoo,instancenames)
|
|
local fontdata = sub and maindata.subfonts and maindata.subfonts[sub] or maindata
|
|
local names = fontdata.names
|
|
local info = nil
|
|
if names then
|
|
local metrics = fontdata.windowsmetrics or { }
|
|
local postscript = fontdata.postscript or { }
|
|
local fontheader = fontdata.fontheader or { }
|
|
local cffinfo = fontdata.cffinfo or { }
|
|
local verticalheader = fontdata.verticalheader or { }
|
|
local filename = fontdata.filename
|
|
local weight = getname(fontdata,"weight") or (cffinfo and cffinfo.weight) or (metrics and metrics.weight)
|
|
local width = getname(fontdata,"width") or (cffinfo and cffinfo.width ) or (metrics and metrics.width )
|
|
local fontname = getname(fontdata,"postscriptname")
|
|
local fullname = getname(fontdata,"fullname")
|
|
local family = getname(fontdata,"family")
|
|
local subfamily = getname(fontdata,"subfamily")
|
|
local familyname = getname(fontdata,"typographicfamily")
|
|
local subfamilyname = getname(fontdata,"typographicsubfamily")
|
|
local compatiblename = getname(fontdata,"compatiblefullname") -- kind of useless
|
|
if rawfamilynames then
|
|
-- for PG (for now, as i need to check / adapt context to catch a no-fallback case)
|
|
else
|
|
if not familyname then familyname = family end
|
|
if not subfamilyname then subfamilyname = subfamily end
|
|
end
|
|
if platformnames then
|
|
platformnames = fontdata.platformnames
|
|
end
|
|
if instancenames then
|
|
local variabledata = fontdata.variabledata
|
|
if variabledata then
|
|
local instances = variabledata and variabledata.instances
|
|
if instances then
|
|
instancenames = { }
|
|
for i=1,#instances do
|
|
instancenames[i] = lower(stripstring(instances[i].subfamily))
|
|
end
|
|
else
|
|
instancenames = nil
|
|
end
|
|
else
|
|
instancenames = nil
|
|
end
|
|
end
|
|
info = { -- we inherit some inconsistencies/choices from ff
|
|
subfontindex = fontdata.subfontindex or sub or 0,
|
|
-- filename = filename,
|
|
version = getname(fontdata,"version"),
|
|
-- format = fontdata.format,
|
|
fontname = fontname,
|
|
fullname = fullname,
|
|
-- cfffullname = cff.fullname,
|
|
family = family,
|
|
subfamily = subfamily,
|
|
familyname = familyname,
|
|
subfamilyname = subfamilyname,
|
|
compatiblename = compatiblename,
|
|
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,
|
|
boundingbox = fontheader and { fontheader.xmin or 0, fontheader.ymin or 0, fontheader.xmax or 0, fontheader.ymax or 0 } or nil,
|
|
monospaced = (tonumber(postscript.monospaced or 0) > 0) or metrics.panosewidth == "monospaced",
|
|
averagewidth = metrics.averagewidth,
|
|
xheight = metrics.xheight, -- can be missing
|
|
capheight = metrics.capheight or fontdata.maxy, -- can be missing
|
|
ascender = metrics.typoascender,
|
|
descender = metrics.typodescender,
|
|
platformnames = platformnames or nil,
|
|
instancenames = instancenames or nil,
|
|
tableoffsets = fontdata.tableoffsets,
|
|
defaultvheight = (verticalheader.ascender or 0) - (verticalheader.descender or 0)
|
|
}
|
|
if metricstoo then
|
|
local keys = {
|
|
"version",
|
|
"ascender", "descender", "linegap",
|
|
-- "caretoffset", "caretsloperise", "caretsloperun",
|
|
"maxadvancewidth", "maxadvanceheight", "maxextent",
|
|
-- "metricdataformat",
|
|
"minbottomsidebearing", "mintopsidebearing",
|
|
}
|
|
local h = fontdata.horizontalheader or { }
|
|
local v = fontdata.verticalheader or { }
|
|
if h then
|
|
local th = { }
|
|
local tv = { }
|
|
for i=1,#keys do
|
|
local key = keys[i]
|
|
th[key] = h[key] or 0
|
|
tv[key] = v[key] or 0
|
|
end
|
|
info.horizontalmetrics = th
|
|
info.verticalmetrics = tv
|
|
end
|
|
end
|
|
elseif n then
|
|
info = {
|
|
filename = fontdata.filename,
|
|
comment = "there is no info for subfont " .. n,
|
|
}
|
|
else
|
|
info = {
|
|
filename = fontdata.filename,
|
|
comment = "there is no info",
|
|
}
|
|
end
|
|
-- inspect(info)
|
|
return info
|
|
end
|
|
|
|
local function loadtables(f,specification,offset)
|
|
if offset then
|
|
setposition(f,offset)
|
|
end
|
|
local tables = { }
|
|
local basename = file.basename(specification.filename)
|
|
local filesize = specification.filesize
|
|
local filetime = specification.filetime
|
|
local fontdata = { -- some can/will go
|
|
filename = basename,
|
|
filesize = filesize,
|
|
filetime = filetime,
|
|
version = readstring(f,4),
|
|
noftables = readushort(f),
|
|
searchrange = readushort(f), -- not needed
|
|
entryselector = readushort(f), -- not needed
|
|
rangeshift = readushort(f), -- not needed
|
|
tables = tables,
|
|
foundtables = false,
|
|
}
|
|
for i=1,fontdata.noftables do
|
|
local tag = lower(stripstring(readstring(f,4)))
|
|
-- local checksum = readulong(f) -- not used
|
|
local checksum = readushort(f) * 0x10000 + readushort(f)
|
|
local offset = readulong(f)
|
|
local length = readulong(f)
|
|
if offset + length > filesize then
|
|
report("bad %a table in file %a",tag,basename)
|
|
end
|
|
tables[tag] = {
|
|
checksum = checksum,
|
|
offset = offset,
|
|
length = length,
|
|
}
|
|
end
|
|
-- inspect(tables)
|
|
fontdata.foundtables = sortedkeys(tables)
|
|
if tables.cff or tables.cff2 then
|
|
fontdata.format = "opentype"
|
|
else
|
|
fontdata.format = "truetype"
|
|
end
|
|
return fontdata, tables
|
|
end
|
|
|
|
local function prepareglyps(fontdata)
|
|
local glyphs = setmetatableindex(function(t,k)
|
|
local v = {
|
|
-- maybe more defaults
|
|
index = k,
|
|
}
|
|
t[k] = v
|
|
return v
|
|
end)
|
|
fontdata.glyphs = glyphs
|
|
fontdata.mapping = { }
|
|
end
|
|
|
|
local function readtable(tag,f,fontdata,specification,...)
|
|
local reader = readers[tag]
|
|
if reader then
|
|
reader(f,fontdata,specification,...)
|
|
end
|
|
end
|
|
|
|
local variablefonts_supported = (context and true) or (logs and logs.application and true) or false
|
|
|
|
local function readdata(f,offset,specification)
|
|
|
|
local fontdata, tables = loadtables(f,specification,offset)
|
|
|
|
if specification.glyphs then
|
|
prepareglyps(fontdata)
|
|
end
|
|
|
|
if not variablefonts_supported then
|
|
specification.instance = nil
|
|
specification.variable = nil
|
|
specification.factors = nil
|
|
end
|
|
|
|
fontdata.temporary = { }
|
|
|
|
readtable("name",f,fontdata,specification)
|
|
|
|
local askedname = specification.askedname
|
|
if askedname then
|
|
local fullname = getname(fontdata,"fullname") or ""
|
|
local cleanname = gsub(askedname,"[^a-zA-Z0-9]","")
|
|
local foundname = gsub(fullname,"[^a-zA-Z0-9]","")
|
|
if lower(cleanname) ~= lower(foundname) then
|
|
return -- keep searching
|
|
end
|
|
end
|
|
|
|
readtable("stat",f,fontdata,specification)
|
|
readtable("avar",f,fontdata,specification)
|
|
readtable("fvar",f,fontdata,specification)
|
|
|
|
if variablefonts_supported then
|
|
|
|
local variabledata = fontdata.variabledata
|
|
|
|
if variabledata then
|
|
local instances = variabledata.instances
|
|
local axis = variabledata.axis
|
|
if axis and (not instances or #instances == 0) then
|
|
instances = { }
|
|
variabledata.instances = instances
|
|
local function add(n,subfamily,value)
|
|
local values = { }
|
|
for i=1,#axis do
|
|
local a = axis[i]
|
|
values[i] = {
|
|
axis = a.tag,
|
|
value = i == n and value or a.default,
|
|
}
|
|
end
|
|
instances[#instances+1] = {
|
|
subfamily = subfamily,
|
|
values = values,
|
|
}
|
|
end
|
|
for i=1,#axis do
|
|
local a = axis[i]
|
|
local tag = a.tag
|
|
add(i,"default"..tag,a.default)
|
|
add(i,"minimum"..tag,a.minimum)
|
|
add(i,"maximum"..tag,a.maximum)
|
|
end
|
|
-- report("%i fake instances added",#instances)
|
|
end
|
|
end
|
|
|
|
if not specification.factors then
|
|
local instance = specification.instance
|
|
if type(instance) == "string" then
|
|
local factors = helpers.getfactors(fontdata,instance)
|
|
if factors then
|
|
specification.factors = factors
|
|
fontdata.factors = factors
|
|
fontdata.instance = instance
|
|
report("user instance: %s, factors: % t",instance,factors)
|
|
else
|
|
report("user instance: %s, bad factors",instance)
|
|
end
|
|
end
|
|
end
|
|
|
|
if not fontdata.factors then
|
|
if fontdata.variabledata then
|
|
local factors = helpers.getfactors(fontdata,true)
|
|
if factors then
|
|
specification.factors = factors
|
|
fontdata.factors = factors
|
|
-- report("factors: % t",factors)
|
|
-- else
|
|
-- report("bad factors")
|
|
end
|
|
else
|
|
-- report("unknown instance")
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
readtable("os/2",f,fontdata,specification)
|
|
readtable("head",f,fontdata,specification)
|
|
readtable("maxp",f,fontdata,specification)
|
|
readtable("hhea",f,fontdata,specification)
|
|
readtable("vhea",f,fontdata,specification)
|
|
readtable("hmtx",f,fontdata,specification)
|
|
readtable("vmtx",f,fontdata,specification)
|
|
readtable("vorg",f,fontdata,specification)
|
|
readtable("post",f,fontdata,specification)
|
|
|
|
readtable("mvar",f,fontdata,specification)
|
|
readtable("hvar",f,fontdata,specification)
|
|
readtable("vvar",f,fontdata,specification)
|
|
|
|
readtable("gdef",f,fontdata,specification)
|
|
|
|
readtable("cff" ,f,fontdata,specification)
|
|
readtable("cff2",f,fontdata,specification)
|
|
|
|
readtable("cmap",f,fontdata,specification)
|
|
readtable("loca",f,fontdata,specification) -- maybe load it in glyf
|
|
readtable("glyf",f,fontdata,specification) -- loads gvar
|
|
|
|
readtable("colr",f,fontdata,specification)
|
|
readtable("cpal",f,fontdata,specification)
|
|
|
|
readtable("svg" ,f,fontdata,specification)
|
|
|
|
readtable("sbix",f,fontdata,specification)
|
|
|
|
readtable("cbdt",f,fontdata,specification)
|
|
readtable("cblc",f,fontdata,specification)
|
|
readtable("ebdt",f,fontdata,specification)
|
|
readtable("eblc",f,fontdata,specification)
|
|
|
|
readtable("kern",f,fontdata,specification)
|
|
readtable("gsub",f,fontdata,specification)
|
|
readtable("gpos",f,fontdata,specification)
|
|
|
|
readtable("math",f,fontdata,specification)
|
|
|
|
fontdata.locations = nil
|
|
fontdata.cidmaps = nil
|
|
fontdata.dictionaries = nil
|
|
-- fontdata.cff = nil
|
|
|
|
if specification.tableoffsets then
|
|
fontdata.tableoffsets = tables
|
|
setmetatableindex(tables, {
|
|
version = fontdata.version,
|
|
noftables = fontdata.noftables,
|
|
searchrange = fontdata.searchrange,
|
|
entryselector = fontdata.entryselector,
|
|
rangeshift = fontdata.rangeshift,
|
|
})
|
|
end
|
|
return fontdata
|
|
end
|
|
|
|
local function loadfontdata(specification)
|
|
local filename = specification.filename
|
|
local fileattr = lfs.attributes(filename)
|
|
local filesize = fileattr and fileattr.size or 0
|
|
local filetime = fileattr and fileattr.modification or 0
|
|
local f = openfile(filename,true) -- 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 ttcversion = readulong(f)
|
|
local nofsubfonts = readulong(f)
|
|
local offsets = readcardinaltable(f,nofsubfonts,ulong)
|
|
if subfont then -- a number of not
|
|
if subfont >= 1 and subfont <= nofsubfonts then
|
|
fontdata = readdata(f,offsets[subfont],specification)
|
|
else
|
|
report("no subfont %a in file %a",subfont,filename)
|
|
end
|
|
else
|
|
subfont = specification.subfont
|
|
if type(subfont) == "string" and subfont ~= "" then
|
|
specification.askedname = subfont
|
|
for i=1,nofsubfonts do
|
|
fontdata = readdata(f,offsets[i],specification)
|
|
if fontdata then
|
|
fontdata.subfontindex = i
|
|
report("subfont named %a has index %a",subfont,i)
|
|
break
|
|
end
|
|
end
|
|
if not fontdata then
|
|
report("no subfont named %a",subfont)
|
|
end
|
|
else
|
|
local subfonts = { }
|
|
fontdata = {
|
|
filename = filename,
|
|
filesize = filesize,
|
|
filetime = filetime,
|
|
version = version,
|
|
subfonts = subfonts,
|
|
ttcversion = ttcversion,
|
|
nofsubfonts = nofsubfonts,
|
|
}
|
|
for i=1,nofsubfonts do
|
|
subfonts[i] = readdata(f,offsets[i],specification)
|
|
end
|
|
end
|
|
end
|
|
else
|
|
report("unknown version %a in file %a",version,filename)
|
|
end
|
|
closefile(f)
|
|
return fontdata or { }
|
|
end
|
|
end
|
|
|
|
local function loadfont(specification,n,instance)
|
|
if type(specification) == "string" then
|
|
specification = {
|
|
filename = specification,
|
|
info = true, -- always true (for now)
|
|
details = true,
|
|
glyphs = true,
|
|
shapes = true,
|
|
kerns = true,
|
|
variable = true,
|
|
globalkerns = true,
|
|
lookups = true,
|
|
-- true or number:
|
|
subfont = n or true,
|
|
tounicode = false,
|
|
instance = instance
|
|
}
|
|
end
|
|
-- if shapes only then
|
|
if specification.shapes or specification.lookups or specification.kerns then
|
|
specification.glyphs = true
|
|
end
|
|
if specification.glyphs then
|
|
specification.details = true
|
|
end
|
|
if specification.details then
|
|
specification.info = true -- not really used any more
|
|
end
|
|
if specification.platformnames then
|
|
specification.platformnames = true -- not really used any more
|
|
end
|
|
if specification.instance or instance then
|
|
specification.variable = true
|
|
specification.instance = specification.instance or instance
|
|
end
|
|
local function message(str)
|
|
report("fatal error in file %a: %s\n%s",specification.filename,str,debug and debug.traceback())
|
|
end
|
|
local ok, result = xpcall(loadfontdata,message,specification)
|
|
if ok then
|
|
return result
|
|
end
|
|
-- return loadfontdata(specification)
|
|
end
|
|
|
|
-- we need even less, but we can have a 'detail' variant
|
|
|
|
function readers.loadshapes(filename,n,instance,streams)
|
|
local fontdata = loadfont {
|
|
filename = filename,
|
|
shapes = true,
|
|
streams = streams,
|
|
variable = true,
|
|
subfont = n,
|
|
instance = instance,
|
|
}
|
|
if fontdata then
|
|
-- easier on luajit but still we can hit the 64 K stack constants issue
|
|
for k, v in next, fontdata.glyphs do
|
|
v.class = nil
|
|
v.index = nil
|
|
v.math = nil
|
|
-- v.name = nil
|
|
end
|
|
local names = fontdata.names
|
|
if names then
|
|
for k, v in next, names do
|
|
names[k] = fullstrip(v.content)
|
|
end
|
|
end
|
|
end
|
|
return fontdata and {
|
|
-- version = 0.123 -- todo
|
|
filename = filename,
|
|
format = fontdata.format,
|
|
glyphs = fontdata.glyphs,
|
|
units = fontdata.fontheader.units,
|
|
cffinfo = fontdata.cffinfo,
|
|
fontheader = fontdata.fontheader,
|
|
horizontalheader = fontdata.horizontalheader,
|
|
verticalheader = fontdata.verticalheader,
|
|
maximumprofile = fontdata.maximumprofile,
|
|
names = fontdata.names,
|
|
postscript = fontdata.postscript,
|
|
} or {
|
|
filename = filename,
|
|
format = "unknown",
|
|
glyphs = { },
|
|
units = 0,
|
|
}
|
|
end
|
|
|
|
function readers.loadfont(filename,n,instance)
|
|
local fontdata = loadfont {
|
|
filename = filename,
|
|
glyphs = true,
|
|
shapes = false,
|
|
lookups = true,
|
|
variable = true,
|
|
-- kerns = true,
|
|
-- globalkerns = true, -- only for testing, e.g. cambria has different gpos and kern
|
|
subfont = n,
|
|
instance = instance,
|
|
}
|
|
if fontdata then
|
|
return {
|
|
tableversion = tableversion,
|
|
creator = "context mkiv",
|
|
size = fontdata.filesize,
|
|
time = fontdata.filetime,
|
|
glyphs = fontdata.glyphs,
|
|
descriptions = fontdata.descriptions,
|
|
format = fontdata.format,
|
|
goodies = { },
|
|
metadata = getinfo(fontdata,n,false,false,true,true), -- no platformnames here !
|
|
properties = {
|
|
hasitalics = fontdata.hasitalics or false,
|
|
maxcolorclass = fontdata.maxcolorclass,
|
|
hascolor = fontdata.hascolor or false,
|
|
instance = fontdata.instance,
|
|
factors = fontdata.factors,
|
|
},
|
|
resources = {
|
|
-- filename = fontdata.filename,
|
|
filename = filename,
|
|
private = privateoffset,
|
|
duplicates = fontdata.duplicates or { },
|
|
features = fontdata.features or { }, -- we need to add these in the loader
|
|
sublookups = fontdata.sublookups or { }, -- we need to add these in the loader
|
|
marks = fontdata.marks or { }, -- we need to add these in the loader
|
|
markclasses = fontdata.markclasses or { }, -- we need to add these in the loader
|
|
marksets = fontdata.marksets or { }, -- we need to add these in the loader
|
|
sequences = fontdata.sequences or { }, -- we need to add these in the loader
|
|
variants = fontdata.variants, -- variant -> unicode -> glyph
|
|
version = getname(fontdata,"version"),
|
|
cidinfo = fontdata.cidinfo,
|
|
mathconstants = fontdata.mathconstants,
|
|
colorpalettes = fontdata.colorpalettes,
|
|
svgshapes = fontdata.svgshapes,
|
|
pngshapes = fontdata.pngshapes,
|
|
variabledata = fontdata.variabledata,
|
|
foundtables = fontdata.foundtables,
|
|
},
|
|
}
|
|
end
|
|
end
|
|
|
|
function readers.getinfo(filename,specification) -- string, nil|number|table
|
|
-- platformnames is optional and not used by context (a too unpredictable mess
|
|
-- that only add to the confusion) .. so it's only for checking things
|
|
local subfont = nil
|
|
local platformnames = false
|
|
local rawfamilynames = false
|
|
local instancenames = true
|
|
local tableoffsets = false
|
|
if type(specification) == "table" then
|
|
subfont = tonumber(specification.subfont)
|
|
platformnames = specification.platformnames
|
|
rawfamilynames = specification.rawfamilynames
|
|
tableoffsets = specification.tableoffsets
|
|
else
|
|
subfont = tonumber(specification)
|
|
end
|
|
local fontdata = loadfont {
|
|
filename = filename,
|
|
details = true,
|
|
platformnames = platformnames,
|
|
instancenames = true,
|
|
tableoffsets = tableoffsets,
|
|
-- rawfamilynames = rawfamilynames,
|
|
}
|
|
if fontdata then
|
|
local subfonts = fontdata.subfonts
|
|
if not subfonts then
|
|
return getinfo(fontdata,nil,platformnames,rawfamilynames,false,instancenames)
|
|
elseif not subfont then
|
|
local info = { }
|
|
for i=1,#subfonts do
|
|
info[i] = getinfo(fontdata,i,platformnames,rawfamilynames,false,instancenames)
|
|
end
|
|
return info
|
|
elseif subfont >= 1 and subfont <= #subfonts then
|
|
return getinfo(fontdata,subfont,platformnames,rawfamilynames,false,instancenames)
|
|
else
|
|
return {
|
|
filename = filename,
|
|
comment = "there is no subfont " .. subfont .. " in this file"
|
|
}
|
|
end
|
|
else
|
|
return {
|
|
filename = filename,
|
|
comment = "the file cannot be opened for reading",
|
|
}
|
|
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
|
|
|
|
function readers.compact(fontdata)
|
|
report("the %a helper is not yet implemented","compact")
|
|
end
|
|
|
|
-- plug in
|
|
|
|
local extenders = { }
|
|
|
|
function readers.registerextender(extender)
|
|
extenders[#extenders+1] = extender
|
|
end
|
|
|
|
function readers.extend(fontdata)
|
|
for i=1,#extenders do
|
|
local extender = extenders[i]
|
|
local name = extender.name or "unknown"
|
|
local action = extender.action
|
|
if action then
|
|
action(fontdata)
|
|
end
|
|
end
|
|
end
|
|
|