From bafe29de59fdc2a37922e198aebc9b04f518f152 Mon Sep 17 00:00:00 2001 From: Context Git Mirror Bot Date: Sat, 13 Jun 2015 10:15:03 +0200 Subject: 2015-06-13 09:54:00 --- metapost/context/base/mp-mlib.mpiv | 13 + scripts/context/lua/mtx-fonts.lua | 7 + scripts/context/lua/mtxrun.lua | 15 +- scripts/context/stubs/mswin/mtxrun.lua | 15 +- scripts/context/stubs/unix/mtxrun | 15 +- scripts/context/stubs/win64/mtxrun.lua | 15 +- tex/context/base/cont-new.mkiv | 2 +- tex/context/base/context-version.pdf | Bin 4205 -> 4202 bytes tex/context/base/context.mkiv | 2 +- tex/context/base/font-afm.lua | 18 +- tex/context/base/font-cff.lua | 1441 ++++++++++++++++ tex/context/base/font-con.lua | 17 +- tex/context/base/font-lib.mkvi | 12 + tex/context/base/font-mis.lua | 2 +- tex/context/base/font-mps.lua | 379 ++++ tex/context/base/font-off.lua | 228 +++ tex/context/base/font-otf.lua | 25 +- tex/context/base/font-otr.lua | 1816 ++++++++++++++++++++ tex/context/base/font-pat.lua | 17 - tex/context/base/font-syn.lua | 276 ++- tex/context/base/font-tmp.lua | 120 ++ tex/context/base/font-ttf.lua | 475 +++++ tex/context/base/lxml-lpt.lua | 4 +- tex/context/base/meta-imp-outlines.mkiv | 150 ++ tex/context/base/mult-def.mkiv | 3 + tex/context/base/publ-ini.lua | 7 +- tex/context/base/publ-ini.mkiv | 4 +- tex/context/base/status-files.pdf | Bin 24422 -> 24425 bytes tex/context/base/status-lua.pdf | Bin 251556 -> 252045 bytes tex/context/base/util-tab.lua | 4 +- tex/generic/context/luatex/luatex-fonts-merged.lua | 41 +- 31 files changed, 4856 insertions(+), 267 deletions(-) create mode 100644 tex/context/base/font-cff.lua create mode 100644 tex/context/base/font-mps.lua create mode 100644 tex/context/base/font-off.lua create mode 100644 tex/context/base/font-otr.lua create mode 100644 tex/context/base/font-tmp.lua create mode 100644 tex/context/base/font-ttf.lua create mode 100644 tex/context/base/meta-imp-outlines.mkiv diff --git a/metapost/context/base/mp-mlib.mpiv b/metapost/context/base/mp-mlib.mpiv index 6d4894fb6..94377e52d 100644 --- a/metapost/context/base/mp-mlib.mpiv +++ b/metapost/context/base/mp-mlib.mpiv @@ -768,6 +768,8 @@ vardef mfun_do_outline_text_flush (expr kind, n, x, y) (text t) = mfun_do_outline_text_b (n, x, y) (t) elseif kind = "r" : mfun_do_outline_text_r (n, x, y) (t) + elseif kind = "p" : + mfun_do_outline_text_p (n, x, y) (t) else : mfun_do_outline_text_n (n, x, y) (t) fi ; @@ -793,6 +795,12 @@ vardef mfun_do_outline_text_d (expr n, x, y) (text t) = endfor ; enddef ; +vardef mfun_do_outline_text_p (expr n, x, y) (text t) = + for i=t : + draw i shifted(x,y) ; + endfor ; +enddef ; + vardef mfun_do_outline_text_b (expr n, x, y) (text t) = mfun_do_outline_n := 0 ; for i=t : @@ -857,6 +865,9 @@ vardef mfun_do_outline_text_set_n text r = def mfun_do_outline_options_r = r enddef ; enddef ; +vardef mfun_do_outline_text_set_p = +enddef ; + def mfun_do_outline_options_d = enddef ; def mfun_do_outline_options_f = enddef ; def mfun_do_outline_options_r = enddef ; @@ -882,6 +893,8 @@ vardef outlinetext@# (expr t) text rest = mfun_do_outline_text_set_b rest ; elseif kind = "r" : mfun_do_outline_text_set_r rest ; + elseif kind = "p" : + mfun_do_outline_text_set_p ; else : mfun_do_outline_text_set_n rest ; fi ; diff --git a/scripts/context/lua/mtx-fonts.lua b/scripts/context/lua/mtx-fonts.lua index 694e6a649..808f20358 100644 --- a/scripts/context/lua/mtx-fonts.lua +++ b/scripts/context/lua/mtx-fonts.lua @@ -84,6 +84,13 @@ local report = application.report if not fontloader then fontloader = fontforge end +dofile(resolvers.findfile("font-otr.lua","tex")) +dofile(resolvers.findfile("font-cff.lua","tex")) +dofile(resolvers.findfile("font-ttf.lua","tex")) +dofile(resolvers.findfile("font-tmp.lua","tex")) +------(resolvers.findfile("font-dsp.lua","tex")) +------(resolvers.findfile("font-off.lua","tex")) + dofile(resolvers.findfile("font-otp.lua","tex")) -- we need to unpack the font for analysis dofile(resolvers.findfile("font-syn.lua","tex")) dofile(resolvers.findfile("font-trt.lua","tex")) diff --git a/scripts/context/lua/mtxrun.lua b/scripts/context/lua/mtxrun.lua index f3a794e4d..111691f61 100644 --- a/scripts/context/lua/mtxrun.lua +++ b/scripts/context/lua/mtxrun.lua @@ -5904,7 +5904,7 @@ do -- create closure to overcome 200 locals limit package.loaded["util-tab"] = package.loaded["util-tab"] or true --- original size: 27822, stripped down to: 18037 +-- original size: 27840, stripped down to: 18055 if not modules then modules={} end modules ['util-tab']={ version=1.001, @@ -6340,10 +6340,10 @@ local f_table_direct=formatters["{"] local f_table_entry=formatters["[%q]={"] local f_table_finish=formatters["}"] local spaces=utilities.strings.newrepeater(" ") -local serialize=table.serialize +local original_serialize=table.serialize local function serialize(root,name,specification) if type(specification)=="table" then - return serialize(root,name,specification) + return original_serialize(root,name,specification) end local t local n=1 @@ -10772,7 +10772,7 @@ do -- create closure to overcome 200 locals limit package.loaded["lxml-lpt"] = package.loaded["lxml-lpt"] or true --- original size: 48229, stripped down to: 30684 +-- original size: 48172, stripped down to: 30632 if not modules then modules={} end modules ['lxml-lpt']={ version=1.001, @@ -11362,13 +11362,12 @@ local function tagstostring(list) end xml.nodesettostring=nodesettostring local lpath -local lshowoptions={ functions=false } local function lshow(parsed) if type(parsed)=="string" then parsed=lpath(parsed) end report_lpath("%s://%s => %s",parsed.protocol or xml.defaultprotocol,parsed.pattern, - table.serialize(parsed,false,lshowoptions)) + table.serialize(parsed,false)) end xml.lshow=lshow local function add_comment(p,str) @@ -18109,8 +18108,8 @@ end -- of closure -- used libraries : l-lua.lua l-package.lua l-lpeg.lua l-function.lua l-string.lua l-table.lua l-io.lua l-number.lua l-set.lua l-os.lua l-file.lua l-gzip.lua l-md5.lua l-url.lua l-dir.lua l-boolean.lua l-unicode.lua l-math.lua util-str.lua util-tab.lua util-fil.lua util-sto.lua util-prs.lua util-fmt.lua trac-set.lua trac-log.lua trac-inf.lua trac-pro.lua util-lua.lua util-deb.lua util-mrg.lua util-tpl.lua util-env.lua luat-env.lua lxml-tab.lua lxml-lpt.lua lxml-mis.lua lxml-aux.lua lxml-xml.lua trac-xml.lua data-ini.lua data-exp.lua data-env.lua data-tmp.lua data-met.lua data-res.lua data-pre.lua data-inp.lua data-out.lua data-fil.lua data-con.lua data-use.lua data-zip.lua data-tre.lua data-sch.lua data-lua.lua data-aux.lua data-tmf.lua data-lst.lua util-lib.lua luat-sta.lua luat-fmt.lua -- skipped libraries : - --- original bytes : 752587 --- stripped bytes : 271654 +-- original bytes : 752548 +-- stripped bytes : 271649 -- end library merge diff --git a/scripts/context/stubs/mswin/mtxrun.lua b/scripts/context/stubs/mswin/mtxrun.lua index f3a794e4d..111691f61 100644 --- a/scripts/context/stubs/mswin/mtxrun.lua +++ b/scripts/context/stubs/mswin/mtxrun.lua @@ -5904,7 +5904,7 @@ do -- create closure to overcome 200 locals limit package.loaded["util-tab"] = package.loaded["util-tab"] or true --- original size: 27822, stripped down to: 18037 +-- original size: 27840, stripped down to: 18055 if not modules then modules={} end modules ['util-tab']={ version=1.001, @@ -6340,10 +6340,10 @@ local f_table_direct=formatters["{"] local f_table_entry=formatters["[%q]={"] local f_table_finish=formatters["}"] local spaces=utilities.strings.newrepeater(" ") -local serialize=table.serialize +local original_serialize=table.serialize local function serialize(root,name,specification) if type(specification)=="table" then - return serialize(root,name,specification) + return original_serialize(root,name,specification) end local t local n=1 @@ -10772,7 +10772,7 @@ do -- create closure to overcome 200 locals limit package.loaded["lxml-lpt"] = package.loaded["lxml-lpt"] or true --- original size: 48229, stripped down to: 30684 +-- original size: 48172, stripped down to: 30632 if not modules then modules={} end modules ['lxml-lpt']={ version=1.001, @@ -11362,13 +11362,12 @@ local function tagstostring(list) end xml.nodesettostring=nodesettostring local lpath -local lshowoptions={ functions=false } local function lshow(parsed) if type(parsed)=="string" then parsed=lpath(parsed) end report_lpath("%s://%s => %s",parsed.protocol or xml.defaultprotocol,parsed.pattern, - table.serialize(parsed,false,lshowoptions)) + table.serialize(parsed,false)) end xml.lshow=lshow local function add_comment(p,str) @@ -18109,8 +18108,8 @@ end -- of closure -- used libraries : l-lua.lua l-package.lua l-lpeg.lua l-function.lua l-string.lua l-table.lua l-io.lua l-number.lua l-set.lua l-os.lua l-file.lua l-gzip.lua l-md5.lua l-url.lua l-dir.lua l-boolean.lua l-unicode.lua l-math.lua util-str.lua util-tab.lua util-fil.lua util-sto.lua util-prs.lua util-fmt.lua trac-set.lua trac-log.lua trac-inf.lua trac-pro.lua util-lua.lua util-deb.lua util-mrg.lua util-tpl.lua util-env.lua luat-env.lua lxml-tab.lua lxml-lpt.lua lxml-mis.lua lxml-aux.lua lxml-xml.lua trac-xml.lua data-ini.lua data-exp.lua data-env.lua data-tmp.lua data-met.lua data-res.lua data-pre.lua data-inp.lua data-out.lua data-fil.lua data-con.lua data-use.lua data-zip.lua data-tre.lua data-sch.lua data-lua.lua data-aux.lua data-tmf.lua data-lst.lua util-lib.lua luat-sta.lua luat-fmt.lua -- skipped libraries : - --- original bytes : 752587 --- stripped bytes : 271654 +-- original bytes : 752548 +-- stripped bytes : 271649 -- end library merge diff --git a/scripts/context/stubs/unix/mtxrun b/scripts/context/stubs/unix/mtxrun index f3a794e4d..111691f61 100644 --- a/scripts/context/stubs/unix/mtxrun +++ b/scripts/context/stubs/unix/mtxrun @@ -5904,7 +5904,7 @@ do -- create closure to overcome 200 locals limit package.loaded["util-tab"] = package.loaded["util-tab"] or true --- original size: 27822, stripped down to: 18037 +-- original size: 27840, stripped down to: 18055 if not modules then modules={} end modules ['util-tab']={ version=1.001, @@ -6340,10 +6340,10 @@ local f_table_direct=formatters["{"] local f_table_entry=formatters["[%q]={"] local f_table_finish=formatters["}"] local spaces=utilities.strings.newrepeater(" ") -local serialize=table.serialize +local original_serialize=table.serialize local function serialize(root,name,specification) if type(specification)=="table" then - return serialize(root,name,specification) + return original_serialize(root,name,specification) end local t local n=1 @@ -10772,7 +10772,7 @@ do -- create closure to overcome 200 locals limit package.loaded["lxml-lpt"] = package.loaded["lxml-lpt"] or true --- original size: 48229, stripped down to: 30684 +-- original size: 48172, stripped down to: 30632 if not modules then modules={} end modules ['lxml-lpt']={ version=1.001, @@ -11362,13 +11362,12 @@ local function tagstostring(list) end xml.nodesettostring=nodesettostring local lpath -local lshowoptions={ functions=false } local function lshow(parsed) if type(parsed)=="string" then parsed=lpath(parsed) end report_lpath("%s://%s => %s",parsed.protocol or xml.defaultprotocol,parsed.pattern, - table.serialize(parsed,false,lshowoptions)) + table.serialize(parsed,false)) end xml.lshow=lshow local function add_comment(p,str) @@ -18109,8 +18108,8 @@ end -- of closure -- used libraries : l-lua.lua l-package.lua l-lpeg.lua l-function.lua l-string.lua l-table.lua l-io.lua l-number.lua l-set.lua l-os.lua l-file.lua l-gzip.lua l-md5.lua l-url.lua l-dir.lua l-boolean.lua l-unicode.lua l-math.lua util-str.lua util-tab.lua util-fil.lua util-sto.lua util-prs.lua util-fmt.lua trac-set.lua trac-log.lua trac-inf.lua trac-pro.lua util-lua.lua util-deb.lua util-mrg.lua util-tpl.lua util-env.lua luat-env.lua lxml-tab.lua lxml-lpt.lua lxml-mis.lua lxml-aux.lua lxml-xml.lua trac-xml.lua data-ini.lua data-exp.lua data-env.lua data-tmp.lua data-met.lua data-res.lua data-pre.lua data-inp.lua data-out.lua data-fil.lua data-con.lua data-use.lua data-zip.lua data-tre.lua data-sch.lua data-lua.lua data-aux.lua data-tmf.lua data-lst.lua util-lib.lua luat-sta.lua luat-fmt.lua -- skipped libraries : - --- original bytes : 752587 --- stripped bytes : 271654 +-- original bytes : 752548 +-- stripped bytes : 271649 -- end library merge diff --git a/scripts/context/stubs/win64/mtxrun.lua b/scripts/context/stubs/win64/mtxrun.lua index f3a794e4d..111691f61 100644 --- a/scripts/context/stubs/win64/mtxrun.lua +++ b/scripts/context/stubs/win64/mtxrun.lua @@ -5904,7 +5904,7 @@ do -- create closure to overcome 200 locals limit package.loaded["util-tab"] = package.loaded["util-tab"] or true --- original size: 27822, stripped down to: 18037 +-- original size: 27840, stripped down to: 18055 if not modules then modules={} end modules ['util-tab']={ version=1.001, @@ -6340,10 +6340,10 @@ local f_table_direct=formatters["{"] local f_table_entry=formatters["[%q]={"] local f_table_finish=formatters["}"] local spaces=utilities.strings.newrepeater(" ") -local serialize=table.serialize +local original_serialize=table.serialize local function serialize(root,name,specification) if type(specification)=="table" then - return serialize(root,name,specification) + return original_serialize(root,name,specification) end local t local n=1 @@ -10772,7 +10772,7 @@ do -- create closure to overcome 200 locals limit package.loaded["lxml-lpt"] = package.loaded["lxml-lpt"] or true --- original size: 48229, stripped down to: 30684 +-- original size: 48172, stripped down to: 30632 if not modules then modules={} end modules ['lxml-lpt']={ version=1.001, @@ -11362,13 +11362,12 @@ local function tagstostring(list) end xml.nodesettostring=nodesettostring local lpath -local lshowoptions={ functions=false } local function lshow(parsed) if type(parsed)=="string" then parsed=lpath(parsed) end report_lpath("%s://%s => %s",parsed.protocol or xml.defaultprotocol,parsed.pattern, - table.serialize(parsed,false,lshowoptions)) + table.serialize(parsed,false)) end xml.lshow=lshow local function add_comment(p,str) @@ -18109,8 +18108,8 @@ end -- of closure -- used libraries : l-lua.lua l-package.lua l-lpeg.lua l-function.lua l-string.lua l-table.lua l-io.lua l-number.lua l-set.lua l-os.lua l-file.lua l-gzip.lua l-md5.lua l-url.lua l-dir.lua l-boolean.lua l-unicode.lua l-math.lua util-str.lua util-tab.lua util-fil.lua util-sto.lua util-prs.lua util-fmt.lua trac-set.lua trac-log.lua trac-inf.lua trac-pro.lua util-lua.lua util-deb.lua util-mrg.lua util-tpl.lua util-env.lua luat-env.lua lxml-tab.lua lxml-lpt.lua lxml-mis.lua lxml-aux.lua lxml-xml.lua trac-xml.lua data-ini.lua data-exp.lua data-env.lua data-tmp.lua data-met.lua data-res.lua data-pre.lua data-inp.lua data-out.lua data-fil.lua data-con.lua data-use.lua data-zip.lua data-tre.lua data-sch.lua data-lua.lua data-aux.lua data-tmf.lua data-lst.lua util-lib.lua luat-sta.lua luat-fmt.lua -- skipped libraries : - --- original bytes : 752587 --- stripped bytes : 271654 +-- original bytes : 752548 +-- stripped bytes : 271649 -- end library merge diff --git a/tex/context/base/cont-new.mkiv b/tex/context/base/cont-new.mkiv index 45216352b..399111c91 100644 --- a/tex/context/base/cont-new.mkiv +++ b/tex/context/base/cont-new.mkiv @@ -11,7 +11,7 @@ %C therefore copyrighted by \PRAGMA. See mreadme.pdf for %C details. -\newcontextversion{2015.06.12 10:06} +\newcontextversion{2015.06.13 09:52} %D This file is loaded at runtime, thereby providing an excellent place for %D hacks, patches, extensions and new features. diff --git a/tex/context/base/context-version.pdf b/tex/context/base/context-version.pdf index 97d3058a2..50515ecec 100644 Binary files a/tex/context/base/context-version.pdf and b/tex/context/base/context-version.pdf differ diff --git a/tex/context/base/context.mkiv b/tex/context/base/context.mkiv index df01cab5a..462bbb538 100644 --- a/tex/context/base/context.mkiv +++ b/tex/context/base/context.mkiv @@ -39,7 +39,7 @@ %D up and the dependencies are more consistent. \edef\contextformat {\jobname} -\edef\contextversion{2015.06.12 10:06} +\edef\contextversion{2015.06.13 09:52} \edef\contextkind {beta} %D For those who want to use this: diff --git a/tex/context/base/font-afm.lua b/tex/context/base/font-afm.lua index a96c6686e..329639b85 100644 --- a/tex/context/base/font-afm.lua +++ b/tex/context/base/font-afm.lua @@ -152,14 +152,14 @@ end local keys = { } -function keys.FontName (data,line) data.metadata.fontname = strip (line) -- get rid of spaces - data.metadata.fullname = strip (line) end -function keys.ItalicAngle (data,line) data.metadata.italicangle = tonumber (line) end -function keys.IsFixedPitch(data,line) data.metadata.isfixedpitch = toboolean(line,true) end -function keys.CharWidth (data,line) data.metadata.charwidth = tonumber (line) end -function keys.XHeight (data,line) data.metadata.xheight = tonumber (line) end -function keys.Descender (data,line) data.metadata.descender = tonumber (line) end -function keys.Ascender (data,line) data.metadata.ascender = tonumber (line) end +function keys.FontName (data,line) data.metadata.fontname = strip (line) -- get rid of spaces + data.metadata.fullname = strip (line) end +function keys.ItalicAngle (data,line) data.metadata.italicangle = tonumber (line) end +function keys.IsFixedPitch(data,line) data.metadata.monospaced = toboolean(line,true) end +function keys.CharWidth (data,line) data.metadata.charwidth = tonumber (line) end +function keys.XHeight (data,line) data.metadata.xheight = tonumber (line) end +function keys.Descender (data,line) data.metadata.descender = tonumber (line) end +function keys.Ascender (data,line) data.metadata.ascender = tonumber (line) end function keys.Comment (data,line) -- Comment DesignSize 12 (pts) -- Comment TFM designsize: 12 (in points) @@ -640,7 +640,7 @@ local function copytotfm(data) local spacer = "space" local spaceunits = 500 -- - local monospaced = metadata.isfixedpitch + local monospaced = metadata.monospaced local charwidth = metadata.charwidth local italicangle = metadata.italicangle local charxheight = metadata.xheight and metadata.xheight > 0 and metadata.xheight diff --git a/tex/context/base/font-cff.lua b/tex/context/base/font-cff.lua new file mode 100644 index 000000000..271de834f --- /dev/null +++ b/tex/context/base/font-cff.lua @@ -0,0 +1,1441 @@ +if not modules then modules = { } end modules ['font-cff'] = { + version = 1.001, + comment = "companion to font-ini.mkiv", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} + +-- todo: option.outlines +-- todo: option.boundingbox +-- per charstring (less memory) + +-- This is a heavy one as it is a rather packed format. We don't need al the information +-- now but we might need it later (who know what magic we can do with metapost). So at +-- some point this might become a module. We just follow Adobe Technical Notes #5176 and +-- #5177. In case of doubt I looked in the fontforge code that comes with LuaTeX. + +-- For now we save the segments in a list of segments with the operator last in an entry +-- because that reflects the original. But it might make more sense to use a single array +-- per segment. For pdf a simple concat works ok, but for other purposes a operator first +-- flush is nicer. + +local next, type = next, type +local byte = string.byte +local concat, remove = table.concat, table.remove +local floor, abs, round, ceil = math.floor, math.abs, math.round, math.ceil +local P, C, R, S, C, Cs, Ct = lpeg.P, lpeg.C, lpeg.R, lpeg.S, lpeg.C, lpeg.Cs, lpeg.Ct +local lpegmatch = lpeg.match + +local 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 setmetatableindex = table.setmetatableindex + +local trace_charstrings = false trackers.register("fonts.cff.charstrings",function(v) trace_charstrings = v end) +local report = logs.reporter("otf reader","cff") + +local parsetopdictionary +local parsecharstrings +local parseprivates + +local defaultstrings = { [0] = -- hijacked from ff + ".notdef", "space", "exclam", "quotedbl", "numbersign", "dollar", "percent", + "ampersand", "quoteright", "parenleft", "parenright", "asterisk", "plus", + "comma", "hyphen", "period", "slash", "zero", "one", "two", "three", "four", + "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less", + "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", "H", + "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", + "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", + "underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", + "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", + "z", "braceleft", "bar", "braceright", "asciitilde", "exclamdown", "cent", + "sterling", "fraction", "yen", "florin", "section", "currency", + "quotesingle", "quotedblleft", "guillemotleft", "guilsinglleft", + "guilsinglright", "fi", "fl", "endash", "dagger", "daggerdbl", + "periodcentered", "paragraph", "bullet", "quotesinglbase", "quotedblbase", + "quotedblright", "guillemotright", "ellipsis", "perthousand", "questiondown", + "grave", "acute", "circumflex", "tilde", "macron", "breve", "dotaccent", + "dieresis", "ring", "cedilla", "hungarumlaut", "ogonek", "caron", "emdash", + "AE", "ordfeminine", "Lslash", "Oslash", "OE", "ordmasculine", "ae", + "dotlessi", "lslash", "oslash", "oe", "germandbls", "onesuperior", + "logicalnot", "mu", "trademark", "Eth", "onehalf", "plusminus", "Thorn", + "onequarter", "divide", "brokenbar", "degree", "thorn", "threequarters", + "twosuperior", "registered", "minus", "eth", "multiply", "threesuperior", + "copyright", "Aacute", "Acircumflex", "Adieresis", "Agrave", "Aring", + "Atilde", "Ccedilla", "Eacute", "Ecircumflex", "Edieresis", "Egrave", + "Iacute", "Icircumflex", "Idieresis", "Igrave", "Ntilde", "Oacute", + "Ocircumflex", "Odieresis", "Ograve", "Otilde", "Scaron", "Uacute", + "Ucircumflex", "Udieresis", "Ugrave", "Yacute", "Ydieresis", "Zcaron", + "aacute", "acircumflex", "adieresis", "agrave", "aring", "atilde", + "ccedilla", "eacute", "ecircumflex", "edieresis", "egrave", "iacute", + "icircumflex", "idieresis", "igrave", "ntilde", "oacute", "ocircumflex", + "odieresis", "ograve", "otilde", "scaron", "uacute", "ucircumflex", + "udieresis", "ugrave", "yacute", "ydieresis", "zcaron", "exclamsmall", + "Hungarumlautsmall", "dollaroldstyle", "dollarsuperior", "ampersandsmall", + "Acutesmall", "parenleftsuperior", "parenrightsuperior", "twodotenleader", + "onedotenleader", "zerooldstyle", "oneoldstyle", "twooldstyle", + "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle", + "sevenoldstyle", "eightoldstyle", "nineoldstyle", "commasuperior", + "threequartersemdash", "periodsuperior", "questionsmall", "asuperior", + "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior", + "lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior", + "tsuperior", "ff", "ffi", "ffl", "parenleftinferior", "parenrightinferior", + "Circumflexsmall", "hyphensuperior", "Gravesmall", "Asmall", "Bsmall", + "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall", "Ismall", + "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall", + "Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall", + "Xsmall", "Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", + "Tildesmall", "exclamdownsmall", "centoldstyle", "Lslashsmall", + "Scaronsmall", "Zcaronsmall", "Dieresissmall", "Brevesmall", "Caronsmall", + "Dotaccentsmall", "Macronsmall", "figuredash", "hypheninferior", + "Ogoneksmall", "Ringsmall", "Cedillasmall", "questiondownsmall", "oneeighth", + "threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds", + "zerosuperior", "foursuperior", "fivesuperior", "sixsuperior", + "sevensuperior", "eightsuperior", "ninesuperior", "zeroinferior", + "oneinferior", "twoinferior", "threeinferior", "fourinferior", + "fiveinferior", "sixinferior", "seveninferior", "eightinferior", + "nineinferior", "centinferior", "dollarinferior", "periodinferior", + "commainferior", "Agravesmall", "Aacutesmall", "Acircumflexsmall", + "Atildesmall", "Adieresissmall", "Aringsmall", "AEsmall", "Ccedillasmall", + "Egravesmall", "Eacutesmall", "Ecircumflexsmall", "Edieresissmall", + "Igravesmall", "Iacutesmall", "Icircumflexsmall", "Idieresissmall", + "Ethsmall", "Ntildesmall", "Ogravesmall", "Oacutesmall", "Ocircumflexsmall", + "Otildesmall", "Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall", + "Uacutesmall", "Ucircumflexsmall", "Udieresissmall", "Yacutesmall", + "Thornsmall", "Ydieresissmall", "001.000", "001.001", "001.002", "001.003", + "Black", "Bold", "Book", "Light", "Medium", "Regular", "Roman", "Semibold", +} + +local cffreaders = { + readbyte, + readushort, + readuint, + readulong, +} + +-- The header contains information about its own size. + +local function readheader(f) + local header = { + offset = f:seek("cur"), + major = readbyte(f), + minor = readbyte(f), + size = readbyte(f), -- headersize + osize = readbyte(f), -- for offsets to start + } + f:seek("set",header.offset+header.size) + return header +end + +-- The indexes all look the same, so we share a loader. We could pass a handler +-- and run over the array but why bother, we only have a few uses. + +local function readlengths(f) + local count = readushort(f) + if count == 0 then + return { } + end + local osize = readbyte(f) + local read = cffreaders[osize] + if not read then + report("bad offset size: %i",osize) + return { } + end + local lengths = { } + local previous = read(f) + for i=1,count do + local offset = read(f) + lengths[i] = offset - previous + previous = offset + end + return lengths +end + +-- There can be subfonts so names is an array. However, in our case it's always +-- one font. The same is true for the top dictionaries. Watch how we only load +-- the dictionary string as for interpretation we need to have the strings loaded +-- as well. + +local function readfontnames(f) + local names = readlengths(f) + for i=1,#names do + names[i] = readstring(f,names[i]) + end + return names +end + +local function readtopdictionaries(f) + local dictionaries = readlengths(f) + for i=1,#dictionaries do + dictionaries[i] = readstring(f,dictionaries[i]) + end + return dictionaries +end + +-- Strings are added to a list of standard strings so we start the font specific +-- one with an offset. Strings are shared so we have one table. + +local function readstrings(f) + local lengths = readlengths(f) + local strings = setmetatableindex({ }, defaultstrings) + local index = #defaultstrings + for i=1,#lengths do + index = index + 1 + strings[index] = readstring(f,lengths[i]) + end + return strings +end + +-- Parsing the dictionaries is delayed till we have the strings loaded. The parser +-- is stack based so the operands come before the operator (like in postscript). + +-- local function delta(t) +-- local n = #t +-- if n > 1 then +-- local p = t[1] +-- for i=2,n do +-- local c = t[i] +-- t[i] = c + p +-- p = c +-- end +-- end +-- end + +do + + -- We use a closure so that we don't need to pass too much around. + + local stack = { } + local top = 0 + local result = { } + local strings = { } + + local p_single = + P("\00") / function() + result.version = strings[stack[top]] or "unset" + top = 0 + end + + P("\01") / function() + result.notice = strings[stack[top]] or "unset" + top = 0 + end + + P("\02") / function() + result.fullname = strings[stack[top]] or "unset" + top = 0 + end + + P("\03") / function() + result.familyname = strings[stack[top]] or "unset" + top = 0 + end + + P("\04") / function() + result.weight = strings[stack[top]] or "unset" + top = 0 + end + + P("\05") / function() + result.fontbbox = { unpack(stack,1,4) } + top = 0 + end + -- + P("\06") / function() end -- bluevalues + -- + P("\07") / function() end -- otherblues + -- + P("\08") / function() end -- familyblues + -- + P("\09") / function() end -- familyotherblues + -- + P("\10") / function() end -- strhw + -- + P("\11") / function() end -- stdvw + + P("\13") / function() + result.uniqueid = stack[top] + top = 0 + end + + P("\14") / function() + result.xuid = concat(stack,"",1,top) + top = 0 + end + + P("\15") / function() + result.charset = stack[top] + top = 0 + end + + P("\16") / function() + result.encoding = stack[top] + top = 0 + end + + P("\17") / function() + result.charstrings = stack[top] + top = 0 + end + + P("\18") / function() + result.private = { + size = stack[top-1], + offset = stack[top], + } + top = 0 + end + + P("\19") / function() + result.subroutines = stack[top] + end + + P("\20") / function() + result.defaultwidthx = stack[top] + end + + P("\21") / function() + result.nominalwidthx = stack[top] + end + -- + P("\22") / function() end -- reserved + -- + P("\23") / function() end -- reserved + -- + P("\24") / function() end -- reserved + -- + P("\25") / function() end -- reserved + -- + P("\26") / function() end -- reserved + -- + P("\27") / function() end -- reserved + + local p_double = P("\12") * ( + P("\00") / function() + result.copyright = stack[top] + top = 0 + end + + P("\01") / function() + result.monospaced = stack[top] == 1 and true or false -- isfixedpitch + top = 0 + end + + P("\02") / function() + result.italicangle = stack[top] + top = 0 + end + + P("\03") / function() + result.underlineposition = stack[top] + top = 0 + end + + P("\04") / function() + result.underlinethickness = stack[top] + top = 0 + end + + P("\05") / function() + result.painttype = stack[top] + top = 0 + end + + P("\06") / function() + result.charstringtype = stack[top] + top = 0 + end + + P("\07") / function() + result.fontmatrix = { unpack(stack,1,6) } + top = 0 + end + + P("\08") / function() + result.strokewidth = stack[top] + top = 0 + end + + P("\20") / function() + result.syntheticbase = stack[top] + top = 0 + end + + P("\21") / function() + result.postscript = strings[stack[top]] or "unset" + top = 0 + end + + P("\22") / function() + result.basefontname = strings[stack[top]] or "unset" + top = 0 + end + + P("\21") / function() + result.basefontblend = stack[top] + top = 0 + end + + P("\30") / function() + result.cid.registry = strings[stack[top-2]] or "unset" + result.cid.ordering = strings[stack[top-1]] or "unset" + result.cid.supplement = stack[top] + top = 0 + end + + P("\31") / function() + result.cid.fontversion = stack[top] + top = 0 + end + + P("\32") / function() + result.cid.fontrevision= stack[top] + top = 0 + end + + P("\33") / function() + result.cid.fonttype = stack[top] + top = 0 + end + + P("\34") / function() + result.cid.count = stack[top] + top = 0 + end + + P("\35") / function() + result.cid.uidbase = stack[top] + top = 0 + end + + P("\36") / function() + result.cid.fdarray = stack[top] + top = 0 + end + + P("\37") / function() + result.cid.fdselect = stack[top] + top = 0 + end + + P("\38") / function() + result.cid.fontname = strings[stack[top]] or "unset" + top = 0 + end + ) + + -- Some lpeg fun ... a first variant split the byte and made a new string but + -- the second variant is much faster. Not that it matters much as we don't see + -- such numbers often. + + local p_last = P("\x0F") / "0" + P("\x1F") / "1" + P("\x2F") / "2" + P("\x3F") / "3" + + P("\x4F") / "4" + P("\x5F") / "5" + P("\x6F") / "6" + P("\x7F") / "7" + + P("\x8F") / "8" + P("\x9F") / "9" + P("\xAF") / "" + P("\xBF") / "" + + P("\xCF") / "" + P("\xDF") / "" + P("\xEF") / "" + R("\xF0\xFF") / "" + + -- local remap = { [0] = + -- "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0.", "0E", "0E-", "0", "0-", "0", + -- "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "0.", "0E", "0E-", "0", "0-", "0", + -- "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "0.", "0E", "0E-", "0", "0-", "0", + -- "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "0.", "0E", "0E-", "0", "0-", "0", + -- "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "0.", "0E", "0E-", "0", "0-", "0", + -- "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "0.", "0E", "0E-", "0", "0-", "0", + -- "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "0.", "0E", "0E-", "0", "0-", "0", + -- "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "0.", "0E", "0E-", "0", "0-", "0", + -- "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "0.", "0E", "0E-", "0", "0-", "0", + -- "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "0.", "0E", "0E-", "0", "0-", "0", + -- ".0", ".1", ".2", ".3", ".4", ".5", ".6", ".7", ".8", ".9", "..", ".E", ".E-", ".", ".-", ".", + -- "E0", "E1", "E2", "E3", "E4", "E5", "E6", "E7", "E8", "E9", "E.", "EE", "EE-", "E", "E-", "E", + -- "E-0", "E-1", "E-2", "E-3", "E-4", "E-5", "E-6", "E-7", "E-8", "E-9", "E-.", "E-E", "E-E-", "E-", "E--", "E-", + -- "-0", "-1", "-2", "-3", "-4", "-5", "-6", "-7", "-8", "-9", "-.", "-E", "-E-", "-", "--", "-", + -- } + + -- local p_nibbles = Cs(((1-p_last)/byte/remap)^0+p_last) + + -- local p = P("\30") * p_nibbles / function(t) + -- print(tonumber(t)) + -- end + + local remap = { + ["\x00"] = "00", ["\x01"] = "01", ["\x02"] = "02", ["\x03"] = "03", ["\x04"] = "04", ["\x05"] = "05", ["\x06"] = "06", ["\x07"] = "07", ["\x08"] = "08", ["\x09"] = "09", ["\x0A"] = "0.", ["\x0B"] = "0E", ["\x0C"] = "0E-", ["\x0D"] = "0", ["\x0E"] = "0-", ["\x0F"] = "0", + ["\x10"] = "10", ["\x11"] = "11", ["\x12"] = "12", ["\x13"] = "13", ["\x14"] = "14", ["\x15"] = "15", ["\x16"] = "16", ["\x17"] = "17", ["\x18"] = "18", ["\x19"] = "19", ["\x1A"] = "0.", ["\x1B"] = "0E", ["\x1C"] = "0E-", ["\x1D"] = "0", ["\x1E"] = "0-", ["\x1F"] = "0", + ["\x20"] = "20", ["\x21"] = "21", ["\x22"] = "22", ["\x23"] = "23", ["\x24"] = "24", ["\x25"] = "25", ["\x26"] = "26", ["\x27"] = "27", ["\x28"] = "28", ["\x29"] = "29", ["\x2A"] = "0.", ["\x2B"] = "0E", ["\x2C"] = "0E-", ["\x2D"] = "0", ["\x2E"] = "0-", ["\x2F"] = "0", + ["\x30"] = "30", ["\x31"] = "31", ["\x32"] = "32", ["\x33"] = "33", ["\x34"] = "34", ["\x35"] = "35", ["\x36"] = "36", ["\x37"] = "37", ["\x38"] = "38", ["\x39"] = "39", ["\x3A"] = "0.", ["\x3B"] = "0E", ["\x3C"] = "0E-", ["\x3D"] = "0", ["\x3E"] = "0-", ["\x3F"] = "0", + ["\x40"] = "40", ["\x41"] = "41", ["\x42"] = "42", ["\x43"] = "43", ["\x44"] = "44", ["\x45"] = "45", ["\x46"] = "46", ["\x47"] = "47", ["\x48"] = "48", ["\x49"] = "49", ["\x4A"] = "0.", ["\x4B"] = "0E", ["\x4C"] = "0E-", ["\x4D"] = "0", ["\x4E"] = "0-", ["\x4F"] = "0", + ["\x50"] = "50", ["\x51"] = "51", ["\x52"] = "52", ["\x53"] = "53", ["\x54"] = "54", ["\x55"] = "55", ["\x56"] = "56", ["\x57"] = "57", ["\x58"] = "58", ["\x59"] = "59", ["\x5A"] = "0.", ["\x5B"] = "0E", ["\x5C"] = "0E-", ["\x5D"] = "0", ["\x5E"] = "0-", ["\x5F"] = "0", + ["\x60"] = "60", ["\x61"] = "61", ["\x62"] = "62", ["\x63"] = "63", ["\x64"] = "64", ["\x65"] = "65", ["\x66"] = "66", ["\x67"] = "67", ["\x68"] = "68", ["\x69"] = "69", ["\x6A"] = "0.", ["\x6B"] = "0E", ["\x6C"] = "0E-", ["\x6D"] = "0", ["\x6E"] = "0-", ["\x6F"] = "0", + ["\x70"] = "70", ["\x71"] = "71", ["\x72"] = "72", ["\x73"] = "73", ["\x74"] = "74", ["\x75"] = "75", ["\x76"] = "76", ["\x77"] = "77", ["\x78"] = "78", ["\x79"] = "79", ["\x7A"] = "0.", ["\x7B"] = "0E", ["\x7C"] = "0E-", ["\x7D"] = "0", ["\x7E"] = "0-", ["\x7F"] = "0", + ["\x80"] = "80", ["\x81"] = "81", ["\x82"] = "82", ["\x83"] = "83", ["\x84"] = "84", ["\x85"] = "85", ["\x86"] = "86", ["\x87"] = "87", ["\x88"] = "88", ["\x89"] = "89", ["\x8A"] = "0.", ["\x8B"] = "0E", ["\x8C"] = "0E-", ["\x8D"] = "0", ["\x8E"] = "0-", ["\x8F"] = "0", + ["\x90"] = "90", ["\x91"] = "91", ["\x92"] = "92", ["\x93"] = "93", ["\x94"] = "94", ["\x95"] = "95", ["\x96"] = "96", ["\x97"] = "97", ["\x98"] = "98", ["\x99"] = "99", ["\x9A"] = "0.", ["\x9B"] = "0E", ["\x9C"] = "0E-", ["\x9D"] = "0", ["\x9E"] = "0-", ["\x9F"] = "0", + ["\xA0"] = ".0", ["\xA1"] = ".1", ["\xA2"] = ".2", ["\xA3"] = ".3", ["\xA4"] = ".4", ["\xA5"] = ".5", ["\xA6"] = ".6", ["\xA7"] = ".7", ["\xA8"] = ".8", ["\xA9"] = ".9", ["\xAA"] = "..", ["\xAB"] = ".E", ["\xAC"] = ".E-", ["\xAD"] = ".", ["\xAE"] = ".-", ["\xAF"] = ".", + ["\xB0"] = "E0", ["\xB1"] = "E1", ["\xB2"] = "E2", ["\xB3"] = "E3", ["\xB4"] = "E4", ["\xB5"] = "E5", ["\xB6"] = "E6", ["\xB7"] = "E7", ["\xB8"] = "E8", ["\xB9"] = "E9", ["\xBA"] = "E.", ["\xBB"] = "EE", ["\xBC"] = "EE-", ["\xBD"] = "E", ["\xBE"] = "E-", ["\xBF"] = "E", + ["\xC0"] = "E-0", ["\xC1"] = "E-1", ["\xC2"] = "E-2", ["\xC3"] = "E-3", ["\xC4"] = "E-4", ["\xC5"] = "E-5", ["\xC6"] = "E-6", ["\xC7"] = "E-7", ["\xC8"] = "E-8", ["\xC9"] = "E-9", ["\xCA"] = "E-.", ["\xCB"] = "E-E", ["\xCC"] = "E-E-", ["\xCD"] = "E-", ["\xCE"] = "E--", ["\xCF"] = "E-", + ["\xD0"] = "-0", ["\xD1"] = "-1", ["\xD2"] = "-2", ["\xD3"] = "-3", ["\xD4"] = "-4", ["\xD5"] = "-5", ["\xD6"] = "-6", ["\xD7"] = "-7", ["\xD8"] = "-8", ["\xD9"] = "-9", ["\xDA"] = "-.", ["\xDB"] = "-E", ["\xDC"] = "-E-", ["\xDD"] = "-", ["\xDE"] = "--", ["\xDF"] = "-", + } + + local p_nibbles = P("\30") * Cs(((1-p_last)/remap)^0+p_last) / function(n) + -- 0-9=digit a=. b=E c=E- d=reserved e=- f=finish + top = top + 1 + stack[top] = tonumber(n) or 0 + end + + local p_byte = C(R("\32\246")) / function(b0) + -- -107 .. +107 + top = top + 1 + stack[top] = byte(b0) - 139 + end + + local p_positive = C(R("\247\250")) * C(1) / function(b0,b1) + -- +108 .. +1131 + top = top + 1 + stack[top] = (byte(b0)-247)*256 + byte(b1) + 108 + end + + local p_negative = C(R("\251\254")) * C(1) / function(b0,b1) + -- -1131 .. -108 + top = top + 1 + stack[top] = -(byte(b0)-251)*256 - byte(b1) - 108 + end + + local p_short = P("\28") * C(1) * C(1) / function(b1,b2) + -- -32768 .. +32767 : b1<<8 | b2 + top = top + 1 + local n = 0x100 * byte(b1) + byte(b2) + if n >= 0x8000 then + stack[top] = n - 0xFFFF - 1 + else + stack[top] = n + end + end + + local p_long = P("\29") * C(1) * C(1) * C(1) * C(1) / function(b1,b2,b3,b4) + -- -2^31 .. +2^31-1 : b1<<24 | b2<<16 | b3<<8 | b4 + top = top + 1 + local n = 0x1000000 * byte(b1) + 0x10000 * byte(b2) + 0x100 * byte(b3) + byte(b4) + if n >= 0x8000000 then + stack[top] = n - 0xFFFFFFFF - 1 + else + stack[top] = n + end + end + + local p_unsupported = P(1) / function(detail) + top = 0 + end + + local p_dictionary = ( + p_byte + + p_positive + + p_negative + + p_short + + p_long + + p_nibbles + + p_single + + p_double + + p_unsupported + )^1 + + parsetopdictionary = function(data) + local dictionaries = data.dictionaries + stack = { } + strings = data.strings + for i=1,#dictionaries do + top = 0 + result = { + monospaced = false, + italicangle = 0, + underlineposition = -100, + underlinethickness = 50, + painttype = 0, + charstringtype = 2, + fontmatrix = { 0.001, 0, 0, 0.001, 0, 0 }, + fontbbox = { 0, 0, 0, 0 }, + strokewidth = 0, + charset = 0, + encoding = 0, + cid = { + fontversion = 0, + fontrevision = 0, + fonttype = 0, + count = 8720, + } + } + lpegmatch(p_dictionary,dictionaries[i]) + dictionaries[i] = result + end + result = { } + top = 0 + stack = { } + end + + parseprivates = function(data) + local dictionaries = data.dictionaries + stack = { } + strings = data.strings + for i=1,#dictionaries do + local private = dictionaries[i].private + if private.data then + top = 0 + result = { + forcebold = false, + languagegroup = 0, + expansionfactor = 0.06, + initialrandomseed = 0, + subroutines = 0, + defaultwidthx = 0, + nominalwidthx = 0, + } + lpegmatch(p_dictionary,private.data) + private.data = result + end + end + result = { } + top = 0 + stack = { } + end + + -- All bezier curves have 6 points with successive pairs relative to + -- the previous pair. Some can be left out and are then copied or zero + -- (optimization). + -- + -- We are not really interested in all the details of a glyph because we + -- only need to calculate the boundingbox. So, todo: a quick no result but + -- calculate only variant. + -- + -- The conversion is straightforward and the specification os clear once + -- you understand that the x and y needs to be updates each step. It's also + -- quite easy to test because in mp a shape will look bad when a few variables + -- are swapped. But still there might be bugs down here because not all + -- variants are seen in a font so far. We are less compact that the ff code + -- because there quite some variants are done in one helper with a lot of + -- testing for states. + + local x = 0 + local y = 0 + local width = false + local r = 0 + local stems = 0 + local globalbias = 0 + local localbias = 0 + local globals = false + local locals = false + local depth = 1 + + local function showstate(where) + report("%w%-10s : [%s] n=%i",depth*2,where,concat(stack," ",1,top),top) + end + + local function showvalue(where,value,showstack) + if showstack then + report("%w%-10s : %s : [%s] n=%i",depth*2,where,tostring(value),concat(stack," ",1,top),top) + else + report("%w%-10s : %s",depth*2,where,tostring(value)) + end + end + + local function rmoveto() + if top > 2 then + if not width then + width = stack[1] + if trace_charstrings then + showvalue("width",width) + end + end + top = top - 1 + elseif not width then + width = true + end + if trace_charstrings then + showstate("rmoveto") + end + x = x + stack[top-1] -- dx1 + y = y + stack[top] -- dy1 + top = 0 + r = r + 1 + result[r] = { x, y, "m" } -- "moveto" + end + + local function hmoveto() + if top > 1 then + if not width then + width = stack[1] + if trace_charstrings then + showvalue("width",width) + end + end + top = top - 1 + elseif not width then + width = true + end + if trace_charstrings then + showstate("hmoveto") + end + x = x + stack[top] -- dx1 + top = 0 + r = r + 1 + result[r] = { x, y, "m" } -- "moveto" + end + + local function vmoveto() + if top > 1 then + if not width then + width = stack[1] + if trace_charstrings then + showvalue("width",width) + end + end + top = top - 1 + elseif not width then + width = true + end + if trace_charstrings then + showstate("vmoveto") + end + y = y + stack[top] -- dy1 + top = 0 + r = r + 1 + result[r] = { x, y, "m" } -- "moveto" + end + + local function rlineto() + if trace_charstrings then + showstate("rlineto") + end + for i=1,top,2 do + x = x + stack[i] -- dxa + y = y + stack[i+1] -- dya + r = r + 1 + result[r] = { x, y, "l" } -- "lineto" + end + top = 0 + end + + local function xlineto(swap) -- x (y,x)+ | (x,y)+ + for i=1,top do + if swap then + x = x + stack[i] + swap = false + else + y = y + stack[i] + swap = true + end + r = r + 1 + result[r] = { x, y, "l" } -- "lineto" + end + top = 0 + end + + local function hlineto() -- x (y,x)+ | (x,y)+ + if trace_charstrings then + showstate("hlineto") + end + xlineto(true) + end + + local function vlineto() -- y (x,y)+ | (y,x)+ + if trace_charstrings then + showstate("vlineto") + end + xlineto(false) + end + + local function rrcurveto() + if trace_charstrings then + showstate("rrcurveto") + end + for i=1,top,6 do + local ax = x + stack[i] -- dxa + local ay = y + stack[i+1] -- dya + local bx = ax + stack[i+2] -- dxb + local by = ay + stack[i+3] -- dyb + x = bx + stack[i+4] -- dxc + y = by + stack[i+5] -- dyc + r = r + 1 + result[r] = { ax, ay, bx, by, x, y, "c" } -- "curveto" + end + top = 0 + end + + local function hhcurveto() + if trace_charstrings then + showstate("hhcurveto") + end + local s = 1 + if top % 2 ~= 0 then + y = y + stack[1] -- dy1 + s = 2 + end + for i=s,top,4 do + local ax = x + stack[i] -- dxa + local ay = y + local bx = ax + stack[i+1] -- dxb + local by = ay + stack[i+2] -- dyb + x = bx + stack[i+3] -- dxc + y = by + r = r + 1 + result[r] = { ax, ay, bx, by, x, y, "c" } -- "curveto" + end + top = 0 + end + + local function vvcurveto() + if trace_charstrings then + showstate("vvcurveto") + end + local s = 1 + local d = 0 + if top % 2 ~= 0 then + d = stack[1] -- dx1 + s = 2 + end + for i=s,top,4 do + local ax = x + d + local ay = y + stack[i] -- dya + local bx = ax + stack[i+1] -- dxb + local by = ay + stack[i+2] -- dyb + x = bx + y = by + stack[i+3] -- dyc + r = r + 1 + result[r] = { ax, ay, bx, by, x, y, "c" } -- "curveto" + d = 0 + end + top = 0 + end + + local function xxcurveto(swap) + local last = top % 4 ~= 0 and stack[top] + if last then + top = top - 1 + end + local sw = swap + for i=1,top,4 do + local ax, ay, bx, by + if swap then + ax = x + stack[i] + ay = y + bx = ax + stack[i+1] + by = ay + stack[i+2] + y = by + stack[i+3] + if last and i+3 == top then + x = bx + last + else + x = bx + end + swap = false + else + ax = x + ay = y + stack[i] + bx = ax + stack[i+1] + by = ay + stack[i+2] + x = bx + stack[i+3] + if last and i+3 == top then + y = by + last + else + y = by + end + swap = true + end + r = r + 1 + result[r] = { ax, ay, bx, by, x, y, "c" } -- "curveto" + end + top = 0 + end + + local function hvcurveto() + if trace_charstrings then + showstate("hvcurveto") + end + xxcurveto(true) + end + + local function vhcurveto() + if trace_charstrings then + showstate("vhcurveto") + end + xxcurveto(false) + end + + local function rcurveline() + if trace_charstrings then + showstate("rcurveline") + end + for i=1,top-2,6 do + local ax = x + stack[i] -- dxa + local ay = y + stack[i+1] -- dya + local bx = ax + stack[i+2] -- dxb + local by = ay + stack[i+3] -- dyb + x = bx + stack[i+4] -- dxc + y = by + stack[i+5] -- dyc + r = r + 1 + result[r] = { ax, ay, bx, by, x, y, "c" } -- "curveto" + end + x = x + stack[top-1] -- dxc + y = y + stack[top] -- dyc + r = r + 1 + result[r] = { x, y, "l" } -- "lineto" + top = 0 + end + + local function rlinecurve() + if trace_charstrings then + showstate("rlinecurve") + end + if top > 6 then + for i=1,top-6,2 do + x = x + stack[i] + y = y + stack[i+1] + r = r + 1 + result[r] = { x, y, "l" } -- "lineto" + end + end + local ax = x + stack[top-5] + local ay = y + stack[top-4] + local bx = ax + stack[top-3] + local by = ay + stack[top-2] + x = bx + stack[top-1] + y = by + stack[top] + r = r + 1 + result[r] = { ax, ay, bx, by, x, y, "c" } -- "curveto" + top = 0 + end + + local function flex() -- fd not used + if trace_charstrings then + showstate("flex") + end + local ax = x + stack[i] -- dx1 + local ay = y + stack[i+1] -- dy1 + local bx = ax + stack[i+2] -- dx2 + local by = ay + stack[i+3] -- dy2 + local cx = bx + stack[i+4] -- dx3 + local cy = by + stack[i+5] -- dy3 + r = r + 1 + result[r] = { ax, ay, bx, by, cx, cy, "c" } -- "curveto" + local dx = cx + stack[i+6] -- dx4 + local dy = cy + stack[i+7] -- dy4 + local ex = dx + stack[i+8] -- dx5 + local ey = dy + stack[i+9] -- dy5 + x = ex + stack[i+10] -- dx6 + y = ey + stack[i+11] -- dy6 + r = r + 1 + result[r] = { dx, dy, ex, ey, x, y, "c" } -- "curveto" + top = 0 + end + + local function hflex() + if trace_charstrings then + showstate("hflex") + end + local ax = x + stack[i ] -- dx1 + local ay = y + local bx = ax + stack[i+1] -- dx2 + local by = ay + stack[i+2] -- dy2 + local cx = bx + stack[i+3] -- dx3 + local cy = by + r = r + 1 + result[r] = { ax, ay, bx, by, cx, cy, "c" } -- "curveto" + local dx = cx + stack[i+4] -- dx4 + local dy = by + local ex = dx + stack[i+5] -- dx5 + local ey = y + x = ex + stack[i+6] -- dx6 + r = r + 1 + result[r] = { dx, dy, ex, ey, x, y, "c" } -- "curveto" + top = 0 + end + + local function hflex1() + if trace_charstrings then + showstate("hflex1") + end + local ax = x + stack[i ] -- dx1 + local ay = y + stack[i+1] -- dy1 + local bx = ax + stack[i+2] -- dx2 + local by = ay + stack[i+3] -- dy2 + local cx = bx + stack[i+4] -- dx3 + local cy = by + r = r + 1 + result[r] = { ax, ay, bx, by, cx, cy, "c" } -- "curveto" + local dx = cx + stack[i+5] -- dx4 + local dy = by + local ex = dx + stack[i+7] -- dx5 + local ey = dy + stack[i+8] -- dy5 + x = ex + stack[i+9] -- dx6 + r = r + 1 + result[r] = { dx, dy, dx, dy, x, y, "c" } -- "curveto" + top = 0 + end + + local function flex1() + if trace_charstrings then + showstate("flex1") + end + local ax = x + stack[i ] --dx1 + local ay = y + stack[i+1] --dy1 + local bx = ax + stack[i+2] --dx2 + local by = ay + stack[i+3] --dy2 + local cx = bx + stack[i+4] --dx3 + local cy = by + stack[i+5] --dy3 + r = r + 1 + result[r] = { ax, ay, bx, by, cx, cy, "c" } -- "curveto" + local dx = cx + stack[i+6] --dx4 + local dy = cy + stack[i+7] --dy4 + local ex = dx + stack[i+8] --dx5 + local ey = dy + stack[i+9] --dy5 + if abs(ex - x) > abs(ey - y) then -- spec: abs(dx) > abs(dy) + x = ex + stack[i+10] + else + y = ey + stack[i+10] + end + r = r + 1 + result[r] = { dx, dy, dx, dy, x, y, "c" } -- "curveto" + top = 0 + end + + local function getstem() + if top % 2 ~= 0 then + if width then + remove(stack,1) + else + width = remove(stack,1) + end + top = top - 1 + if trace_charstrings then + showvalue("width",width) + end + end + if trace_charstrings then + showstate("stem") + end + stems = stems + top/2 + top = 0 + end + + local function getmask() + if top % 2 ~= 0 then + if width then + remove(stack,1) + else + width = remove(stack,1) + end + top = top - 1 + if trace_charstrings then + showvalue("width",width) + end + end + if trace_charstrings then + showstate(operator == 19 and "hintmark" or "cntrmask") + end + stems = stems + top/2 + top = 0 + if stems <= 8 then + return 1 + else + return floor((stems+7)/8) + end + end + + local function unsupported() + if trace_charstrings then + showstate("unsupported") + end + top = 0 + end + + -- Bah, we cannot use a fast lpeg because a hint has an unknown size and a + -- runtime capture cannot handle that well. + + local actions = { [0] = + unsupported, -- 0 + getstem, -- 1 -- hstem + unsupported, -- 2 + getstem, -- 3 -- vstem + vmoveto, -- 4 + rlineto, -- 5 + hlineto, -- 6 + vlineto, -- 7 + rrcurveto, -- 8 + unsupported, -- 9 -- closepath + unsupported, -- 10 -- calllocal, + unsupported, -- 11 -- callreturn, + unsupported, -- 12 -- elsewhere + unsupported, -- 13 -- hsbw + unsupported, -- 14 -- endchar, + unsupported, -- 15 + unsupported, -- 16 + unsupported, -- 17 + getstem, -- 18 -- hstemhm + getmask, -- 19 -- hintmask + getmask, -- 20 -- cntrmask + rmoveto, -- 21 + hmoveto, -- 22 + getstem, -- 23 -- vstemhm + rcurveline, -- 24 + rlinecurve, -- 25 + vvcurveto, -- 26 + hhcurveto, -- 27 + unsupported, -- 28 -- elsewhere + unsupported, -- 29 -- elsewhere + vhcurveto, -- 30 + hvcurveto, -- 31 + } + + local subactions = { + [034] = hflex, + [035] = flex, + [036] = hflex1, + [037] = flex1, + } + + local p_bytes = Ct((P(1)/byte)^0) + + local function call(scope,list,bias,process) + local index = stack[top] + bias + top = top - 1 + if trace_charstrings then + showvalue(scope,index,true) + end + local str = list[index] + if str then + if type(str) == "string" then + str = lpegmatch(p_bytes,str) + list[index] = str + end + depth = depth + 1 + process(str) + depth = depth - 1 + else + report("unknown %s %i",scope,index) + end + end + + local function process(tab) -- I should profile this and optimize the order + local i = 1 -- which is something for a cold dark evening. + local n = #tab + while i <= n do + local t = tab[i] + if t >= 32 and t<=246 then + -- -107 .. +107 + top = top + 1 + stack[top] = t - 139 + i = i + 1 + elseif t >= 247 and t <= 250 then + -- +108 .. +1131 + top = top + 1 + stack[top] = (t-247)*256 + tab[i+1] + 108 + i = i + 2 + elseif t >= 251 and t <= 254 then + -- -1131 .. -108 + top = top + 1 + stack[top] = -(t-251)*256 - tab[i+1] - 108 + i = i + 2 + elseif t == 28 then + -- -32768 .. +32767 : b1<<8 | b2 + top = top + 1 + local n = 0x100 * tab[i+1] + tab[i+2] + if n >= 0x8000 then + stack[top] = n - 0xFFFF - 1 + else + stack[top] = n + end + i = i + 3 + elseif t == 255 then + local n = 0x100 * tab[i+1] + tab[i+2] + top = top + 1 + if n >= 0x8000 then + stack[top] = n - 0xFFFF - 1 + (0x100 * tab[i+3] + tab[i+4])/0xFFFF + else + stack[top] = n + (0x100 * tab[i+3] + tab[i+4])/0xFFFF + end + i = i + 5 + elseif t == 12 then + i = i + 1 + local t = tab[i] + local a = subactions[t] + if a then + a() + else + if trace_charstrings then + showvalue("",t) + end + top = 0 + end + i = i + 1 + elseif t == 14 then -- endchar + if width then + -- okay + elseif top > 0 then + width = stack[1] + if trace_charstrings then + showvalue("width",width) + end + else + width = true + end + if trace_charstrings then + showstate("endchar") + end + return + elseif t == 11 then + if trace_charstrings then + showstate("return") + end + return + elseif t == 10 then + call("local",locals,localbias,process) + i = i + 1 + elseif t == 29 then + call("global",globals,globalbias,process) + i = i + 1 + else + local a = actions[t] + if a then + local s = a() + if s then + i = i + s + end + else + if trace_charstrings then + showvalue("",t) + end + top = 0 + end + i = i + 1 + end + end + end + + local function calculatebounds(segments,x,y) + local nofsegments = #segments + if nofsegments == 0 then + return { x, y, x, y } + else + local xmin = 10000 + local xmax = -10000 + local ymin = 10000 + local ymax = -10000 + if x < xmin then xmin = x end + if x > xmax then xmax = x end + if y < ymin then ymin = y end + if y > ymax then ymax = y end + -- we now have a reasonable start so we could + -- simplyfy the next checks + for i=1,nofsegments do + local s = segments[i] + local x = s[1] + local y = s[2] + if x < xmin then xmin = x end + if x > xmax then xmax = x end + if y < ymin then ymin = y end + if y > ymax then ymax = y end + if s[#s] == "c" then -- "curveto" + local x = s[3] + local y = s[4] + if x < xmin then xmin = x elseif x > xmax then xmax = x end + if y < ymin then ymin = y elseif y > ymax then ymax = y end + local x = s[5] + local y = s[6] + if x < xmin then xmin = x elseif x > xmax then xmax = x end + if y < ymin then ymin = y elseif y > ymax then ymax = y end + end + end + return { round(xmin), round(ymin), round(xmax), round(ymax) } -- doesn't make ceil more sense + end + end + + parsecharstrings = function(data,glyphs,doshapes) + -- for all charstrings + local dictionary = data.dictionaries[1] + local charstrings = data.charstrings + local charset = data.charset + stack = { } + glyphs = glyphs or { } + strings = data.strings + locals = dictionary.subroutines + globals = data.routines + globalbias = #globals + localbias = #locals + globalbias = ((globalbias < 1240 and 107) or (globalbias < 33900 and 1131) or 32768) + 1 + localbias = ((localbias < 1240 and 107) or (localbias < 33900 and 1131) or 32768) + 1 + local nominalwidth = dictionary.private.data.nominalwidthx or 0 + local defaultwidth = dictionary.private.data.defaultwidthx or 0 + + for i=1,#charstrings do + local str = charstrings[i] + local tab = lpegmatch(p_bytes,str) + local index = i - 1 + x = 0 + y = 0 + width = false + r = 0 + top = 0 + stems = 0 + result = { } + if trace_charstrings then + report("glyph: %i",index) + report("data: % t",tab) + end + -- + process(tab) + -- + local boundingbox = calculatebounds(result,x,y) + -- + if width == true or width == false then + width = defaultwidth + else + width = nominalwidth + width + end + -- + local glyph = glyphs[index] -- can be autodefined in otr + if not glyph then + glyphs[index] = { + segments = doshapes ~= false and result, -- optional + boundingbox = boundingbox, + width = width, + name = charset[index], + -- sidebearing = 0, + } + else + glyph.segments = doshapes ~= false and result + glyph.boundingbox = boundingbox + if not glyph.width then + glyph.width = width + end + if charset and not glyph.name then + glyph.name = charset[index] + end + -- glyph.sidebearing = 0 -- todo + end + if trace_charstrings then + report("width: %s",tostring(width)) + report("boundingbox: % t",boundingbox) + end + charstrings[i] = nil -- free memory + end + result = { } + top = 0 + stack = { } + return glyphs + end + +end + +local function readglobals(f,data) + local routines = readlengths(f) + for i=1,#routines do + routines[i] = readstring(f,routines[i]) + end + data.routines = routines +end + +local function readencodings(f,data) + data.encodings = { } +end + +local function readcharsets(f,data) + local header = data.header + local dictionaries = data.dictionaries + local strings = data.strings + f:seek("set",header.offset+dictionaries[1].charset) + local format = readbyte(f) + if format == 0 then + local charset = { [0] = ".notdef" } + for i=1,data.nofglyphs do + charset[i] = strings[readushort(f)] + end + data.charset = charset + elseif format == 1 then + report("cff parser: todo charset format %a",format) + elseif format == 2 then + report("cff parser: todo charset format %a",format) + else + report("cff parser: unsupported charset format %a",format) + end +end + +local function readfdselect(f,data) +end + +local function readprivates(f,data) + local header = data.header + local dictionaries = data.dictionaries + local private = dictionaries[1].private + if private then + f:seek("set",header.offset+private.offset) + private.data = readstring(f,private.size) + end +end + +local function readlocals(f,data) + -- todo: make them local indeed + local header = data.header + local dictionaries = data.dictionaries + local dictionary = dictionaries[1] + local private = dictionary.private + if private then + f:seek("set",header.offset+private.offset+private.data.subroutines) + local subroutines = readlengths(f) + for i=1,#subroutines do + subroutines[i] = readstring(f,subroutines[i]) + end + dictionary.subroutines = subroutines + private.data.subroutines = nil + end +end + +-- These charstrings are little programs and described in: Technical Note #5177. A truetype +-- font has only one dictionary. + +local function readcharstrings(f,data) + local header = data.header + local dictionaries = data.dictionaries + local dictionary = dictionaries[1] + local type = dictionary.charstringtype + if type == 2 then + f:seek("set",header.offset+dictionary.charstrings) + -- could be a metatable .. delayed loading + local charstrings = readlengths(f) + local nofglyphs = #charstrings + for i=1,nofglyphs do + charstrings[i] = readstring(f,charstrings[i]) + end + data.nofglyphs = nofglyphs + data.charstrings = charstrings + else + report("unsupported charstr type %i",type) + data.nofglyphs = 0 + data.charstrings = { } + end +end + +function fonts.handlers.otf.readers.cff(f,fontdata,specification) +-- if specification.glyphs then + if specification.details then + local datatable = fontdata.tables.cff + if datatable then + local offset = datatable.offset + local glyphs = fontdata.glyphs + if not f then + report("invalid filehandle") + return + end + if offset then + f:seek("set",offset) + end + local header = readheader(f) + if header.major > 1 then + report("version mismatch") + return + end + local names = readfontnames(f) + local dictionaries = readtopdictionaries(f) + local strings = readstrings(f) + local data = { + header = header, + names = names, + dictionaries = dictionaries, + strings = strings, + } + -- + parsetopdictionary(data) + -- + local d = dictionaries[1] + fontdata.cffinfo = { + familynamename = d.familyname, + fullname = d.fullname, + boundingbox = d.boundingbox, + weight = d.weight, + italicangle = d.italicangle, + underlineposition = d.underlineposition, + underlinethickness = d.underlinethickness, + monospaced = d.monospaced, + } + -- + if specification.glyphs then + readglobals(f,data) + readcharstrings(f,data) + readencodings(f,data) + readcharsets(f,data) + readfdselect(f,data) + -- + readprivates(f,data) + parseprivates(data) + readlocals(f,data) + -- + parsecharstrings(data,glyphs,specification.shapes or false) + end + -- + -- cleanup (probably more can go) + -- + -- for i=1,#dictionaries do + -- local d = dictionaries[i] + -- d.subroutines = nil + -- end + -- data.strings = nil + -- if data then + -- data.charstrings = nil + -- data.routines = nil + -- end + end + end +end diff --git a/tex/context/base/font-con.lua b/tex/context/base/font-con.lua index 72fbb5c0d..137c21e48 100644 --- a/tex/context/base/font-con.lua +++ b/tex/context/base/font-con.lua @@ -426,6 +426,7 @@ function constructors.scale(tfmdata,specification) local vdelta = delta -- target.designsize = parameters.designsize -- not really needed so it might become obsolete + target.units = units target.units_per_em = units -- just a trigger for the backend -- local direction = properties.direction or tfmdata.direction or 0 -- pointless, as we don't use omf fonts at all @@ -895,12 +896,21 @@ function constructors.finalize(tfmdata) parameters.slantfactor = tfmdata.slant or 0 end -- - if not parameters.designsize then - parameters.designsize = tfmdata.designsize or (factors.pt * 10) + local designsize = parameters.designsize + if designsize then + parameters.minsize = tfmdata.minsize or designsize + parameters.maxsize = tfmdata.maxsize or designsize + else + designsize = factors.pt * 10 + parameters.designsize = designsize + parameters.minsize = designsize + parameters.maxsize = designsize end + parameters.minsize = tfmdata.minsize or parameters.designsize + parameters.maxsize = tfmdata.maxsize or parameters.designsize -- if not parameters.units then - parameters.units = tfmdata.units_per_em or 1000 + parameters.units = tfmdata.units or tfmdata.units_per_em or 1000 end -- if not tfmdata.descriptions then @@ -976,6 +986,7 @@ function constructors.finalize(tfmdata) tfmdata.auto_protrude = nil tfmdata.extend = nil tfmdata.slant = nil + tfmdata.units = nil tfmdata.units_per_em = nil -- tfmdata.cache = nil diff --git a/tex/context/base/font-lib.mkvi b/tex/context/base/font-lib.mkvi index 9cc14e02f..b1050f7f5 100644 --- a/tex/context/base/font-lib.mkvi +++ b/tex/context/base/font-lib.mkvi @@ -22,6 +22,16 @@ \registerctxluafile{font-agl}{1.001} % if needed we can comment this and delay loading \registerctxluafile{font-cid}{1.001} % cid maps \registerctxluafile{font-map}{1.001} + +% the otf font loader: + +\registerctxluafile{font-otr}{1.001} % opentype fontloader +\registerctxluafile{font-cff}{1.001} % quadratic outlines +\registerctxluafile{font-ttf}{1.001} % cubic outlines +\registerctxluafile{font-tmp}{1.001} % temporary placeholder +%registerctxluafile{font-dsp}{1.001} % ... for this one +\registerctxluafile{font-off}{1.001} % the old loader + \registerctxluafile{font-syn}{1.001} \registerctxluafile{font-tfm}{1.001} @@ -58,6 +68,8 @@ \registerctxluafile{node-fnt}{1.001} % here +\registerctxluafile{font-mps}{1.001} % outline fun + \registerctxluafile{font-lua}{1.001} \registerctxluafile{font-vf} {1.001} diff --git a/tex/context/base/font-mis.lua b/tex/context/base/font-mis.lua index 5169dd4e1..9fe6e224f 100644 --- a/tex/context/base/font-mis.lua +++ b/tex/context/base/font-mis.lua @@ -22,7 +22,7 @@ local handlers = fonts.handlers handlers.otf = handlers.otf or { } local otf = handlers.otf -otf.version = otf.version or 2.814 +otf.version = otf.version or 2.815 otf.cache = otf.cache or containers.define("fonts", "otf", otf.version, true) local fontloader = fontloader diff --git a/tex/context/base/font-mps.lua b/tex/context/base/font-mps.lua new file mode 100644 index 000000000..1465b475b --- /dev/null +++ b/tex/context/base/font-mps.lua @@ -0,0 +1,379 @@ +if not modules then modules = { } end modules ['font-mps'] = { + version = 1.001, + comment = "companion to font-ini.mkiv", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} + +local concat = table.concat +local formatters = string.formatters + +-- QP0 [QP1] QP2 => CP0 [CP1 CP2] CP3 + +-- CP0 = QP0 +-- CP3 = QP2 +-- +-- CP1 = QP0 + 2/3 *(QP1-QP0) +-- CP2 = QP2 + 2/3 *(QP1-QP2) + +fonts = fonts or { } +local metapost = fonts.metapost or { } +fonts.metapost = metapost + +local f_moveto = formatters["(%.4G,%.4G)"] +local f_lineto = formatters["--(%.4G,%.4G)"] +local f_curveto = formatters["..controls(%.4G,%.4G)and(%.4G,%.4G)..(%.4G,%.4G)"] +local s_cycle = "--cycle" + +local f_nofill = formatters["nofill %s;"] +local f_dofill = formatters["fill %s;"] + +local f_draw_trace = formatters["drawpathonly %s;"] +local f_draw = formatters["draw %s;"] + +local f_boundingbox = formatters["((%.4G,%.4G)--(%.4G,%.4G)--(%.4G,%.4G)--(%.4G,%.4G)--cycle)"] +local f_vertical = formatters["((%.4G,%.4G)--(%.4G,%.4G))"] + +function metapost.boundingbox(d,factor) + local bounds = d.boundingbox + local factor = factor or 1 + local llx = factor*bounds[1] + local lly = factor*bounds[2] + local urx = factor*bounds[3] + local ury = factor*bounds[4] + return f_boundingbox(llx,lly,urx,lly,urx,ury,llx,ury) +end + +function metapost.widthline(d,factor) + local bounds = d.boundingbox + local factor = factor or 1 + local lly = factor*bounds[2] + local ury = factor*bounds[4] + local width = factor*d.width + return f_vertical(width,lly,width,ury) +end + +function metapost.zeroline(d,factor) + local bounds = d.boundingbox + local factor = factor or 1 + local lly = factor*bounds[2] + local ury = factor*bounds[4] + return f_vertical(0,lly,0,ury) +end + +function metapost.paths(d,factor) + local sequence = d.sequence + local segments = d.segments + local list = { } + local path = { } -- recycled + local size = 0 + local factor = factor or 1 + if sequence then + local i = 1 + local n = #sequence + while i < n do + local operator = sequence[i] + if operator == "m" then -- "moveto" + if size > 0 then + size = size + 1 + path[size] = s_cycle + list[#list+1] = concat(path,"",1,size) + size = 1 + else + size = size + 1 + end + path[size] = f_moveto(factor*sequence[i+1],factor*sequence[i+2]) + i = i + 3 + elseif operator == "l" then -- "lineto" + size = size + 1 + path[size] = f_lineto(factor*sequence[i+1],factor*sequence[i+2]) + i = i + 3 + elseif operator == "c" then -- "curveto" + size = size + 1 + path[size] = f_curveto(factor*sequence[i+1],factor*sequence[i+2],factor*sequence[i+3],factor*sequence[i+4],factor*sequence[i+5],factor*sequence[i+6]) + i = i + 7 + elseif operator =="q" then -- "quadraticto" + size = size + 1 + -- first is always a moveto + local l_x, l_y = factor*sequence[i-2], factor*sequence[i-1] + local m_x, m_y = factor*sequence[i+1], factor*sequence[i+2] + local r_x, r_y = factor*sequence[i+3], factor*sequence[i+4] + path[size] = f_curveto ( + l_x + 2/3 * (m_x-l_x), + l_y + 2/3 * (m_y-l_y), + r_x + 2/3 * (m_x-r_x), + r_y + 2/3 * (m_y-r_y), + r_x, r_y + ) + i = i + 5 + else + -- weird + i = i + 1 + end + end + elseif segments then + for i=1,#segments do + local segment = segments[i] + local operator = segment[#segment] + if operator == "m" then -- "moveto" + if size > 0 then + size = size + 1 + path[size] = s_cycle + list[#list+1] = concat(path,"",1,size) + size = 1 + else + size = size + 1 + end + path[size] = f_moveto(factor*segment[1],factor*segment[2]) + elseif operator == "l" then -- "lineto" + size = size + 1 + path[size] = f_lineto(factor*segment[1],factor*segment[2]) + elseif operator == "c" then -- "curveto" + size = size + 1 + path[size] = f_curveto(factor*segment[1],factor*segment[2],factor*segment[3],factor*segment[4],factor*segment[5],factor*segment[6]) + elseif operator =="q" then -- "quadraticto" + size = size + 1 + -- first is always a moveto + local prev = segments[i-1] + local l_x, l_y = factor*prev[#prev-2], factor*prev[#prev-1] + local m_x, m_y = factor*segment[1], factor*segment[2] + local r_x, r_y = factor*segment[3], factor*segment[4] + path[size] = f_curveto ( + l_x + 2/3 * (m_x-l_x), + l_y + 2/3 * (m_y-l_y), + r_x + 2/3 * (m_x-r_x), + r_y + 2/3 * (m_y-r_y), + r_x, r_y + ) + else + -- weird + end + end + else + return + end + if size > 0 then + size = size + 1 + path[size] = s_cycle + list[#list+1] = concat(path,"",1,size) + end + return list +end + +function metapost.fill(paths) + local r = { } + local n = #paths + for i=1,n do + if i < n then + r[i] = f_nofill(paths[i]) + else + r[i] = f_dofill(paths[i]) + end + end + return concat(r) +end + +function metapost.draw(paths,trace) + local r = { } + local n = #paths + for i=1,n do + if trace then + r[i] = f_draw_trace(paths[i]) + else + r[i] = f_draw(paths[i]) + end + end + return concat(r) +end + +function metapost.maxbounds(data,index,factor) + local maxbounds = data.maxbounds + local factor = factor or 1 + local glyphs = data.glyphs + local glyph = glyphs[index] + local boundingbox = glyph.boundingbox + local xmin, ymin, xmax, ymax + if not maxbounds then + xmin, ymin, xmax, ymax = 0, 0, 0, 0 + for i=1,#glyphs do + local d = glyphs[i] + if d then + local b = d.boundingbox + if b then + if b[1] < xmin then xmin = b[1] end + if b[2] < ymin then ymin = b[2] end + if b[3] > xmax then xmax = b[3] end + if b[4] > ymax then ymax = b[4] end + end + end + end + maxbounds = { xmin, ymin, xmax, ymax } + data.maxbounds = maxbounds + else + xmin = maxbounds[1] + ymin = maxbounds[2] + xmax = maxbounds[3] + ymax = maxbounds[4] + end + local llx = boundingbox[1] + local lly = boundingbox[2] + local urx = boundingbox[3] + local ury = boundingbox[4] + local width = glyph.width + if llx > 0 then + llx = 0 + end + if width > urx then + urx = width + end + return f_boundingbox( + factor*llx,factor*ymin, + factor*urx,factor*ymin, + factor*urx,factor*ymax, + factor*llx,factor*ymax + ) +end + +----- formatters = string.formatters +----- concat = table.concat + +local nodecodes = nodes.nodecodes -- no nuts yet + +local glyph_code = nodecodes.glyph +local disc_code = nodecodes.disc +local kern_code = nodecodes.kern +local glue_code = nodecodes.glue +local hlist_code = nodecodes.hlist +local vlist_code = nodecodes.vlist +local penalty_code = nodecodes.penalty + +----- metapost = fonts.glyphs.metapost + +local characters = fonts.hashes.characters +local shapes = fonts.hashes.shapes +local topaths = fonts.metapost.paths + +local f_code = formatters["mfun_do_outline_text_flush(%q,%i,%.4G,%.4G)(%,t);"] +local s_nothing = "(origin scaled 10)" + +local sc = 10 +local fc = number.dimenfactors.bp * sc / 10 + +-- todo: make the next more efficient: + +function metapost.output(kind,font,char,advance,shift) + local character = characters[font][char] + if char then + local index = character.index + if index then + local shapedata = shapes[font] + local glyphs = shapedata.glyphs -- todo: subfonts fonts.shapes.indexed(font,sub) + if glyphs then + local glyf = data.glyphs[index] + if glyf then + local units = data.fontheader and data.fontheader.units or data.units or 1000 + local factor = sc/units + local shift = shift or 0 + local advance = advance or 0 + local paths = topaths(glyf,factor) + local code = f_code(kind,#paths,advance,shift,paths) + return code, glyf.width * factor + end + end + end + end + return s_nothing, 10 * sc/1000 +end + +-- shifted hboxes + +function fonts.metapost.boxtomp(n,kind) + + local result = { } + local advance = 0 + local distance = 0 + + local boxtomp + + local function horizontal(current,shift,glue_sign,glue_set,glue_order) + while current do + local id = current.id + if id == glyph_code then + local code, width = metapost.output(kind,current.font,current.char,advance,-(shift or 0)* fc) + result[#result+1] = code + advance = advance + width + elseif id == disc_code then + local replace = current.replace + if replace then + horizontal(replace,shift,glue_sign,glue_set,glue_order) + end + elseif id == kern_code then + advance = advance + current.kern * fc + elseif id == glue_code then + local spec = current.spec + local width = spec.width + if glue_sign == 1 then + if spec.stretch_order == glue_order then + advance = advance + (width + spec.stretch * glue_set) * fc + else + advance = advance + width * fc + end + elseif glue_sign == 2 then + if spec.shrink_order == glue_order then + advance = advance + (width - spec.shrink * glue_set) * fc + else + advance = advance + width * fc + end + else + advance = advance + width * fc + end + elseif id == hlist_code then + local a = advance + boxtomp(current,(shift or 0)+current.shift,current.glue_sign,current.glue_set,current.glue_order) + advance = a + current.width * fc + elseif id == vlist_code then + boxtomp(current) -- ,distance + (shift or 0),current.glue_set*current.glue_sign) + else -- todo: rule + -- print("horizontal >>>",nodecodes[id]) + end + current = current.next + end + end + + local function vertical(current,shift) + while current do + local id = current.id + if id == hlist_code then + distance = distance + current.height + boxtomp(current,distance + (shift or 0),current.glue_set*current.glue_sign) + distance = distance + current.depth + elseif id == vlist_code then + print("vertical >>>",nodecodes[id]) + elseif id == kern_code then + distance = distance + current.kern + advance = 0 + elseif id == glue_code then + distance = distance + current.spec.width + advance = 0 + end + current = current.next + end + end + + boxtomp = function(list,shift) + local current = list.list + if current then + if list.id == hlist_code then + horizontal(current,shift,list.glue_sign,list.glue_set,list.glue_order) + else + vertical(current,shift) + end + end + end + + local box = tex.box[n] + boxtomp(box,box.shift,box.glue_sign,box.glue_set,box.glue_order) + return concat(result) + +end diff --git a/tex/context/base/font-off.lua b/tex/context/base/font-off.lua new file mode 100644 index 000000000..34a4d963b --- /dev/null +++ b/tex/context/base/font-off.lua @@ -0,0 +1,228 @@ +if not modules then modules = { } end modules ['font-off'] = { + version = 1.001, + comment = "companion to font-ini.mkiv", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} + +local lower = string.lower +local round = math.round +local setmetatableindex = table.setmetatableindex + +local fontloader = fontloader +local font_to_table = fontloader.to_table +local open_font = fontloader.open +local get_font_info = fontloader.info +local close_font = fontloader.close +local font_fields = fontloader.fields + +-- table={ +-- ["familyname"]="TeXGyrePagella", +-- ["fontname"]="TeXGyrePagella-Regular", +-- ["fullname"]="TeXGyrePagella-Regular", +-- ["italicangle"]=0, +-- ["names"]={ +-- { +-- ["lang"]="English (US)", +-- ["names"]={ +-- ["copyright"]="Copyright 2006, 2009 for TeX Gyre extensions by B. Jackowski and J.M. Nowacki (on behalf of TeX users groups). This work is released under the GUST Font License -- see http://tug.org/fonts/licenses/GUST-FONT-LICENSE.txt for details.", +-- ["family"]="TeXGyrePagella", +-- ["fullname"]="TeXGyrePagella-Regular", +-- ["postscriptname"]="TeXGyrePagella-Regular", +-- ["preffamilyname"]="TeX Gyre Pagella", +-- ["subfamily"]="Regular", +-- ["trademark"]="Please refer to the Copyright section for the font trademark attribution notices.", +-- ["uniqueid"]="2.004;UKWN;TeXGyrePagella-Regular", +-- ["version"]="Version 2.004;PS 2.004;hotconv 1.0.49;makeotf.lib2.0.14853", +-- }, +-- }, +-- }, +-- ["pfminfo"]={ +-- ["avgwidth"]=528, +-- ["codepages"]={ 536871315, 0 }, +-- ["firstchar"]=32, +-- ["fstype"]=12, +-- ["hhead_ascent"]=1098, +-- ["hhead_descent"]=-283, +-- ["hheadascent_add"]=0, +-- ["hheaddescent_add"]=0, +-- ["hheadset"]=1, +-- ["lastchar"]=64260, +-- ["linegap"]=0, +-- ["os2_breakchar"]=32, +-- ["os2_capheight"]=692, +-- ["os2_defaultchar"]=0, +-- ["os2_family_class"]=0, +-- ["os2_strikeypos"]=269, +-- ["os2_strikeysize"]=50, +-- ["os2_subxoff"]=0, +-- ["os2_subxsize"]=650, +-- ["os2_subyoff"]=75, +-- ["os2_subysize"]=600, +-- ["os2_supxoff"]=0, +-- ["os2_supxsize"]=650, +-- ["os2_supyoff"]=350, +-- ["os2_supysize"]=600, +-- ["os2_typoascent"]=726, +-- ["os2_typodescent"]=-274, +-- ["os2_typolinegap"]=200, +-- ["os2_vendor"]="UKWN", +-- ["os2_winascent"]=1098, +-- ["os2_windescent"]=283, +-- ["os2_xheight"]=449, +-- ["panose"]={ +-- ["armstyle"]="Any", +-- ["contrast"]="Any", +-- ["familytype"]="Any", +-- ["letterform"]="Any", +-- ["midline"]="Any", +-- ["proportion"]="Any", +-- ["serifstyle"]="Any", +-- ["strokevariation"]="Any", +-- ["weight"]="Book", +-- ["xheight"]="Any", +-- }, +-- ["panose_set"]=1, +-- ["pfmfamily"]=81, +-- ["pfmset"]=1, +-- ["subsuper_set"]=1, +-- ["typoascent_add"]=0, +-- ["typodescent_add"]=0, +-- ["unicoderanges"]={ 536871047, 0, 0, 0 }, +-- ["vheadset"]=0, +-- ["vlinegap"]=0, +-- ["weight"]=400, +-- ["width"]=5, +-- ["winascent_add"]=0, +-- ["windescent_add"]=0, +-- }, +-- ["units_per_em"]=1000, +-- ["version"]="2.004;PS 2.004;hotconv 1.0.49;makeotf.lib2.0.14853", +-- ["weight"]="Book", +-- } + +-- We had this as temporary solution because we needed a bit more info but in the +-- meantime it got an interesting side effect: currently luatex delays loading of e.g. +-- glyphs so here we first load and then discard which is a waste. In the past it did +-- free memory because a full load was done. One of these things that goes unnoticed. +-- +-- local function get_full_info(...) -- check with taco what we get / could get +-- local ff = open_font(...) +-- if ff then +-- local d = ff -- and font_to_table(ff) +-- d.glyphs, d.subfonts, d.gpos, d.gsub, d.lookups = nil, nil, nil, nil, nil +-- close_font(ff) +-- return d +-- else +-- return nil, "error in loading font" +-- end +-- end + +-- Phillip suggested this faster variant but it's still a hack as fontloader.info should +-- return these keys/values (and maybe some more) but at least we close the loader which +-- might save some memory in the end. + +-- local function get_full_info(name) +-- local ff = open_font(name) +-- if ff then +-- local fields = table.tohash(font_fields(ff),true) -- isn't that one stable +-- local d = { +-- names = fields.names and ff.names, +-- familyname = fields.familyname and ff.familyname, +-- fullname = fields.fullname and ff.fullname, +-- fontname = fields.fontname and ff.fontname, +-- weight = fields.weight and ff.weight, +-- italicangle = fields.italicangle and ff.italicangle, +-- units = fields.units_per_em and ff.units_per_em, +-- designsize = fields.design_size and ff.design_size, +-- minsize = fields.design_range_bottom and ff.design_range_bottom, +-- maxsize = fields.design_range_top and ff.design_range_top, +-- italicangle = fields.italicangle and ff.italicangle, +-- pfmweight = pfminfo and pfminfo.weight or 400, +-- pfmwidth = pfminfo and pfminfo.width or 5, +-- } +-- -- setmetatableindex(d,function(t,k) +-- -- report_names("warning, trying to access field %a in font table of %a",k,name) +-- -- end) +-- close_font(ff) +-- return d +-- else +-- return nil, "error in loading font" +-- end +-- end + +-- more efficient: + +local fields = nil + +local function check_names(names) + if names then + for i=1,#names do + local name = names[i] + if name.lang == "English (US)" then + return name.names + end + end + end +end + +local function get_full_info(name) + local ff = open_font(name) + if ff then + -- unfortunately luatex aborts when a field is not available but we just make + -- sure that we only access a few known ones + local pfminfo = ff.pfminfo or { } + local names = check_names(ff.names) or { } + local weight = names.weight or ff.weight + local width = names.width -- no: ff.width + local d = { + familyname = names.preffamilyname or names.family or ff.familyname, + fullname = names.fullname or ff.fullname, + fontname = ff.fontname, + subfamily = names.subfamily, + modifiers = names.prefmodifiers, + weight = weight and lower(weight), + width = width and lower(width), + italicangle = round(1000*(tonumber(ff.italicangle) or 0))/1000 or 0, + units = ff.units_per_em, + designsize = ff.design_size, + minsize = ff.design_range_bottom, + maxsize = ff.design_range_top, + pfmweight = pfminfo.weight or 400, + pfmwidth = pfminfo.width or 5, + monospaced = pfminfo.panose and pfminfo.panose.proportion == "Monospaced", + } + close_font(ff) + return d + else + return nil, "error in loading font" + end +end + +-- As we have lazy loading anyway, this one still is full and with less code than +-- the previous one. But this depends on the garbage collector to kick in and in the +-- current version that somehow happens not that often (on my machine I end up with +-- some 3 GB extra before that happens). + +-- local function get_full_info(...) +-- local ff = open_font(...) +-- if ff then +-- local d = { } -- ff is userdata so [1] or # fails on it +-- setmetatableindex(d,ff) +-- return d -- garbage collection will do the close_font(ff) +-- else +-- return nil, "error in loading font" +-- end +-- 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 + +fontloader.fullinfo = get_full_info +readers.getinfo = readers.getinfo or get_full_info diff --git a/tex/context/base/font-otf.lua b/tex/context/base/font-otf.lua index db0118d84..03463fdaa 100644 --- a/tex/context/base/font-otf.lua +++ b/tex/context/base/font-otf.lua @@ -60,7 +60,7 @@ local otf = fonts.handlers.otf otf.glists = { "gsub", "gpos" } -otf.version = 2.814 -- beware: also sync font-mis.lua +otf.version = 2.815 -- beware: also sync font-mis.lua otf.cache = containers.define("fonts", "otf", otf.version, true) local hashes = fonts.hashes @@ -2394,10 +2394,14 @@ local function copytotfm(data,cache_id) local spaceunits = 500 local spacer = "space" local designsize = metadata.designsize or metadata.design_size or 100 + local minsize = metadata.minsize or metadata.design_range_bottom or designsize + local maxsize = metadata.maxsize or metadata.design_range_top or designsize local mathspecs = metadata.math -- if designsize == 0 then designsize = 100 + minsize = 100 + maxsize = 100 end if mathspecs then for name, value in next, mathspecs do @@ -2474,15 +2478,15 @@ local function copytotfm(data,cache_id) local fontname = metadata.fontname local fullname = metadata.fullname or fontname local psname = fontname or fullname - local units = metadata.units_per_em or 1000 + local units = metadata.units or metadata.units_per_em or 1000 -- if units == 0 then -- catch bugs in fonts units = 1000 -- maybe 2000 when ttf - metadata.units_per_em = 1000 + metadata.units = 1000 report_otf("changing %a units to %a",0,units) end -- - local monospaced = metadata.isfixedpitch or (pfminfo.panose and pfminfo.panose.proportion == "Monospaced") + local monospaced = metadata.monospaced or metadata.isfixedpitch or (pfminfo.panose and pfminfo.panose.proportion == "Monospaced") local charwidth = pfminfo.avgwidth -- or unset local charxheight = pfminfo.os2_xheight and pfminfo.os2_xheight > 0 and pfminfo.os2_xheight -- charwidth = charwidth * units/1000 @@ -2552,17 +2556,16 @@ local function copytotfm(data,cache_id) end end -- - parameters.designsize = (designsize/10)*65536 - parameters.ascender = abs(metadata.ascent or 0) - parameters.descender = abs(metadata.descent or 0) - parameters.units = units + parameters.designsize = (designsize/10)*65536 + parameters.minsize = (minsize /10)*65536 + parameters.maxsize = (maxsize /10)*65536 + parameters.ascender = abs(metadata.ascender or metadata.ascent or 0) + parameters.descender = abs(metadata.descender or metadata.descent or 0) + parameters.units = units -- properties.space = spacer properties.encodingbytes = 2 properties.format = data.format or otf_format(filename) or formats.otf --- if units ~= 1000 and format ~= "truetype" then --- properties.format = "truetype" --- end properties.noglyphnames = true properties.filename = filename properties.fontname = fontname diff --git a/tex/context/base/font-otr.lua b/tex/context/base/font-otr.lua new file mode 100644 index 000000000..a83766f85 --- /dev/null +++ b/tex/context/base/font-otr.lua @@ -0,0 +1,1816 @@ +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" +} + +-- 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. +-- +-- todo: add checks for versions +-- todo: check all unsigned / signed +-- todo: save mode for context font loader (also deal with unicode dups) +-- +-- widths and weights are kind of messy: for instance lmmonolt ias a pfmweight of +-- 400 while it should be 300 +-- +-- we can have a bit more in the info data if needed as it will nto really slow +-- down identifying +-- +-- 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) + +if not characters then + require("char-def") + require("char-ini") +end + +local next, type, unpack = next, type, unpack +local byte, lower, char = string.byte, string.lower, string.char +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 lpegmatch = lpeg.match + +local setmetatableindex = table.setmetatableindex +local formatters = string.formatters +local sortedkeys = table.sortedkeys +local sortedhash = table.sortedhash +local stripstring = string.strip +local utf16_to_utf8_be = utf.utf16_to_utf8_be + +local report = logs.reporter("otf reader") + +fonts = fonts or { } +local handlers = fonts.handlers or { } +fonts.handlers = handlers +local otf = handlers.otf or { } +handlers.otf = otf +local readers = otf.readers or { } +otf.readers = readers + +local 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 readoffset = readushort +local read2dot14 = files.read2dot14 -- 16-bit signed fixed number with the low 14 bits of fraction (2.14) (F2DOT14) + +local readtag = function(f) return f:read(4) end +local skipshort = function(f,n) f:read(n and 2*n or 2) end + +local reportedskipped = { } + +local function reportskippedtable(tag) + if not reportedskipped[tag] then + report("loading of table %a skipped (reported once only)",tag) + reportedskipped[tag] = true + end +end +-- 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 +-- up with a different ordering and naming. + +local reservednames = { [0] = + "copyright", + "family", + "subfamily", + "uniqueid", + "fullname", + "version", + "postscriptname", + "trademark", + "manufacturer", + "designer", + "description", -- descriptor in ff + "venderurl", + "designerurl", + "license", + "licenseurl", + "reserved", + "typographicfamily", -- preffamilyname + "typographicsubfamily", -- prefmodifiers + "compatiblefullname", -- for mac + "sampletext", + "cidfindfontname", + "wwsfamily", + "wwssubfamily", + "lightbackgroundpalette", + "darkbackgroundpalette", +} + +-- 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 = { + 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 + }, + macintosh = { [0] = + "roman", "japanese", "chinese (traditional)", "korean", "arabic", "hebrew", "greek", "russian", + "rsymbol", "devanagari", "gurmukhi", "gujarati", "oriya", "bengali", "tamil", "telugu", "kannada", + "malayalam", "sinhalese", "burmese", "khmer", "thai", "laotian", "georgian", "armenian", + "chinese (simplified)", "tibetan", "mongolian", "geez", "slavic", "vietnamese", "sindhi", + "uninterpreted", + }, + iso = { [0] = + "7-bit ascii", + "iso 10646", + "iso 8859-1", + }, + windows = { [0] = + "symbol", + "unicode bmp", -- 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 = { + ["unicode bmp"] = 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 = { + unicode = { + [ 0] = "english", + }, + 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)", + }, + iso = { + }, + 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] = -- hijacked 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", + [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" +-- print("weight:",k,r,v) + return v +end) + +setmetatableindex(widths,function(t,k) +-- print("width:",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. + +-- 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. + +function readers.name(f,fontdata) + local datatable = fontdata.tables.name + if datatable then + f:seek("set",datatable.offset) + 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 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 name = reservednames[readushort(f)] + if name then + namelist[#namelist+1] = { + platform = platform, + encoding = encoding, + language = language, + name = name, + length = readushort(f), + offset = readushort(f), + } + else + skipshort(f,2) + end + else + skipshort(f,3) + end + else + skipshort(f,3) + end + else + skipshort(f,5) + end + else + skipshort(f,5) + end + end + -- 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 start = datatable.offset + offset + local names = { } + local done = { } + -- + -- 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 filter(platform,e,l) + local namelist = namelists[platform] + for i=1,#namelist do + local name = namelist[i] + local nametag = name.name + if not done[nametag] then + local encoding = name.encoding + local language = name.language + if (not e or encoding == e) and (not l or language == l) then + f:seek("set",start+name.offset) + local content = readstring(f,name.length) + local decoder = decoders[platform] + if decoder then + decoder = decoder[encoding] + end + if decoder then + content = decoder(content) + end + names[nametag] = { + content = content, + platform = platform, + encoding = encoding, + language = language, + } + done[nametag] = true + end + end + end + end + -- + filter("windows","unicode bmp","english - united states") + -- filter("unicode") -- which one ? + filter("macintosh","roman","english") + filter("windows") + filter("macintosh") + filter("unicode") + -- + fontdata.names = names + else + fontdata.names = { } + 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 datatable = fontdata.tables["os/2"] + if datatable then + f:seek("set",datatable.offset) + local version = readushort(f) + local windowsmetrics = { + version = version, + averagewidth = readushort(f), + weightclass = readushort(f), + widthclass = readushort(f), + fstype = readushort(f), + subscriptxsize = readushort(f), + subscriptysize = readushort(f), + subscriptxoffset = readushort(f), + subscriptyoffset = readushort(f), + superscriptxsize = readushort(f), + superscriptysize = readushort(f), + superscriptxoffset = readushort(f), + superscriptyoffset = readushort(f), + strikeoutsize = readushort(f), + strikeoutpos = readushort(f), + familyclass = readushort(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 = readushort(f), + typodescender = readushort(f), + typolinegap = readushort(f), + winascent = readushort(f), + windescent = readushort(f), + } + if version >= 1 then + windowsmetrics.codepageranges = { readulong(f), readulong(f) } + end + if version >= 3 then + windowsmetrics.xheight = readshort(f) + windowsmetrics.capheight = readshort(f) + windowsmetrics.defaultchar = readushort(f) + windowsmetrics.breakchar = readushort(f) + -- 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 datatable = fontdata.tables.head + if datatable then + f:seek("set",datatable.offset) + local fontheader = { + version = readfixed(f), + revision = readfixed(f), + checksum = readulong(f), + magic = readulong(f), + flags = readushort(f), + units = readushort(f), + created = readlongdatetime(f), + modified = readlongdatetime(f), + xmin = readshort(f), + ymin = readshort(f), + xmax = readshort(f), + ymax = readshort(f), + macstyle = readushort(f), + smallpixels = readushort(f), + directionhint = readshort(f), + indextolocformat = readshort(f), + glyphformat = readshort(f), + } + fontdata.fontheader = fontheader + fontdata.nofglyphs = 0 + else + fontdata.fontheader = { } + fontdata.nofglyphs = 0 + end +end + +-- This table is a rather simple one. No treatment of values is needed here. Most +-- variables are not used but nofhmetrics is quite important. + +readers.hhea = function(f,fontdata,specification) + if specification.details then + local datatable = fontdata.tables.hhea + if datatable then + f:seek("set",datatable.offset) + fontdata.horizontalheader = { + version = readfixed(f), + ascender = readfword(f), + descender = readfword(f), + linegap = readfword(f), + maxadvancewidth = readufword(f), + minleftsidebearing = readfword(f), + minrightsidebearing = readfword(f), + maxextent = readfword(f), + caretsloperise = readshort(f), + caretsloperun = readshort(f), + caretoffset = readshort(f), + reserved_1 = readshort(f), + reserved_2 = readshort(f), + reserved_3 = readshort(f), + reserved_4 = readshort(f), + metricdataformat = readshort(f), + nofhmetrics = readushort(f), + } + else + fontdata.horizontalheader = { + nofhmetrics = 0, + } + end + end +end + +-- 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) + if specification.details then + local datatable = fontdata.tables.maxp + if datatable then + f:seek("set",datatable.offset) + local version = readfixed(f) + if version == 0.5 then + fontdata.maximumprofile = { + version = version, + nofglyphs = readushort(f), + } + return + elseif version == 1.0 then + fontdata.maximumprofile = { + version = version, + nofglyphs = readushort(f), + points = readushort(f), + contours = readushort(f), + compositepoints = readushort(f), + compositecontours = readushort(f), + zones = readushort(f), + twilightpoints = readushort(f), + storage = readushort(f), + functiondefs = readushort(f), + instructiondefs = readushort(f), + stackelements = readushort(f), + sizeofinstructions = readushort(f), + componentelements = readushort(f), + componentdepth = readushort(f), + } + return + end + end + fontdata.maximumprofile = { + version = version, + nofglyphs = 0, + } + end +end + +-- Here we filter the (advance) widths (that can be different from the boundingbox +-- width of course). + +readers.hmtx = function(f,fontdata,specification) + if specification.glyphs then + local datatable = fontdata.tables.hmtx + if datatable then + f:seek("set",datatable.offset) + local nofmetrics = fontdata.horizontalheader.nofhmetrics + local glyphs = fontdata.glyphs + local nofglyphs = fontdata.nofglyphs + local nofrepeated = nofglyphs - nofmetrics + local width = 0 -- advance + local leftsidebearing = 0 + for i=0,nofmetrics-1 do + local glyph = glyphs[i] + width = readshort(f) + leftsidebearing = readshort(f) + if advance ~= 0 then + glyph.width = width + end + 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,nofrepeated do + local glyph = glyphs[i] + if width ~= 0 then + glyph.width = width + end + if leftsidebearing ~= 0 then + glyph.lsb = leftsidebearing + end + end + end + end +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 datatable = fontdata.tables.post + if datatable then + f:seek("set",datatable.offset) + local version = readfixed(f) + fontdata.postscript = { + version = version, + italicangle = round(1000*readfixed(f))/1000, + underlineposition = readfword(f), + underlinethickness = readfword(f), + monospaced = readulong(f), + minmemtype42 = readulong(f), + maxmemtype42 = readulong(f), + minmemtype1 = readulong(f), + maxmemtype1 = readulong(f), + } + if not specification.glyphs then + -- enough done + elseif version == 1.0 then + -- mac encoding (258 glyphs) + for index=0,#standardromanencoding do + glyphs[index].name = standardromanencoding[index] + end + elseif version == 2.0 then + local glyphs = fontdata.glyphs + local nofglyphs = readushort(f) + local filesize = fontdata.filesize + 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 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) + break + end + end + elseif version == 2.5 then + -- depricated, will be done when needed + elseif version == 3.0 then + -- no ps name information + end + else + fontdata.postscript = { } + end +end + +readers.cff = function(f,fontdata,specification) + if specification.glyphs then + reportskippedtable("cff") + end +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. + +local formatreaders = { } + +formatreaders[4] = function(f,fontdata,offset) + f:seek("set",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 endchars = { } + local startchars = { } + local deltas = { } + local offsets = { } + local indices = { } + local mapmap = fontdata.map.map + local glyphs = fontdata.glyphs + -- + for i=1,nofsegments do + endchars[i] = readushort(f) + end + local reserved = readushort(f) + for i=1,nofsegments do + startchars[i] = readushort(f) + end + for i=1,nofsegments do + deltas[i] = readshort(f) + end + for i=1,nofsegments do + offsets[i] = readushort(f) + end + -- format length language nofsegments searchrange entryselector rangeshift 4-tables + local size = (length - 2 * 2 - 5 * 2 - 4 * nofsegments * 2) / 2 + for i=1,size-1 do + indices[i] = readushort(f) + end + -- + for segment=1,nofsegments do + local startchar = startchars[segment] + local endchar = endchars[segment] + local offset = offsets[segment] + local delta = deltas[segment] + if startchar == 0xFFFF and endchar == 0xFFFF then + break + elseif offset == 0 then + for char=startchar,endchar do + local unicode = char + local index = mod(char + delta,65536) + if index and index > 0 then + local glyph = glyphs[index] + if not glyph.unicode then + glyph.unicode = unicode + end + mapmap[index] = unicode + -- report("%C %04i %05i %s",unicode,index,glyphs[index].name) + end + end + else + local shift = (segment-nofsegments+offset/2) - startchar + for char=startchar,endchar do + local unicode = mod(char + delta,65536) + local slot = shift + char + local index = indices[slot] + if index and index > 0 then + local glyph = glyphs[index] + if not glyph.unicode then + glyph.unicode = unicode + end + mapmap[index] = unicode + -- report("%C %04i %05i %s",unicode,index,glyphs[index].name) + end + end + end + end + +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) + for unicode=start,start+count-1 do + local index = readushort(f) + if index > 0 then + local glyph = glyphs[index] + if not glyph.unicode then + glyph.unicode = unicode + end + mapmap[unicode] = index + 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 + local nofgroups = readulong(f) + for i=1,nofgroups do + local first = readulong(f) + local last = readulong(f) + local index = readulong(f) + for unicode=first,last do + local glyph = glyphs[index] + if not glyph.unicode then + glyph.unicode = unicode + end + mapmap[unicode] = index + index = index + 1 + end + end +end + +local function checkcmap(f,fontdata,records,platform,encoding,format) + local data = records[platform] + if not data then + return + end + data = data[encoding] + if not data then + return + end + data = data[format] + if not data then + return + end + local reader = formatreaders[format] + if not reader then + return + end + -- report("checking cmap: platform %a, encoding %a, format %a",platform,encoding,format) + reader(f,fontdata,data) + return true +end + +function readers.cmap(f,fontdata,specification) + if specification.glyphs then + local datatable = fontdata.tables.cmap + if datatable then + local tableoffset = datatable.offset + f:seek("set",tableoffset) + local version = readushort(f) + local noftables = readushort(f) + local records = { } + local unicodecid = false + local variantcid = false + 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 + for platform, record in next, records do + for encoding, subtables in next, record do + local offsets = subtables.offsets + local formats = subtables.formats + for i=1,#offsets do + local offset = tableoffset + offsets[i] + f:seek("set",offset) + formats[readushort(f)] = offset + end + record[encoding] = formats + end + end + -- + checkcmap(f,fontdata,records,3, 1, 4) + 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] + -- + fontdata.cidmaps = { + version = version, + noftables = noftables, + records = records, + } + else + fontdata.cidmaps = { } + end + 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 (todo: cleanup finalizer). + +function readers.loca(f,fontdata,specification) + if specification.glyphs then + reportskippedtable("loca") + end +end + +function readers.glyf(f,fontdata,specification) -- part goes to cff module + if specification.glyphs then + reportskippedtable("glyf") + end +end + +-- Here we have a table that we really need for later processing although a more +-- advanced gpos table can also be available. Todo: we need a 'fake' lookup for +-- this (analogue to ff). + +function readers.kern(f,fontdata,specification) + if specification.kerns then + local datatable = fontdata.tables.kern + if datatable then + f:seek("set",datatable.offset) + local version = readushort(f) + local noftables = readushort(f) + for i=1,noftables do + local version = readushort(f) + local length = readushort(f) + local coverage = readushort(f) + -- bit 8-15 of coverage: format 0 or 2 + local format = bit32.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 + -- fontdata.kerns = kerns + elseif format == 2 then + report("todo: kern classes") + else + report("todo: kerns") + end + end + end + end +end + +function readers.gdef(f,fontdata,specification) + if specification.details then + reportskippedtable("gdef") + end +end + +function readers.gsub(f,fontdata,specification) + if specification.details then + reportskippedtable("gsub") + end +end + +function readers.gpos(f,fontdata,specification) + if specification.details then + reportskippedtable("gpos") + end +end + +function readers.math(f,fontdata,specification) + if specification.glyphs then + 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 + end +end + +-- Goodie. A sequence instead of segments costs a bit more memory, some 300K on a +-- dejavu serif and about the same on a pagella regular. + +local function packoutlines(data,makesequence) + local subfonts = data.subfonts + if subfonts then + for i=1,#subfonts do + packoutlines(subfonts[i],makesequence) + end + return + end + local common = data.segments + if common then + return + end + local glyphs = data.glyphs + if not glyphs then + return + end + if makesequence then + for index=1,#glyphs do + local glyph = glyphs[index] + local segments = glyph.segments + if segments then + local sequence = { } + local nofsequence = 0 + for i=1,#segments do + local segment = segments[i] + local nofsegment = #segment + nofsequence = nofsequence + 1 + sequence[nofsequence] = segment[nofsegment] + for i=1,nofsegment-1 do + nofsequence = nofsequence + 1 + sequence[nofsequence] = segment[i] + end + end + glyph.sequence = sequence + glyph.segments = nil + end + end + else + local hash = { } + local common = { } + local reverse = { } + local last = 0 + for index=1,#glyphs do + local segments = glyphs[index].segments + if segments then + for i=1,#segments do + local h = concat(segments[i]," ") + hash[h] = (hash[h] or 0) + 1 + end + end + end + for index=1,#glyphs do + local segments = glyphs[index].segments + if segments then + for i=1,#segments do + local segment = segments[i] + local h = concat(segment," ") + if hash[h] > 1 then + local idx = reverse[h] + if not idx then + last = last + 1 + reverse[h] = last + common[last] = segment + idx = last + end + segments[i] = idx + end + end + end + end + if last > 0 then + data.segments = common + end + end +end + +local function unpackoutlines(data) + local subfonts = data.subfonts + if subfonts then + for i=1,#subfonts do + unpackoutlines(subfonts[i]) + end + return + end + local common = data.segments + if not common then + return + end + local glyphs = data.glyphs + if not glyphs then + return + end + for index=1,#glyphs do + local segments = glyphs[index].segments + if segments then + for i=1,#segments do + local c = common[segments[i]] + if c then + segments[i] = c + end + end + end + end + data.segments = nil +end + +otf.packoutlines = packoutlines +otf.unpackoutlines = unpackoutlines + +-- 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. + +-- options: +-- +-- properties : common metrics, names, list of features +-- glyphs : metrics, encoding +-- shapes : sequences or segments +-- kerns : global (ttf) kerns +-- lookups : gsub and gpos lookups + +local function readdata(f,offset,specification) + if offset then + f:seek("set",offset) + end + local tables = { } + local basename = file.basename(specification.filename) + local filesize = specification.filesize + local fontdata = { -- some can/will go + filename = basename, + filesize = filesize, + version = readstring(f,4), + noftables = readushort(f), + searchrange = readushort(f), -- not needed + entryselector = readushort(f), -- not needed + rangeshift = readushort(f), -- not needed + tables = tables, + } + for i=1,fontdata.noftables do + local tag = lower(stripstring(readstring(f,4))) + local checksum = readulong(f) -- not used + local offset = readulong(f) + local length = readulong(f) + if offset + length > filesize then + report("bad %a table in file %a",tag,basename) + end + tables[tag] = { + checksum = checksum, + offset = offset, + length = length, + } + end + if 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 = { }, + } + fontdata.glyphs = glyphs + fontdata.map = map + end + readers["name"](f,fontdata,specification) + readers["os/2"](f,fontdata,specification) + readers["head"](f,fontdata,specification) + readers["maxp"](f,fontdata,specification) + readers["hhea"](f,fontdata,specification) + readers["hmtx"](f,fontdata,specification) + readers["post"](f,fontdata,specification) + readers["cff" ](f,fontdata,specification) + readers["cmap"](f,fontdata,specification) + readers["loca"](f,fontdata,specification) + readers["glyf"](f,fontdata,specification) + readers["kern"](f,fontdata,specification) + readers["gdef"](f,fontdata,specification) + readers["gsub"](f,fontdata,specification) + readers["gpos"](f,fontdata,specification) + readers["math"](f,fontdata,specification) + -- + fontdata.locations = nil + fontdata.tables = nil + fontdata.cidmaps = nil + fontdata.dictionaries = nil + -- + -- 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) + end + if subfont then + if subfont > 1 and subfont <= nofsubfonts then + fontdata = readdata(f,offsets[subfont],specification) + else + report("no subfont %a in file %a",subfont,filename) + end + else + local subfonts = { } + fontdata = { + filename = filename, + filesize = filesize, + version = version, + subfonts = subfonts, + ttcversion = ttcversion, + nofsubfonts = nofsubfonts, + } + for i=1,fontdata.nofsubfonts do + 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() + end + else + report("unable to open %a",filename) + end +end + +local function loadfont(specification) + if type(specification) == "string" then + specification = { + filename = specification, + info = true, -- always true (for now) + details = true, + glyphs = true, + shapes = true, + kerns = true, + lookups = true, + -- true or number: + subfont = true, + } + 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 + end + local function message(str) + report("fatal error in file %a: %s",specification.filename,str) + end + local ok, result = xpcall(loadfontdata,message,specification) + if ok then + return result + 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) + local fontdata = loadfont { + filename = filename, + shapes = true, + subfont = n, + } + return fontdata and { + -- version = 0.123 -- todo + filename = filename, + glyphs = fontdata.glyphs, + units = fontdata.fontheader.units, + } or { + filename = filename, + glyphs = { }, + units = 0, + } +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 + return getinfo(fontdata) + elseif type(n) ~= "number" then + local info = { } + for i=1,#subfonts do + info[i] = getinfo(fontdata,i) + end + return info + elseif n > 1 and n <= subfonts then + return getinfo(fontdata,n) + else + return { + filename = filename, + comment = "there is no subfont " .. n .. " in this file" + } + end + else + return { + filename = filename, + comment = "the file cannot be opened for reading", + } + end +end + +-- + +if fonts.hashes then + + local identifiers = fonts.hashes.identifiers + local loadshapes = readers.loadshapes + + readers.version = 0.006 + readers.cache = containers.define("fonts", "shapes", readers.version, true) + + -- todo: loaders per format + + local function load(filename,sub) + local base = file.basename(filename) + local name = file.removesuffix(base) + local kind = file.suffix(filename) + local attr = lfs.attributes(filename) + local size = attr and attr.size or 0 + local time = attr and attr.modification or 0 + local sub = tonumber(sub) + if size > 0 and (kind == "otf" or kind == "ttf" or kind == "tcc") then + local hash = containers.cleanname(base) -- including suffix + if sub then + hash = hash .. "-" .. sub + end + data = containers.read(readers.cache,hash) + if not data or data.time ~= time or data.size ~= size then + data = loadshapes(filename,sub) + if data then + data.size = size + data.format = "opentype" + data.time = time + packoutlines(data) + containers.write(readers.cache,hash,data) + data = containers.read(readers.cache,hash) -- frees old mem + end + end + unpackoutlines(data) + else + data = { + filename = filename, + size = 0, + time = time, + format = "unknown", + units = 1000, + glyphs = { } + } + end + return data + end + + fonts.hashes.shapes = table.setmetatableindex(function(t,k) + local d = identifiers[k] + local v = load(d.properties.filename,d.subindex) + t[k] = v + return v + end) + +end diff --git a/tex/context/base/font-pat.lua b/tex/context/base/font-pat.lua index 049853796..357b12ea0 100644 --- a/tex/context/base/font-pat.lua +++ b/tex/context/base/font-pat.lua @@ -11,29 +11,12 @@ if not modules then modules = { } end modules ['font-pat'] = { local match, lower = string.match, string.lower --- Older versions of latin modern didn't have the designsize set so for them we --- get it from the name reporter moved to elsewhere. - local fonts = fonts local otf = fonts.handlers.otf local patches = otf.enhancers.patches local register = patches.register local report = patches.report --- local function patch(data,filename) --- if not metadata.design_size or metadata.design_size == 0 then --- local ds = match(file.basename(lower(filename)),"(%d+)") --- if ds then --- report("font %a has design size %a",filename,ds) --- metadata.design_size = tonumber(ds) * 10 --- end --- end --- end --- --- register("after","migrate metadata","^lmroman", patch) --- register("after","migrate metadata","^lmsans", patch) --- register("after","migrate metadata","^lmtypewriter",patch) - -- For some reason (either it's a bug in the font, or it's a problem in the -- library) the palatino arabic fonts don't have the mkmk features properly -- set up. diff --git a/tex/context/base/font-syn.lua b/tex/context/base/font-syn.lua index da9c19967..fa152466d 100644 --- a/tex/context/base/font-syn.lua +++ b/tex/context/base/font-syn.lua @@ -15,14 +15,15 @@ if not modules then modules = { } end modules ['font-syn'] = { -- new lua loader: 5 sec local next, tonumber, type, tostring = next, tonumber, type, tostring -local sub, gsub, lower, match, find, lower, upper = string.sub, string.gsub, string.lower, string.match, string.find, string.lower, string.upper -local find, gmatch = string.find, string.gmatch -local concat, sort, format = table.concat, table.sort, string.format +local sub, gsub, match, find, lower, upper = string.sub, string.gsub, string.match, string.find, string.lower, string.upper +local concat, sort = table.concat, table.sort local serialize, sortedhash = table.serialize, table.sortedhash local lpegmatch = lpeg.match local unpack = unpack or table.unpack local formatters, topattern = string.formatters, string.topattern local round = math.round +local P, R, S, C, Cc, Ct, Cs = lpeg.P, lpeg.R, lpeg.S, lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cs +local lpegmatch, lpegpatterns = lpeg.match, lpeg.patterns local allocate = utilities.storage.allocate local sparse = utilities.storage.sparse @@ -42,12 +43,8 @@ local findfile = resolvers.findfile local cleanpath = resolvers.cleanpath local resolveprefix = resolvers.resolve -local fontloader = fontloader -local font_to_table = fontloader.to_table -local open_font = fontloader.open -local get_font_info = fontloader.info -local close_font = fontloader.close -local font_fields = fontloader.fields +----- fontloader = fontloader -- still needed for pfb (now) +----- get_font_info = fontloader.info local settings_to_hash = utilities.parsers.settings_to_hash_tolerant @@ -76,7 +73,7 @@ fonts.treatments = treatments names.data = names.data or allocate { } -names.version = 1.123 +names.version = 1.125 names.basename = "names" names.saved = false names.loaded = false @@ -94,8 +91,6 @@ directives.register("fonts.usesystemfonts", function(v) usesystemfonts = toboole

A few helpers.

--ldx]]-- -local P, C, Cc, Cs = lpeg.P, lpeg.C, lpeg.Cc, lpeg.Cs - -- -- what to do with these -- -- -- -- thin -> thin @@ -313,122 +308,54 @@ end but to keep the overview, we define them here.

--ldx]]-- -filters.otf = get_font_info -filters.ttf = get_font_info -filters.ttc = get_font_info -filters.dfont = get_font_info +-- filters.dfont = get_font_info --- We had this as temporary solution because we needed a bit more info but in the --- meantime it got an interesting side effect: currently luatex delays loading of e.g. --- glyphs so here we first load and then discard which is a waste. In the past it did --- free memory because a full load was done. One of these things that goes unnoticed. --- --- missing: names, units_per_em, design_range_bottom, design_range_top, design_size, --- pfminfo, top_side_bearing - --- local function get_full_info(...) -- check with taco what we get / could get --- local ff = open_font(...) --- if ff then --- local d = ff -- and font_to_table(ff) --- d.glyphs, d.subfonts, d.gpos, d.gsub, d.lookups = nil, nil, nil, nil, nil --- close_font(ff) --- return d --- else --- return nil, "error in loading font" --- end --- end +filters.otf = fonts.handlers.otf.readers.getinfo +filters.ttf = filters.otf +filters.ttc = filters.otf --- Phillip suggested this faster variant but it's still a hack as fontloader.info should --- return these keys/values (and maybe some more) but at least we close the loader which --- might save some memory in the end. - --- local function get_full_info(name) --- local ff = open_font(name) --- if ff then --- local fields = table.tohash(font_fields(ff),true) -- isn't that one stable --- local d = { --- names = fields.names and ff.names, --- familyname = fields.familyname and ff.familyname, --- fullname = fields.fullname and ff.fullname, --- fontname = fields.fontname and ff.fontname, --- weight = fields.weight and ff.weight, --- italicangle = fields.italicangle and ff.italicangle, --- units_per_em = fields.units_per_em and ff.units_per_em, --- design_range_bottom = fields.design_range_bottom and ff.design_range_bottom, --- design_range_top = fields.design_range_top and ff.design_range_top, --- design_size = fields.design_size and ff.design_size, --- italicangle = fields.italicangle and ff.italicangle, --- pfminfo = fields.pfminfo and ff.pfminfo, --- top_side_bearing = fields.top_side_bearing and ff.top_side_bearing, --- } --- -- setmetatableindex(d,function(t,k) --- -- report_names("warning, trying to access field %a in font table of %a",k,name) --- -- end) --- close_font(ff) --- return d --- else --- return nil, "error in loading font" --- end --- end - --- more efficient: - -local fields = nil - -local function get_full_info(name) - local ff = open_font(name) - if ff then - if not fields then - fields = table.tohash(font_fields(ff),true) - end - -- unfortunately luatex aborts when a field is not available - local d = { - names = fields.names and ff.names, - familyname = fields.familyname and ff.familyname, - fullname = fields.fullname and ff.fullname, - fontname = fields.fontname and ff.fontname, - weight = fields.weight and ff.weight, - italicangle = fields.italicangle and ff.italicangle, - units_per_em = fields.units_per_em and ff.units_per_em, - design_range_bottom = fields.design_range_bottom and ff.design_range_bottom, - design_range_top = fields.design_range_top and ff.design_range_top, - design_size = fields.design_size and ff.design_size, - italicangle = fields.italicangle and ff.italicangle, - pfminfo = fields.pfminfo and ff.pfminfo, - top_side_bearing = fields.top_side_bearing and ff.top_side_bearing, -- not there - } - if d.italicangle then - d.italicangle = round(1000*d.italicangle)/1000 - end - -- setmetatableindex(d,function(t,k) - -- report_names("warning, trying to access field %a in font table of %a",k,name) - -- end) - close_font(ff) - return d +local function normalize(t) + local boundingbox = t.fontbbox + if boundingbox then + for i=1,#boundingbox do + boundingbox[i] = tonumber(boundingbox[i]) + end else - return nil, "error in loading font" - end + boundingbox = { 0, 0, 0, 0 } + end + return { + copyright = t.copyright, + fontname = t.fontname, + fullname = t.fullname, + familyname = t.familyname, + weight = t.weight, + widtht = t.width, + italicangle = tonumber(t.italicangle) or 0, + monospaced = toboolean(t.isfixedpitch) or false, + boundingbox = boundingbox, + version = t.version, + capheight = tonumber(t.capheight), + xheight = tonumber(t.xheight), + ascender = tonumber(t.ascender), + descender = tonumber(t.descender), + } end --- As we have lazy loading anyway, this one still is full and with less code than --- the previous one. But this depends on the garbage collector to kick in and in the --- current version that somehow happens not that often (on my machine I end up with --- soem 3 GB extra before that happens). +local p_spaces = lpegpatterns.whitespace +local p_number = (R("09")+S(".-+"))^1 / tonumber +local p_boolean = P("false") * Cc(false) + + P("false") * Cc(false) +local p_string = P("(") * C((lpegpatterns.nestedparents + 1 - P(")"))^1) * P(")") +local p_array = P("[") * Ct((p_number + p_boolean + p_string + p_spaces^1)^1) * P("]") + + P("{") * Ct((p_number + p_boolean + p_string + p_spaces^1)^1) * P("}") --- local function get_full_info(...) --- local ff = open_font(...) --- if ff then --- local d = { } -- ff is userdata so [1] or # fails on it --- setmetatableindex(d,ff) --- return d -- garbage collection will do the close_font(ff) --- else --- return nil, "error in loading font" --- end --- end +local p_key = P("/") * C(R("AZ","az")^1) +local p_value = p_string + + p_number + + p_boolean + + p_array -fontloader.fullinfo = get_full_info -filters .otf = get_full_info -filters .ttf = get_full_info +local p_entry = p_key * p_spaces^0 * p_value function filters.afm(name) -- we could parse the afm file as well, and then report an error but @@ -441,24 +368,46 @@ function filters.afm(name) local f = io.open(name) if f then local hash = { } - for line in f:lines() do -- slow - local key, value = match(line,"^(.+)%s+(.+)%s*$") - if key and #key > 0 then - hash[lower(key)] = value - end + local okay = false + for line in f:lines() do -- slow but only a few lines at the beginning if find(line,"StartCharMetrics",1,true) then break + else + local key, value = match(line,"^(.+)%s+(.+)%s*$") + if key and #key > 0 then + hash[lower(key)] = value + end end end f:close() - return hash + return normalize(hash) end end return nil, "no matching pfb file" end function filters.pfb(name) - return get_font_info(name) + local f = io.open(name) + if f then + local hash = { } + local okay = false + for line in f:lines() do -- slow but only a few lines at the beginning + if find(line,"dict begin") then + okay = true + elseif not okay then + -- go on + elseif find(line,"currentdict end") then + break + else + local key, value = lpegmatch(p_entry,line) + if key and value then + hash[lower(key)] = value + end + end + end + f:close() + return normalize(hash) + end end --[[ldx-- @@ -468,8 +417,7 @@ for combination with the weight of a font.

--ldx]]-- filters.list = { - "otf", "ttf", "ttc", "dfont", "afm", - -- "ttc", "otf", "ttf", "dfont", "afm", + "otf", "ttf", "ttc", "afm", -- no longer dfont support (for now) } -- to be considered: loop over paths per list entry (so first all otf ttf etc) @@ -574,18 +522,18 @@ end names.cleanname = cleanname names.cleanfilename = cleanfilename -local function check_names(result) - local names = result.names - if names then - for i=1,#names do - local name = names[i] - if name.lang == "English (US)" then - return name.names - end - end - end - return result -end +-- local function check_names(result) +-- local names = result.names +-- if names then +-- for i=1,#names do +-- local name = names[i] +-- if name.lang == "English (US)" then +-- return name.names +-- end +-- end +-- end +-- return result +-- end local function walk_tree(pathlist,suffix,identify) if pathlist then @@ -611,27 +559,16 @@ end local function check_name(data,result,filename,modification,suffix,subfont) -- shortcuts local specifications = data.specifications - -- prepare - local names = check_names(result) -- fetch --- if string.find(string.lower(filename),"ebgaramond") then --- inspect(result) --- inspect(names) --- end - -if string.find(filename,"avkv") then - inspect(result) -end - - local familyname = names and names.preffamilyname or result.familyname - local fullname = names and names.fullname or result.fullname + local familyname = result.familyname + local fullname = result.fullname local fontname = result.fontname - local subfamily = names and names.subfamily or result.subfamily - local modifiers = names and names.prefmodifiers or result.modifiers - local weight = names and names.weight or result.weight + local subfamily = result.subfamily + local modifiers = result.modifiers + local weight = result.weight local italicangle = tonumber(result.italicangle) - local subfont = subfont or nil - local rawname = fullname or fontname or familyname + local subfont = subfont + local rawname = fullname or fontname or familyname local filebase = removesuffix(basename(filename)) local cleanfilename = cleanname(filebase) -- for WS -- normalize @@ -667,18 +604,18 @@ end fullname = fullname or fontname familyname = familyname or fontname -- we do these sparse -- todo: check table type or change names in ff loader - local units = result.units_per_em or result.emunits or 1000 -- can be zero too - local minsize = result.design_range_bottom or result.mindesignsize or 0 - local maxsize = result.design_range_top or result.maxdesignsize or 0 - local designsize = result.design_size or result.designsize or 0 - local angle = result.italicangle or 0 - local pfminfo = result.pfminfo - local pfmwidth = (pfminfo and pfminfo.width ) or result.pfmwidth or 0 - local pfmweight = (pfminfo and pfminfo.weight) or result.pfmweight or 0 + local units = result.units or 1000 -- can be zero too + local designsize = result.designsize or 0 + local minsize = result.mindesign or 0 + local maxsize = result.maxdesign or 0 + local angle = result.italicangle or 0 + local pfmwidth = result.pfmwidth or 0 + local pfmweight = result.pfmweight or 0 -- specifications[#specifications + 1] = { filename = filename, -- unresolved cleanfilename = cleanfilename, + -- subfontindex = subfont, format = lower(suffix), subfont = subfont, rawname = rawname, @@ -700,9 +637,6 @@ end designsize = designsize ~= 0 and designsize or nil, modification = modification ~= 0 and modification or nil, } --- inspect(filename) --- inspect(result) --- inspect(specifications[#specifications]) end local function cleanupkeywords() diff --git a/tex/context/base/font-tmp.lua b/tex/context/base/font-tmp.lua new file mode 100644 index 000000000..5b4a08740 --- /dev/null +++ b/tex/context/base/font-tmp.lua @@ -0,0 +1,120 @@ +if not modules then modules = { } end modules ['font-tmp'] = { + 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" +} + +-- There is a complet efeature loader but it needs a bit of testing, first so this +-- one does design size only (as needed for identifying). + +local next, type = next, type + +local report = logs.reporter("otf reader") + +local files = utilities.files + +local readushort = files.readcardinal2 -- 16-bit unsigned integer +local readulong = files.readcardinal4 -- 24-bit unsigned integer +local readshort = files.readinteger2 -- 16-bit signed integer + +local readtag = function(f) return f:read(4) end +local skipshort = function(f,n) f:read(n and 2*n or 2) end + +local readers = fonts.handlers.otf.readers + +local plugins = { } + +function plugins.size(f,fontdata,tableoffset,parameters) + if not fontdata.designsize then + f:seek("set",tableoffset+parameters) + local designsize = readushort(f) + if designsize > 0 then + fontdata.designsize = designsize + skipshort(f,2) + fontdata.minsize = readushort(f) + fontdata.maxsize = readushort(f) + end + end +end + +local function readscripts(f,fontdata,what) + local datatable = fontdata.tables[what] + if not datatable then + return + end + local tableoffset = datatable.offset + f:seek("set",tableoffset) + local version = readulong(f) + if version ~= 0x00010000 then + report("table version %a of %a is not supported (yet), maybe font %s is bad",version,what,fontdata.filename) + return + end + -- + local scriptoffset = tableoffset + readushort(f) + local featureoffset = tableoffset + readushort(f) + local lookupoffset = tableoffset + readushort(f) + -- + f:seek("set",scriptoffset) + local nofscripts = readushort(f) + local scripts = { } + for i=1,nofscripts do + scripts[readtag(f)] = scriptoffset + readushort(f) + end + local languagesystems = table.setmetatableindex("table") -- we share when possible + for script, offset in next, scripts do + f:seek("set",offset) + local defaultoffset = readushort(f) + local noflanguages = readushort(f) + local languages = { } + if defaultoffset > 0 then + languages.dflt = languagesystems[offset + defaultoffset] + end + for i=1,noflanguages do + local language = readtag(f) + local offset = offset + readushort(f) + languages[language] = languagesystems[offset] + end + scripts[script] = languages + end + -- + f:seek("set",featureoffset) + local features = { } + local noffeatures = readushort(f) + for i=1,noffeatures do + features[i] = { + tag = readtag(f), + offset = readushort(f) + } + end + -- + for i=1,noffeatures do + local feature = features[i] + local offset = featureoffset + feature.offset + f:seek("set",offset) + local parameters = readushort(f) -- feature.parameters + local noflookups = readushort(f) + skipshort(f,noflookups+1) + if parameters > 0 then + feature.parameters = parameters + local plugin = plugins[feature.tag] + if plugin then + plugin(f,fontdata,offset,parameters) + end + end + end +end + +function readers.gsub(f,fontdata,specification) + if specification.details then + readscripts(f,fontdata,"gsub") + end +end + +function readers.gpos(f,fontdata,specification) + if specification.details then + readscripts(f,fontdata,"gpos") + end +end + diff --git a/tex/context/base/font-ttf.lua b/tex/context/base/font-ttf.lua new file mode 100644 index 000000000..f89c0b14e --- /dev/null +++ b/tex/context/base/font-ttf.lua @@ -0,0 +1,475 @@ +if not modules then modules = { } end modules ['font-ttf'] = { + version = 1.001, + comment = "companion to font-ini.mkiv", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} + +local next, type, unpack = next, type, unpack +local bittest = bit32.btest +local sqrt = math.sqrt + +local report = logs.reporter("otf reader","ttf") + +local files = utilities.files + +local readbyte = files.readcardinal1 -- 8-bit unsigned integer +local readushort = files.readcardinal2 -- 16-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 read2dot14 = files.read2dot14 -- 16-bit signed fixed number with the low 14 bits of fraction (2.14) (F2DOT14) + +local function mergecomposites(glyphs,shapes) + + local function merge(index,shape,components) + local contours = { } + local nofcontours = 0 + for i=1,#components do + local component = components[i] + local subindex = component.index + local subshape = shapes[subindex] + local subcontours = subshape.contours + if not subcontours then + local subcomponents = subshape.components + if subcomponents then + subcontours = merge(subindex,subshape,subcomponents) + end + end + if subcontours then + local matrix = component.matrix + local xscale = matrix[1] + local xrotate = matrix[2] + local yrotate = matrix[3] + local yscale = matrix[4] + local xoffset = matrix[5] + local yoffset = matrix[6] + for i=1,#subcontours do + local points = subcontours[i] + local result = { } + for i=1,#points do + local p = points[i] + local x = p[1] + local y = p[2] + result[i] = { + xscale * x + xrotate * y + xoffset, + yscale * y + yrotate * x + yoffset, + p[3] + } + end + nofcontours = nofcontours + 1 + contours[nofcontours] = result + end + else + report("missing contours composite %s, component %s of %s, glyph %s",index,i,#components,subindex) + end + end + shape.contours = contours + shape.components = nil + return contours + end + + for index=1,#glyphs do + local shape = shapes[index] + local components = shape.components + if components then + merge(index,shape,components) + end + end + +end + +local function readnothing(f,nofcontours) + return { + type = "nothing", + } +end + +-- begin of converter + +-- make paths: the ff code is quite complex but it looks like we need to deal +-- with all kind of on curve border cases + +local function curveto(m_x,m_y,l_x,l_y,r_x,r_y) -- todo: inline this + return { + l_x + 2/3 *(m_x-l_x), l_y + 2/3 *(m_y-l_y), + r_x + 2/3 *(m_x-r_x), r_y + 2/3 *(m_y-r_y), + r_x, r_y, "c" -- "curveto" + } +end + +-- We could omit the operator which saves some 10%: +-- +-- #2=lineto #4=quadratic #6=cubic #3=moveto (with "m") +-- +-- For the moment we keep the original outlines but that default might change +-- in the future. In any case, a backend should support both. +-- +-- The code is a bit messy. I looked at the ff code but it's messy too. It has +-- to do with the fact that we need to look at points on the curve and control +-- points in between. This also means that we start at point 2 and have to look at +-- point 1 when we're at the end. We still use a ps like storage with the operator +-- last in an entry. It's typical code that evolves stepwise till a point of no +-- comprehension. + +local function contours2outlines(glyphs,shapes) + local quadratic = true + -- local quadratic = false + for index=1,#glyphs do + local glyph = glyphs[index] + local shape = shapes[index] + local contours = shape.contours + if contours then + local nofcontours = #contours + local segments = { } + local nofsegments = 0 + glyph.segments = segments + if nofcontours > 0 then + for i=1,nofcontours do + local contour = contours[i] + local nofcontour = #contour + if nofcontour > 0 then + local first_pt = contour[1] + local first_on = first_pt[3] + -- todo no new tables but reuse lineto and quadratic + if nofcontour == 1 then + -- this can influence the boundingbox + first_pt[3] = "m" -- "moveto" + nofsegments = nofsegments + 1 + segments[nofsegments] = first_pt + else -- maybe also treat n == 2 special + local first_on = first_pt[3] + local last_pt = contour[nofcontour] + local last_on = last_pt[3] + local start = 1 + local control_pt = false + if first_on then + start = 2 + else + if last_on then + first_pt = last_pt + else + first_pt = { (first_pt[1]+last_pt[1])/2, (first_pt[2]+last_pt[2])/2, false } + end + control_pt = first_pt + end + nofsegments = nofsegments + 1 + segments[nofsegments] = { first_pt[1], first_pt[2], "m" } -- "moveto" + local previous_pt = first_pt + for i=start,nofcontour do + local current_pt = contour[i] + local current_on = current_pt[3] + local previous_on = previous_pt[3] + if previous_on then + if current_on then + -- both normal points + nofsegments = nofsegments + 1 + segments[nofsegments] = { current_pt[1], current_pt[2], "l" } -- "lineto" + else + control_pt = current_pt + end + elseif current_on then + local ps = segments[nofsegments] + nofsegments = nofsegments + 1 + if quadratic then + segments[nofsegments] = { control_pt[1], control_pt[2], current_pt[1], current_pt[2], "q" } -- "quadraticto" + else + local p = segments[nofsegments-1] local n = #p + segments[nofsegments] = curveto(control_pt[1],control_pt[2],p[n-2],p[n-1],current_pt[1],current_pt[2]) + end + control_pt = false + else + nofsegments = nofsegments + 1 + local halfway_x = (previous_pt[1]+current_pt[1])/2 + local halfway_y = (previous_pt[2]+current_pt[2])/2 + if quadratic then + segments[nofsegments] = { control_pt[1], control_pt[2], halfway_x, halfway_y, "q" } -- "quadraticto" + else + local p = segments[nofsegments-1] local n = #p + segments[nofsegments] = curveto(control_pt[1],control_pt[2],p[n-2],p[n-1],halfway_x,halfway_y) + end + control_pt = current_pt + end + previous_pt = current_pt + end + if first_pt == last_pt then + -- we're already done, probably a simple curve + else + nofsegments = nofsegments + 1 + if not control_pt then + segments[nofsegments] = { first_pt[1], first_pt[2], "l" } -- "lineto" + elseif quadratic then + segments[nofsegments] = { control_pt[1], control_pt[2], first_pt[1], first_pt[2], "q" } -- "quadraticto" + else + local p = last_pt local n = #p + segments[nofsegments] = curveto(control_pt[1],control_pt[2],p[n-2],p[n-1],first_pt[1],first_pt[2]) + end + end + end + end + end + end + end + end +end + +-- end of converter + +local function readglyph(f,nofcontours) + local points = { } + local endpoints = { } + local instructions = { } + local flags = { } + for i=1,nofcontours do + endpoints[i] = readshort(f) + 1 + end + local nofpoints = endpoints[nofcontours] + local nofinstructions = readushort(f) + f:seek("set",f:seek()+nofinstructions) + -- because flags can repeat we don't know the amount ... in fact this is + -- not that efficient (small files but more mem) + local i = 1 + while i <= nofpoints do + local flag = readbyte(f) + flags[i] = flag + if bittest(flag,0x0008) then + for j=1,readbyte(f) do + i = i + 1 + flags[i] = flag + end + end + i = i + 1 + end + -- first come the x coordinates, and next the y coordinates and they + -- can be repeated + local x = 0 + for i=1,nofpoints do + local flag = flags[i] + local short = bittest(flag,0x0002) + local same = bittest(flag,0x0010) + if short then + if same then + x = x + readbyte(f) + else + x = x - readbyte(f) + end + elseif same then + -- copy + else + x = x + readshort(f) + end + points[i] = { x, y, bittest(flag,0x0001) } + end + local y = 0 + for i=1,nofpoints do + local flag = flags[i] + local short = bittest(flag,0x0004) + local same = bittest(flag,0x0020) + if short then + if same then + y = y + readbyte(f) + else + y = y - readbyte(f) + end + elseif same then + -- copy + else + y = y + readshort(f) + end + points[i][2] = y + end + -- we could integrate this if needed + local first = 1 + for i=1,#endpoints do + local last = endpoints[i] + endpoints[i] = { unpack(points,first,last) } + first = last + 1 + end + return { + type = "glyph", + -- points = points, + contours = endpoints, + } +end + +local function readcomposite(f) + local components = { } + local nofcomponents = 0 + local instructions = false + while true do + local flags = readushort(f) + local index = readushort(f) + ----- f_words = bittest(flags,0x0001) + local f_xyarg = bittest(flags,0x0002) + ----- f_round = bittest(flags,0x0004+0x0002) + ----- f_scale = bittest(flags,0x0008) + ----- f_reserved = bittest(flags,0x0010) + ----- f_more = bittest(flags,0x0020) + ----- f_xyscale = bittest(flags,0x0040) + ----- f_matrix = bittest(flags,0x0080) + ----- f_instruct = bittest(flags,0x0100) + ----- f_usemine = bittest(flags,0x0200) + ----- f_overlap = bittest(flags,0x0400) + local f_offset = bittest(flags,0x0800) + ----- f_uoffset = bittest(flags,0x1000) + local xscale = 1 + local xrotate = 0 + local yrotate = 0 + local yscale = 1 + local xoffset = 0 + local yoffset = 0 + local base = false + local reference = false + if f_xyarg then + if bittest(flags,0x0001) then -- f_words + xoffset = readshort(f) + yoffset = readshort(f) + else + xoffset = readchar(f) -- signed byte, stupid name + yoffset = readchar(f) -- signed byte, stupid name + end + else + if bittest(flags,0x0001) then -- f_words + base = readshort(f) + reference = readshort(f) + else + base = readchar(f) -- signed byte, stupid name + reference = readchar(f) -- signed byte, stupid name + end + end + if bittest(flags,0x0008) then -- f_scale + xscale = read2dot14(f) + yscale = xscale + if f_xyarg and f_offset then + xoffset = xoffset * xscale + yoffset = yoffset * yscale + end + elseif bittest(flags,0x0040) then -- f_xyscale + xscale = read2dot14(f) + yscale = read2dot14(f) + if f_xyarg and f_offset then + xoffset = xoffset * xscale + yoffset = yoffset * yscale + end + elseif bittest(flags,0x0080) then -- f_matrix + xscale = read2dot14(f) + xrotate = read2dot14(f) + yrotate = read2dot14(f) + yscale = read2dot14(f) + if f_xyarg and f_offset then + xoffset = xoffset * sqrt(xscale ^2 + xrotate^2) + yoffset = yoffset * sqrt(yrotate^2 + yscale ^2) + end + end + nofcomponents = nofcomponents + 1 + components[nofcomponents] = { + index = index, + usemine = bittest(flags,0x0200), -- f_usemine + round = bittest(flags,0x0006), -- f_round, + base = base, + reference = reference, + matrix = { xscale, xrotate, yrotate, yscale, xoffset, yoffset }, + } + if bittest(flags,0x0100) then + instructions = true + end + if not bittest(flags,0x0020) then -- f_more + break + end + end + return { + type = "composite", + components = components, + } +end + +-- function readers.cff(f,offset,glyphs,doshapes) -- false == no shapes (nil or true otherwise) + +-- 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 (todo: cleanup finalizer). + +function fonts.handlers.otf.readers.loca(f,fontdata,specification) + if specification.glyphs then + local datatable = fontdata.tables.loca + if datatable then + -- locations are relative to the glypdata table (glyf) + local offset = fontdata.tables.glyf.offset + local format = fontdata.fontheader.indextolocformat + local locations = { } + f:seek("set",datatable.offset) + if format == 1 then + local nofglyphs = datatable.length/4 - 1 + -1 + for i=0,nofglyphs do + locations[i] = offset + readulong(f) + end + fontdata.nofglyphs = nofglyphs + else + local nofglyphs = datatable.length/2 - 1 + -1 + for i=0,nofglyphs do + locations[i] = offset + readushort(f) * 2 + end + fontdata.nofglyphs = nofglyphs + end + fontdata.locations = locations + end + end +end + +function fonts.handlers.otf.readers.glyf(f,fontdata,specification) -- part goes to cff module + if specification.glyphs then + local datatable = fontdata.tables.glyf + if datatable then + local locations = fontdata.locations + if locations then + local glyphs = fontdata.glyphs + local nofglyphs = fontdata.nofglyphs + local filesize = fontdata.filesize + local nothing = { 0, 0, 0, 0 } + local shapes = { } + local loadshapes = specification.shapes + for index=0,nofglyphs do + local location = locations[index] + if location >= filesize then + report("discarding %s glyphs due to glyph location bug",nofglyphs-index+1) + fontdata.nofglyphs = index - 1 + fontdata.badfont = true + break + elseif location > 0 then + f:seek("set",location) + local nofcontours = readshort(f) + glyphs[index].boundingbox = { + readshort(f), -- xmin + readshort(f), -- ymin + readshort(f), -- xmax + readshort(f), -- ymax + } + if not loadshapes then + -- save space + elseif nofcontours == 0 then + shapes[index] = readnothing(f,nofcontours) + elseif nofcontours > 0 then + shapes[index] = readglyph(f,nofcontours) + else + shapes[index] = readcomposite(f,nofcontours) + end + else + if loadshapes then + shapes[index] = { } + end + glyphs[index].boundingbox = nothing + end + end + if loadshapes then + mergecomposites(glyphs,shapes) + contours2outlines(glyphs,shapes) + end + end + end + end +end diff --git a/tex/context/base/lxml-lpt.lua b/tex/context/base/lxml-lpt.lua index decb6567b..add29241e 100644 --- a/tex/context/base/lxml-lpt.lua +++ b/tex/context/base/lxml-lpt.lua @@ -837,14 +837,12 @@ xml.nodesettostring = nodesettostring local lpath -- we have a harmless kind of circular reference -local lshowoptions = { functions = false } - local function lshow(parsed) if type(parsed) == "string" then parsed = lpath(parsed) end report_lpath("%s://%s => %s",parsed.protocol or xml.defaultprotocol,parsed.pattern, - table.serialize(parsed,false,lshowoptions)) + table.serialize(parsed,false)) end xml.lshow = lshow diff --git a/tex/context/base/meta-imp-outlines.mkiv b/tex/context/base/meta-imp-outlines.mkiv new file mode 100644 index 000000000..d47ec7754 --- /dev/null +++ b/tex/context/base/meta-imp-outlines.mkiv @@ -0,0 +1,150 @@ +%D \module +%D [ file=meta-imp-outlines, +%D version=2015.06.02, +%D title=\METAPOST\ Graphics, +%D subtitle=Outlines, +%D author=Hans Hagen, +%D date=\currentdate, +%D copyright={PRAGMA ADE \& \CONTEXT\ Development Team}] +%C +%C This module is part of the \CONTEXT\ macro||package and is +%C therefore copyrighted by \PRAGMA. See mreadme.pdf for +%C details. + +\startluacode + +local concat = table.concat +local formatters = string.formatters +local validstring = string.valid + +local f_setbounds = formatters["setbounds currentpicture to (%s) enlarged %.4G;"] +local f_index = formatters['draw anchored.bot(textext("\\tttf\\setstrut\\strut index %i") ysized 2bp ,.5[llcorner currentpicture,lrcorner currentpicture] shifted (0,%.4G));'] +local f_unicode = formatters['draw anchored.bot(textext("\\tttf\\setstrut\\strut unicode %05X") ysized 2bp ,.5[llcorner currentpicture,lrcorner currentpicture] shifted (0,%.4G));'] + +local f_in_red = formatters["draw %s withpen pencircle scaled .15 withcolor .5red;"] +local f_in_green = formatters["draw %s withpen pencircle scaled .15 withcolor .5green;"] +local f_in_blue = formatters["draw %s withpen pencircle scaled .15 withcolor .5blue;"] +local f_in_gray = formatters["draw image(%s) withcolor .75yellow;"] + +local f_glyph = formatters [ [[ +pickup pencircle scaled .15; +pointlabelfont := "Mono sa .125"; +pointlabelscale := 1bp ; +drawoptionsfactor := .2bp ; +originlength := 2bp ; +%s; +]] ] + +local metapost = fonts.metapost + +local variables = interfaces.variables + +local v_all = variables.all +local v_page = variables.page +local v_text = variables.text +local v_command = variables.command + +function metapost.showglyph(specification) + local fontid = font.current() + local shapedata = fonts.hashes.shapes[fontid] -- by index + local chardata = fonts.hashes.characters[fontid] -- by unicode + local shapeglyphs = shapedata.glyphs + local character = validstring(specification.character) + local index = validstring(specification.index) + local alternative = validstring(specification.alternative) + local command = validstring(specification.command) + + local function shape(index,what,f_comment) + if not index then + return + end + local glyph = shapeglyphs[index] + if glyph and glyph.segments or glyph.sequence then + local units = data.fontheader and data.fontheader.emsize or 1000 + local factor = 100/units + local paths = metapost.paths(glyph,factor) + if #paths > 0 then + local graphic = f_glyph(concat{ + f_in_gray(metapost.fill(paths)), + metapost.draw(paths,true), + f_in_red(metapost.boundingbox(glyph,factor)), + f_in_green(metapost.widthline(glyph,factor)), + f_in_blue(metapost.zeroline(glyph,factor)), + f_setbounds(metapost.maxbounds(data,index,factor),offset or 1), + f_comment(what,1) + }) + if alternative == v_page then + context.startMPpage() + context(graphic) + context.stopMPpage() + elseif alternative == v_command then + context[command](graphic) + else -- v_text + context.startMPcode() + context(graphic) + context.stopMPcode() + end + end + end + end + + if character == v_all then + for u, c in table.sortedhash(chardata) do + shape(c.index,u,f_unicode) + end + return + end + if type(character) == "string" then + character = utf.byte(character) + end + if type(character) == "number" then + local c = chardata[character] + if c then + shape(c.index,c.index,f_index) + end + return + end + if type(index) == "number" then + shape(index,index,f_index) + return + end + for index=1,#shapeglyphs do + shape(index,index,f_index) + end +end + +\stopluacode + +\unprotect + +\unexpanded\def\showshape + {\dosingleargument\meta_shapes_show} + +\def\meta_shapes_show[#1]% + {\begingroup + \getdummyparameters[\c!alternative=\v!text,#1]% + \ctxlua{fonts.metapost.showglyph{ + character = "\dummyparameter\c!character", + index = "\dummyparameter\c!index", + alternative = "\dummyparameter\c!alternative", + command = "\dummyparameter\c!command", + }}% + \endgroup} + +\protect + +\continueifinputfile{meta-imp-outlines.mkiv} + +\starttext + +\setupbodyfont[pagella] + +\showshape[character=all,alternative=page] + +% \setupbodyfont[dejavu] +% \showshape[character=P,alternative=text] + +% \definedfont[almfixed] +% \showshape[character=all,alternative=page] + +\stoptext diff --git a/tex/context/base/mult-def.mkiv b/tex/context/base/mult-def.mkiv index d547a7b81..bad77eac2 100644 --- a/tex/context/base/mult-def.mkiv +++ b/tex/context/base/mult-def.mkiv @@ -72,6 +72,9 @@ \def\c!keeptogether {keeptogether} \def\c!viewerprefix {viewerprefix} +\def\c!index {index} % not a register but a number (of a glyph) +\def\c!character {character} + \def\v!display {display} \def\v!inline {inline} diff --git a/tex/context/base/publ-ini.lua b/tex/context/base/publ-ini.lua index 3791bd532..368b8c6ff 100644 --- a/tex/context/base/publ-ini.lua +++ b/tex/context/base/publ-ini.lua @@ -2336,7 +2336,7 @@ do marked_dataset = dataset marked_list = list marked_method = method - -- btxflushmarked() -- here (could also be done in caller) + btxflushmarked() -- here (could also be done in caller) else marked_todo = false end @@ -2502,6 +2502,9 @@ do -- local found, todo, list = findallused(dataset,reference,internal,method == v_text or method == v_always) -- also when not in list -- +-- inspect(found) +-- inspect(todo) +-- inspect(list) if not found or #found == 0 then report("no entry %a found in dataset %a",reference,dataset) elseif not setup then @@ -2629,7 +2632,7 @@ do marked_dataset = dataset marked_list = list marked_method = method - -- btxflushmarked() -- here (could also be done in caller) + btxflushmarked() -- here (could also be done in caller) else marked_todo = false end diff --git a/tex/context/base/publ-ini.mkiv b/tex/context/base/publ-ini.mkiv index fd9e4ad97..fb61788be 100644 --- a/tex/context/base/publ-ini.mkiv +++ b/tex/context/base/publ-ini.mkiv @@ -1315,7 +1315,7 @@ after {\p_publ_cite_after}% \relax \iftrialtypesetting\else - \clf_btxflushmarked + %\clf_btxflushmarked \fi} \let\dobtxcitevariantblob\publ_cite_handle_variant_blob % command can use it via lua @@ -1369,7 +1369,7 @@ dataset {\currentbtxdataset}% reference {\currentbtxreference}% \relax - \clf_btxflushmarked + %\clf_btxflushmarked \endgroup \fi} diff --git a/tex/context/base/status-files.pdf b/tex/context/base/status-files.pdf index 0349ed89b..0f9551bc7 100644 Binary files a/tex/context/base/status-files.pdf and b/tex/context/base/status-files.pdf differ diff --git a/tex/context/base/status-lua.pdf b/tex/context/base/status-lua.pdf index 72620604e..f405b93ea 100644 Binary files a/tex/context/base/status-lua.pdf and b/tex/context/base/status-lua.pdf differ diff --git a/tex/context/base/util-tab.lua b/tex/context/base/util-tab.lua index 0ab388826..d6f3d6731 100644 --- a/tex/context/base/util-tab.lua +++ b/tex/context/base/util-tab.lua @@ -557,7 +557,7 @@ local f_table_finish = formatters["}"] local spaces = utilities.strings.newrepeater(" ") -local serialize = table.serialize -- the extensive one, the one we started with +local original_serialize = table.serialize -- the extensive one, the one we started with -- there is still room for optimization: index run, key run, but i need to check with the -- latest lua for the value of #n (with holes) .. anyway for tracing purposes we want @@ -566,7 +566,7 @@ local serialize = table.serialize -- the extensive one, the one we started with local function serialize(root,name,specification) if type(specification) == "table" then - return serialize(root,name,specification) -- the original one + return original_serialize(root,name,specification) -- the original one end local t -- = { } diff --git a/tex/generic/context/luatex/luatex-fonts-merged.lua b/tex/generic/context/luatex/luatex-fonts-merged.lua index dee3ebec7..2d39df62b 100644 --- a/tex/generic/context/luatex/luatex-fonts-merged.lua +++ b/tex/generic/context/luatex/luatex-fonts-merged.lua @@ -1,6 +1,6 @@ -- merged file : luatex-fonts-merged.lua -- parent file : luatex-fonts.lua --- merge date : 06/12/15 10:06:12 +-- merge date : 06/13/15 09:52:29 do -- begin closure to overcome local limits and interference @@ -4380,6 +4380,7 @@ function constructors.scale(tfmdata,specification) local hdelta=delta local vdelta=delta target.designsize=parameters.designsize + target.units=units target.units_per_em=units local direction=properties.direction or tfmdata.direction or 0 target.direction=direction @@ -4781,11 +4782,20 @@ function constructors.finalize(tfmdata) if not parameters.slantfactor then parameters.slantfactor=tfmdata.slant or 0 end - if not parameters.designsize then - parameters.designsize=tfmdata.designsize or (factors.pt*10) + local designsize=parameters.designsize + if designsize then + parameters.minsize=tfmdata.minsize or designsize + parameters.maxsize=tfmdata.maxsize or designsize + else + designsize=factors.pt*10 + parameters.designsize=designsize + parameters.minsize=designsize + parameters.maxsize=designsize end + parameters.minsize=tfmdata.minsize or parameters.designsize + parameters.maxsize=tfmdata.maxsize or parameters.designsize if not parameters.units then - parameters.units=tfmdata.units_per_em or 1000 + parameters.units=tfmdata.units or tfmdata.units_per_em or 1000 end if not tfmdata.descriptions then local descriptions={} @@ -4848,6 +4858,7 @@ function constructors.finalize(tfmdata) tfmdata.auto_protrude=nil tfmdata.extend=nil tfmdata.slant=nil + tfmdata.units=nil tfmdata.units_per_em=nil tfmdata.cache=nil properties.finalized=true @@ -6086,7 +6097,7 @@ local keys={} function keys.FontName (data,line) data.metadata.fontname=strip (line) data.metadata.fullname=strip (line) end function keys.ItalicAngle (data,line) data.metadata.italicangle=tonumber (line) end -function keys.IsFixedPitch(data,line) data.metadata.isfixedpitch=toboolean(line,true) end +function keys.IsFixedPitch(data,line) data.metadata.monospaced=toboolean(line,true) end function keys.CharWidth (data,line) data.metadata.charwidth=tonumber (line) end function keys.XHeight (data,line) data.metadata.xheight=tonumber (line) end function keys.Descender (data,line) data.metadata.descender=tonumber (line) end @@ -6508,7 +6519,7 @@ local function copytotfm(data) local emdash=0x2014 local spacer="space" local spaceunits=500 - local monospaced=metadata.isfixedpitch + local monospaced=metadata.monospaced local charwidth=metadata.charwidth local italicangle=metadata.italicangle local charxheight=metadata.xheight and metadata.xheight>0 and metadata.xheight @@ -7195,7 +7206,7 @@ local report_otf=logs.reporter("fonts","otf loading") local fonts=fonts local otf=fonts.handlers.otf otf.glists={ "gsub","gpos" } -otf.version=2.814 +otf.version=2.815 otf.cache=containers.define("fonts","otf",otf.version,true) local hashes=fonts.hashes local definers=fonts.definers @@ -9165,9 +9176,13 @@ local function copytotfm(data,cache_id) local spaceunits=500 local spacer="space" local designsize=metadata.designsize or metadata.design_size or 100 + local minsize=metadata.minsize or metadata.design_range_bottom or designsize + local maxsize=metadata.maxsize or metadata.design_range_top or designsize local mathspecs=metadata.math if designsize==0 then designsize=100 + minsize=100 + maxsize=100 end if mathspecs then for name,value in next,mathspecs do @@ -9227,13 +9242,13 @@ local function copytotfm(data,cache_id) local fontname=metadata.fontname local fullname=metadata.fullname or fontname local psname=fontname or fullname - local units=metadata.units_per_em or 1000 + local units=metadata.units or metadata.units_per_em or 1000 if units==0 then units=1000 - metadata.units_per_em=1000 + metadata.units=1000 report_otf("changing %a units to %a",0,units) end - local monospaced=metadata.isfixedpitch or (pfminfo.panose and pfminfo.panose.proportion=="Monospaced") + local monospaced=metadata.monospaced or metadata.isfixedpitch or (pfminfo.panose and pfminfo.panose.proportion=="Monospaced") local charwidth=pfminfo.avgwidth local charxheight=pfminfo.os2_xheight and pfminfo.os2_xheight>0 and pfminfo.os2_xheight local italicangle=metadata.italicangle @@ -9298,8 +9313,10 @@ local function copytotfm(data,cache_id) end end parameters.designsize=(designsize/10)*65536 - parameters.ascender=abs(metadata.ascent or 0) - parameters.descender=abs(metadata.descent or 0) + parameters.minsize=(minsize/10)*65536 + parameters.maxsize=(maxsize/10)*65536 + parameters.ascender=abs(metadata.ascender or metadata.ascent or 0) + parameters.descender=abs(metadata.descender or metadata.descent or 0) parameters.units=units properties.space=spacer properties.encodingbytes=2 -- cgit v1.2.3