diff options
author | Hans Hagen <pragma@wxs.nl> | 2020-12-08 12:12:33 +0100 |
---|---|---|
committer | Context Git Mirror Bot <phg@phi-gamma.net> | 2020-12-08 12:12:33 +0100 |
commit | 59aead50be62c503185af6459f099dac0ebee313 (patch) | |
tree | c41bf2d7d49046bfe474663e6371ac4889f1e4ff | |
parent | aff2893a6d6652223e92e3de18e1260cbd533fea (diff) | |
download | context-59aead50be62c503185af6459f099dac0ebee313.tar.gz |
2020-12-08 11:09:00
72 files changed, 8550 insertions, 436 deletions
diff --git a/doc/context/documents/general/manuals/luametatex.pdf b/doc/context/documents/general/manuals/luametatex.pdf Binary files differindex 0b73a9556..6d7e0b892 100644 --- a/doc/context/documents/general/manuals/luametatex.pdf +++ b/doc/context/documents/general/manuals/luametatex.pdf diff --git a/doc/context/sources/general/manuals/luametatex/luametatex.tex b/doc/context/sources/general/manuals/luametatex/luametatex.tex index 4251b8770..77cf98019 100644 --- a/doc/context/sources/general/manuals/luametatex/luametatex.tex +++ b/doc/context/sources/general/manuals/luametatex/luametatex.tex @@ -68,6 +68,10 @@ % Thanks to sebastian.miele@gmail.com for close reading the manual and sending % fixes. +\pushoverloadmode \unprotect + % test code +\protect \popoverloadmode + \enabletrackers[system.usage=summary] \environment luametatex-style diff --git a/tex/context/base/mkii/cont-new.mkii b/tex/context/base/mkii/cont-new.mkii index d6a41419c..96a3acfec 100644 --- a/tex/context/base/mkii/cont-new.mkii +++ b/tex/context/base/mkii/cont-new.mkii @@ -11,7 +11,7 @@ %C therefore copyrighted by \PRAGMA. See mreadme.pdf for %C details. -\newcontextversion{2020.12.06 18:12} +\newcontextversion{2020.12.08 11:06} %D This file is loaded at runtime, thereby providing an %D excellent place for hacks, patches, extensions and new diff --git a/tex/context/base/mkii/context.mkii b/tex/context/base/mkii/context.mkii index d3ff08369..864df0c99 100644 --- a/tex/context/base/mkii/context.mkii +++ b/tex/context/base/mkii/context.mkii @@ -20,7 +20,7 @@ %D your styles an modules. \edef\contextformat {\jobname} -\edef\contextversion{2020.12.06 18:12} +\edef\contextversion{2020.12.08 11:06} %D For those who want to use this: diff --git a/tex/context/base/mkiv/cont-new.mkiv b/tex/context/base/mkiv/cont-new.mkiv index b8b97f52b..f60590c20 100644 --- a/tex/context/base/mkiv/cont-new.mkiv +++ b/tex/context/base/mkiv/cont-new.mkiv @@ -13,7 +13,7 @@ % \normalend % uncomment this to get the real base runtime -\newcontextversion{2020.12.06 18:12} +\newcontextversion{2020.12.08 11:06} %D This file is loaded at runtime, thereby providing an excellent place for hacks, %D patches, extensions and new features. There can be local overloads in cont-loc diff --git a/tex/context/base/mkiv/context.mkiv b/tex/context/base/mkiv/context.mkiv index dc6bc1a4c..907146f68 100644 --- a/tex/context/base/mkiv/context.mkiv +++ b/tex/context/base/mkiv/context.mkiv @@ -45,7 +45,7 @@ %D {YYYY.MM.DD HH:MM} format. \edef\contextformat {\jobname} -\edef\contextversion{2020.12.06 18:12} +\edef\contextversion{2020.12.08 11:06} %D Kind of special: diff --git a/tex/context/base/mkiv/font-cft.lua b/tex/context/base/mkiv/font-cft.lua index ee9ffa6a7..cf6c232a6 100644 --- a/tex/context/base/mkiv/font-cft.lua +++ b/tex/context/base/mkiv/font-cft.lua @@ -6,7 +6,7 @@ if not modules then modules = { } end modules ['font-cft'] = { license = "see context related readme files" } --- context font tables +-- context font tables (needs updating) -- -- todo: extra: -- @@ -229,7 +229,7 @@ do data.scaled = { properties = { - encodingbytes = t_cardinal, + encodingbytes = t_cardinal, -- not in lmtx embedding = t_cardinal, -- ? cidinfo = t_hash, format = t_string, @@ -238,7 +238,7 @@ do filename = t_string, psname = t_string, name = t_string, - virtualized = t_boolean, + virtualized = t_boolean, -- not in lmtx hasitalics = t_boolean, autoitalicamount = t_float, nostackmath = t_boolean, diff --git a/tex/context/base/mkiv/font-chk.lua b/tex/context/base/mkiv/font-chk.lua index d2a0943f6..e18e4b804 100644 --- a/tex/context/base/mkiv/font-chk.lua +++ b/tex/context/base/mkiv/font-chk.lua @@ -273,12 +273,6 @@ registerotffeature { fonts.loggers.add_placeholders = function(id) addmissingsymbols(fontdata[id or true]) end fonts.loggers.category_to_placeholder = mapping -function commands.getplaceholderchar(name) - local id = currentfont() - addmissingsymbols(fontdata[id]) - context(getprivatenode(fontdata[id],name)) -end - -- todo in luatex: option to add characters (just slots, no kerns etc) -- we can do that now so ... diff --git a/tex/context/base/mkiv/font-ctx.lua b/tex/context/base/mkiv/font-ctx.lua index 6c21bc0cc..5c93f302f 100644 --- a/tex/context/base/mkiv/font-ctx.lua +++ b/tex/context/base/mkiv/font-ctx.lua @@ -167,11 +167,6 @@ addformatter(formatters,"font:features",[["'"..sequenced(%s," ",true).."'"]],{ s -- ... like font-sfm or so constructors.resolvevirtualtoo = true -- context specific (due to resolver) - -if CONTEXTLMTXMODE and CONTEXTLMTXMODE > 0 then - constructors.fixprotrusion = false -end - constructors.sharefonts = true -- experimental constructors.nofsharedfonts = 0 constructors.nofsharedhashes = 0 @@ -339,12 +334,7 @@ implement { permanent = false, actions = function() for i=1,7 do - if CONTEXTLMTXMODE > 0 then - font.setfontdimen(0,i,0) - else - -- we have no direct method - context([[\fontdimen%s\nullfont\zeropoint]],i) - end + context([[\fontdimen%s\nullfont\zeropoint]],i) end definers.resetnullfont() end @@ -1657,17 +1647,7 @@ tfmdata.original = specification.specification return id, csnames[id] end - local read - - if CONTEXTLMTXMODE and CONTEXTLMTXMODE > 0 then -- maybe always - read = function(name,size) - return (define { name = name, size = size } or 0) - end - else - read = definers.read - end - - callbacks.register('define_font', read, "definition of fonts (tfmdata preparation)") + callbacks.register('define_font', definers.read, "definition of fonts (tfmdata preparation)") -- here @@ -1833,7 +1813,7 @@ function mappings.loadfile(name) if trace_mapfiles then report_mapfiles("loading map file %a",name) end - lpdf.setmapfile(name) + lpdf.setmapfile(name) -- bah. will go in lmtx loaded[name] = true end end @@ -1851,7 +1831,7 @@ function mappings.loadline(how,line) if trace_mapfiles then report_mapfiles("processing map line %a",line) end - lpdf.setmapline(how) + lpdf.setmapline(how) -- bah. will go in lmtx loaded[how] = true end end @@ -1860,6 +1840,10 @@ function mappings.reset() lpdf.setmapfile("") -- tricky ... backend related end +function mappings.getentry(...) + return lpdf.getmapentry(...) -- tricky ... backend related +end + implement { name = "loadmapfile", actions = mappings.loadfile, @@ -3390,8 +3374,3 @@ function fonts.helpers.collectanchors(tfmdata) return anchors end - -if CONTEXTLMTXMODE > 0 then - fonts.constructors.addtounicode = false - fonts.constructors.autocleanup = false -end diff --git a/tex/context/base/mkiv/font-one.lua b/tex/context/base/mkiv/font-one.lua index a76c92985..5ef6e4749 100644 --- a/tex/context/base/mkiv/font-one.lua +++ b/tex/context/base/mkiv/font-one.lua @@ -592,15 +592,18 @@ local function copytotfm(data) parameters.descender = abs(metadata.descender or 0) parameters.units = 1000 -- - properties.spacer = spacer + properties.spacer = spacer + properties.format = fonts.formats[filename] or "type1" + properties.filename = filename + properties.fontname = fontname + properties.fullname = fullname + properties.psname = fullname + properties.name = filename or fullname or fontname + properties.private = properties.private or data.private or privateoffset + -- +if not CONTEXTLMTXMODE or CONTEXTLMTXMODE == 0 then properties.encodingbytes = 2 - properties.format = fonts.formats[filename] or "type1" - properties.filename = filename - properties.fontname = fontname - properties.fullname = fullname - properties.psname = fullname - properties.name = filename or fullname or fontname - properties.private = properties.private or data.private or privateoffset +end -- if next(characters) then return { diff --git a/tex/context/base/mkiv/font-otl.lua b/tex/context/base/mkiv/font-otl.lua index e8f89b4e7..b8e13f107 100644 --- a/tex/context/base/mkiv/font-otl.lua +++ b/tex/context/base/mkiv/font-otl.lua @@ -486,23 +486,26 @@ local function copytotfm(data,cache_id) end end -- - parameters.designsize = (designsize/10)*65536 - parameters.minsize = (minsize /10)*65536 - parameters.maxsize = (maxsize /10)*65536 - parameters.ascender = abs(metadata.ascender or 0) - parameters.descender = abs(metadata.descender or 0) - parameters.units = units - parameters.vheight = metadata.defaultvheight + parameters.designsize = (designsize/10)*65536 + parameters.minsize = (minsize /10)*65536 + parameters.maxsize = (maxsize /10)*65536 + parameters.ascender = abs(metadata.ascender or 0) + parameters.descender = abs(metadata.descender or 0) + parameters.units = units + parameters.vheight = metadata.defaultvheight -- - properties.space = spacer + properties.space = spacer + properties.format = data.format or formats.otf + properties.filename = filename + properties.fontname = fontname + properties.fullname = fullname + properties.psname = psname + properties.name = filename or fullname + properties.subfont = subfont + -- +if not CONTEXTLMTXMODE or CONTEXTLMTXMODE == 0 then properties.encodingbytes = 2 - properties.format = data.format or formats.otf - properties.filename = filename - properties.fontname = fontname - properties.fullname = fullname - properties.psname = psname - properties.name = filename or fullname - properties.subfont = subfont +end -- -- properties.name = specification.name -- properties.sub = specification.sub diff --git a/tex/context/base/mkiv/font-tfm.lua b/tex/context/base/mkiv/font-tfm.lua index e2f0d22f2..63f3276b1 100644 --- a/tex/context/base/mkiv/font-tfm.lua +++ b/tex/context/base/mkiv/font-tfm.lua @@ -139,10 +139,12 @@ local function read_from_tfm(specification) -- If reencode returns a new table, we assume that we're doing something -- special. An 'auto' reencode picks up its vector from the pfb file. - if lpdf and lpdf.getmapentry and not features.reencode then + local getmapentry = fonts.mappings.getentry + + if getmapentry and not features.reencode then -- This can happen multiple times but not that often so we don't -- optimize this. - local encoding, pfbfile, encfile = lpdf.getmapentry(filename) + local encoding, pfbfile, encfile = getmapentry(filename) if encoding and pfbfile then features.reencode = encfile features.pfbfile = pfbfile @@ -170,9 +172,9 @@ local function read_from_tfm(specification) properties.format = tfmdata.format or fonts.formats.tfm -- better than nothing properties.usedbitmap = tfmdata.usedbitmap -- -if lpdf and lpdf.getmapentry and newtfmdata then - properties.filename = features.pfbfile -end + if getmapentry and newtfmdata then + properties.filename = features.pfbfile + end -- tfmdata.properties = properties tfmdata.resources = resources @@ -234,8 +236,10 @@ end -- -- The tounicode data is passed to the backend that constructs the vectors for us. -- - tfmdata.tounicode = 1 - local tounicode = fonts.mappings.tounicode +if not CONTEXTLMTXMODE or CONTEXTLMTXMODE == 0 then + tfmdata.tounicode = 1 +end + local tounicode = fonts.mappings.tounicode for unicode, v in next, tfmdata.characters do local u = v.unicode if u then @@ -514,11 +518,13 @@ do tfmdata.fullname = tfmdata.fullname or tfmdata.name tfmdata.psname = file.nameonly(pfbfile or tfmdata.name) tfmdata.filename = pfbfile - tfmdata.encodingbytes = 2 -- tfmdata.format = bitmap and "type3" or "type1" tfmdata.format = "type1" +if not CONTEXTLMTXMODE or CONTEXTLMTXMODE == 0 then + tfmdata.encodingbytes = 2 tfmdata.tounicode = 1 tfmdata.embedding = "subset" +end tfmdata.usedbitmap = bitmap and virtualid tfmdata.private = private diff --git a/tex/context/base/mkiv/font-tpk.lua b/tex/context/base/mkiv/font-tpk.lua index c24af1df8..16b89c393 100644 --- a/tex/context/base/mkiv/font-tpk.lua +++ b/tex/context/base/mkiv/font-tpk.lua @@ -848,7 +848,6 @@ do direction = direction, -- checksum = checksum, -- embedding = "unknown", - -- encodingbytes = 0, -- extend = 1000, -- slant = 0, -- squeeze = 0, diff --git a/tex/context/base/mkiv/grph-bmp.lua b/tex/context/base/mkiv/grph-bmp.lua index e10b01aa4..6669b5db1 100644 --- a/tex/context/base/mkiv/grph-bmp.lua +++ b/tex/context/base/mkiv/grph-bmp.lua @@ -39,10 +39,6 @@ function bitmaps.new(xsize,ysize,colorspace,colordepth,mask,index) } end --- function backends.codeinjections.bitmap(bitmap) --- return lpdf.injectors.bitmap(bitmap) --- end - local function flush(bitmap) local specification = backends.codeinjections.bitmap(bitmap) if specification then @@ -89,7 +85,7 @@ local function placeholder(nx,ny) end end - return lpdf.injectors.bitmap(bitmap) + return backends.codeinjections.bitmap(bitmap) end diff --git a/tex/context/base/mkiv/grph-chk.lua b/tex/context/base/mkiv/grph-chk.lua index 0688f46ef..0cfc9ba08 100644 --- a/tex/context/base/mkiv/grph-chk.lua +++ b/tex/context/base/mkiv/grph-chk.lua @@ -35,7 +35,7 @@ function checkers.pdf(data) local request = data.request local used = data.used if request and used and not request.scanimage then - local image = lpdf.epdf.image + local image = lpdf.epdf.image -- ok, this is a pdf specific main function local openpdf = image.open local closepdf = image.close local querypdf = image.query @@ -163,7 +163,6 @@ function checkers.jpg(data) local used = data.used if request and used and not request.scanimage then local identify = graphics.identify - local inject = lpdf.injectors.jpg local found = false request.scanimage = function(t) local result = wrappedidentify(identify,t.filename,"jpg") @@ -187,7 +186,7 @@ function checkers.jpg(data) request.copyimage = function(t) if found then found = false - return inject(t) + return backends.codeinjections.jpg(t) end end end @@ -199,7 +198,6 @@ function checkers.jp2(data) -- idem as jpg local used = data.used if request and used and not request.scanimage then local identify = graphics.identify - local inject = lpdf.injectors.jp2 local found = false request.scanimage = function(t) local result = wrappedidentify(identify,t.filename,"jp2") @@ -223,7 +221,7 @@ function checkers.jp2(data) -- idem as jpg request.copyimage = function(t) if found then found = false - return inject(t) + return backends.codeinjections.jp2(t) end end end @@ -235,7 +233,6 @@ function checkers.png(data) -- same as jpg (for now) local used = data.used if request and used and not request.scanimage then local identify = graphics.identify - local inject = lpdf.injectors.png -- currently pdf specific local found = false request.scanimage = function(t) local result = wrappedidentify(identify,t.filename,"png") @@ -263,7 +260,7 @@ function checkers.png(data) -- same as jpg (for now) t.colorref = used.colorref -- this is a bit of a hack if found then found = false - local ok, result = pcall(inject,t) + local ok, result = pcall(backends.codeinjections.png,t) if ok then return result else diff --git a/tex/context/base/mkiv/grph-pat.lua b/tex/context/base/mkiv/grph-pat.lua index e38a9a674..4310af672 100644 --- a/tex/context/base/mkiv/grph-pat.lua +++ b/tex/context/base/mkiv/grph-pat.lua @@ -38,7 +38,7 @@ interfaces.implement { return end nodes.handlers.finalizebox(number) - names[name] = lpdf.registerpattern { + names[name] = backends.codeinjections.registerpattern { number = number, width = specification.width or box.width, height = specification.height or (box.height + box.depth) , diff --git a/tex/context/base/mkiv/lpdf-grp.lua b/tex/context/base/mkiv/lpdf-grp.lua index e827419a6..963b1dde3 100644 --- a/tex/context/base/mkiv/lpdf-grp.lua +++ b/tex/context/base/mkiv/lpdf-grp.lua @@ -293,3 +293,5 @@ end function lpdf.patternstream(n,width,height) return f_pattern("Pt" .. n,width*basepoints,height*basepoints) end + +backends.pdf.codeinjections.registerpattern = lpdf.registerpattern diff --git a/tex/context/base/mkiv/luat-cod.lua b/tex/context/base/mkiv/luat-cod.lua index 0b7521180..927f6dfab 100644 --- a/tex/context/base/mkiv/luat-cod.lua +++ b/tex/context/base/mkiv/luat-cod.lua @@ -68,7 +68,7 @@ function lua.registercode(filename,options) end end if barename == filename then - filename = filename .. (opts.autosuffix and CONTEXTLMTXMODE > 0 and ".lmt" or ".lua") + filename = filename .. ".lua" end local code = environment.luafilechunk(filename,false,opts.optimize) if code then diff --git a/tex/context/base/mkiv/math-act.lua b/tex/context/base/mkiv/math-act.lua index 03f44dd1c..4f38b4195 100644 --- a/tex/context/base/mkiv/math-act.lua +++ b/tex/context/base/mkiv/math-act.lua @@ -247,10 +247,10 @@ function mathematics.overloaddimensions(target,original,set) local factor = parameters.factor local hfactor = parameters.hfactor local vfactor = parameters.vfactor - -- to be sure +if not CONTEXTLMTXMODE or CONTEXTLMTXMODE == 0 then target.type = "virtual" target.properties.virtualized = true - -- +end local function overload(dimensions) for unicode, data in next, dimensions do local character = characters[unicode] @@ -603,8 +603,10 @@ interfaces.implement { -- if not fonts then -- fonts = { } -- target.fonts = fonts +-- if not CONTEXTLMTXMODE or CONTEXTLMTXMODE == 0 then -- target.type = "virtual" -- target.properties.virtualized = true +-- end -- end -- if #fonts == 0 then -- fonts[1] = { id = 0, size = size } -- sel, will be resolved later @@ -709,8 +711,10 @@ function mathematics.finishfallbacks(target,specification,fallbacks) fonts = { } target.fonts = fonts end +if not CONTEXTLMTXMODE or CONTEXTLMTXMODE == 0 then target.type = "virtual" target.properties.virtualized = true +end if #fonts == 0 then fonts[1] = { id = 0, size = size } -- self, will be resolved later end diff --git a/tex/context/base/mkiv/math-vfu.lua b/tex/context/base/mkiv/math-vfu.lua index 9090955e9..c7079caca 100644 --- a/tex/context/base/mkiv/math-vfu.lua +++ b/tex/context/base/mkiv/math-vfu.lua @@ -837,7 +837,6 @@ function vfmath.define(specification,set,goodies) setmetatableindex(goodies,parent.goodies) end -- - properties.virtualized = true properties.hasitalics = true properties.hasmath = true -- @@ -850,9 +849,13 @@ function vfmath.define(specification,set,goodies) -- we need to set some values in main as well (still?) -- main.fullname = properties.fullname - main.type = "virtual" main.nomath = false -- +if not CONTEXTLMTXMODE or CONTEXTLMTXMODE == 0 then + properties.virtualized = true + main.type = "virtual" +end + -- parameters.x_height = parameters.x_height or 0 -- local already_reported = false diff --git a/tex/context/base/mkiv/node-acc.lua b/tex/context/base/mkiv/node-acc.lua index e6c617602..d8f4b25bf 100644 --- a/tex/context/base/mkiv/node-acc.lua +++ b/tex/context/base/mkiv/node-acc.lua @@ -129,10 +129,9 @@ end) -- local done = false -- for n, id in nextnode, tonuts(head) do -- if id == disc then --- local r = getfield(n,"replace") --- local p = getfield(n,"pre") --- if r and p then --- local str = compact(r) +-- local pre, post, replace = getdisc(n) +-- if replace and pre then +-- local str = compact(replace) -- local hsh = hyphenated[str] -- if not hsh then -- hsh = #codes + 1 diff --git a/tex/context/base/mkiv/node-res.lua b/tex/context/base/mkiv/node-res.lua index 03de8f843..478a46906 100644 --- a/tex/context/base/mkiv/node-res.lua +++ b/tex/context/base/mkiv/node-res.lua @@ -626,10 +626,10 @@ do return v end) - traversers.node = nodes.traverse (glyph) - traversers.char = nodes.traverse_char (glyph) - if nuts.traverse_glyph then traversers.glyph = nodes.traverse_glyph (glyph) end - if nuts.traverse_list then traversers.list = nodes.traverse_list (glyph) end + traversers.node = nodes.traverse (glyph) + traversers.char = nodes.traverse_char (glyph) + if nodes.traverse_glyph then traversers.glyph = nodes.traverse_glyph(glyph) end + if nodes.traverse_list then traversers.list = nodes.traverse_list (glyph) end nodes.traversers = traversers diff --git a/tex/context/base/mkiv/node-ser.lua b/tex/context/base/mkiv/node-ser.lua index 6fc2b7ea4..25a6dd6c3 100644 --- a/tex/context/base/mkiv/node-ser.lua +++ b/tex/context/base/mkiv/node-ser.lua @@ -19,7 +19,6 @@ local context = context local nodes = nodes local node = node -local traverse = nodes.traverse local is_node = nodes.is_node local nodecodes = nodes.nodecodes diff --git a/tex/context/base/mkiv/node-tra.lua b/tex/context/base/mkiv/node-tra.lua index a86dfb620..83c072c19 100644 --- a/tex/context/base/mkiv/node-tra.lua +++ b/tex/context/base/mkiv/node-tra.lua @@ -180,18 +180,8 @@ end nodes.tosequence = tosequence nuts .tosequence = tosequence -if CONTEXTLMTXMODE > 0 then - - function nodes.report(t) - report_nodes("output %a, %s nodes",tex.getoutputactive(),count_nodes(t)) - end - -else - - function nodes.report(t) - report_nodes("output %a, %s nodes",status.output_active,count_nodes(t)) - end - +function nodes.report(t) + report_nodes("output %a, %s nodes",status.output_active,count_nodes(t)) end function nodes.packlist(head) diff --git a/tex/context/base/mkiv/status-files.pdf b/tex/context/base/mkiv/status-files.pdf Binary files differindex f61ff2582..1f59f1008 100644 --- a/tex/context/base/mkiv/status-files.pdf +++ b/tex/context/base/mkiv/status-files.pdf diff --git a/tex/context/base/mkiv/status-lua.pdf b/tex/context/base/mkiv/status-lua.pdf Binary files differindex 75f0d1787..c39495b08 100644 --- a/tex/context/base/mkiv/status-lua.pdf +++ b/tex/context/base/mkiv/status-lua.pdf diff --git a/tex/context/base/mkiv/strc-tag.lua b/tex/context/base/mkiv/strc-tag.lua index 694f7bdf0..83ff515e3 100644 --- a/tex/context/base/mkiv/strc-tag.lua +++ b/tex/context/base/mkiv/strc-tag.lua @@ -266,8 +266,8 @@ end function structures.atlocation(str) local specification = taglist[texgetattribute(a_tagged)] if specification then + local list = specification.taglist if list then - local taglist = specification.taglist local pattern = patterns[str] for i=#list,1,-1 do if find(list[i],pattern) then diff --git a/tex/context/base/mkiv/typo-drp.lua b/tex/context/base/mkiv/typo-drp.lua index 12864e52d..ddc6d68ae 100644 --- a/tex/context/base/mkiv/typo-drp.lua +++ b/tex/context/base/mkiv/typo-drp.lua @@ -321,13 +321,8 @@ actions[v_default] = function(head,setting) if trace_initials then report_initials("setting hangafter to %i and hangindent to %p",hangafter,hangindent) end - if CONTEXTLMTXMODE > 0 then - texset("hangafter",hangafter,true) - texset("hangindent",hangindent,true) - else - texset("hangafter",hangafter) - texset("hangindent",hangindent) - end + texset("hangafter",hangafter) + texset("hangindent",hangindent) end if indent then insert_after(first,first,new_kern(-parindent)) diff --git a/tex/context/base/mkxl/cont-new.mkxl b/tex/context/base/mkxl/cont-new.mkxl index 3c11528a8..ac5966acb 100644 --- a/tex/context/base/mkxl/cont-new.mkxl +++ b/tex/context/base/mkxl/cont-new.mkxl @@ -13,7 +13,7 @@ % \normalend % uncomment this to get the real base runtime -\newcontextversion{2020.12.06 18:12} +\newcontextversion{2020.12.08 11:06} %D This file is loaded at runtime, thereby providing an excellent place for hacks, %D patches, extensions and new features. There can be local overloads in cont-loc diff --git a/tex/context/base/mkxl/context.mkxl b/tex/context/base/mkxl/context.mkxl index 5eca52575..5dde967b5 100644 --- a/tex/context/base/mkxl/context.mkxl +++ b/tex/context/base/mkxl/context.mkxl @@ -29,7 +29,7 @@ %D {YYYY.MM.DD HH:MM} format. \immutable\edef\contextformat {\jobname} -\immutable\edef\contextversion{2020.12.06 18:12} +\immutable\edef\contextversion{2020.12.08 11:06} %overloadmode 1 % check frozen / warning %overloadmode 2 % check frozen / error diff --git a/tex/context/base/mkxl/core-sys.lmt b/tex/context/base/mkxl/core-sys.lmt index 12f001e37..0d471133d 100644 --- a/tex/context/base/mkxl/core-sys.lmt +++ b/tex/context/base/mkxl/core-sys.lmt @@ -74,14 +74,6 @@ implement { name = "inputfilesuffix", public = true, actions = function() co implement { name = "inputfilename", public = true, actions = function() context(environment.inputfilename) end } implement { name = "outputfilename", public = true, actions = function() context(environment.outputfilename) end } -statistics.register("result saved in file", function() - -- suffix will be fetched from backend - local outputfilename = environment.outputfilename or environment.jobname or tex.jobname or "<unset>" - return lpdf and format("%s.%s, compresslevel %s, objectcompresslevel %s",outputfilename,"pdf", - lpdf.getcompression() - ) or "error" -end) - implement { name = "systemlog", arguments = "3 strings", diff --git a/tex/context/base/mkxl/driv-shp.lmt b/tex/context/base/mkxl/driv-shp.lmt index 8e3a936bb..7a385d7ab 100644 --- a/tex/context/base/mkxl/driv-shp.lmt +++ b/tex/context/base/mkxl/driv-shp.lmt @@ -184,6 +184,14 @@ local tospace = false directives.register("backends.spaces", function(v) tospac local default = 16384 * number.dimenfactors.bp -- 65536 // 4 +local startcolor = function() end +local stopcolor = function() end + +updaters.register("backend.update",function() + startcolor = fonts.vfcommands.startcolor + stopcolor = fonts.vfcommands.stopcolor +end) + local function flush_vf_packet(pos_h,pos_v,pos_r,font,char,data,factor,vfcommands) if nesting > 100 then @@ -293,6 +301,7 @@ local function flush_vf_packet(pos_h,pos_v,pos_r,font,char,data,factor,vfcommand level = level - 1 end elseif command == "pdf" then + -- this will disappear and become a plug flushliteral(false,pos_h,pos_v,packet[2],packet[3]) elseif command == "rule" then local size_v = packet[2] @@ -307,6 +316,7 @@ local function flush_vf_packet(pos_h,pos_v,pos_r,font,char,data,factor,vfcommand end end elseif command == "frame" then + -- d:width d:height d:depth d:rulethickness b:outline b:advance b:baseline s:color local width = packet[2] if width > 0 then local height = packet[3] or 0 @@ -317,10 +327,18 @@ local function flush_vf_packet(pos_h,pos_v,pos_r,font,char,data,factor,vfcommand width = width + width * factor / 1000 end if width > 0 then - local line = packet[5] or default - local outline = not packet[6] - local advance = not packet[7] - flushspecialrule(pos_h,pos_v,pos_r,width,height,depth,line,outline) + local line = packet[5] or default + local outline = not packet[6] + local advance = not packet[7] + local baseline = outline and packet[8] + local color = packet[9] + if color then + startcolor(color) + end + flushspecialrule(pos_h,pos_v,pos_r,width,height,depth,line,outline,baseline) + if color then + stopcolor() + end if advance then pos_h = pos_h + width end @@ -335,22 +353,24 @@ local function flush_vf_packet(pos_h,pos_v,pos_r,font,char,data,factor,vfcommand end elseif command == "lua" then local code = packet[2] - if type(code) ~= "function" then + local kind = type(code) + if kind ~= "function" then code = loadstring(code) + kind = type(code) end - if type(code) == "function" then + if kind == "function" then code(font,char,pos_h,pos_v) end elseif command == "node" then local h = packet[2] hlist_out(h,getlist(h)) - elseif command == "image" then - -- doesn't work because intercepted by engine so we use a different - -- mechanism (for now) - local image = packet[2] - -- to do - elseif command == "pdfmode" then - -- doesn't happen + -- elseif command == "image" then + -- -- doesn't work because intercepted by engine so we use a different + -- -- mechanism (for now) + -- local image = packet[2] + -- -- to do + -- elseif command == "pdfmode" then + -- -- doesn't happen -- elseif command == "special" then -- -- not supported -- elseif command == "nop" then diff --git a/tex/context/base/mkxl/font-chk.lmt b/tex/context/base/mkxl/font-chk.lmt new file mode 100644 index 000000000..7141be2c7 --- /dev/null +++ b/tex/context/base/mkxl/font-chk.lmt @@ -0,0 +1,475 @@ +if not modules then modules = { } end modules ['font-chk'] = { + 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 is kind of old but it makes no real sense to upgrade it to for instance +-- using delayed type 3 fonts in order to be lean and mean and cut'n'paste +-- compliant. When this kicks one needs to fix the choice of fonts anyway! So, +-- instead we just keep the method we use but slightly adapted to the backend +-- of lmtx. + +local next = next +local floor = math.floor + +local context = context + +local formatters = string.formatters +local bpfactor = number.dimenfactors.bp +local fastcopy = table.fastcopy +local sortedkeys = table.sortedkeys +local sortedhash = table.sortedhash + +local report = logs.reporter("fonts") +local report_checking = logs.reporter("fonts","checking") + +local allocate = utilities.storage.allocate + +local fonts = fonts + +fonts.checkers = fonts.checkers or { } +local checkers = fonts.checkers + +local fonthashes = fonts.hashes +local fontdata = fonthashes.identifiers +local fontcharacters = fonthashes.characters + +local currentfont = font.current +local addcharacters = font.addcharacters + +local helpers = fonts.helpers + +local addprivate = helpers.addprivate +local hasprivate = helpers.hasprivate +local getprivateslot = helpers.getprivateslot +local getprivatecharornode = helpers.getprivatecharornode + +local otffeatures = fonts.constructors.features.otf +local afmfeatures = fonts.constructors.features.afm + +local registerotffeature = otffeatures.register +local registerafmfeature = afmfeatures.register + +local is_character = characters.is_character +local chardata = characters.data + +local tasks = nodes.tasks +local enableaction = tasks.enableaction +local disableaction = tasks.disableaction + +local implement = interfaces.implement + +local glyph_code = nodes.nodecodes.glyph + +local new_special = nodes.pool.special -- todo: literal +local hpack_node = node.hpack + +local nuts = nodes.nuts +local tonut = nuts.tonut + +local isglyph = nuts.isglyph +local setchar = nuts.setchar + +local nextglyph = nuts.traversers.glyph + +local remove_node = nuts.remove +local insert_node_after = nuts.insert_after + +local action = false + +-- to tfmdata.properties ? + +local function onetimemessage(font,char,message) -- char == false returns table + local tfmdata = fontdata[font] + local shared = tfmdata.shared + if not shared then + shared = { } + tfmdata.shared = shared + end + local messages = shared.messages + if not messages then + messages = { } + shared.messages = messages + end + local category = messages[message] + if not category then + category = { } + messages[message] = category + end + if char == false then + return sortedkeys(category), category + end + local cc = category[char] + if not cc then + report_checking("char %C in font %a with id %a: %s",char,tfmdata.properties.fullname,font,message) + category[char] = 1 + else + category[char] = cc + 1 + end +end + +fonts.loggers.onetimemessage = onetimemessage + +local fakes = { + lowercase = { width = .45, height = .55, depth = .20 }, + uppercase = { width = .65, height = .70, depth = .25 }, + mark = { width = .15, height = .70, depth = -.50 }, + punctuation = { width = .15, height = .55, depth = .20 }, + unknown = { width = .45, height = .20, depth = 0 }, +} + +local mapping = allocate { + lu = { "uppercase", "darkred" }, + ll = { "lowercase", "darkred" }, + lt = { "uppercase", "darkred" }, + lm = { "lowercase", "darkred" }, + lo = { "lowercase", "darkred" }, + mn = { "mark", "darkgreen" }, + mc = { "mark", "darkgreen" }, + me = { "mark", "darkgreen" }, + nd = { "lowercase", "darkblue" }, + nl = { "lowercase", "darkblue" }, + no = { "lowercase", "darkblue" }, + pc = { "punctuation", "darkcyan" }, + pd = { "punctuation", "darkcyan" }, + ps = { "punctuation", "darkcyan" }, + pe = { "punctuation", "darkcyan" }, + pi = { "punctuation", "darkcyan" }, + pf = { "punctuation", "darkcyan" }, + po = { "punctuation", "darkcyan" }, + sm = { "lowercase", "darkmagenta" }, + sc = { "lowercase", "darkyellow" }, + sk = { "lowercase", "darkyellow" }, + so = { "lowercase", "darkyellow" }, +} + +table.setmetatableindex(mapping, { "unknown", "darkgray" }) + +-- We provide access by (private) name for tracing purposes. We also need +-- to make sure the dimensions are known at the lua and tex end. + +local cache = { } + +local function add(tfmdata,name,color,size,collected) + local hash = formatters["%s_%s_%i"](name,color,floor(size)) + local chardata = cache[hash] + if not chardata then + local fake = fakes[name] + local width = size * fake.width + local height = size * fake.height + local depth = size * fake.depth + chardata = { + width = width, + height = height, + depth = depth, + commands = { + { "frame", width, height, depth, 65536/5, false, true, true, color }, + } + } + cache[hash] = chardata + end + if not hasprivate(tfmdata,privatename) then + local privatename = formatters["placeholder %s %s"](name,color) + local privatecode = addprivate(tfmdata, privatename, chardata) + collected[privatecode] = chardata + end + return chardata +end + +local function addplaceholder(font,char) + local tfmdata = fontdata[font or true] + local characters = tfmdata.characters + local size = tfmdata.parameters.size + local scale = size * bpfactor + local collected = { } + local category = chardata[char].category or "unknown" + local fakedata = mapping[category] + local chardata = add(tfmdata,fakedata[1],fakedata[2],size,collected) + collected [char] = chardata + characters[char] = chardata + addcharacters(font, { characters = collected }) + return "char", char -- needed for math-noa +end + +-- For old times sake we keep this: a whole bunch of fake symbols + +local function addplaceholders(tfmdata) + local properties = tfmdata.properties + local size = tfmdata.parameters.size + local scale = size * bpfactor + local collected = { } + local colors = { "darkred", "darkgreen", "darkblue", "darkcyan", "darkmagenta", "darkyellow", "darkgray" } + for name, v in sortedhash(fakes) do + for i=1,#colors do + add(tfmdata,name,colors[i],size,collected) + end + end + if next(collected) then + local id = properties.id + if id then + addcharacters(id, { characters = collected }) + end + end +end + +registerotffeature { + name = "missing", + description = "missing symbols", + manipulators = { + base = addplaceholders, + node = addplaceholders, + } +} + +-- fonts.loggers.add_placeholders = function(id) addplaceholders(fontdata[id or true]) end +-- fonts.loggers.category_to_placeholder = mapping + +checkers.placeholder = addplaceholder + +function checkers.missing(head) + local lastfont = nil + local characters = nil + if action == "replace" then + for n, char, font in nextglyph, head do + if font ~= lastfont then + lastfont = font + characters = fontcharacters[font] + end + if font > 0 and not characters[char] and is_character[chardata[char].category or "unknown"] then + onetimemessage(font,char,"missing (will be flagged)") + addplaceholder(font,char) + end + end + elseif action == "remove" then + -- faster than while loop so we delay removal + local found = nil + for n, char, font in nextglyph, head do + if font ~= lastfont then + lastfont = font + characters = fontcharacters[font] + end + if font > 0 and not characters[char] and is_character[chardata[char].category or "unknown"] then + onetimemessage(font,char,"missing (will be deleted)") + if not found then + found = { n } + else + found[#found+1] = n + end + end + end + if found then + for i=1,#found do + head = remove_node(head,found[i],true) + end + end + else + for n, char, font in nextglyph, head do + if font ~= lastfont then + lastfont = font + characters = fontcharacters[font] + end + if font > 0 and not characters[char] and is_character[chardata[char].category or "unknown"] then + onetimemessage(font,char,"missing") + end + end + end + return head +end + +local relevant = { + "missing (will be deleted)", + "missing (will be flagged)", + "missing" +} + +local function getmissing(id) + if id then + local list = getmissing(currentfont()) + if list then + local _, list = next(getmissing(currentfont())) + return list + else + return { } + end + else + local t = { } + for id, d in next, fontdata do + local shared = d.shared + local messages = shared and shared.messages + if messages then + local filename = d.properties.filename + if not filename then + filename = tostring(d) + end + local tf = t[filename] or { } + for i=1,#relevant do + local tm = messages[relevant[i]] + if tm then + for k, v in next, tm do + tf[k] = (tf[k] or 0) + v + end + end + end + if next(tf) then + t[filename] = tf + end + end + end + local l = { } + for k, v in next, t do + l[k] = sortedkeys(v) + end + return l, t + end +end + +checkers.getmissing = getmissing + + +do + + local reported = true + + callback.register("glyph_not_found",function(font,char) + if font > 0 then + if char > 0 then + onetimemessage(font,char,"missing") + else + -- we have a special case + end + elseif not reported then + report("nullfont is used, maybe no bodyfont is defined") + reported = true + end + end) + + trackers.register("fonts.missing", function(v) + if v then + enableaction("processors","fonts.checkers.missing") + else + disableaction("processors","fonts.checkers.missing") + end + if v == "replace" then + otffeatures.defaults.missing = true + end + action = v + end) + + logs.registerfinalactions(function() + local collected, details = getmissing() + if next(collected) then + for filename, list in sortedhash(details) do + logs.startfilelogging(report,"missing characters",filename) + for u, v in sortedhash(list) do + report("%4i %U %c %s",v,u,u,chardata[u].description) + end + logs.stopfilelogging() + end + if logs.loggingerrors() then + for filename, list in sortedhash(details) do + logs.starterrorlogging(report,"missing characters",filename) + for u, v in sortedhash(list) do + report("%4i %U %c %s",v,u,u,chardata[u].description) + end + logs.stoperrorlogging() + end + end + end + end) + +end + +-- for the moment here + +local function expandglyph(characters,index,done) + done = done or { } + if not done[index] then + local data = characters[index] + if data then + done[index] = true + local d = fastcopy(data) + local n = d.next + if n then + d.next = expandglyph(characters,n,done) + end + local h = d.horiz_variants + if h then + for i=1,#h do + h[i].glyph = expandglyph(characters,h[i].glyph,done) + end + end + local v = d.vert_variants + if v then + for i=1,#v do + v[i].glyph = expandglyph(characters,v[i].glyph,done) + end + end + return d + end + end +end + +helpers.expandglyph = expandglyph + +-- should not be needed as we add .notdef in the engine + +local dummyzero = { + -- width = 0, + -- height = 0, + -- depth = 0, + commands = { { "special", "" } }, +} + +local function adddummysymbols(tfmdata) + local characters = tfmdata.characters + if not characters[0] then + characters[0] = dummyzero + end + -- if not characters[1] then + -- characters[1] = dummyzero -- test only + -- end +end + +local dummies_specification = { + name = "dummies", + description = "dummy symbols", + default = true, + manipulators = { + base = adddummysymbols, + node = adddummysymbols, + } +} + +registerotffeature(dummies_specification) +registerafmfeature(dummies_specification) + +-- + +local function addvisualspace(tfmdata) + local spacechar = tfmdata.characters[32] + if spacechar and not spacechar.commands then + local w = spacechar.width + local h = tfmdata.parameters.xheight + local c = { + width = w, + commands = { { "rule", h, w } } + } + local u = addprivate(tfmdata, "visualspace", c) + end +end + +local visualspace_specification = { + name = "visualspace", + description = "visual space", + default = true, + manipulators = { + base = addvisualspace, + node = addvisualspace, + } +} + +registerotffeature(visualspace_specification) +registerafmfeature(visualspace_specification) diff --git a/tex/context/base/mkxl/font-chk.mkxl b/tex/context/base/mkxl/font-chk.mkxl index b84056e4e..29b159fc2 100644 --- a/tex/context/base/mkxl/font-chk.mkxl +++ b/tex/context/base/mkxl/font-chk.mkxl @@ -13,7 +13,7 @@ \writestatus{loading}{ConTeXt Font Macros / Checking} -\registerctxluafile{font-chk}{} +\registerctxluafile{font-chk}{autosuffix} \tracinglostchars\zerocount diff --git a/tex/context/base/mkxl/font-col.lmt b/tex/context/base/mkxl/font-col.lmt new file mode 100644 index 000000000..a0e40d0ba --- /dev/null +++ b/tex/context/base/mkxl/font-col.lmt @@ -0,0 +1,477 @@ +if not modules then modules = { } end modules ['font-col'] = { + 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" +} + +-- possible optimization: delayed initialization of vectors +-- we should also share equal vectors (math) + +local context, commands, trackers, logs = context, commands, trackers, logs +local node, nodes, fonts, characters = node, nodes, fonts, characters +local file, lpeg, table, string = file, lpeg, table, string + +local type, next, tonumber, toboolean = type, next, tonumber, toboolean +local gmatch = string.gmatch +local fastcopy = table.fastcopy +local formatters = string.formatters + +local nuts = nodes.nuts + +local setfont = nuts.setfont + +----- traverse_char = nuts.traverse_char +local nextchar = nuts.traversers.char + +local settings_to_hash = utilities.parsers.settings_to_hash + +local trace_collecting = false trackers.register("fonts.collecting", function(v) trace_collecting = v end) + +local report_fonts = logs.reporter("fonts","collections") + +local enableaction = nodes.tasks.enableaction +local disableaction = nodes.tasks.disableaction + +local collections = fonts.collections or { } +fonts.collections = collections + +local definitions = collections.definitions or { } +collections.definitions = definitions + +local vectors = collections.vectors or { } +collections.vectors = vectors + +local helpers = fonts.helpers +local charcommand = helpers.commands.char +local rightcommand = helpers.commands.right +local addprivate = helpers.addprivate +local hasprivate = helpers.hasprivate +local fontpatternhassize = helpers.fontpatternhassize + +local hashes = fonts.hashes +local fontdata = hashes.identifiers +local fontquads = hashes.quads +local chardata = hashes.characters +local propdata = hashes.properties +local mathparameters = hashes.mathparameters + +local currentfont = font.current +local addcharacters = font.addcharacters + +local implement = interfaces.implement + +local list = { } +local current = 0 +local enabled = false + +local validvectors = table.setmetatableindex(function(t,k) + local v = false + if not mathparameters[k] then + v = vectors[k] + end + t[k] = v + return v +end) + +local function checkenabled() + -- a bit ugly but nicer than a fuzzy state while defining math + if next(vectors) then + if not enabled then + enableaction("processors","fonts.collections.process") + enabled = true + end + else + if enabled then + disableaction("processors","fonts.collections.process") + enabled = false + end + end +end + +collections.checkenabled = checkenabled + +function collections.reset(name,font) + if font and font ~= "" then + local d = definitions[name] + if d then + d[font] = nil + if not next(d) then + definitions[name] = nil + end + end + else + definitions[name] = nil + end +end + +function collections.define(name,font,ranges,details) + -- todo: details -> method=force|conditional rscale= + -- todo: remap=name + local d = definitions[name] + if not d then + d = { } + definitions[name] = d + end + if name and trace_collecting then + report_fonts("extending collection %a using %a",name,font) + end + details = settings_to_hash(details) + -- todo, combine per font start/stop as arrays + local offset = details.offset + if type(offset) == "string" then + offset = characters.getrange(offset,true) or false + else + offset = tonumber(offset) or false + end + local target = details.target + if type(target) == "string" then + target = characters.getrange(target,true) or false + else + target = tonumber(target) or false + end + local rscale = tonumber (details.rscale) or 1 + local force = toboolean(details.force,true) + local check = toboolean(details.check,true) + local factor = tonumber(details.factor) + local features = details.features + for s in gmatch(ranges,"[^, ]+") do + local start, stop, description, gaps = characters.getrange(s,true) + if start and stop then + if trace_collecting then + if description then + report_fonts("using range %a, slots %U - %U, description %a)",s,start,stop,description) + end + for i=1,#d do + local di = d[i] + if (start >= di.start and start <= di.stop) or (stop >= di.start and stop <= di.stop) then + report_fonts("overlapping ranges %U - %U and %U - %U",start,stop,di.start,di.stop) + end + end + end + d[#d+1] = { + font = font, + start = start, + stop = stop, + gaps = gaps, + offset = offset, + target = target, + rscale = rscale, + force = force, + check = check, + method = details.method, + factor = factor, + features = features, + } + end + end +end + +-- todo: provide a lua variant (like with definefont) + +function collections.registermain(name) + local last = currentfont() + if trace_collecting then + report_fonts("registering font %a with name %a",last,name) + end + list[#list+1] = last +end + +-- check: when true, only set when present in font +-- force: when false, then not set when already set + +local uccodes = characters.uccodes +local lccodes = characters.lccodes + +local methods = { + lowercase = function(oldchars,newchars,vector,start,stop,cloneid) + for k, v in next, oldchars do + if k >= start and k <= stop then + local lccode = lccodes[k] + if k ~= lccode and newchars[lccode] then + vector[k] = { cloneid, lccode } + end + end + end + end, + uppercase = function(oldchars,newchars,vector,start,stop,cloneid) + for k, v in next, oldchars do + if k >= start and k <= stop then + local uccode = uccodes[k] + if k ~= uccode and newchars[uccode] then + vector[k] = { cloneid, uccode } + end + end + end + end, +} + +function collections.clonevector(name) + statistics.starttiming(fonts) + if trace_collecting then + report_fonts("processing collection %a",name) + end + local definitions = definitions[name] + local vector = { } + vectors[current] = vector + for i=1,#definitions do + local definition = definitions[i] + local name = definition.font + local start = definition.start + local stop = definition.stop + local check = definition.check + local force = definition.force + local offset = definition.offset or start + local remap = definition.remap -- not used + local target = definition.target + local method = definition.method + local cloneid = list[i] + local oldchars = fontdata[current].characters + local newchars = fontdata[cloneid].characters + local factor = definition.factor + if factor then + vector.factor = factor + end + if trace_collecting then + if target then + report_fonts("remapping font %a to %a for range %U - %U, offset %X, target %U",current,cloneid,start,stop,offset,target) + else + report_fonts("remapping font %a to %a for range %U - %U, offset %X",current,cloneid,start,stop,offset) + end + end + if method then + method = methods[method] + end + if method then + method(oldchars,newchars,vector,start,stop,cloneid) + elseif check then + if target then + for unicode = start, stop do + local unic = unicode + offset - start + if not newchars[target] then + -- not in font + elseif force or (not vector[unic] and not oldchars[unic]) then + vector[unic] = { cloneid, target } + end + target = target + 1 + end + elseif remap then + -- not used + else + for unicode = start, stop do + local unic = unicode + offset - start + if not newchars[unicode] then + -- not in font + elseif force or (not vector[unic] and not oldchars[unic]) then + vector[unic] = cloneid + end + end + end + else + if target then + for unicode = start, stop do + local unic = unicode + offset - start + if force or (not vector[unic] and not oldchars[unic]) then + vector[unic] = { cloneid, target } + end + target = target + 1 + end + elseif remap then + for unicode = start, stop do + local unic = unicode + offset - start + if force or (not vector[unic] and not oldchars[unic]) then + vector[unic] = { cloneid, remap[unicode] } + end + end + else + for unicode = start, stop do + local unic = unicode + offset - start + if force or (not vector[unic] and not oldchars[unic]) then + vector[unic] = cloneid + end + end + end + end + end + if trace_collecting then + report_fonts("activating collection %a for font %a",name,current) + end + statistics.stoptiming(fonts) + -- for WS: needs checking + if validvectors[current] then + checkenabled() + end +end + +-- we already have this parser +-- +-- local spec = (P("sa") + P("at") + P("scaled") + P("at") + P("mo")) * P(" ")^1 * (1-P(" "))^1 * P(" ")^0 * -1 +-- local okay = ((1-spec)^1 * spec * Cc(true)) + Cc(false) +-- +-- if lpegmatch(okay,name) then + +function collections.prepare(name) -- we can do this in lua now .. todo + current = currentfont() + if vectors[current] then + return + end + local properties = propdata[current] + local mathsize = properties.mathsize + if mathsize == 1 or mathsize == 2 or mathsize == 3 then + return + end + local d = definitions[name] + if d then + if trace_collecting then + local filename = file.basename(properties.filename or "?") + report_fonts("applying collection %a to %a, file %a",name,current,filename) + end + list = { } + context.pushcatcodes("prt") -- context.unprotect() + context.font_fallbacks_start_cloning() + for i=1,#d do + local f = d[i] + local name = f.font + local scale = f.rscale or 1 + if fontpatternhassize(name) then + context.font_fallbacks_clone_unique(name,scale) + else + context.font_fallbacks_clone_inherited(name,scale) + end + context.font_fallbacks_register_main(name) + end + context.font_fallbacks_prepare_clone_vectors(name) + context.font_fallbacks_stop_cloning() + context.popcatcodes() -- context.protect() + end +end + +function collections.report(message) + if trace_collecting then + report_fonts("tex: %s",message) + end +end + +local function monoslot(font,char,parent,factor) + local tfmdata = fontdata[font] + local privatename = formatters["faked mono %s"](char) + local privateslot = hasprivate(tfmdata,privatename) + if privateslot then + return privateslot + else + local characters = tfmdata.characters + local properties = tfmdata.properties + local width = factor * fontquads[parent] + local character = characters[char] + if character then + -- runtime patching of the font (can only be new characters) + -- instead of messing with existing dimensions + local data = { + -- no features so a simple copy + width = width, + height = character.height, + depth = character.depth, + commands = { + rightcommand[(width - character.width or 0)/2], + charcommand[char], + } + } + local u = addprivate(tfmdata, privatename, data) + addcharacters(properties.id, { characters = { [u] = data } } ) + return u + else + return char + end + end +end + +function collections.process(head) -- this way we keep feature processing + for n, char, font in nextchar, head do + local vector = validvectors[font] + if vector then + local vect = vector[char] + if not vect then + -- keep it + elseif type(vect) == "table" then + local newfont = vect[1] + local newchar = vect[2] + if trace_collecting then + report_fonts("remapping character %C in font %a to character %C in font %a%s", + char,font,newchar,newfont,not chardata[newfont][newchar] and " (missing)" or "" + ) + end + setfont(n,newfont,newchar) + else + local fakemono = vector.factor + if trace_collecting then + report_fonts("remapping font %a to %a for character %C%s", + font,vect,char,not chardata[vect][char] and " (missing)" or "" + ) + end + if fakemono then + setfont(n,vect,monoslot(vect,char,font,fakemono)) + else + setfont(n,vect) + end + end + end + end + return head +end + +function collections.found(font,char) -- this way we keep feature processing + if not char then + font, char = currentfont(), font + end + if chardata[font][char] then + return true -- in normal font + else + local v = vectors[font] + return v and v[char] and true or false + end +end + +-- interface + +implement { + name = "fontcollectiondefine", + actions = collections.define, + arguments = "4 strings", +} + +implement { + name = "fontcollectionreset", + actions = collections.reset, + arguments = "2 strings", +} + +implement { + name = "fontcollectionprepare", + actions = collections.prepare, + arguments = "string" +} + +implement { + name = "fontcollectionreport", + actions = collections.report, + arguments = "string" +} + +implement { + name = "fontcollectionregister", + actions = collections.registermain, + arguments = "string" +} + +implement { + name = "fontcollectionclone", + actions = collections.clonevector, + arguments = "string" +} + +implement { + name = "doifelsecharinfont", + actions = { collections.found, commands.doifelse }, + arguments = "integer" +} diff --git a/tex/context/base/mkxl/font-col.mklx b/tex/context/base/mkxl/font-col.mklx index 1dd5b25c6..f9874958d 100644 --- a/tex/context/base/mkxl/font-col.mklx +++ b/tex/context/base/mkxl/font-col.mklx @@ -27,7 +27,7 @@ \writestatus{loading}{ConTeXt Font Macros / Collections} -\registerctxluafile{font-col}{} +\registerctxluafile{font-col}{autosuffix} \unprotect diff --git a/tex/context/base/mkxl/font-con.lmt b/tex/context/base/mkxl/font-con.lmt new file mode 100644 index 000000000..f7a725160 --- /dev/null +++ b/tex/context/base/mkxl/font-con.lmt @@ -0,0 +1,1492 @@ +if not modules then modules = { } end modules ['font-con'] = { + 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" +} + +-- some names of table entries will be changed (no _) + +local next, tostring, tonumber, rawget = next, tostring, tonumber, rawget +local format, match, lower, gsub, find = string.format, string.match, string.lower, string.gsub, string.find +local sort, insert, concat = table.sort, table.insert, table.concat +local sortedkeys, sortedhash, serialize, fastcopy = table.sortedkeys, table.sortedhash, table.serialize, table.fastcopy +local derivetable = table.derive +local ioflush = io.flush +local round = math.round +local setmetatable, getmetatable, rawget, rawset = setmetatable, getmetatable, rawget, rawset + +local trace_defining = false trackers.register("fonts.defining", function(v) trace_defining = v end) +local trace_scaling = false trackers.register("fonts.scaling", function(v) trace_scaling = v end) + +local report_defining = logs.reporter("fonts","defining") + +-- watch out: no negative depths and negative eights permitted in regular fonts + +--[[ldx-- +<p>Here we only implement a few helper functions.</p> +--ldx]]-- + +local fonts = fonts +local constructors = fonts.constructors or { } +fonts.constructors = constructors +local handlers = fonts.handlers or { } -- can have preloaded tables +fonts.handlers = handlers + +local allocate = utilities.storage.allocate +local setmetatableindex = table.setmetatableindex + +-- will be directives + +constructors.version = 1.01 +constructors.cache = containers.define("fonts", "constructors", constructors.version, false) + +constructors.privateoffset = fonts.privateoffsets.textbase or 0xF0000 + +-- This might become an interface: + +local designsizes = allocate() +constructors.designsizes = designsizes +local loadedfonts = allocate() +constructors.loadedfonts = loadedfonts + +--[[ldx-- +<p>We need to normalize the scale factor (in scaled points). This has to +do with the fact that <l n='tex'/> uses a negative multiple of 1000 as +a signal for a font scaled based on the design size.</p> +--ldx]]-- + +local factors = { + pt = 65536.0, + bp = 65781.8, +} + +function constructors.setfactor(f) + constructors.factor = factors[f or 'pt'] or factors.pt +end + +constructors.setfactor() + +function constructors.scaled(scaledpoints, designsize) -- handles designsize in sp as well + if scaledpoints < 0 then + local factor = constructors.factor + if designsize then + if designsize > factor then -- or just 1000 / when? mp? + return (- scaledpoints/1000) * designsize -- sp's + else + return (- scaledpoints/1000) * designsize * factor + end + else + return (- scaledpoints/1000) * 10 * factor + end + else + return scaledpoints + end +end + +function constructors.getprivate(tfmdata) + local properties = tfmdata.properties + local private = properties.private + properties.private = private + 1 + return private +end + +function constructors.setmathparameter(tfmdata,name,value) + local m = tfmdata.mathparameters + local c = tfmdata.MathConstants + if m then + m[name] = value + end + if c and c ~= m then + c[name] = value + end +end + +function constructors.getmathparameter(tfmdata,name) + local p = tfmdata.mathparameters or tfmdata.MathConstants + if p then + return p[name] + end +end + +--[[ldx-- +<p>Beware, the boundingbox is passed as reference so we may not overwrite it +in the process; numbers are of course copies. Here 65536 equals 1pt. (Due to +excessive memory usage in CJK fonts, we no longer pass the boundingbox.)</p> +--ldx]]-- + +-- The scaler is only used for otf and afm and virtual fonts. If a virtual font has italic +-- correction make sure to set the hasitalics flag. Some more flags will be added in the +-- future. + +--[[ldx-- +<p>The reason why the scaler was originally split, is that for a while we experimented +with a helper function. However, in practice the <l n='api'/> calls are too slow to +make this profitable and the <l n='lua'/> based variant was just faster. A days +wasted day but an experience richer.</p> +--ldx]]-- + +-- experimental, sharing kerns (unscaled and scaled) saves memory +-- local sharedkerns, basekerns = constructors.check_base_kerns(tfmdata) +-- loop over descriptions (afm and otf have descriptions, tfm not) +-- there is no need (yet) to assign a value to chr.tonunicode + +-- constructors.prepare_base_kerns(tfmdata) -- optimalization + +-- we have target.name=metricfile and target.fullname=RealName and target.filename=diskfilename +-- when collapsing fonts, luatex looks as both target.name and target.fullname as ttc files +-- can have multiple subfonts + +function constructors.calculatescale(tfmdata,scaledpoints) + -- implemented in font-ctx.lmt +end + +function constructors.assignmathparameters(target,original) + -- implemented in math-act.lua +end + +function constructors.beforecopyingcharacters(target,original) + -- can be used for additional tweaking +end + +function constructors.aftercopyingcharacters(target,original) + -- can be used for additional tweaking +end + +function constructors.trytosharefont(target,tfmdata) + -- implemented in font-def.lmt +end + +local synonyms = { + exheight = "x_height", + xheight = "x_height", + ex = "x_height", + emwidth = "quad", + em = "quad", + spacestretch = "space_stretch", + stretch = "space_stretch", + spaceshrink = "space_shrink", + shrink = "space_shrink", + extraspace = "extra_space", + xspace = "extra_space", + slantperpoint = "slant", +} + +function constructors.enhanceparameters(parameters) + local mt = getmetatable(parameters) + local getter = function(t,k) + if not k then + return nil + end + local s = synonyms[k] + if s then + return rawget(t,s) or (mt and mt[s]) or nil + end + if k == "spacing" then + return { + width = t.space, + stretch = t.space_stretch, + shrink = t.space_shrink, + extra = t.extra_space, + } + end + return mt and mt[k] or nil + end + local setter = function(t,k,v) + if not k then + return 0 + end + local s = synonyms[k] + if s then + rawset(t,s,v) + elseif k == "spacing" then + if type(v) == "table" then + rawset(t,"space",v.width or 0) + rawset(t,"space_stretch",v.stretch or 0) + rawset(t,"space_shrink",v.shrink or 0) + rawset(t,"extra_space",v.extra or 0) + end + else + rawset(t,k,v) + end + end + setmetatable(parameters, { + __index = getter, + __newindex = setter, + }) +end + +local function mathkerns(v,vdelta) + local k = { } + for i=1,#v do + local entry = v[i] + local height = entry.height + local kern = entry.kern + k[i] = { + height = height and vdelta*height or 0, + kern = kern and vdelta*kern or 0, + } + end + return k +end + +local psfake = 0 + +local function fixedpsname(psname,fallback) + local usedname = psname + if psname and psname ~= "" then + if find(psname," ",1,true) then + usedname = gsub(psname,"[%s]+","-") + else + -- we assume that the name is sane enough (we might sanitize completely some day) + end + elseif not fallback or fallback == "" then + psfake = psfake + 1 + psname = "fakename-" .. psfake + else + -- filenames can be a mess so we do a drastic cleanup + psname = fallback + usedname = gsub(psname,"[^a-zA-Z0-9]+","-") + end + return usedname, psname ~= usedname +end + +function constructors.scale(tfmdata,specification) + local target = { } -- the new table + -- + if tonumber(specification) then + specification = { size = specification } + end + target.specification = specification + -- + local scaledpoints = specification.size + local relativeid = specification.relativeid + -- + local properties = tfmdata.properties or { } + local goodies = tfmdata.goodies or { } + local resources = tfmdata.resources or { } + local descriptions = tfmdata.descriptions or { } -- bad news if empty + local characters = tfmdata.characters or { } -- bad news if empty + local changed = tfmdata.changed or { } -- for base mode + local shared = tfmdata.shared or { } + local parameters = tfmdata.parameters or { } + local mathparameters = tfmdata.mathparameters or { } + -- + local targetcharacters = { } + local targetdescriptions = derivetable(descriptions) + local targetparameters = derivetable(parameters) + local targetproperties = derivetable(properties) + local targetgoodies = goodies -- we need to loop so no metatable + target.characters = targetcharacters + target.descriptions = targetdescriptions + target.parameters = targetparameters + -- target.mathparameters = targetmathparameters -- happens elsewhere + target.properties = targetproperties + target.goodies = targetgoodies + target.shared = shared + target.resources = resources + target.unscaled = tfmdata -- the original unscaled one + -- + -- specification.mathsize : 1=text 2=script 3=scriptscript + -- specification.textsize : natural (text)size + -- parameters.mathsize : 1=text 2=script 3=scriptscript >1000 enforced size (feature value other than yes) + -- + local mathsize = tonumber(specification.mathsize) or 0 + local textsize = tonumber(specification.textsize) or scaledpoints + local forcedsize = tonumber(parameters.mathsize ) or 0 -- can be set by the feature "mathsize" + local extrafactor = tonumber(specification.factor ) or 1 + if (mathsize == 2 or forcedsize == 2) and parameters.scriptpercentage then + scaledpoints = parameters.scriptpercentage * textsize / 100 + elseif (mathsize == 3 or forcedsize == 3) and parameters.scriptscriptpercentage then + scaledpoints = parameters.scriptscriptpercentage * textsize / 100 + elseif forcedsize > 1000 then -- safeguard + scaledpoints = forcedsize + else + -- in context x and xx also use mathsize + end + targetparameters.mathsize = mathsize + targetparameters.textsize = textsize + targetparameters.forcedsize = forcedsize + targetparameters.extrafactor = extrafactor + -- + local defaultwidth = resources.defaultwidth or 0 + local defaultheight = resources.defaultheight or 0 + local defaultdepth = resources.defaultdepth or 0 + local units = parameters.units or 1000 + -- + -- boundary keys are no longer needed as we now have a string 'right_boundary' + -- that can be used in relevant tables (kerns and ligatures) ... not that I ever + -- used them + -- + -- boundarychar_label = 0, -- not needed + -- boundarychar = 65536, -- there is now a string 'right_boundary' + -- false_boundarychar = 65536, -- produces invalid tfm in luatex + -- + targetproperties.language = properties.language or "dflt" -- inherited + targetproperties.script = properties.script or "dflt" -- inherited + targetproperties.mode = properties.mode or "base" -- inherited + targetproperties.method = properties.method + -- + local askedscaledpoints = scaledpoints + local scaledpoints, delta = constructors.calculatescale(tfmdata,scaledpoints,nil,specification) -- no shortcut, dan be redefined + -- + local hdelta = delta + local vdelta = delta + -- + target.designsize = parameters.designsize -- not really needed so it might become obsolete + target.units = units + -- + target.size = scaledpoints + -- + target.subfont = properties.subfont + target.cidinfo = properties.cidinfo + target.format = properties.format + -- + local original = properties.original or tfmdata.original + local fontname = properties.fontname or tfmdata.fontname + local fullname = properties.fullname or tfmdata.fullname + local filename = properties.filename or tfmdata.filename + local psname = properties.psname or tfmdata.psname + local name = properties.name or tfmdata.name + -- + -- The psname used in pdf file as well as for selecting subfont in ttc although + -- we don't need that subfont look up here (mapfile stuff). + -- + local psname, psfixed = fixedpsname(psname,fontname or fullname or file.nameonly(filename)) + -- + target.original = original + target.fontname = fontname + target.fullname = fullname + target.filename = filename + target.psname = psname + target.name = name + -- + properties.fontname = fontname + properties.fullname = fullname + properties.filename = filename + properties.psname = psname + properties.name = name + -- expansion (hz) + local expansion = parameters.expansion + if expansion then + target.stretch = expansion.stretch + target.shrink = expansion.shrink + target.step = expansion.step + end + -- slanting + local slantfactor = parameters.slantfactor or 0 + if slantfactor ~= 0 then + target.slant = slantfactor * 1000 + else + target.slant = 0 + end + -- widening + local extendfactor = parameters.extendfactor or 0 + if extendfactor ~= 0 and extendfactor ~= 1 then + hdelta = hdelta * extendfactor + target.extend = extendfactor * 1000 + else + target.extend = 1000 -- extent ? + end + -- squeezing + local squeezefactor = parameters.squeezefactor or 0 + if squeezefactor ~= 0 and squeezefactor ~= 1 then + vdelta = vdelta * squeezefactor + target.squeeze = squeezefactor * 1000 + else + target.squeeze = 1000 -- extent ? + end + -- effects + local mode = parameters.mode or 0 + if mode ~= 0 then + target.mode = mode + end + local width = parameters.width or 0 + if width ~= 0 then + target.width = width * delta * 1000 / 655360 + end + -- + targetparameters.factor = delta + targetparameters.hfactor = hdelta + targetparameters.vfactor = vdelta + targetparameters.size = scaledpoints + targetparameters.units = units + targetparameters.scaledpoints = askedscaledpoints + targetparameters.mode = mode + targetparameters.width = width + -- + local hasquality = parameters.expansion or parameters.protrusion + local hasitalics = properties.hasitalics + local autoitalicamount = properties.autoitalicamount + local stackmath = not properties.nostackmath + local haskerns = properties.haskerns or properties.mode == "base" -- we can have afm in node mode + local hasligatures = properties.hasligatures or properties.mode == "base" -- we can have afm in node mode + local realdimensions = properties.realdimensions + local writingmode = properties.writingmode or "horizontal" + -- + if changed and not next(changed) then + changed = false + end + -- + target.writingmode = writingmode == "vertical" and "vertical" or "horizontal" + -- + target.postprocessors = tfmdata.postprocessors + -- + local targetslant = (parameters.slant or parameters[1] or 0) * factors.pt -- per point + local targetspace = (parameters.space or parameters[2] or 0) * hdelta + local targetspace_stretch = (parameters.space_stretch or parameters[3] or 0) * hdelta + local targetspace_shrink = (parameters.space_shrink or parameters[4] or 0) * hdelta + local targetx_height = (parameters.x_height or parameters[5] or 0) * vdelta + local targetquad = (parameters.quad or parameters[6] or 0) * hdelta + local targetextra_space = (parameters.extra_space or parameters[7] or 0) * hdelta + -- + targetparameters.slant = targetslant -- slantperpoint + targetparameters.space = targetspace + targetparameters.space_stretch = targetspace_stretch + targetparameters.space_shrink = targetspace_shrink + targetparameters.x_height = targetx_height + targetparameters.quad = targetquad + targetparameters.extra_space = targetextra_space + -- + local hshift = parameters.hshift + if hshift then + targetparameters.hshift = delta * hshift + end + local vshift = parameters.vshift + if vshift then + targetparameters.vshift = delta * vshift + end + -- + local ascender = parameters.ascender + if ascender then + targetparameters.ascender = delta * ascender + end + local descender = parameters.descender + if descender then + targetparameters.descender = delta * descender + end + -- + constructors.enhanceparameters(targetparameters) -- official copies for us, now virtual + -- + local scaledwidth = defaultwidth * hdelta + local scaledheight = defaultheight * vdelta + local scaleddepth = defaultdepth * vdelta + -- + local hasmath = (properties.hasmath or next(mathparameters)) and true + -- + if hasmath then + constructors.assignmathparameters(target,tfmdata) -- does scaling and whatever is needed + properties.hasmath = true + target.nomath = false + target.MathConstants = target.mathparameters + else + properties.hasmath = false + target.nomath = true + target.mathparameters = nil -- nop + end + -- + -- During the transition to opentype the engine had troubles with italics so we had some + -- additional code for fixing that. In node mode (text) we don't care much if italics gets + -- passed because the engine does nothing with them then. + -- + if hasmath then + local mathitalics = properties.mathitalics + if mathitalics == false then + if trace_defining then + report_defining("%s italics %s for font %a, fullname %a, filename %a","math",hasitalics and "ignored" or "disabled",name,fullname,filename) + end + hasitalics = false + autoitalicamount = false + end + else + local textitalics = properties.textitalics + if textitalics == false then + if trace_defining then + report_defining("%s italics %s for font %a, fullname %a, filename %a","text",hasitalics and "ignored" or "disabled",name,fullname,filename) + end + hasitalics = false + autoitalicamount = false + end + end + -- + -- end of context specific trickery + -- + if trace_defining then + report_defining("defining tfm, name %a, fullname %a, filename %a, %spsname %a, hscale %a, vscale %a, math %a, italics %a", + name,fullname,filename,psfixed and "(fixed) " or "",psname,hdelta,vdelta, + hasmath and "enabled" or "disabled",hasitalics and "enabled" or "disabled") + end + -- + constructors.beforecopyingcharacters(target,tfmdata) + -- + local sharedkerns = { } + -- + -- we can have a dumb mode (basemode without math etc) that skips most + -- + for unicode, character in next, characters do + local chr, description, index + if changed then + local c = changed[unicode] + if c and c ~= unicode then + if c then + description = descriptions[c] or descriptions[unicode] or character + character = characters[c] or character + index = description.index or c + else + description = descriptions[unicode] or character + index = description.index or unicode + end + else + description = descriptions[unicode] or character + index = description.index or unicode + end + else + description = descriptions[unicode] or character + index = description.index or unicode + end + local width = description.width + local height = description.height + local depth = description.depth + local isunicode = description.unicode + if realdimensions then + -- this is mostly for checking issues + if not height or height == 0 then + local bb = description.boundingbox + local ht = bb[4] + if ht ~= 0 then + height = ht + end + if not depth or depth == 0 then + local dp = -bb[2] + if dp ~= 0 then + depth = dp + end + end + elseif not depth or depth == 0 then + local dp = -description.boundingbox[2] + if dp ~= 0 then + depth = dp + end + end + end + if width then width = hdelta*width else width = scaledwidth end + if height then height = vdelta*height else height = scaledheight end + -- if depth then depth = vdelta*depth else depth = scaleddepth end + if depth and depth ~= 0 then + depth = delta*depth + if isunicode then + chr = { + index = index, + height = height, + depth = depth, + width = width, + unicode = isunicode, + } + else + chr = { + index = index, + height = height, + depth = depth, + width = width, + } + end + else + if isunicode then + chr = { + index = index, + height = height, + width = width, + unicode = isunicode, + } + else + chr = { + index = index, + height = height, + width = width, + } + end + end + if hasquality then + -- we could move these calculations elsewhere (saves calculations) + local ve = character.expansion_factor + if ve then + chr.expansion_factor = ve*1000 -- expansionfactor, hm, can happen elsewhere + end + local vl = character.left_protruding + if vl then + chr.left_protruding = width*vl + end + local vr = character.right_protruding + if vr then + chr.right_protruding = width*vr + end + end + -- + if hasmath then + -- + -- todo, just operate on descriptions.math + local vn = character.next + if vn then + chr.next = vn + else + local vv = character.vert_variants + if vv then + local t = { } + for i=1,#vv do + local vvi = vv[i] + local s = vvi["start"] or 0 + local e = vvi["end"] or 0 + local a = vvi["advance"] or 0 + t[i] = { -- zero check nicer for 5.3 + ["start"] = s == 0 and 0 or s * vdelta, + ["end"] = e == 0 and 0 or e * vdelta, + ["advance"] = a == 0 and 0 or a * vdelta, + ["extender"] = vvi["extender"], + ["glyph"] = vvi["glyph"], + } + end + chr.vert_variants = t + else + local hv = character.horiz_variants + if hv then + local t = { } + for i=1,#hv do + local hvi = hv[i] + local s = hvi["start"] or 0 + local e = hvi["end"] or 0 + local a = hvi["advance"] or 0 + t[i] = { -- zero check nicer for 5.3 + ["start"] = s == 0 and 0 or s * hdelta, + ["end"] = e == 0 and 0 or e * hdelta, + ["advance"] = a == 0 and 0 or a * hdelta, + ["extender"] = hvi["extender"], + ["glyph"] = hvi["glyph"], + } + end + chr.horiz_variants = t + end + end + -- todo also check mathitalics (or that one can go away) + end + local vi = character.vert_italic + if vi and vi ~= 0 then + chr.vert_italic = vi*hdelta + end + local va = character.accent + if va then + chr.top_accent = vdelta*va + end + if stackmath then + local mk = character.mathkerns + if mk then + local tr = mk.topright + local tl = mk.topleft + local br = mk.bottomright + local bl = mk.bottomleft + chr.mathkern = { -- singular -> should be patched in luatex ! + top_right = tr and mathkerns(tr,vdelta) or nil, + top_left = tl and mathkerns(tl,vdelta) or nil, + bottom_right = br and mathkerns(br,vdelta) or nil, + bottom_left = bl and mathkerns(bl,vdelta) or nil, + } + end + end + if hasitalics then + local vi = character.italic + if vi and vi ~= 0 then + chr.italic = vi*hdelta + end + end + elseif autoitalicamount then -- itlc feature + local vi = description.italic + if not vi then + local bb = description.boundingbox + if bb then + local vi = bb[3] - description.width + autoitalicamount + if vi > 0 then -- < 0 indicates no overshoot or a very small auto italic + chr.italic = vi*hdelta + end + else + -- report_defining("no boundingbox for character %C in font %a, fullname %a, filename %a",unicode,name,fullname,filename) + end + elseif vi ~= 0 then + chr.italic = vi*hdelta + end + elseif hasitalics then -- unlikely + local vi = character.italic + if vi and vi ~= 0 then + chr.italic = vi*hdelta + end + end + if haskerns then + local vk = character.kerns + if vk then + local s = sharedkerns[vk] + if not s then + s = { } + for k,v in next, vk do s[k] = v*hdelta end + sharedkerns[vk] = s + end + chr.kerns = s + end + end + if hasligatures then + local vl = character.ligatures + if vl then + if true then + chr.ligatures = vl -- shared + else + local tt = { } + for i, l in next, vl do + tt[i] = l + end + chr.ligatures = tt + end + end + end + local vc = character.commands + if vc then + -- we assume non scaled commands here + -- tricky .. we need to scale pseudo math glyphs too + -- which is why we deal with rules too + local ok = false + for i=1,#vc do + local key = vc[i][1] + if key == "right" or key == "down" or key == "rule" then + ok = true + break + end + end + if ok then + local tt = { } + for i=1,#vc do + local ivc = vc[i] + local key = ivc[1] + if key == "right" then + tt[i] = { key, ivc[2]*hdelta } + elseif key == "down" then + tt[i] = { key, ivc[2]*vdelta } + elseif key == "rule" then + tt[i] = { key, ivc[2]*vdelta, ivc[3]*hdelta } + else -- not comment + tt[i] = ivc -- shared since in cache and untouched + end + end + chr.commands = tt + else + chr.commands = vc + end + -- chr.index = nil + end + targetcharacters[unicode] = chr + end + -- + properties.setitalics = hasitalics -- for postprocessing + -- + constructors.aftercopyingcharacters(target,tfmdata) + -- + constructors.trytosharefont(target,tfmdata) + -- + -- catch inconsistencies (for now) + -- + local vfonts = target.fonts + if not vfonts or #vfonts == 0 then + target.fonts = { { id = 0 } } + end + -- + return target +end + +function constructors.finalize(tfmdata) + if tfmdata.properties and tfmdata.properties.finalized then + return + end + -- + if not tfmdata.characters then + return nil + end + -- + if not tfmdata.goodies then + tfmdata.goodies = { } + end + -- + local parameters = tfmdata.parameters + if not parameters then + return nil + end + -- + if not parameters.expansion then + parameters.expansion = { + stretch = tfmdata.stretch or 0, + shrink = tfmdata.shrink or 0, + step = tfmdata.step or 0, + } + end + -- + if not parameters.size then + parameters.size = tfmdata.size + end + -- + if not parameters.mode then + parameters.mode = 0 + end + -- + if not parameters.width then + parameters.width = 0 + end + -- + if not parameters.slantfactor then + parameters.slantfactor = (tfmdata.slant or 0)/1000 + end + -- + if not parameters.extendfactor then + parameters.extendfactor = (tfmdata.extend or 1000)/1000 + end + -- + if not parameters.squeezefactor then + parameters.squeezefactor = (tfmdata.squeeze or 1000)/1000 + end + -- + 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 or 1000 + end + -- + if not tfmdata.descriptions then + local descriptions = { } -- yes or no + setmetatableindex(descriptions, function(t,k) local v = { } t[k] = v return v end) + tfmdata.descriptions = descriptions + end + -- + local properties = tfmdata.properties + if not properties then + properties = { } + tfmdata.properties = properties + end + -- + properties.fontname = properties.fontname or tfmdata.fontname + properties.filename = properties.filename or tfmdata.filename + properties.fullname = properties.fullname or tfmdata.fullname + properties.name = properties.name or tfmdata.name + properties.psname = properties.psname or tfmdata.psname + -- + properties.subfont = tfmdata.subfont or nil + properties.cidinfo = tfmdata.cidinfo or nil + properties.format = tfmdata.format or "type1" + properties.writingmode = tfmdata.writingmode or "horizontal" + properties.usedbitmap = tfmdata.usedbitmap + -- + if not tfmdata.resources then + tfmdata.resources = { } + end + if not tfmdata.shared then + tfmdata.shared = { } + end + -- + -- tfmdata.fonts + -- tfmdata.unscaled + -- + if not properties.hasmath then + properties.hasmath = not tfmdata.nomath + end + -- + tfmdata.MathConstants = nil + tfmdata.postprocessors = nil + -- + tfmdata.fontname = nil + tfmdata.filename = nil + tfmdata.fullname = nil + tfmdata.name = nil -- most tricky part + tfmdata.psname = nil + -- + tfmdata.subfont = nil + tfmdata.cidinfo = nil + tfmdata.format = nil + tfmdata.nomath = nil + tfmdata.designsize = nil + -- + tfmdata.size = nil + tfmdata.stretch = nil + tfmdata.shrink = nil + tfmdata.step = nil + tfmdata.slant = nil + tfmdata.extend = nil + tfmdata.squeeze = nil + tfmdata.mode = nil + tfmdata.width = nil + tfmdata.units = nil + -- + tfmdata.cache = nil + -- + properties.finalized = true + -- + return tfmdata +end + +--[[ldx-- +<p>A unique hash value is generated by:</p> +--ldx]]-- + +local hashmethods = { } +constructors.hashmethods = hashmethods + +function constructors.hashfeatures(specification) -- will be overloaded + local features = specification.features + if features then + local t, n = { }, 0 + for category, list in sortedhash(features) do + if next(list) then + local hasher = hashmethods[category] + if hasher then + local hash = hasher(list) + if hash then + n = n + 1 + t[n] = category .. ":" .. hash + end + end + end + end + if n > 0 then + return concat(t," & ") + end + end + return "unknown" +end + +hashmethods.normal = function(list) + local s = { } + local n = 0 + for k, v in next, list do + if not k then + -- no need to add to hash + elseif k == "number" or k == "features" then + -- no need to add to hash (maybe we need a skip list) + else + n = n + 1 + if type(v) == "table" then + -- table.sequenced + local t = { } + local m = 0 + for k, v in next, v do + m = m + 1 + t[m] = k .. '=' .. tostring(v) + end + sort(t) + s[n] = k .. '={' .. concat(t,",") .. "}" + else + s[n] = k .. '=' .. tostring(v) + end + end + end + if n > 0 then + sort(s) + return concat(s,"+") + end +end + +--[[ldx-- +<p>In principle we can share tfm tables when we are in need for a font, but then +we need to define a font switch as an id/attr switch which is no fun, so in that +case users can best use dynamic features ... so, we will not use that speedup. Okay, +when we get rid of base mode we can optimize even further by sharing, but then we +loose our testcases for <l n='luatex'/>.</p> +--ldx]]-- + +function constructors.hashinstance(specification,force) + -- implemented in font-ctx.lmt +end + +function constructors.setname(tfmdata,specification) + -- dummy (still called in generic font-otl) +end + +function constructors.checkedfilename(data) + local foundfilename = data.foundfilename + if not foundfilename then + local askedfilename = data.filename or "" + if askedfilename ~= "" then + askedfilename = resolvers.resolve(askedfilename) -- no shortcut + foundfilename = resolvers.findbinfile(askedfilename,"") or "" + if foundfilename == "" then + report_defining("source file %a is not found",askedfilename) + foundfilename = resolvers.findbinfile(file.basename(askedfilename),"") or "" + if foundfilename ~= "" then + report_defining("using source file %a due to cache mismatch",foundfilename) + end + end + end + data.foundfilename = foundfilename + end + return foundfilename +end + +local formats = allocate() +fonts.formats = formats + +setmetatableindex(formats, function(t,k) + local l = lower(k) + if rawget(t,k) then + t[k] = l + return l + end + return rawget(t,file.suffix(l)) +end) + +do + + local function setindeed(mode,source,target,group,name,position) + local action = source[mode] + if not action then + return + end + local t = target[mode] + if not t then + report_defining("fatal error in setting feature %a, group %a, mode %a",name,group,mode) + os.exit() + elseif position then + -- todo: remove existing + insert(t, position, { name = name, action = action }) + else + for i=1,#t do + local ti = t[i] + if ti.name == name then + ti.action = action + return + end + end + insert(t, { name = name, action = action }) + end + end + + local function set(group,name,target,source) + target = target[group] + if not target then + report_defining("fatal target error in setting feature %a, group %a",name,group) + os.exit() + end + local source = source[group] + if not source then + report_defining("fatal source error in setting feature %a, group %a",name,group) + os.exit() + end + local position = source.position + setindeed("node",source,target,group,name,position) + setindeed("base",source,target,group,name,position) + setindeed("plug",source,target,group,name,position) + end + + local function register(where,specification) + local name = specification.name + if name and name ~= "" then + local default = specification.default + local description = specification.description + local initializers = specification.initializers + local processors = specification.processors + local manipulators = specification.manipulators + local modechecker = specification.modechecker + if default then + where.defaults[name] = default + end + if description and description ~= "" then + where.descriptions[name] = description + end + if initializers then + set('initializers',name,where,specification) + end + if processors then + set('processors', name,where,specification) + end + if manipulators then + set('manipulators',name,where,specification) + end + if modechecker then + where.modechecker = modechecker + end + end + end + + constructors.registerfeature = register + + function constructors.getfeatureaction(what,where,mode,name) + what = handlers[what].features + if what then + where = what[where] + if where then + mode = where[mode] + if mode then + for i=1,#mode do + local m = mode[i] + if m.name == name then + return m.action + end + end + end + end + end + end + + local newfeatures = { } + constructors.newfeatures = newfeatures -- downward compatible + constructors.features = newfeatures + + local function setnewfeatures(what) + local handler = handlers[what] + local features = handler.features + if not features then + local tables = handler.tables -- can be preloaded + local statistics = handler.statistics -- can be preloaded + features = allocate { + defaults = { }, + descriptions = tables and tables.features or { }, + used = statistics and statistics.usedfeatures or { }, + initializers = { base = { }, node = { }, plug = { } }, + processors = { base = { }, node = { }, plug = { } }, + manipulators = { base = { }, node = { }, plug = { } }, + } + features.register = function(specification) return register(features,specification) end + handler.features = features -- will also become hidden + end + return features + end + + setmetatable(newfeatures, { + __call = function(t,k) local v = t[k] return v end, + __index = function(t,k) local v = setnewfeatures(k) t[k] = v return v end, + }) + +end + +do + + local newhandler = { } + constructors.handlers = newhandler -- downward compatible + constructors.newhandler = newhandler + + local function setnewhandler(what) -- could be a metatable newindex + local handler = handlers[what] + if not handler then + handler = { } + handlers[what] = handler + end + return handler + end + + setmetatable(newhandler, { + __call = function(t,k) local v = t[k] return v end, + __index = function(t,k) local v = setnewhandler(k) t[k] = v return v end, + }) + +end + +do + -- a pitty that we need to be generic as we have nicer mechanisms for this ... + + local newenhancer = { } + constructors.enhancers = newenhancer + constructors.newenhancer = newenhancer + + local function setnewenhancer(format) + + local handler = handlers[format] + local enhancers = handler.enhancers + + if not enhancers then + + local actions = allocate() -- no need to allocate thee + local before = allocate() + local after = allocate() + local order = allocate() + local known = { } + local nofsteps = 0 + local patches = { before = before, after = after } + + local trace = false + local report = logs.reporter("fonts",format .. " enhancing") + + trackers.register(format .. ".loading", function(v) trace = v end) + + local function enhance(name,data,filename,raw) + local enhancer = actions[name] + if enhancer then + if trace then + report("apply enhancement %a to file %a",name,filename) + ioflush() + end + enhancer(data,filename,raw) + else + -- no message as we can have private ones + end + end + + local function apply(data,filename,raw) + local basename = file.basename(lower(filename)) + if trace then + report("%s enhancing file %a","start",filename) + end + ioflush() -- we want instant messages + for e=1,nofsteps do + local enhancer = order[e] + local b = before[enhancer] + if b then + for pattern, action in next, b do + if find(basename,pattern) then + action(data,filename,raw) + end + end + end + enhance(enhancer,data,filename,raw) -- we have one installed: check extra features + local a = after[enhancer] + if a then + for pattern, action in next, a do + if find(basename,pattern) then + action(data,filename,raw) + end + end + end + ioflush() -- we want instant messages + end + if trace then + report("%s enhancing file %a","stop",filename) + end + ioflush() -- we want instant messages + end + + local function register(what,action) + if action then + if actions[what] then + -- overloading, e.g."check extra features" + else + nofsteps = nofsteps + 1 + order[nofsteps] = what + known[what] = nofsteps + end + actions[what] = action + else + report("bad enhancer %a",what) + end + end + + -- We used to have a lot of enhancers but no longer with the new font loader. The order of enhancers + -- is the order of definition. The before/after patches are there for old times sake and happen + -- before or after a (named) enhancer. An example of a set enhancer is "check extra features" so one + -- one set patches before or after that is applied. Unknown enhancers are auto-registered. It's a bit + -- messy but we keep it for compatibility reasons. + -- + -- fonts.handlers.otf.enhancers.patches.register("before","some patches","somefont",function(data,filename) + -- print("!!!!!!!") -- before | after + -- end) + -- + -- fonts.handlers.otf.enhancers.register("more patches",function(data,filename) + -- print("???????") -- enhance + -- end) + + local function patch(what,where,pattern,action) + local pw = patches[what] + if pw then + local ww = pw[where] + if ww then + ww[pattern] = action + else + pw[where] = { [pattern] = action } + if not known[where] then + nofsteps = nofsteps + 1 + order[nofsteps] = where + known[where] = nofsteps + end + end + end + end + + enhancers = { + register = register, + apply = apply, + patch = patch, + report = report, + patches = { + register = patch, + report = report, + }, -- for old times sake + } + + handler.enhancers = enhancers + end + return enhancers + end + + setmetatable(newenhancer, { + __call = function(t,k) local v = t[k] return v end, + __index = function(t,k) local v = setnewenhancer(k) t[k] = v return v end, + }) + +end + +--[[ldx-- +<p>We need to check for default features. For this we provide +a helper function.</p> +--ldx]]-- + +function constructors.checkedfeatures(what,features) + local defaults = handlers[what].features.defaults + if features and next(features) then + features = fastcopy(features) -- can be inherited (mt) but then no loops possible + for key, value in next, defaults do + if features[key] == nil then + features[key] = value + end + end + return features + else + return fastcopy(defaults) -- we can change features in place + end +end + +-- before scaling + +function constructors.initializefeatures(what,tfmdata,features,trace,report) + if features and next(features) then + local properties = tfmdata.properties or { } -- brrr + local whathandler = handlers[what] + local whatfeatures = whathandler.features + local whatmodechecker = whatfeatures.modechecker + -- properties.mode can be enforces (for instance in font-otd) + local mode = properties.mode or (whatmodechecker and whatmodechecker(tfmdata,features,features.mode)) or features.mode or "base" + properties.mode = mode -- also status + features.mode = mode -- both properties.mode or features.mode can be changed + -- + local done = { } + while true do + local redo = false + local initializers = whatfeatures.initializers[mode] + if initializers then + for i=1,#initializers do + local step = initializers[i] + local feature = step.name +-- we could intercept mode here .. needs a rewrite of this whole loop then but it's cleaner that way + local value = features[feature] + if not value then + -- disabled + elseif done[feature] then + -- already done + else + local action = step.action + if trace then + report("initializing feature %a to %a for mode %a for font %a",feature, + value,mode,tfmdata.properties.fullname) + end + action(tfmdata,value,features) -- can set mode (e.g. goodies) so it can trigger a restart + if mode ~= properties.mode or mode ~= features.mode then + if whatmodechecker then + properties.mode = whatmodechecker(tfmdata,features,properties.mode) -- force checking + features.mode = properties.mode + end + if mode ~= properties.mode then + mode = properties.mode + redo = true + end + end + done[feature] = true + end + if redo then + break + end + end + if not redo then + break + end + else + break + end + end + properties.mode = mode -- to be sure + return true + else + return false + end +end + +-- while typesetting + +function constructors.collectprocessors(what,tfmdata,features,trace,report) + local processes = { } + local nofprocesses = 0 + if features and next(features) then + local properties = tfmdata.properties + local whathandler = handlers[what] + local whatfeatures = whathandler.features + local whatprocessors = whatfeatures.processors + local mode = properties.mode + local processors = whatprocessors[mode] + if processors then + for i=1,#processors do + local step = processors[i] + local feature = step.name + if features[feature] then + local action = step.action + if trace then + report("installing feature processor %a for mode %a for font %a",feature,mode,tfmdata.properties.fullname) + end + if action then + nofprocesses = nofprocesses + 1 + processes[nofprocesses] = action + end + end + end + elseif trace then + report("no feature processors for mode %a for font %a",mode,properties.fullname) + end + end + return processes +end + +-- after scaling + +function constructors.applymanipulators(what,tfmdata,features,trace,report) + if features and next(features) then + local properties = tfmdata.properties + local whathandler = handlers[what] + local whatfeatures = whathandler.features + local whatmanipulators = whatfeatures.manipulators + local mode = properties.mode + local manipulators = whatmanipulators[mode] + if manipulators then + for i=1,#manipulators do + local step = manipulators[i] + local feature = step.name + local value = features[feature] + if value then + local action = step.action + if trace then + report("applying feature manipulator %a for mode %a for font %a",feature,mode,properties.fullname) + end + if action then + action(tfmdata,feature,value) + end + end + end + end + end +end + +function constructors.addcoreunicodes(unicodes) -- maybe make this a metatable if used at all + if not unicodes then + unicodes = { } + end + unicodes.space = 0x0020 + unicodes.hyphen = 0x002D + unicodes.zwj = 0x200D + unicodes.zwnj = 0x200C + return unicodes +end diff --git a/tex/context/base/mkxl/font-ctx.lmt b/tex/context/base/mkxl/font-ctx.lmt new file mode 100644 index 000000000..3d269d8cb --- /dev/null +++ b/tex/context/base/mkxl/font-ctx.lmt @@ -0,0 +1,3180 @@ +if not modules then modules = { } end modules ['font-ctx'] = { + 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" +} + +-- At some point I will clean up the code here so that at the tex end +-- the table interface is used. +-- +-- Todo: make a proper 'next id' mechanism (register etc) or wait till 'true' +-- in virtual fonts indices is implemented. +-- +-- Olders code can be found in the mkiv lua variant. + +local tostring, next, type, rawget, tonumber = tostring, next, type, rawget, tonumber + +local format, gmatch, match, find, lower, upper, gsub, byte, topattern = string.format, string.gmatch, string.match, string.find, string.lower, string.upper, string.gsub, string.byte, string.topattern +local concat, serialize, sort, fastcopy, mergedtable = table.concat, table.serialize, table.sort, table.fastcopy, table.merged +local sortedhash, sortedkeys, sequenced = table.sortedhash, table.sortedkeys, table.sequenced +local parsers = utilities.parsers +local settings_to_hash, hash_to_string, settings_to_array = parsers.settings_to_hash, parsers.hash_to_string, parsers.settings_to_array +local formatcolumns = utilities.formatters.formatcolumns +local mergehashes = utilities.parsers.mergehashes +local formatters = string.formatters +local basename = file.basename + +local utfchar, utfbyte = utf.char, utf.byte +local round = math.round + +local context, commands = context, commands + +local P, S, C, Cc, Cf, Cg, Ct, lpegmatch = lpeg.P, lpeg.S, lpeg.C, lpeg.Cc, lpeg.Cf, lpeg.Cg, lpeg.Ct, lpeg.match + +local trace_features = false trackers.register("fonts.features", function(v) trace_features = v end) +local trace_defining = false trackers.register("fonts.defining", function(v) trace_defining = v end) +local trace_designsize = false trackers.register("fonts.designsize", function(v) trace_designsize = v end) +local trace_usage = false trackers.register("fonts.usage", function(v) trace_usage = v end) +local trace_mapfiles = false trackers.register("fonts.mapfiles", function(v) trace_mapfiles = v end) +local trace_automode = false trackers.register("fonts.automode", function(v) trace_automode = v end) +local trace_merge = false trackers.register("fonts.merge", function(v) trace_merge = v end) + +local report = logs.reporter("fonts") +local report_features = logs.reporter("fonts","features") +local report_cummulative = logs.reporter("fonts","cummulative") +local report_defining = logs.reporter("fonts","defining") +local report_status = logs.reporter("fonts","status") +local report_mapfiles = logs.reporter("fonts","mapfiles") + +local setmetatableindex = table.setmetatableindex + +local implement = interfaces.implement + +local chardata = characters.data + +local fonts = fonts +local handlers = fonts.handlers +local otf = handlers.otf -- brrr +local afm = handlers.afm -- brrr +local tfm = handlers.tfm -- brrr +local names = fonts.names +local definers = fonts.definers +local specifiers = fonts.specifiers +local constructors = fonts.constructors +local loggers = fonts.loggers +local fontgoodies = fonts.goodies +local helpers = fonts.helpers +local hashes = fonts.hashes +local currentfont = font.current +local definefont = font.define + +local getprivateslot = helpers.getprivateslot + +local cleanname = names.cleanname + +local encodings = fonts.encodings +----- aglunicodes = encodings.agl.unicodes +local aglunicodes = nil -- delayed loading + +local nuts = nodes.nuts +local tonut = nuts.tonut + +local nextchar = nuts.traversers.char + +local getattr = nuts.getattr +local setattr = nuts.setattr +local getstate = nuts.getstate +local setsubtype = nuts.setsubtype + +local texgetdimen = tex.getdimen +local texsetcount = tex.setcount +local texget = tex.get + +local texdefinefont = tex.definefont +local texsp = tex.sp + +local fontdata = hashes.identifiers +local characters = hashes.characters +local descriptions = hashes.descriptions +local properties = hashes.properties +local resources = hashes.resources +local unicodes = hashes.unicodes +local csnames = hashes.csnames +local lastmathids = hashes.lastmathids +local exheights = hashes.exheights +local emwidths = hashes.emwidths +local parameters = hashes.parameters + +local designsizefilename = fontgoodies.designsizes.filename + +local ctx_char = context.char +local ctx_safechar = context.safechar +local ctx_getvalue = context.getvalue + +local otffeatures = otf.features +local otftables = otf.tables + +local registerotffeature = otffeatures.register + +local sequencers = utilities.sequencers +local appendgroup = sequencers.appendgroup +local appendaction = sequencers.appendaction + +specifiers.contextsetups = specifiers.contextsetups or { } +specifiers.contextnumbers = specifiers.contextnumbers or { } +specifiers.contextmerged = specifiers.contextmerged or { } +specifiers.synonyms = specifiers.synonyms or { } + +local setups = specifiers.contextsetups +local numbers = specifiers.contextnumbers +local merged = specifiers.contextmerged +local synonyms = specifiers.synonyms + +storage.register("fonts/setups" , setups , "fonts.specifiers.contextsetups" ) +storage.register("fonts/numbers", numbers, "fonts.specifiers.contextnumbers") +storage.register("fonts/merged", merged, "fonts.specifiers.contextmerged") +storage.register("fonts/synonyms", synonyms, "fonts.specifiers.synonyms") + +-- inspect(setups) + +setmetatableindex(setups, environment.initex and + function(t,k) + return type(k) == "number" and rawget(t,numbers[k]) or nil + end +or + function(t,k) + local v = type(k) == "number" and rawget(t,numbers[k]) + if v then + t[k] = v + return v + end + end +) + +-- this will move elsewhere ... + +local function getfontname(tfmdata) + return basename(type(tfmdata) == "number" and properties[tfmdata].name or tfmdata.properties.name) +end + +helpers.name = getfontname + +local addformatter = utilities.strings.formatters.add + +addformatter(formatters,"font:name", [["'"..fontname(%s).."'"]], { fontname = helpers.name }) +addformatter(formatters,"font:features",[["'"..sequenced(%s," ",true).."'"]],{ sequenced = table.sequenced }) + +-- ... like font-sfm or so + +constructors.resolvevirtualtoo = true -- context specific (due to resolver) +constructors.sharefonts = true -- experimental +constructors.nofsharedfonts = 0 +constructors.nofsharedhashes = 0 +constructors.nofsharedvectors = 0 +constructors.noffontsloaded = 0 + +-- we can get rid of the tfm instance when we have fast access to the +-- scaled character dimensions at the tex end, e.g. a fontobject.width +-- actually we already have some of that now as virtual keys in glyphs +-- +-- flushing the kern and ligature tables from memory saves a lot (only +-- base mode) but it complicates vf building where the new characters +-- demand this data .. solution: functions that access them + +-- font.getcopy = font.getfont -- we always want the table that context uses + +do + + -- Does this still make sense? + + local shares = { } + local hashes = { } + local nofinstances = 0 + local instances = setmetatableindex(function(t,k) + nofinstances = nofinstances + 1 + t[k] = nofinstances + return nofinstances + end) + + function constructors.trytosharefont(target,tfmdata) + constructors.noffontsloaded = constructors.noffontsloaded + 1 + if constructors.sharefonts then + local fonthash = target.specification.hash + if fonthash then + local properties = target.properties + local fullname = target.fullname + local fontname = target.fontname + local psname = target.psname + -- for the moment here: + local instance = properties.instance + if instance then + local format = tfmdata.properties.format + if format == "opentype" then + target.streamprovider = 1 + elseif format == "truetype" then + target.streamprovider = 2 + else + target.streamprovider = 0 + end + if target.streamprovider > 0 then + if fullname then + fullname = fullname .. ":" .. instances[instance] + target.fullname = fullname + end + if fontname then + fontname = fontname .. ":" .. instances[instance] + target.fontname = fontname + end + if psname then + -- this one is used for the funny prefix in font names in pdf + -- so it has to be kind of unique in order to avoid subset prefix + -- clashes being reported + psname = psname .. ":" .. instances[instance] + target.psname = psname + end + end + end + -- + local sharedname = hashes[fonthash] + if sharedname then + -- this is ok for context as we know that only features can mess with font definitions + -- so a similar hash means that the fonts are similar too + if trace_defining then + report_defining("font %a uses backend resources of font %a (%s)",target.fullname,sharedname,"common hash") + end + target.fullname = sharedname + properties.sharedwith = sharedname + constructors.nofsharedfonts = constructors.nofsharedfonts + 1 + constructors.nofsharedhashes = constructors.nofsharedhashes + 1 + else + -- the one takes more time (in the worst case of many cjk fonts) but it also saves + -- embedding time .. haha, this is interesting: when i got a clash on subset tag + -- collision i saw in the source that these tags are also using a hash like below + -- so maybe we should have an option to pass it from lua + local characters = target.characters + local n = 1 + local t = { target.psname } + -- for the moment here: + if instance then + n = n + 1 + t[n] = instance + end + -- + local u = sortedkeys(characters) + for i=1,#u do + local k = u[i] + n = n + 1 ; t[n] = k + n = n + 1 ; t[n] = characters[k].index or k + end + local checksum = md5.HEX(concat(t," ")) + local sharedname = shares[checksum] + local fullname = target.fullname + if sharedname then + if trace_defining then + report_defining("font %a uses backend resources of font %a (%s)",fullname,sharedname,"common vector") + end + fullname = sharedname + properties.sharedwith = sharedname + constructors.nofsharedfonts = constructors.nofsharedfonts + 1 + constructors.nofsharedvectors = constructors.nofsharedvectors + 1 + else + shares[checksum] = fullname + end + target.fullname = fullname + hashes[fonthash] = fullname + end + end + end + end + +end + +directives.register("fonts.checksharing",function(v) + if not v then + report_defining("font sharing in backend is disabled") + end + constructors.sharefonts = v +end) + +function definers.resetnullfont() + -- resetting is needed because tikz misuses nullfont + local parameters = fonts.nulldata.parameters + -- + parameters.slant = 0 -- 1 + parameters.space = 0 -- 2 + parameters.space_stretch = 0 -- 3 + parameters.space_shrink = 0 -- 4 + parameters.x_height = 0 -- 5 + parameters.quad = 0 -- 6 + parameters.extra_space = 0 -- 7 + parameters.designsize = 655360 + -- + constructors.enhanceparameters(parameters) -- official copies for us + -- + definers.resetnullfont = function() end +end + +-- This is some initialization code and we don't want (tracing) clutter in lmtx +-- which is why we follow a different route there. I admit that is sounds freaky. +-- Watch out: in lmtx the font dimen array is no longer resized automatically. + +implement { + name = "resetnullfont", + onlyonce = true, + permanent = false, + actions = function() + for i=1,7 do + font.setfontdimen(0,i,0) + end + definers.resetnullfont() + end +} + +-- this cannot be a feature initializer as there is no auto namespace +-- so we never enter the loop then; we can store the defaults in the tma +-- file (features.gpos.mkmk = 1 etc) + +local needsnodemode = { -- we will have node mode by default anyway + -- gsub_single = true, + gsub_multiple = true, + -- gsub_alternate = true, + -- gsub_ligature = true, + gsub_context = true, + gsub_contextchain = true, + gsub_reversecontextchain = true, + -- chainsub = true, + -- reversesub = true, + gpos_mark2base = true, + gpos_mark2ligature = true, + gpos_mark2mark = true, + gpos_cursive = true, + -- gpos_single = true, + -- gpos_pair = true, + gpos_context = true, + gpos_contextchain = true, +} + +otftables.scripts.auto = "automatic fallback to latn when no dflt present" + +-- setmetatableindex(otffeatures.descriptions,otftables.features) + +local function checkedscript(tfmdata,resources,features) + local latn = false + local script = false + if resources.features then + for g, list in next, resources.features do + for f, scripts in next, list do + if scripts.dflt then + script = "dflt" + break + elseif scripts.latn then + latn = true + end + end + end + end + if not script then + script = latn and "latn" or "dflt" + end + if trace_automode then + report_defining("auto script mode, using script %a in font %!font:name!",script,tfmdata) + end + features.script = script + return script +end + +-- basemode combined with dynamics is somewhat tricky + +local function checkedmode(tfmdata,resources,features) + local sequences = resources.sequences + if sequences and #sequences > 0 then + local script = features.script or "dflt" + local language = features.language or "dflt" + for feature, value in next, features do + if value then + local found = false + for i=1,#sequences do + local sequence = sequences[i] + local features = sequence.features + if features then + local scripts = features[feature] + if scripts then + local languages = scripts[script] + if languages and languages[language] then + if found then + -- more than one lookup + if trace_automode then + report_defining("forcing mode %a, font %!font:name!, feature %a, script %a, language %a, %s", + "node",tfmdata,feature,script,language,"multiple lookups") + end + features.mode = "node" + return "node" + elseif needsnodemode[sequence.type] then + if trace_automode then + report_defining("forcing mode %a, font %!font:name!, feature %a, script %a, language %a, %s", + "node",tfmdata,feature,script,language,"no base support") + end + features.mode = "node" + return "node" + else + -- at least one lookup + found = true + end + end + end + end + end + end + end + end + if trace_automode then + report_defining("forcing mode base, font %!font:name!",tfmdata) + end + features.mode = "base" -- new, or is this wrong? + return "base" +end + +definers.checkedscript = checkedscript +definers.checkedmode = checkedmode + +-- We only set the checker and leave other settings of the mode feature as +-- they are. + +local function modechecker(tfmdata,features,mode) -- we cannot adapt features as they are shared! + if trace_features then + report_features("fontname %!font:name!, features %!font:features!",tfmdata,features) + end + local rawdata = tfmdata.shared.rawdata + local resources = rawdata and rawdata.resources + local script = features.script + if resources then + if script == "auto" then + script = checkedscript(tfmdata,resources,features) + end + if mode == "auto" then + mode = checkedmode(tfmdata,resources,features) + end + else + report_features("missing resources for font %!font:name!",tfmdata) + end + return mode +end + +registerotffeature { + name = "mode", + modechecker = modechecker, +} + +do + + -- copying will end up here too + + local beforecopyingcharacters = sequencers.new { + name = "beforecopyingcharacters", + arguments = "target,original", + } + + appendgroup(beforecopyingcharacters,"before") -- user + appendgroup(beforecopyingcharacters,"system") -- private + appendgroup(beforecopyingcharacters,"after" ) -- user + + function constructors.beforecopyingcharacters(original,target) + local runner = beforecopyingcharacters.runner + if runner then + runner(original,target) + end + end + + local aftercopyingcharacters = sequencers.new { + name = "aftercopyingcharacters", + arguments = "target,original", + } + + appendgroup(aftercopyingcharacters,"before") -- user + appendgroup(aftercopyingcharacters,"system") -- private + appendgroup(aftercopyingcharacters,"after" ) -- user + + function constructors.aftercopyingcharacters(original,target) + local runner = aftercopyingcharacters.runner + if runner then + runner(original,target) + end + end + +end + +--[[ldx-- +<p>So far we haven't really dealt with features (or whatever we want +to pass along with the font definition. We distinguish the following +situations:</p> +situations:</p> + +<code> +name:xetex like specs +name@virtual font spec +name*context specification +</code> +--ldx]]-- + +-- Currently fonts are scaled while constructing the font, so we have to do scaling +-- of commands in the vf at that point using e.g. "local scale = g.parameters.factor +-- or 1" after all, we need to work with copies anyway and scaling needs to be done +-- at some point; however, when virtual tricks are used as feature (makes more +-- sense) we scale the commands in fonts.constructors.scale (and set the factor +-- there). + +local loadfont = definers.loadfont + +function definers.loadfont(specification,size,id) -- overloads the one in font-def + local variants = definers.methods.variants + local virtualfeatures = specification.features.virtual + if virtualfeatures and virtualfeatures.preset then + local variant = variants[virtualfeatures.preset] + if variant then + return variant(specification,size,id) + end + else + return loadfont(specification,size,id) + end +end + +local function predefined(specification) + local variants = definers.methods.variants + local detail = specification.detail + if detail ~= "" and variants[detail] then + specification.features.virtual = { preset = detail } + end + return specification +end + +definers.registersplit("@", predefined,"virtual") + +local normalize_features = otffeatures.normalize -- should be general + +local function definecontext(name,t) -- can be shared + local number = setups[name] and setups[name].number or 0 -- hm, numbers[name] + if number == 0 then + number = #numbers + 1 + numbers[number] = name + end + t.number = number + setups[name] = t + return number, t +end + +-- {a,b,c} as table (so we don' need to parse again when it gets applied) +-- we will update this ... when we have foo={a,b,c} then we can keep the table + +-- \definefontfeature[demo][a={b,c}] +-- \definefontfeature[demo][a={b=12,c={34,35}}] + +local h = setmetatableindex(function(t,k) + local v = "," .. k .. "," + t[k] = v + return v +end) + +-- local function removefromhash(hash,key) +-- local pattern = h[key] +-- for k in next, hash do +-- if k ~= key and find(h[k],pattern) then -- if find(k,",") and ... +-- hash[k] = nil +-- end +-- end +-- end + +local function presetcontext(name,parent,features) -- will go to con and shared + if features == "" and find(parent,"=",1,true) then + features = parent + parent = "" + end + if not features or features == "" then + features = { } + elseif type(features) == "string" then + features = normalize_features(settings_to_hash(features)) + -- if type(value) == "string" and find(value,"[=:]") then + -- local t = settings_to_hash_colon_too(value) -- clashes with foo=file:bar + for key, value in next, features do + if type(value) == "string" and find(value,"[=]") then + local t = settings_to_hash(value) + if next(t) then + features[key] = sequenced(normalize_features(t,true),",") + end + end + end + else + features = normalize_features(features) + end + -- todo: synonyms, and not otf bound + if parent ~= "" then + for p in gmatch(parent,"[^, ]+") do + local s = setups[p] + if s then + for k, v in next, s do + -- no, as then we cannot overload: e.g. math,mathextra + -- reverted, so we only take from parent when not set + if features[k] == nil then + features[k] = v + end + end + else + -- just ignore an undefined one .. i.e. we can refer to not yet defined + end + end + end + -- these are auto set so in order to prevent redundant definitions + -- we need to preset them (we hash the features and adding a default + -- setting during initialization may result in a different hash) + -- + -- for k,v in next, triggers do + -- if features[v] == nil then -- not false ! + -- local vv = default_features[v] + -- if vv then features[v] = vv end + -- end + -- end + -- + for feature,value in next, features do + if value == nil then -- not false ! + local default = default_features[feature] + if default ~= nil then + features[feature] = default + end + end + end + -- sparse 'm so that we get a better hash and less test (experimental + -- optimization) + local t = { } -- can we avoid t ? + for k,v in next, features do + -- if v then t[k] = v end + t[k] = v + end + -- the number is needed for dynamic features; maybe number should always be + -- renewed as we can redefine features ... i need a test + local number = setups[name] and setups[name].number or 0 + if number == 0 then + number = #numbers + 1 + numbers[number] = name + end + -- + t.number = number + -- there is the special case of combined features as we have in math but maybe + -- this has to change some day ... otherwise we mess up dynamics (ok, we could + -- impose a limit there: no combined features) + -- + -- done elsewhere (!) + -- + -- removefromhash(setups,name) -- can have changed (like foo,extramath) + -- + setups[name] = t + return number, t +end + +local function adaptcontext(pattern,features) + local pattern = topattern(pattern,false,true) + for name in next, setups do + if find(name,pattern) then + presetcontext(name,name,features) + end + end +end + +-- See for old code in the lua file. We now share code. + +local function contextnumber(name) + local t = setups[name] + return t and t.number or 0 +end + +local function mergecontext(currentnumber,extraname,option) -- number string number (used in scrp-ini + local extra = setups[extraname] + if extra then + local current = setups[numbers[currentnumber]] + local mergedfeatures = { } + local mergedname = nil + if option < 0 then + if current then + for k, v in next, current do + if not extra[k] then + mergedfeatures[k] = v + end + end + end + mergedname = currentnumber .. "-" .. extraname + else + if current then + for k, v in next, current do + mergedfeatures[k] = v + end + end + for k, v in next, extra do + mergedfeatures[k] = v + end + mergedname = currentnumber .. "+" .. extraname + end + local number = #numbers + 1 + mergedfeatures.number = number + numbers[number] = mergedname + merged[number] = option + setups[mergedname] = mergedfeatures + return number + else + return currentnumber + end +end + +local extrasets = { } + +setmetatableindex(extrasets,function(t,k) + local v = mergehashes(setups,k) + t[k] = v + return v +end) + +local function mergecontextfeatures(currentname,extraname,how,mergedname) -- string string + local extra = setups[extraname] or extrasets[extraname] + if extra then + local current = setups[currentname] + local mergedfeatures = { } + if how == "+" then + if current then + for k, v in next, current do + mergedfeatures[k] = v + end + end + for k, v in next, extra do + mergedfeatures[k] = v + end + if trace_merge then + report_features("merge %a, method %a, current %|T, extra %|T, result %|T",mergedname,"add",current or { },extra,mergedfeatures) + end + elseif how == "-" then + if current then + for k, v in next, current do + mergedfeatures[k] = v + end + end + for k, v in next, extra do + -- only boolean features + if v == true then + mergedfeatures[k] = false + end + end + if trace_merge then + report_features("merge %a, method %a, current %|T, extra %|T, result %|T",mergedname,"subtract",current or { },extra,mergedfeatures) + end + else -- = + for k, v in next, extra do + mergedfeatures[k] = v + end + if trace_merge then + report_features("merge %a, method %a, result %|T",mergedname,"replace",mergedfeatures) + end + end + local number = #numbers + 1 + mergedfeatures.number = number + numbers[number] = mergedname + merged[number] = option + setups[mergedname] = mergedfeatures + return number + else + return numbers[currentname] or 0 + end +end + +local function registercontext(fontnumber,extraname,option) + local extra = setups[extraname] + if extra then + local mergedfeatures = { } + local mergedname = nil + if option < 0 then + mergedname = fontnumber .. "-" .. extraname + else + mergedname = fontnumber .. "+" .. extraname + end + for k, v in next, extra do + mergedfeatures[k] = v + end + local number = #numbers + 1 + mergedfeatures.number = number + numbers[number] = mergedname + merged[number] = option + setups[mergedname] = mergedfeatures + return number + else + return 0 + end +end + +local function registercontextfeature(mergedname,extraname,how) + local extra = setups[extraname] + if extra then + local mergedfeatures = { } + for k, v in next, extra do + mergedfeatures[k] = v + end + local number = #numbers + 1 -- we somehow end up with steps of 2 + mergedfeatures.number = number + numbers[number] = mergedname + merged[number] = how == "=" and 1 or 2 -- 1=replace, 2=combine + setups[mergedname] = mergedfeatures + return number + else + report_features("unknown feature %a cannot be merged into %a using method %a",extraname,mergedname,how) + return 0 + end +end + +specifiers.presetcontext = presetcontext +specifiers.contextnumber = contextnumber +specifiers.mergecontext = mergecontext +specifiers.registercontext = registercontext +specifiers.definecontext = definecontext + +constructors.hashmethods.normal = function(list) + local s = { } + local n = 0 + for k, v in next, list do + if not k then + -- no need to add to hash + elseif k == "number" or k == "features" then + -- no need to add to hash (maybe we need a skip list) + else + n = n + 1 + if type(v) == "table" then + -- table.sequenced + local t = { } + local m = 0 + for k, v in next, v do + m = m + 1 + t[m] = format("%q=%q",k,v) + end + sort(t) + s[n] = format("%q={%s}",k,concat(t,",")) + else + s[n] = format("%q=%q",k,v) + end + end + end + if n > 0 then + sort(s) + return concat(s,"+") + end +end + +constructors.hashmethods.virtual = function(list) + local s = { } + local n = 0 + for k, v in next, list do + n = n + 1 + s[n] = format("%q=%q",k,v) + end + if n > 0 then + sort(s) + return concat(s,"+") + end +end + +-- end of redefine + +-- local withcache = { } -- concat might be less efficient than nested tables +-- +-- local function withset(name,what) +-- local zero = texgetattribute(0) +-- local hash = zero .. "+" .. name .. "*" .. what +-- local done = withcache[hash] +-- if not done then +-- done = mergecontext(zero,name,what) +-- withcache[hash] = done +-- end +-- texsetattribute(0,done) +-- end +-- +-- local function withfnt(name,what,font) +-- local font = font or currentfont() +-- local hash = font .. "*" .. name .. "*" .. what +-- local done = withcache[hash] +-- if not done then +-- done = registercontext(font,name,what) +-- withcache[hash] = done +-- end +-- texsetattribute(0,done) +-- end + +function specifiers.showcontext(name) + return setups[name] or setups[numbers[name]] or setups[numbers[tonumber(name)]] or { } +end + +-- we need a copy as we will add (fontclass) goodies to the features and +-- that is bad for a shared table + +-- local function splitcontext(features) -- presetcontext creates dummy here +-- return fastcopy(setups[features] or (presetcontext(features,"","") and setups[features])) +-- end + +-- local function splitcontext(features) -- presetcontext creates dummy here +-- local sf = setups[features] +-- if not sf then +-- local n -- number +-- if find(features,",") then +-- -- let's assume a combination which is not yet defined but just specified (as in math) +-- n, sf = presetcontext(features,features,"") +-- else +-- -- we've run into an unknown feature and or a direct spec so we create a dummy +-- n, sf = presetcontext(features,"","") +-- end +-- end +-- return fastcopy(sf) +-- end + +local function splitcontext(features) -- presetcontext creates dummy here + local n, sf + -- We can have: "a=yes,b=yes" "a,b" "a" "a=yes" etc. + if find(features,"[,=]") then + -- + -- from elsewhere (!) + -- + -- this will become: + -- + -- if find(features,"^reset," then + setups[features] = nil + -- end + -- let's assume a combination which is not yet defined but just specified (as in math) + n, sf = presetcontext(features,features,"") + else + sf = setups[features] + if not sf then + -- we've run into an unknown feature and or a direct spec so we create a dummy + n, sf = presetcontext(features,"","") + end + end + return fastcopy(sf) +end + +-- local splitter = lpeg.splitat("=") +-- +-- local function splitcontext(features) +-- local setup = setups[features] +-- if setup then +-- return setup +-- elseif find(features,",",1,true) then +-- -- This is not that efficient but handy anyway for quick and dirty tests +-- -- beware, due to the way of caching setups you can get the wrong results +-- -- when components change. A safeguard is to nil the cache. +-- local merge = nil +-- for feature in gmatch(features,"[^, ]+") do +-- if find(feature,"=",1,true) then +-- local k, v = lpegmatch(splitter,feature) +-- if k and v then +-- if not merge then +-- merge = { k = v } +-- else +-- merge[k] = v +-- end +-- end +-- else +-- local s = setups[feature] +-- if not s then +-- -- skip +-- elseif not merge then +-- merge = s +-- else +-- for k, v in next, s do +-- merge[k] = v +-- end +-- end +-- end +-- end +-- setup = merge and presetcontext(features,"",merge) and setups[features] +-- -- actually we have to nil setups[features] in order to permit redefinitions +-- setups[features] = nil +-- end +-- return setup or (presetcontext(features,"","") and setups[features]) -- creates dummy +-- end + +specifiers.splitcontext = splitcontext + +function specifiers.contexttostring(name,kind,separator,yes,no,strict,omit) -- not used + return hash_to_string( + mergedtable(handlers[kind].features.defaults or {},setups[name] or {}), + separator, yes, no, strict, omit or { "number" } + ) +end + +local function starred(features) -- no longer fallbacks here + local detail = features.detail + if detail and detail ~= "" then + features.features.normal = splitcontext(detail) + else + features.features.normal = { } + end + return features +end + +definers.registersplit('*',starred,"featureset") + +-- sort of xetex mode, but without [] and / as we have file: and name: etc + +local space = P(" ") +local spaces = space^0 +local separator = S(";,") +local equal = P("=") +local sometext = C((1-equal-space-separator)^1) +local truevalue = P("+") * spaces * sometext * Cc(true) +local falsevalue = P("-") * spaces * sometext * Cc(false) +local somevalue = sometext * spaces * Cc(true) +local keyvalue = sometext * spaces * equal * spaces * sometext +local pattern = Cf(Ct("") * (space + separator + Cg(falsevalue + truevalue + keyvalue + somevalue))^0, rawset) + +local function colonized(specification) + specification.features.normal = normalize_features(lpegmatch(pattern,specification.detail)) + return specification +end + +definers.registersplit(":",colonized,"direct") + +-- define (two steps) + +local sizepattern, splitpattern, specialscale do + + ----- space = P(" ") + ----- spaces = space^0 + local leftparent = (P"(") + local rightparent = (P")") + local leftbrace = (P"{") + local rightbrace = (P"}") + local withinparents = leftparent * (1-rightparent)^0 * rightparent + local withinbraces = leftbrace * (1-rightbrace )^0 * rightbrace + local value = C((withinparents + withinbraces + (1-space))^1) + local dimension = C((space/"" + P(1))^1) + local rest = C(P(1)^0) + local scale_none = Cc(0) + local scale_at = (P("at") +P("@")) * Cc(1) * spaces * dimension -- dimension + local scale_sa = P("sa") * Cc(2) * spaces * dimension -- number + local scale_mo = P("mo") * Cc(3) * spaces * dimension -- number + local scale_scaled = P("scaled") * Cc(4) * spaces * dimension -- number + local scale_ht = P("ht") * Cc(5) * spaces * dimension -- dimension + local scale_cp = P("cp") * Cc(6) * spaces * dimension -- dimension + + specialscale = { [5] = "ht", [6] = "cp" } + + sizepattern = spaces * (scale_at + scale_sa + scale_mo + scale_ht + scale_cp + scale_scaled + scale_none) + splitpattern = spaces * value * spaces * rest + +end + +function helpers.splitfontpattern(str) + local name, size = lpegmatch(splitpattern,str) + local kind, size = lpegmatch(sizepattern,size) + return name, kind, size +end + +function helpers.fontpatternhassize(str) + local name, size = lpegmatch(splitpattern,str) + local kind, size = lpegmatch(sizepattern,size) + return size or false +end + +local specification -- still needed as local ? + +local getspecification = definers.getspecification + +-- we can make helper macros which saves parsing (but normaly not +-- that many calls, e.g. in mk a couple of 100 and in metafun 3500) + +local specifiers = { } + +do -- else too many locals + + local starttiming = statistics.starttiming + local stoptiming = statistics.stoptiming + + local setmacro = tokens.setters.macro + + implement { + name = "definefont_one", + arguments = "string", + actions = function(str) + starttiming(fonts) + if trace_defining then + report_defining("memory usage before: %s",statistics.memused()) + report_defining("start stage one: %s",str) + end + local fullname, size = lpegmatch(splitpattern,str) + local lookup, name, sub, method, detail = getspecification(fullname) + if not name then + report_defining("strange definition %a",str) + -- ctx_setdefaultfontname() + elseif name == "unknown" then + -- ctx_setdefaultfontname() + else + -- ctx_setsomefontname(name) + setmacro("somefontname",name,"global") + end + -- we can also use a count for the size + if size and size ~= "" then + local mode, size = lpegmatch(sizepattern,size) + if size and mode then + texsetcount("scaledfontmode",mode) + -- ctx_setsomefontsize(size) + setmacro("somefontsize",size) + else + texsetcount("scaledfontmode",0) + -- ctx_setemptyfontsize() + end + elseif true then + -- so we don't need to check in tex + texsetcount("scaledfontmode",2) + -- ctx_setemptyfontsize() + else + texsetcount("scaledfontmode",0) + -- ctx_setemptyfontsize() + end + specification = definers.makespecification(str,lookup,name,sub,method,detail,size) +-- specification.original = str + if trace_defining then + report_defining("stop stage one") + end + end + } + + local function nice_cs(cs) + return (gsub(cs,".->", "")) + end + + local n = 0 + local busy = false + local combinefeatures = false + + directives.register("fonts.features.combine",function(v) + combinefeatures = v + end) + + implement { + name = "definefont_two", + arguments = { + "boolean", "string", "string", "integer", "integer", "string", "string", "string", "string", + "integer", "integer", "integer", "string", "string", "string", "string", "integer", + }, + actions = function ( + global, -- \ifx\fontclass\empty\s!false\else\s!true\fi + cs, -- {#csname}% + str, -- \somefontfile + size, -- \d_font_scaled_font_size + inheritancemode, -- \c_font_feature_inheritance_mode + classfeatures, -- \m_font_class_features + fontfeatures, -- \m_font_features + classfallbacks, -- \m_font_class_fallbacks + fontfallbacks, -- \m_font_fallbacks + mathsize, -- \fontface + textsize, -- \d_font_scaled_text_face + relativeid, -- \relativefontid + classgoodies, -- \m_font_class_goodies + goodies, -- \m_font_goodies + classdesignsize, -- \m_font_class_designsize + fontdesignsize, -- \m_font_designsize + scaledfontmode -- \scaledfontmode + ) + if trace_defining then + report_defining("start stage two: %s, size %s, features %a & %a, mode %a",str,size,classfeatures,fontfeatures,inheritancemode) + end + -- name is now resolved and size is scaled cf sa/mo + local lookup, name, sub, method, detail = getspecification(str or "") + -- new (todo: inheritancemode) + local designsize = fontdesignsize ~= "" and fontdesignsize or classdesignsize or "" + local designname = designsizefilename(name,designsize,size) + if designname and designname ~= "" then + if trace_defining or trace_designsize then + report_defining("remapping name %a, specification %a, size %a, designsize %a",name,designsize,size,designname) + end + -- we don't catch detail here + local o_lookup, o_name, o_sub, o_method, o_detail = getspecification(designname) + if o_lookup and o_lookup ~= "" then lookup = o_lookup end + if o_method and o_method ~= "" then method = o_method end + if o_detail and o_detail ~= "" then detail = o_detail end + name = o_name + sub = o_sub + end + -- so far + -- some settings can have been overloaded + if lookup and lookup ~= "" then + specification.lookup = lookup + end + if relativeid and relativeid ~= "" then -- experimental hook + local id = tonumber(relativeid) or 0 + specification.relativeid = id > 0 and id + end + -- + specification.name = name + specification.size = size + specification.sub = (sub and sub ~= "" and sub) or specification.sub + specification.mathsize = mathsize + specification.textsize = textsize + specification.goodies = goodies + specification.cs = cs + specification.global = global + specification.scalemode = scaledfontmode -- context specific + if detail and detail ~= "" then + specification.method = method or "*" + specification.detail = detail + elseif specification.detail and specification.detail ~= "" then + -- already set + elseif inheritancemode == 0 then + -- nothing + elseif inheritancemode == 1 then + -- fontonly + if fontfeatures and fontfeatures ~= "" then + specification.method = "*" + specification.detail = fontfeatures + end + if fontfallbacks and fontfallbacks ~= "" then + specification.fallbacks = fontfallbacks + end + elseif inheritancemode == 2 then + -- classonly + if classfeatures and classfeatures ~= "" then + specification.method = "*" + specification.detail = classfeatures + end + if classfallbacks and classfallbacks ~= "" then + specification.fallbacks = classfallbacks + end + elseif inheritancemode == 3 then + -- fontfirst + if combinefeatures then + if classfeatures and classfeatures ~= "" then + specification.method = "*" + if fontfeatures and fontfeatures ~= "" and fontfeatures ~= classfeatures then + specification.detail = classfeatures .. "," .. fontfeatures + else + specification.detail = classfeatures + end + elseif fontfeatures and fontfeatures ~= "" then + specification.method = "*" + specification.detail = fontfeatures + end + else + if fontfeatures and fontfeatures ~= "" then + specification.method = "*" + specification.detail = fontfeatures + elseif classfeatures and classfeatures ~= "" then + specification.method = "*" + specification.detail = classfeatures + end + end + if fontfallbacks and fontfallbacks ~= "" then + specification.fallbacks = fontfallbacks + elseif classfallbacks and classfallbacks ~= "" then + specification.fallbacks = classfallbacks + end + elseif inheritancemode == 4 then + -- classfirst + if combinefeatures then + if fontfeatures and fontfeatures ~= "" then + specification.method = "*" + if classfeatures and classfeatures ~= "" and classfeatures ~= fontfeatures then + specification.detail = fontfeatures .. "," .. classfeatures + else + specification.detail = fontfeatures + end + elseif classfeatures and classfeatures ~= "" then + specification.method = "*" + specification.detail = classfeatures + end + else + if classfeatures and classfeatures ~= "" then + specification.method = "*" + specification.detail = classfeatures + elseif fontfeatures and fontfeatures ~= "" then + specification.method = "*" + specification.detail = fontfeatures + end + end + if classfallbacks and classfallbacks ~= "" then + specification.fallbacks = classfallbacks + elseif fontfallbacks and fontfallbacks ~= "" then + specification.fallbacks = fontfallbacks + end + end + -- + local tfmdata = definers.read(specification,size) -- id not yet known (size in spec?) + -- + local lastfontid = 0 + local tfmtype = type(tfmdata) + if tfmtype == "table" then + -- setting the extra characters will move elsewhere + local characters = tfmdata.characters + local parameters = tfmdata.parameters + local properties = tfmdata.properties + -- we use char0 as signal; cf the spec pdf can handle this (no char in slot) + characters[0] = nil + -- characters[0x00A0] = { width = parameters.space } + -- characters[0x2007] = { width = characters[0x0030] and characters[0x0030].width or parameters.space } -- figure + -- characters[0x2008] = { width = characters[0x002E] and characters[0x002E].width or parameters.space } -- period + -- + local fallbacks = specification.fallbacks or "" + local mathsize = (mathsize == 1 or mathsize == 2 or mathsize == 3) and mathsize or nil -- can be unset so we test 1 2 3 + if fallbacks ~= "" and mathsize and not busy then + busy = true + -- We need this ugly hack in order to resolve fontnames (at the \TEX end). Originally + -- math was done in Lua after loading (plugged into aftercopying). + -- + -- After tl 2017 I'll also do text fallbacks this way (although backups there are done + -- in a completely different way.) + if trace_defining then + report_defining("defining %a, id %a, target %a, features %a / %a, fallbacks %a / %a, step %a", + name,id,nice_cs(cs),classfeatures,fontfeatures,classfallbacks,fontfallbacks,1) + end + mathematics.resolvefallbacks(tfmdata,specification,fallbacks) + context(function() + busy = false + mathematics.finishfallbacks(tfmdata,specification,fallbacks) +tfmdata.original = specification.specification + local id = definefont(tfmdata) + csnames[id] = specification.cs + properties.id = id + definers.register(tfmdata,id) -- to be sure, normally already done + texdefinefont(global,cs,id) + constructors.finalize(tfmdata) + if trace_defining then + report_defining("defining %a, id %a, target %a, features %a / %a, fallbacks %a / %a, step %a", + name,id,nice_cs(cs),classfeatures,fontfeatures,classfallbacks,fontfallbacks,2) + end + -- resolved (when designsize is used): + local size = round(tfmdata.parameters.size or 655360) + setmacro("somefontsize",size.."sp") + -- ctx_setsomefontsize(size .. "sp") + texsetcount("scaledfontsize",size) + lastfontid = id + -- + if trace_defining then + report_defining("memory usage after: %s",statistics.memused()) + report_defining("stop stage two") + end + -- + texsetcount("global","lastfontid",lastfontid) + specifiers[lastfontid] = { str, size } + if not mathsize then + -- forget about it (can't happen here) + elseif mathsize == 0 then + -- can't happen (here) + else + -- maybe only 1 2 3 (we already test for this) + lastmathids[mathsize] = lastfontid + end + stoptiming(fonts) + end) + return + else +tfmdata.original = specification.specification + local id = definefont(tfmdata) + csnames[id] = specification.cs + properties.id = id + definers.register(tfmdata,id) -- to be sure, normally already done + texdefinefont(global,cs,id) + constructors.finalize(tfmdata) + if trace_defining then + report_defining("defining %a, id %a, target %a, features %a / %a, fallbacks %a / %a, step %a", + name,id,nice_cs(cs),classfeatures,fontfeatures,classfallbacks,fontfallbacks,"-") + end + -- resolved (when designsize is used): + local size = round(tfmdata.parameters.size or 655360) + setmacro("somefontsize",size.."sp") + -- ctx_setsomefontsize(size .. "sp") + texsetcount("scaledfontsize",size) + lastfontid = id + end + elseif tfmtype == "number" then + if trace_defining then + report_defining("reusing %s, id %a, target %a, features %a / %a, fallbacks %a / %a, goodies %a / %a, designsize %a / %a", + name,tfmdata,nice_cs(cs),classfeatures,fontfeatures,classfallbacks,fontfallbacks,classgoodies,goodies,classdesignsize,fontdesignsize) + end + csnames[tfmdata] = specification.cs + texdefinefont(global,cs,tfmdata) + -- resolved (when designsize is used): + local size = round(fontdata[tfmdata].parameters.size or 0) + -- ctx_setsomefontsize(size .. "sp") + setmacro("somefontsize",size.."sp") + texsetcount("scaledfontsize",size) + lastfontid = tfmdata + else + report_defining("unable to define %a as %a",name,nice_cs(cs)) + lastfontid = -1 + texsetcount("scaledfontsize",0) + -- ctx_letvaluerelax(cs) -- otherwise the current definition takes the previous one + end + if trace_defining then + report_defining("memory usage after: %s",statistics.memused()) + report_defining("stop stage two") + end + -- + texsetcount("global","lastfontid",lastfontid) + specifiers[lastfontid] = { str, size } + if not mathsize then + -- forget about it + elseif mathsize == 0 then + -- can't happen (here) + else + -- maybe only 1 2 3 + lastmathids[mathsize] = lastfontid + end + -- + stoptiming(fonts) + end + } + + implement { + name = "specifiedfontspec", + arguments = "integer", + actions = function(id) + local f = specifiers[id] + if f then + context(f[1]) + end + end + } + + implement { + name = "specifiedfontsize", + arguments = "integer", + actions = function(id) + local f = specifiers[id] + if f then + context(f[2]) + end + end + } + + implement { + name = "specifiedfont", + arguments = { "integer", "number" }, + actions = function(id,size) + local f = specifiers[id] + if f and size then + context("%s at %0.2p",f[1],size * f[2]) -- we round to 2 decimals (as at the tex end) + end + end + } + -- + + local function define(specification) + -- + local name = specification.name + if not name or name == "" then + return -1 + else + starttiming(fonts) + -- + -- following calls expect a few properties to be set: + -- + local lookup, name, sub, method, detail = getspecification(name or "") + -- + specification.name = (name ~= "" and name) or specification.name + -- + specification.lookup = specification.lookup or (lookup ~= "" and lookup) or "file" + specification.size = specification.size or 655260 + specification.sub = specification.sub or (sub ~= "" and sub) or "" + specification.method = specification.method or (method ~= "" and method) or "*" + specification.detail = specification.detail or (detail ~= "" and detail) or "" + -- + if type(specification.size) == "string" then + specification.size = texsp(specification.size) or 655260 + end + -- + specification.specification = "" -- not used + specification.resolved = "" + specification.forced = "" + specification.features = { } -- via detail, maybe some day + -- + -- we don't care about mathsize textsize goodies fallbacks + -- + local cs = specification.cs + if cs == "" then + cs = nil + specification.cs = nil + specification.global = false + elseif specification.global == nil then + specification.global = false + end + -- + local tfmdata = definers.read(specification,specification.size) + if not tfmdata then + return -1, nil + elseif type(tfmdata) == "number" then + if cs then + texdefinefont(specification.global,cs,tfmdata) + csnames[tfmdata] = cs + end + stoptiming(fonts) + return tfmdata, fontdata[tfmdata] + else + local id = definefont(tfmdata) + tfmdata.properties.id = id + definers.register(tfmdata,id) + if cs then + texdefinefont(specification.global,cs,id) + csnames[id] = cs + end + constructors.finalize(tfmdata) + stoptiming(fonts) + return id, tfmdata + end + end + end + + definers.define = define + + -- local id, cs = fonts.definers.internal { } + -- local id, cs = fonts.definers.internal { number = 2 } + -- local id, cs = fonts.definers.internal { name = "dejavusans" } + + local n = 0 + + function definers.internal(specification,cs) + specification = specification or { } + local name = specification.name + local size = tonumber(specification.size) + local number = tonumber(specification.number) + local id = nil + if not size then + size = texgetdimen("bodyfontsize") + end + if number then + id = number + elseif name and name ~= "" then + local cs = cs or specification.cs + if not cs then + n = n + 1 -- beware ... there can be many and they are often used once + -- cs = formatters["internal font %s"](n) + cs = "internal font " .. n + else + specification.cs = cs + end + id = define { + name = name, + size = size, + cs = cs, + } + end + if not id then + id = currentfont() + end + return id, csnames[id] + end + + local function read(name,size) + return (define { name = name, size = size } or 0) + end + + callbacks.register('define_font', read, "definition of fonts (tfmdata preparation)") + + -- here + + local infofont = 0 + + function fonts.infofont() + if infofont == 0 then + infofont = define { name = "dejavusansmono", size = texsp("6pt") } + end + return infofont + end + + -- abstract interfacing + + implement { name = "tf", actions = function() setmacro("fontalternative","tf") end } + implement { name = "bf", actions = function() setmacro("fontalternative","bf") end } + implement { name = "it", actions = function() setmacro("fontalternative","it") end } + implement { name = "sl", actions = function() setmacro("fontalternative","sl") end } + implement { name = "bi", actions = function() setmacro("fontalternative","bi") end } + implement { name = "bs", actions = function() setmacro("fontalternative","bs") end } + +end + +-- Not ok, we can best use a database for this. The problem is that we +-- have delayed definitions and so we never know what style is taken +-- as start. + +function constructors.calculatescale(tfmdata,scaledpoints,relativeid,specification) + local parameters = tfmdata.parameters + local units = parameters.units or 1000 + if specification then + local scalemode = specification.scalemode + local special = scalemode and specialscale[scalemode] + if special == "ht" then + local height = parameters.ascender / units + scaledpoints = scaledpoints / height + elseif special == "cp" then + local glyph = tfmdata.descriptions[utfbyte("X")] + local height = (glyph and glyph.height or parameters.ascender) / units + scaledpoints = scaledpoints / height + end + end + if scaledpoints < 0 then + scaledpoints = (- scaledpoints/1000) * (tfmdata.designsize or parameters.designsize) -- already in sp + end + return round(scaledpoints), round(scaledpoints/units) -- delta +end + +local designsizes = constructors.designsizes + +-- called quite often when in mp labels +-- otf.normalizedaxis + +function constructors.hashinstance(specification,force) + local hash = specification.hash + local size = specification.size + local fallbacks = specification.fallbacks + if force or not hash then + hash = constructors.hashfeatures(specification) + specification.hash = hash + end + if size < 1000 and designsizes[hash] then + size = round(constructors.scaled(size,designsizes[hash])) + else + size = round(size) + end + specification.size = size + if fallbacks then + return hash .. ' @ ' .. size .. ' @ ' .. fallbacks + else + local scalemode = specification.scalemode + local special = scalemode and specialscale[scalemode] + if special then + return hash .. ' @ ' .. size .. ' @ ' .. special + else + return hash .. ' @ ' .. size + end + end +end + +-- We overload the (generic) resolver: + +local resolvers = definers.resolvers +local hashfeatures = constructors.hashfeatures + +function definers.resolve(specification) -- overload function in font-con.lua + if not specification.resolved or specification.resolved == "" then -- resolved itself not per se in mapping hash + local r = resolvers[specification.lookup] + if r then + r(specification) + end + end + if specification.forced == "" then + specification.forced = nil + else + specification.forced = specification.forced + end + -- goodies are a context specific thing and are not always defined + -- as feature, so we need to make sure we add them here before + -- hashing because otherwise we get funny goodies applied + local goodies = specification.goodies + if goodies and goodies ~= "" then + -- this adapts the features table so it has best be a copy + local normal = specification.features.normal + if not normal then + specification.features.normal = { goodies = goodies } + elseif not normal.goodies then + local g = normal.goodies + if g and g ~= "" then + normal.goodies = formatters["%s,%s"](g,goodies) + else + normal.goodies = goodies + end + end + end + -- so far for goodie hacks + local hash = hashfeatures(specification) + local name = specification.name or "badfont" + local sub = specification.sub + if sub and sub ~= "" then + specification.hash = lower(name .. " @ " .. sub .. ' @ ' .. hash) + else + specification.hash = lower(name .. " @ " .. ' @ ' .. hash) + end + -- + return specification +end + +-- we need an 'do after the banner hook' + +-- => commands + +local pattern = P("P") + * (lpeg.patterns.hexdigit^4 / function(s) return tonumber(s,16) end) + * P(-1) + +local function nametoslot(name) -- also supports PXXXXX (4+ positions) + local t = type(name) + if t == "string" then + local unic = unicodes[true] + local slot = unic[name] + if slot then + return slot + end + -- + local slot = unic[gsub(name,"_"," ")] or unic[gsub(name,"_","-")] or + unic[gsub(name,"-"," ")] or unic[gsub(name,"-","_")] or + unic[gsub(name," ","_")] or unic[gsub(name," ","-")] + if slot then + return slot + end + -- + if not aglunicodes then + aglunicodes = encodings.agl.unicodes + end + local char = characters[true] + local slot = aglunicodes[name] + if slot and char[slot] then + return slot + end + local slot = lpegmatch(pattern,name) + if slot and char[slot] then + return slot + end + -- not in font + elseif t == "number" then + if characters[true][name] then + return slot + else + -- not in font + end + end +end + +local found = { } + +local function descriptiontoslot(name) + local t = type(name) + if t == "string" then + -- slow + local list = sortedkeys(chardata) -- can be a cache with weak tables + local slot = found[name] + local char = characters[true] + if slot then + return char[slot] and slot or nil + end + local NAME = upper(name) + for i=1,#list do + slot = list[i] + local c = chardata[slot] + local d = c.description + if d == NAME then + found[name] = slot + return char[slot] and slot or nil + end + end + for i=1,#list do + slot = list[i] + local c = chardata[slot] + local s = c.synonyms + if s then + for i=1,#s do + local si = s[i] + if si == name then + found[name] = si + return char[slot] and slot or nil + end + end + end + end + for i=1,#list do + slot = list[i] + local c = chardata[slot] + local d = c.description + if d and find(d,NAME) then + found[name] = slot + return char[slot] and slot or nil + end + end + for i=1,#list do + slot = list[i] + local c = chardata[slot] + local s = c.synonyms + if s then + for i=1,#s do + local si = s[i] + if find(s[i],name) then + found[name] = si + return char[slot] and slot or nil + end + end + end + end + -- not in font + elseif t == "number" then + if characters[true][name] then + return slot + else + -- not in font + end + end +end + +local function indextoslot(font,index) + if not index then + index = font + font = true + end + local r = resources[font] + if r then + local indices = r.indices + if not indices then + indices = { } + local c = characters[font] + for unicode, data in next, c do + local di = data.index + if di then + indices[di] = unicode + end + end + r.indices = indices + end + return indices[tonumber(index)] + end +end + +do -- else too many locals + + local entities = characters.entities + local lowered = { } -- delayed initialization + + setmetatableindex(lowered,function(t,k) + for k, v in next, entities do + local l = lower(k) + if not entities[l] then + lowered[l] = v + end + end + setmetatableindex(lowered,nil) + return lowered[k] + end) + + local methods = { + -- entity + e = function(name) + return entities[name] or lowered[name] or name + end, + -- hexadecimal unicode + x = function(name) + local n = tonumber(name,16) + return n and utfchar(n) or name + end, + -- decimal unicode + d = function(name) + local n = tonumber(name) + return n and utfchar(n) or name + end, + -- hexadecimal index (slot) + s = function(name) + local n = tonumber(name,16) + local n = n and indextoslot(n) + return n and utfchar(n) or name + end, + -- decimal index + i = function(name) + local n = tonumber(name) + local n = n and indextoslot(n) + return n and utfchar(n) or name + end, + -- name + n = function(name) + local n = nametoslot(name) + return n and utfchar(n) or name + end, + -- unicode description (synonym) + u = function(name) + local n = descriptiontoslot(name,false) + return n and utfchar(n) or name + end, + -- all + a = function(name) + local n = nametoslot(name) or descriptiontoslot(name) + return n and utfchar(n) or name + end, + -- char + c = function(name) + return name + end, + } + + -- -- nicer: + -- + -- setmetatableindex(methods,function(t,k) return methods.c end) + -- + -- local splitter = (C(1) * P(":") + Cc("c")) * C(P(1)^1) / function(method,name) + -- return methods[method](name) + -- end + -- + -- -- more efficient: + + local splitter = C(1) * P(":") * C(P(1)^1) / function(method,name) + local action = methods[method] + return action and action(name) or name + end + + local function tochar(str) + local t = type(str) + if t == "number" then + return utfchar(str) + elseif t == "string" then + return lpegmatch(splitter,str) or str + else + return str + end + end + + helpers.nametoslot = nametoslot + helpers.descriptiontoslot = descriptiontoslot + helpers.indextoslot = indextoslot + helpers.tochar = tochar + + -- interfaces: + + implement { + name = "fontchar", + actions = { nametoslot, ctx_char }, + arguments = "string", + } + + implement { + name = "fontcharbyindex", + actions = { indextoslot, ctx_char }, + arguments = "integer", + } + + implement { + name = "tochar", + actions = { tochar, ctx_safechar }, + arguments = "string", + } + +end + +-- this will change ... + +function loggers.reportdefinedfonts() + if trace_usage then + local t, tn = { }, 0 + for id, data in sortedhash(fontdata) do + local properties = data.properties or { } + local parameters = data.parameters or { } + tn = tn + 1 + t[tn] = { + formatters["%03i"](id or 0), + formatters["%p" ](parameters.size or 0), + properties.format or "unknown", + properties.name or "", + properties.psname or "", + properties.fullname or "", + properties.sharedwith or "", + } + end + formatcolumns(t," ") + -- + logs.startfilelogging(report,"defined fonts") + for k=1,tn do + report(t[k]) + end + logs.stopfilelogging() + end +end + +logs.registerfinalactions(loggers.reportdefinedfonts) + +function loggers.reportusedfeatures() + -- numbers, setups, merged + if trace_usage then + local t, n = { }, #numbers + for i=1,n do + local name = numbers[i] + local setup = setups[name] + local n = setup.number + setup.number = nil -- we have no reason to show this + t[i] = { i, name, sequenced(setup,false,true) } -- simple mode + setup.number = n -- restore it (normally not needed as we're done anyway) + end + formatcolumns(t," ") + logs.startfilelogging(report,"defined featuresets") + for k=1,n do + report(t[k]) + end + logs.stopfilelogging() + end +end + +logs.registerfinalactions(loggers.reportusedfeatures) + +-- maybe move this to font-log.lua: + +statistics.register("font engine", function() + local elapsed = statistics.elapsedseconds(fonts) + local nofshared = constructors.nofsharedfonts or 0 + local nofloaded = constructors.noffontsloaded or 0 + if nofshared > 0 then + return format("otf %0.3f, afm %0.3f, tfm %0.3f, %s instances, %s shared in backend, %s common vectors, %s common hashes, load time %s", + otf.version,afm.version,tfm.version,nofloaded, + nofshared,constructors.nofsharedvectors,constructors.nofsharedhashes, + elapsed) + elseif nofloaded > 0 and elapsed then + return format("otf %0.3f, afm %0.3f, tfm %0.3f, %s instances, load time %s", + otf.version,afm.version,tfm.version,nofloaded, + elapsed) + else + return format("otf %0.3f, afm %0.3f, tfm %0.3f", + otf.version,afm.version,tfm.version) + end +end) + +-- experimental mechanism for Mojca: +-- +-- fonts.definetypeface { +-- name = "mainbodyfont-light", +-- preset = "antykwapoltawskiego-light", +-- } +-- +-- fonts.definetypeface { +-- name = "mojcasfavourite", +-- preset = "antykwapoltawskiego", +-- normalweight = "light", +-- boldweight = "bold", +-- width = "condensed", +-- } + +local Shapes = { + serif = "Serif", + sans = "Sans", + mono = "Mono", +} + +local ctx_startfontclass = context.startfontclass +local ctx_stopfontclass = context.stopfontclass +local ctx_definefontsynonym = context.definefontsynonym +local ctx_dofastdefinetypeface = context.dofastdefinetypeface + +function fonts.definetypeface(name,t) + if type(name) == "table" then + -- {name=abc,k=v,...} + t = name + elseif t then + if type(t) == "string" then + -- "abc", "k=v,..." + t = settings_to_hash(name) + else + -- "abc", {k=v,...} + end + t.name = t.name or name + else + -- "name=abc,k=v,..." + t = settings_to_hash(name) + end + local p = t.preset and fonts.typefaces[t.preset] or { } + local name = t.name or "unknowntypeface" + local shortcut = t.shortcut or p.shortcut or "rm" + local size = t.size or p.size or "default" + local shape = t.shape or p.shape or "serif" + local fontname = t.fontname or p.fontname or "unknown" + local normalweight = t.normalweight or t.weight or p.normalweight or p.weight or "normal" + local boldweight = t.boldweight or t.weight or p.boldweight or p.weight or "normal" + local normalwidth = t.normalwidth or t.width or p.normalwidth or p.width or "normal" + local boldwidth = t.boldwidth or t.width or p.boldwidth or p.width or "normal" + Shape = Shapes[shape] or "Serif" + ctx_startfontclass { name } + ctx_definefontsynonym( { formatters["%s"] (Shape) }, { formatters["spec:%s-%s-regular-%s"] (fontname, normalweight, normalwidth) } ) + ctx_definefontsynonym( { formatters["%sBold"] (Shape) }, { formatters["spec:%s-%s-regular-%s"] (fontname, boldweight, boldwidth ) } ) + ctx_definefontsynonym( { formatters["%sBoldItalic"](Shape) }, { formatters["spec:%s-%s-italic-%s"] (fontname, boldweight, boldwidth ) } ) + ctx_definefontsynonym( { formatters["%sItalic"] (Shape) }, { formatters["spec:%s-%s-italic-%s"] (fontname, normalweight, normalwidth) } ) + ctx_stopfontclass() + local settings = sequenced({ features= t.features },",") + ctx_dofastdefinetypeface(name, shortcut, shape, size, settings) +end + +implement { + name = "definetypeface", + actions = fonts.definetypeface, + arguments = "2 strings" +} + +function fonts.current() -- todo: also handle name + return fontdata[currentfont()] or fontdata[0] +end + +function fonts.currentid() + return currentfont() or 0 +end + +-- for the moment here, this will become a chain of extras that is +-- hooked into the ctx registration (or scaler or ...) + +local dimenfactors = number.dimenfactors + +function helpers.dimenfactor(unit,id) + if unit == "ex" then + return id and exheights[id] or 282460 -- lm 10pt + elseif unit == "em" then + return id and emwidths [id] or 655360 -- lm 10pt + else + local du = dimenfactors[unit] + return du and 1/du or tonumber(unit) or 1 + end +end + +local function digitwidth(font) -- max(quad/2,wd(0..9)) + local tfmdata = fontdata[font] + local parameters = tfmdata.parameters + local width = parameters.digitwidth + if not width then + width = round(parameters.quad/2) -- maybe tex.scale + local characters = tfmdata.characters + for i=48,57 do + local wd = round(characters[i].width) + if wd > width then + width = wd + end + end + parameters.digitwidth = width + end + return width +end + +helpers.getdigitwidth = digitwidth +helpers.setdigitwidth = digitwidth + +-- + +function helpers.getparameters(tfmdata) + local p = { } + local m = p + local parameters = tfmdata.parameters + while true do + for k, v in next, parameters do + m[k] = v + end + parameters = getmetatable(parameters) + parameters = parameters and parameters.__index + if type(parameters) == "table" then + m = { } + p.metatable = m + else + break + end + end + return p +end + +if environment.initex then + + local function names(t) + local nt = #t + if nt > 0 then + local n = { } + for i=1,nt do + n[i] = t[i].name + end + return concat(n," ") + else + return "-" + end + end + + statistics.register("font processing", function() + local l = { } + for what, handler in table.sortedpairs(handlers) do + local features = handler and handler.features + if features then + l[#l+1] = format("[%s (base initializers: %s) (base processors: %s) (base manipulators: %s) (node initializers: %s) (node processors: %s) (node manipulators: %s)]", + what, + names(features.initializers.base), + names(features.processors .base), + names(features.manipulators.base), + names(features.initializers.node), + names(features.processors .node), + names(features.manipulators.node) + ) + end + end + return concat(l, " | ") + end) + +end + +-- redefinition + +-- local hashes = fonts.hashes +-- local emwidths = hashes.emwidths +-- local exheights = hashes.exheights + +setmetatableindex(dimenfactors, function(t,k) + if k == "ex" then + return 1/exheights[currentfont()] + elseif k == "em" then + return 1/emwidths[currentfont()] + elseif k == "pct" or k == "%" then + return 1/(texget("hsize")/100) + else + -- error("wrong dimension: " .. (s or "?")) -- better a message + return false + end +end) + +dimenfactors.ex = nil +dimenfactors.em = nil +dimenfactors["%"] = nil +dimenfactors.pct = nil + +--[[ldx-- +<p>Before a font is passed to <l n='tex'/> we scale it. Here we also need +to scale virtual characters.</p> +--ldx]]-- + +do + + -- can become luat-tex.lua + + local texsetglyphdata = tex.setglyphdata + local texgetglyphdata = tex.getglyphdata + + if not texsetglyphdata then + + local texsetattribute = tex.setattribute + local texgetattribute = tex.getattribute + + texsetglyphdata = function(n) return texsetattribute(0,n) end + texgetglyphdata = function() return texgetattribute(0) end + + tex.setglyphdata = texsetglyphdata + tex.getglyphdata = texgetglyphdata + + end + + -- till here + + local setmacro = tokens.setters.macro + + function constructors.currentfonthasfeature(n) + local f = fontdata[currentfont()] + if not f then return end f = f.shared + if not f then return end f = f.rawdata + if not f then return end f = f.resources + if not f then return end f = f.features + return f and (f.gpos[n] or f.gsub[n]) + end + + local ctx_doifelse = commands.doifelse + local ctx_doif = commands.doif + + implement { + name = "doifelsecurrentfonthasfeature", + actions = { constructors.currentfonthasfeature, ctx_doifelse }, + arguments = "string" + } + + local f_strip = formatters["%0.2fpt"] -- normally this value is changed only once + local stripper = lpeg.patterns.stripzeros + + local cache = { } + + local hows = { + ["+"] = "add", + ["-"] = "subtract", + ["="] = "replace", + } + + local function setfeature(how,parent,name,font) -- 0/1 test temporary for testing + if not how or how == 0 then + if trace_features and texgetglyphdata() ~= 0 then + report_cummulative("font %!font:name!, reset",fontdata[font or true]) + end + texsetglyphdata(0) + elseif how == true or how == 1 then + local hash = "feature > " .. parent + local done = cache[hash] + if trace_features and done then + report_cummulative("font %!font:name!, revive %a : %!font:features!",fontdata[font or true],parent,setups[numbers[done]]) + end + texsetglyphdata(done or 0) + else + local full = parent .. how .. name + local hash = "feature > " .. full + local done = cache[hash] + if not done then + local n = setups[full] + if n then + -- already defined + else + n = mergecontextfeatures(parent,name,how,full) + end + done = registercontextfeature(hash,full,how) + cache[hash] = done + if trace_features then + report_cummulative("font %!font:name!, %s %a : %!font:features!",fontdata[font or true],hows[how],full,setups[numbers[done]]) + end + end + texsetglyphdata(done) + end + end + + local function resetfeature() + if trace_features and texgetglyphdata() ~= 0 then + report_cummulative("font %!font:name!, reset",fontdata[true]) + end + texsetglyphdata(0) + end + + local function setfontfeature(tag) + texsetglyphdata(contextnumber(tag)) + end + + local function resetfontfeature() + texsetglyphdata(0) + end + + implement { + name = "nbfs", + arguments = "dimen", + actions = function(d) + context(lpegmatch(stripper,f_strip(d/65536))) + end + } + + implement { + name = "featureattribute", + arguments = "string", + actions = { contextnumber, context } + } + + implement { + name = "setfontfeature", + arguments = "string", + actions = setfontfeature, + } + + implement { + name = "resetfontfeature", + -- arguments = { 0, 0 }, + actions = resetfontfeature, + } + + implement { + name = "setfontofid", + arguments = "integer", + actions = function(id) + ctx_getvalue(csnames[id]) + end + } + + implement { + name = "definefontfeature", + arguments = "3 strings", + actions = presetcontext, + } + + implement { + name = "doifelsefontfeature", + arguments = "string", + actions = function(name) ctx_doifelse(contextnumber(name) > 1) end, + } + + implement { + name = "doifunknownfontfeature", + arguments = "string", + actions = function(name) ctx_doif(contextnumber(name) == 0) end, + } + + implement { + name = "adaptfontfeature", + arguments = "2 strings", + actions = adaptcontext + } + + local function registerlanguagefeatures() + local specifications = languages.data.specifications + for i=1,#specifications do + local specification = specifications[i] + local language = specification.opentype + if language then + local script = specification.opentypescript or specification.script + if script then + local context = specification.context + if type(context) == "table" then + for i=1,#context do + definecontext(context[i], { language = language, script = script}) + end + elseif type(context) == "string" then + definecontext(context, { language = language, script = script}) + end + end + end + end + end + + constructors.setfeature = setfeature + constructors.resetfeature = resetfeature + + implement { name = "resetfeature", actions = resetfeature } + implement { name = "addfeature", actions = setfeature, arguments = { "'+'", "string", "string" } } + implement { name = "subtractfeature", actions = setfeature, arguments = { "'-'", "string", "string" } } + implement { name = "replacefeature", actions = setfeature, arguments = { "'='", "string", "string" } } + implement { name = "revivefeature", actions = setfeature, arguments = { true, "string" } } + + implement { + name = "featurelist", + actions = { fonts.specifiers.contexttostring, context }, + arguments = { "string", "'otf'", "string", "'yes'", "'no'", true } + } + + implement { + name = "registerlanguagefeatures", + actions = registerlanguagefeatures, + } + +end + +-- a fontkern plug: + +-- nodes.injections.installnewkern(nuts.pool.fontkern) + +do + + local report = logs.reporter("otf","variants") + + local function replace(tfmdata,feature,value) + local characters = tfmdata.characters + local variants = tfmdata.resources.variants + if variants then + local t = { } + for k, v in sortedhash(variants) do + t[#t+1] = formatters["0x%X (%i)"](k,k) + end + value = tonumber(value) or 0xFE00 -- 917762 + report("fontname : %s",tfmdata.properties.fontname) + report("available: % t",t) + local v = variants[value] + if v then + report("using : %X (%i)",value,value) + for k, v in next, v do + local c = characters[v] + if c then + characters[k] = c + end + end + else + report("unknown : %X (%i)",value,value) + end + end + end + + registerotffeature { + name = 'variant', + description = 'unicode variant', + manipulators = { + base = replace, + node = replace, + } + } + +end + +-- here (todo: closure) + +-- make a closure (200 limit): + +do + + local trace_analyzing = false trackers.register("otf.analyzing", function(v) trace_analyzing = v end) + + local analyzers = fonts.analyzers + local methods = analyzers.methods + + local unsetvalue = attributes.unsetvalue + + local a_color = attributes.private('color') + local a_colormodel = attributes.private('colormodel') + local m_color = attributes.list[a_color] or { } + + local glyph_code = nodes.nodecodes.glyph + + local states = analyzers.states + + local colornames = { + [states.init] = "font:1", + [states.medi] = "font:2", + [states.fina] = "font:3", + [states.isol] = "font:4", + [states.mark] = "font:5", + [states.rest] = "font:6", + [states.rphf] = "font:1", + [states.half] = "font:2", + [states.pref] = "font:3", + [states.blwf] = "font:4", + [states.pstf] = "font:5", + } + + -- todo: traversers + -- todo: check attr_list so that we can use the same .. helper: setcolorattr + + local function markstates(head) + if head then + head = tonut(head) + local model = getattr(head,a_colormodel) or 1 + for glyph in nextchar, head do + local a = getstate(glyph) + if a then + local name = colornames[a] + if name then + local color = m_color[name] + if color then + setattr(glyph,a_colormodel,model) + setattr(glyph,a_color,color) + end + end + end + end + end + end + + local function analyzeprocessor(head,font,attr) + local tfmdata = fontdata[font] + local script, language = otf.scriptandlanguage(tfmdata,attr) + local action = methods[script] + if not action then + return head, false + end + if type(action) == "function" then + local head, done = action(head,font,attr) + if done and trace_analyzing then + markstates(head) + end + return head, done + end + action = action[language] + if action then + local head, done = action(head,font,attr) + if done and trace_analyzing then + markstates(head) + end + return head, done + else + return head, false + end + end + + registerotffeature { -- adapts + name = "analyze", + processors = { + node = analyzeprocessor, + } + } + + + function methods.nocolor(head,font,attr) + for n, c, f in nextchar, head do + if not font or f == font then + setattr(n,a_color,unsetvalue) + end + end + return head, true + end + +end + + +local function purefontname(name) + if type(name) == "number" then + name = getfontname(name) + end + if type(name) == "string" then + return basename(name) + end +end + +implement { + name = "purefontname", + actions = { purefontname, context }, + arguments = "string", +} + +local sharedstorage = storage.shared + +local list = sharedstorage.bodyfontsizes or { } +local unknown = sharedstorage.unknownbodyfontsizes or { } + +sharedstorage.bodyfontsizes = list +sharedstorage.unknownbodyfontsizes = unknown + +implement { + name = "registerbodyfontsize", + arguments = "string", + actions = function(size) + list[size] = true + end +} + +interfaces.implement { + name = "registerunknownbodysize", + arguments = "string", + actions = function(size) + if not unknown[size] then + interfaces.showmessage("fonts",14,size) + end + unknown[size] = true + end, +} + +implement { + name = "getbodyfontsizes", + arguments = "string", + actions = function(separator) + context(concat(sortedkeys(list),separator)) + end +} + +implement { + name = "processbodyfontsizes", + arguments = "string", + actions = function(command) + local keys = sortedkeys(list) + if command then + local action = context[command] + for i=1,#keys do + action(keys[i]) + end + else + context(concat(keys,",")) + end + end +} + +implement { + name = "cleanfontname", + actions = { cleanname, context }, + arguments = "string" +} + +implement { + name = "fontlookupinitialize", + actions = names.lookup, + arguments = "string", +} + +implement { + name = "fontlookupnoffound", + actions = { names.noflookups, context }, +} + +implement { + name = "fontlookupgetkeyofindex", + actions = { names.getlookupkey, context }, + arguments = { "string", "integer"} +} + +implement { + name = "fontlookupgetkey", + actions = { names.getlookupkey, context }, + arguments = "string" +} + +-- this might move to a runtime module: + +function commands.showchardata(n) + local tfmdata = fontdata[currentfont()] + if tfmdata then + if type(n) == "string" then + n = utfbyte(n) + end + local chr = tfmdata.characters[n] + if chr then + report_status("%s @ %s => %U => %c => %s",tfmdata.properties.fullname,tfmdata.parameters.size,n,n,serialize(chr,false)) + end + end +end + +function commands.showfontparameters(tfmdata) + -- this will become more clever + local tfmdata = tfmdata or fontdata[currentfont()] + if tfmdata then + local parameters = tfmdata.parameters + local mathparameters = tfmdata.mathparameters + local properties = tfmdata.properties + local hasparameters = parameters and next(parameters) + local hasmathparameters = mathparameters and next(mathparameters) + if hasparameters then + report_status("%s @ %s => text parameters => %s",properties.fullname,parameters.size,serialize(parameters,false)) + end + if hasmathparameters then + report_status("%s @ %s => math parameters => %s",properties.fullname,parameters.size,serialize(mathparameters,false)) + end + if not hasparameters and not hasmathparameters then + report_status("%s @ %s => no text parameters and/or math parameters",properties.fullname,parameters.size) + end + end +end + +implement { + name = "currentdesignsize", + actions = function() + context(parameters[currentfont()].designsize) + end +} + +implement { + name = "doifelsefontpresent", + actions = { names.exists, commands.doifelse }, + arguments = "string" +} + +-- we use 0xFE000+ and 0xFF000+ in math and for runtime (text) extensions we +-- use 0xFD000+ + +constructors.privateslots = constructors.privateslots or { } + +storage.register("fonts/constructors/privateslots", constructors.privateslots, "fonts.constructors.privateslots") + +do + + local privateslots = constructors.privateslots + local lastprivateslot = 0xFD000 + + constructors.privateslots = setmetatableindex(privateslots,function(t,k) + local v = lastprivateslot + lastprivateslot = lastprivateslot + 1 + t[k] = v + return v + end) + + implement { + name = "getprivateglyphslot", + actions = function(name) context(privateslots[name]) end, + arguments = "string", + } + +end + +-- an extra helper + +function helpers.getcoloredglyphs(tfmdata) + if type(tfmdata) == "number" then + tfmdata = fontdata[tfmdata] + end + if not tfmdata then + tfmdata = fontdata[true] + end + local characters = tfmdata.characters + local descriptions = tfmdata.descriptions + local collected = { } + for unicode, character in next, characters do + local description = descriptions[unicode] + if description and (description.colors or character.svg) then + collected[#collected+1] = unicode + end + end + table.sort(collected) + return collected +end + +-- for the font manual + +statistics.register("body font sizes", function() + if next(unknown) then + return formatters["defined: % t, undefined: % t"](sortedkeys(list),sortedkeys(unknown)) + end +end) + +statistics.register("used fonts",function() + if trace_usage then + local filename = file.nameonly(environment.jobname) .. "-fonts-usage.lua" + if next(fontdata) then + local files = { } + local list = { } + for id, tfmdata in sortedhash(fontdata) do + local filename = tfmdata.properties.filename + if filename then + local filedata = files[filename] + if filedata then + filedata.instances = filedata.instances + 1 + else + local rawdata = tfmdata.shared and tfmdata.shared.rawdata + local metadata = rawdata and rawdata.metadata + files[filename] = { + instances = 1, + filename = filename, + version = metadata and metadata.version, + size = rawdata and rawdata.size, + } + end + else + -- what to do + end + end + for k, v in sortedhash(files) do + list[#list+1] = v + end + table.save(filename,list) + else + os.remove(filename) + end + end +end) + +-- new + +do + + local settings_to_array = utilities.parsers.settings_to_array + + implement { + name = "definefontcolorpalette", + arguments = "2 strings", + actions = function(name,set) + otf.registerpalette(name,settings_to_array(set)) + end + } + +end + +do + + local pattern = C((1-S("* "))^1) -- strips all after * or ' at' + + implement { + name = "truefontname", + arguments = "string", + actions = function(s) + -- context(match(s,"[^* ]+") or s) + context(lpegmatch(pattern,s) or s) + end + } + +end + +do + + local function getinstancespec(id) + local data = fontdata[id or true] + local shared = data.shared + local resources = shared and shared.rawdata.resources + if resources then + local instancespec = data.properties.instance + if instancespec then + local variabledata = resources.variabledata + if variabledata then + local instances = variabledata.instances + if instances then + for i=1,#instances do + local instance = instances[i] + if cleanname(instance.subfamily)== instancespec then + local values = table.copy(instance.values) + local axis = variabledata.axis + for i=1,#values do + for j=1,#axis do + if values[i].axis == axis[j].tag then + values[i].name = axis[j].name + break + end + end + end + return values + end + end + end + end + end + end + end + + helpers.getinstancespec = getinstancespec + + implement { + name = "currentfontinstancespec", + actions = function() + local t = getinstancespec() -- current font + if t then + for i=1,#t do + if i > 1 then + context.space() + end + local ti = t[i] + context("%s=%s",ti.name,ti.value) + end + end + end + } + +end + +-- for the moment here (and not in font-con.lua): + +do + + local identical = table.identical + local copy = table.copy + local fontdata = fonts.hashes.identifiers + local addcharacters = font.addcharacters + + -- This helper is mostly meant to add last-resort (virtual) characters + -- or runtime generated fonts (so we forget about features and such). It + -- will probably take a while before it get used. + + local trace_adding = false + local report_adding = logs.reporter("fonts","add characters") + + trackers.register("fonts.addcharacters",function(v) trace_adding = v end) + + function constructors.addcharacters(id,list) + local newchar = list.characters + if newchar then + local data = fontdata[id] + local newfont = list.fonts + local oldchar = data.characters + local oldfont = data.fonts + addcharacters(id, { + characters = newchar, + fonts = newfont, + nomath = not data.properties.hasmath, + }) + -- this is just for tracing, as the assignment only uses the fonts list + -- and doesn't store it otherwise + if newfont then + if oldfont then + local oldn = #oldfont + local newn = #newfont + for n=1,newn do + local ok = false + local nf = newfont[n] + for o=1,oldn do + if identical(nf,oldfont[o]) then + ok = true + break + end + end + if not ok then + oldn = oldn + 1 + oldfont[oldn] = newfont[i] + end + end + else + data.fonts = newfont + end + end + -- this is because we need to know what goes on and also might + -- want to access character data + for u, c in next, newchar do + if trace_adding then + report_adding("adding character %U to font %!font:name!",u,id) + end + oldchar[u] = c + end + end + end + + implement { + name = "addfontpath", + arguments = "string", + actions = function(list) + names.addruntimepath(settings_to_array(list)) + end + } + +end + +-- moved here + +do + + local getfontoffamily = font.getfontoffamily + local new_glyph = nodes.pool.glyph + local fontproperties = fonts.hashes.properties + + local function getprivateslot(id,name) + if not name then + name = id + id = currentfont() + end + local properties = fontproperties[id] + local privates = properties and properties.privates + return privates and privates[name] + end + + local function getprivatenode(tfmdata,name) + if type(tfmdata) == "number" then + tfmdata = fontdata[tfmdata] + end + local properties = tfmdata.properties + local font = properties.id + local slot = getprivateslot(font,name) + if slot then + -- todo: set current attribibutes + local char = tfmdata.characters[slot] + local tonode = char.tonode + if tonode then + return tonode(font,char) + else + return new_glyph(font,slot) + end + end + end + + local function getprivatecharornode(tfmdata,name) + if type(tfmdata) == "number" then + tfmdata = fontdata[tfmdata] + end + local properties = tfmdata.properties + local font = properties.id + local slot = getprivateslot(font,name) + if slot then + -- todo: set current attributes + local char = tfmdata.characters[slot] + local tonode = char.tonode + if tonode then + return "node", tonode(tfmdata,char) + else + return "char", slot + end + end + end + + helpers.getprivateslot = getprivateslot + helpers.getprivatenode = getprivatenode + helpers.getprivatecharornode = getprivatecharornode + + implement { + name = "getprivatechar", + arguments = "string", + actions = function(name) + local p = getprivateslot(name) + if p then + context(utfchar(p)) + end + end + } + + implement { + name = "getprivatemathchar", + arguments = "string", + actions = function(name) + local p = getprivateslot(getfontoffamily(0),name) + if p then + context(utfchar(p)) + end + end + } + + implement { + name = "getprivateslot", + arguments = "string", + actions = function(name) + local p = getprivateslot(name) + if p then + context(p) + end + end + } + +end + +-- handy, for now here: + +function fonts.helpers.collectanchors(tfmdata) + + local resources = tfmdata.resources -- todo: use shared + + if not resources or resources.anchors then + return resources.anchors + end + + local anchors = { } + + local function set(unicode,target,class,anchor) + local a = anchors[unicode] + if not a then + anchors[unicode] = { [target] = { anchor } } + return + end + local t = a[target] + if not t then + a[target] = { anchor } + return + end + local x = anchor[1] + local y = anchor[2] + for k, v in next, t do + if v[1] == x and v[2] == y then + return + end + end + t[#t+1] = anchor + end + + local function getanchors(steps,target) + for i=1,#steps do + local step = steps[i] + local coverage = step.coverage + for unicode, data in next, coverage do + local class = data[1] + local anchor = data[2] + if anchor[1] ~= 0 or anchor[2] ~= 0 then + set(unicode,target,class,anchor) + end + end + end + end + + local function getcursives(steps) + for i=1,#steps do + local step = steps[i] + local coverage = step.coverage + for unicode, data in next, coverage do + local class = data[1] + local en = data[2] + local ex = data[3] + if en then + set(unicode,"entry",class,en) + end + if ex then + set(unicode,"exit", class,ex) + end + end + end + end + + local function collect(list) + if list then + for i=1,#list do + local entry = list[i] + local steps = entry.steps + local kind = entry.type + if kind == "gpos_mark2mark" then + getanchors(steps,"mark") + elseif kind == "gpos_mark2base" then + getanchors(steps,"base") + elseif kind == "gpos_mark2ligature" then + getanchors(steps,"ligature") + elseif kind == "gpos_cursive" then + getcursives(steps) + end + end + end + end + + collect(resources.sequences) + collect(resources.sublookups) + + local function sorter(a,b) + if a[1] == b[1] then + return a[2] < b[2] + else + return a[1] < b[1] + end + end + + for unicode, old in next, anchors do + for target, list in next, old do + sort(list,sorter) + end + end + + resources.anchors = anchors + + return anchors + +end diff --git a/tex/context/base/mkxl/font-def.lmt b/tex/context/base/mkxl/font-def.lmt new file mode 100644 index 000000000..4b6616cd9 --- /dev/null +++ b/tex/context/base/mkxl/font-def.lmt @@ -0,0 +1,504 @@ +if not modules then modules = { } end modules ['font-def'] = { + 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" +} + +-- We can overload some of the definers.functions so we don't local them. + +local lower, gsub = string.lower, string.gsub +local tostring, next = tostring, next +local lpegmatch = lpeg.match +local suffixonly, removesuffix, basename = file.suffix, file.removesuffix, file.basename +local formatters = string.formatters +local sortedhash, sortedkeys = table.sortedhash, table.sortedkeys + +local allocate = utilities.storage.allocate + +local trace_defining = false trackers .register("fonts.defining", function(v) trace_defining = v end) +local directive_embedall = false directives.register("fonts.embedall", function(v) directive_embedall = v end) + +trackers.register("fonts.loading", "fonts.defining", "otf.loading", "afm.loading", "tfm.loading") + +local report_defining = logs.reporter("fonts","defining") + +--[[ldx-- +<p>Here we deal with defining fonts. We do so by intercepting the +default loader that only handles <l n='tfm'/>.</p> +--ldx]]-- + +local fonts = fonts +local fontdata = fonts.hashes.identifiers +local readers = fonts.readers +local definers = fonts.definers +local specifiers = fonts.specifiers +local constructors = fonts.constructors +local fontgoodies = fonts.goodies + +readers.sequence = allocate { 'otf', 'ttf', 'afm', 'tfm', 'lua' } -- dfont ttc + +local variants = allocate() +specifiers.variants = variants + +definers.methods = definers.methods or { } + +local internalized = allocate() -- internal tex numbers (private) + +local loadedfonts = constructors.loadedfonts +local designsizes = constructors.designsizes + +-- not in generic (some day I'll make two defs, one for context, one for generic) + +local resolvefile = fontgoodies and fontgoodies.filenames and fontgoodies.filenames.resolve or function(s) return s end + +--[[ldx-- +<p>We hardly gain anything when we cache the final (pre scaled) +<l n='tfm'/> table. But it can be handy for debugging, so we no +longer carry this code along. Also, we now have quite some reference +to other tables so we would end up with lots of catches.</p> +--ldx]]-- + +--[[ldx-- +<p>We can prefix a font specification by <type>name:</type> or +<type>file:</type>. The first case will result in a lookup in the +synonym table.</p> + +<typing> +[ name: | file: ] identifier [ separator [ specification ] ] +</typing> + +<p>The following function split the font specification into components +and prepares a table that will move along as we proceed.</p> +--ldx]]-- + +-- beware, we discard additional specs +-- +-- method:name method:name(sub) method:name(sub)*spec method:name*spec +-- name name(sub) name(sub)*spec name*spec +-- name@spec*oeps + +local function makespecification(specification,lookup,name,sub,method,detail,size) + size = size or 655360 + if not lookup or lookup == "" then + lookup = definers.defaultlookup + end + if trace_defining then + report_defining("specification %a, lookup %a, name %a, sub %a, method %a, detail %a", + specification, lookup, name, sub, method, detail) + end + local t = { + lookup = lookup, -- forced type + specification = specification, -- full specification + size = size, -- size in scaled points or -1000*n + name = name, -- font or filename + sub = sub, -- subfont (eg in ttc) + method = method, -- specification method + detail = detail, -- specification + resolved = "", -- resolved font name + forced = "", -- forced loader + features = { }, -- preprocessed features + } + return t +end + +definers.makespecification = makespecification + +do + + local splitter, splitspecifiers = nil, "" -- not so nice + + local P, C, S, Cc, Cs = lpeg.P, lpeg.C, lpeg.S, lpeg.Cc, lpeg.Cs + + local left = P("(") + local right = P(")") + local colon = P(":") + local space = P(" ") + local lbrace = P("{") + local rbrace = P("}") + + definers.defaultlookup = "file" + + local prefixpattern = P(false) + + local function addspecifier(symbol) + splitspecifiers = splitspecifiers .. symbol + local method = S(splitspecifiers) + local lookup = C(prefixpattern) * colon + local sub = left * C(P(1-left-right-method)^1) * right + local specification = C(method) * C(P(1)^1) + local name = Cs((lbrace/"") * (1-rbrace)^1 * (rbrace/"") + (1-sub-specification)^1) + splitter = P((lookup + Cc("")) * name * (sub + Cc("")) * (specification + Cc(""))) + end + + local function addlookup(str) + prefixpattern = prefixpattern + P(str) + end + + definers.addlookup = addlookup + + addlookup("file") + addlookup("name") + addlookup("spec") + + local function getspecification(str) + return lpegmatch(splitter,str or "") -- weird catch + end + + definers.getspecification = getspecification + + function definers.registersplit(symbol,action,verbosename) + addspecifier(symbol) + variants[symbol] = action + if verbosename then + variants[verbosename] = action + end + end + + function definers.analyze(specification, size) + -- can be optimized with locals + local lookup, name, sub, method, detail = getspecification(specification or "") + return makespecification(specification, lookup, name, sub, method, detail, size) + end + +end + +--[[ldx-- +<p>We can resolve the filename using the next function:</p> +--ldx]]-- + +definers.resolvers = definers.resolvers or { } +local resolvers = definers.resolvers + +-- todo: reporter + +function resolvers.file(specification) + local name = resolvefile(specification.name) -- catch for renames + local suffix = lower(suffixonly(name)) + if fonts.formats[suffix] then + specification.forced = suffix + specification.forcedname = name + specification.name = removesuffix(name) + else + specification.name = name -- can be resolved + end +end + +function resolvers.name(specification) + local resolve = fonts.names.resolve + if resolve then + local resolved, sub, subindex, instance = resolve(specification.name,specification.sub,specification) -- we pass specification for overloaded versions + if resolved then + specification.resolved = resolved + specification.sub = sub + specification.subindex = subindex + -- new, needed for experiments + if instance then + specification.instance = instance + local features = specification.features + if not features then + features = { } + specification.features = features + end + local normal = features.normal + if not normal then + normal = { } + features.normal = normal + end + normal.instance = instance + end + -- + local suffix = lower(suffixonly(resolved)) + if fonts.formats[suffix] then + specification.forced = suffix + specification.forcedname = resolved + specification.name = removesuffix(resolved) + else + specification.name = resolved + end + end + else + resolvers.file(specification) + end +end + +function resolvers.spec(specification) + local resolvespec = fonts.names.resolvespec + if resolvespec then + local resolved, sub, subindex = resolvespec(specification.name,specification.sub,specification) -- we pass specification for overloaded versions + if resolved then + specification.resolved = resolved + specification.sub = sub + specification.subindex = subindex + specification.forced = lower(suffixonly(resolved)) + specification.forcedname = resolved + specification.name = removesuffix(resolved) + end + else + resolvers.name(specification) + end +end + +function definers.resolve(specification) + if not specification.resolved or specification.resolved == "" then -- resolved itself not per se in mapping hash + local r = resolvers[specification.lookup] + if r then + r(specification) + end + end + if specification.forced == "" then + specification.forced = nil + specification.forcedname = nil + end + specification.hash = lower(specification.name .. ' @ ' .. constructors.hashfeatures(specification)) + if specification.sub and specification.sub ~= "" then + specification.hash = specification.sub .. ' @ ' .. specification.hash + end + return specification +end + +--[[ldx-- +<p>The main read function either uses a forced reader (as determined by +a lookup) or tries to resolve the name using the list of readers.</p> + +<p>We need to cache when possible. We do cache raw tfm data (from <l +n='tfm'/>, <l n='afm'/> or <l n='otf'/>). After that we can cache based +on specificstion (name) and size, that is, <l n='tex'/> only needs a number +for an already loaded fonts. However, it may make sense to cache fonts +before they're scaled as well (store <l n='tfm'/>'s with applied methods +and features). However, there may be a relation between the size and +features (esp in virtual fonts) so let's not do that now.</p> + +<p>Watch out, here we do load a font, but we don't prepare the +specification yet.</p> +--ldx]]-- + +-- very experimental: + +function definers.applypostprocessors(tfmdata) + local postprocessors = tfmdata.postprocessors + if postprocessors then + local properties = tfmdata.properties + for i=1,#postprocessors do + local extrahash = postprocessors[i](tfmdata) -- after scaling etc + if type(extrahash) == "string" and extrahash ~= "" then + -- e.g. a reencoding needs this + extrahash = gsub(lower(extrahash),"[^a-z]","-") + properties.fullname = formatters["%s-%s"](properties.fullname,extrahash) + end + end + end + return tfmdata +end + +-- function definers.applypostprocessors(tfmdata) +-- return tfmdata +-- end + +local function checkfeatures(tfmdata) + local resources = tfmdata.resources + local shared = tfmdata.shared + if resources and shared then + local features = resources.features + local usedfeatures = shared.features + if features and usedfeatures then + local usedlanguage = usedfeatures.language or "dflt" + local usedscript = usedfeatures.script or "dflt" + local function check(what) + if what then + local foundlanguages = { } + for feature, scripts in next, what do + if usedscript == "auto" or scripts["*"] then + -- ok + elseif not scripts[usedscript] then + -- report_defining("font %!font:name!, feature %a, no script %a", + -- tfmdata,feature,usedscript) + else + for script, languages in next, scripts do + if languages["*"] then + -- ok + elseif not languages[usedlanguage] then + report_defining("font %!font:name!, feature %a, script %a, no language %a", + tfmdata,feature,script,usedlanguage) + end + end + end + for script, languages in next, scripts do + for language in next, languages do + foundlanguages[language] = true + end + end + end + if false then + foundlanguages["*"] = nil + foundlanguages = sortedkeys(foundlanguages) + for feature, scripts in sortedhash(what) do + for script, languages in next, scripts do + if not languages["*"] then + for i=1,#foundlanguages do + local language = foundlanguages[i] + if not languages[language] then + report_defining("font %!font:name!, feature %a, script %a, no language %a", + tfmdata,feature,script,language) + end + end + end + end + end + end + end + end + check(features.gsub) + check(features.gpos) + end + end +end + +function definers.loadfont(specification) + local hash = constructors.hashinstance(specification) + -- todo: also hash by instance / factors + local tfmdata = loadedfonts[hash] -- hashes by size ! + if not tfmdata then + -- normally context will not end up here often (if so there is an issue somewhere) + local forced = specification.forced or "" + if forced ~= "" then + local reader = readers[lower(forced)] -- normally forced is already lowered + tfmdata = reader and reader(specification) + if not tfmdata then + report_defining("forced type %a of %a not found",forced,specification.name) + end + else + local sequence = readers.sequence -- can be overloaded so only a shortcut here + for s=1,#sequence do + local reader = sequence[s] + if readers[reader] then -- we skip not loaded readers + if trace_defining then + report_defining("trying (reader sequence driven) type %a for %a with file %a",reader,specification.name,specification.filename) + end + tfmdata = readers[reader](specification) + if tfmdata then + break + else + specification.filename = nil + end + end + end + end + if tfmdata then + tfmdata = definers.applypostprocessors(tfmdata) + loadedfonts[hash] = tfmdata + designsizes[specification.hash] = tfmdata.parameters.designsize + checkfeatures(tfmdata) + end + end + if not tfmdata then + report_defining("font with asked name %a is not found using lookup %a",specification.name,specification.lookup) + end + return tfmdata +end + +function constructors.readanddefine(name,size) -- no id -- maybe a dummy first + local specification = definers.analyze(name,size) + local method = specification.method + if method and variants[method] then + specification = variants[method](specification) + end + specification = definers.resolve(specification) + local hash = constructors.hashinstance(specification) + local id = definers.registered(hash) + if not id then + local tfmdata = definers.loadfont(specification) + if tfmdata then + tfmdata.properties.hash = hash + id = font.define(tfmdata) + definers.register(tfmdata,id) + else + id = 0 -- signal + end + end + return fontdata[id], id +end + +--[[ldx-- +<p>So far the specifiers. Now comes the real definer. Here we cache +based on id's. Here we also intercept the virtual font handler. Since +it evolved stepwise I may rewrite this bit (combine code).</p> + +In the previously defined reader (the one resulting in a <l n='tfm'/> +table) we cached the (scaled) instances. Here we cache them again, but +this time based on id. We could combine this in one cache but this does +not gain much. By the way, passing id's back to in the callback was +introduced later in the development.</p> +--ldx]]-- + +function definers.registered(hash) + local id = internalized[hash] + return id, id and fontdata[id] +end + +function definers.register(tfmdata,id) + if tfmdata and id then + local hash = tfmdata.properties.hash + if not hash then + report_defining("registering font, id %a, name %a, invalid hash",id,tfmdata.properties.filename or "?") + elseif not internalized[hash] then + internalized[hash] = id + if trace_defining then + report_defining("registering font, id %s, hash %a",id,hash) + end + fontdata[id] = tfmdata + end + end +end + +function definers.read(specification,size,id) -- id can be optional, name can already be table + statistics.starttiming(fonts) + if type(specification) == "string" then + specification = definers.analyze(specification,size) + end + local method = specification.method + if method and variants[method] then + specification = variants[method](specification) + end + specification = definers.resolve(specification) + local hash = constructors.hashinstance(specification) + local tfmdata = definers.registered(hash) -- id + if tfmdata then + if trace_defining then + report_defining("already hashed: %s",hash) + end + else + tfmdata = definers.loadfont(specification) -- can be overloaded +-- put in properties instead + if tfmdata then + tfmdata.original = specification.specification + if trace_defining then + report_defining("loaded and hashed: %s",hash) + end + tfmdata.properties.hash = hash + if id then + definers.register(tfmdata,id) + end + else + if trace_defining then + report_defining("not loaded and hashed: %s",hash) + end + end + end + if not tfmdata then -- or id? + report_defining( "unknown font %a, loading aborted",specification.name) + elseif trace_defining and type(tfmdata) == "table" then + local properties = tfmdata.properties or { } + local parameters = tfmdata.parameters or { } + report_defining("using %a font with id %a, name %a, size %a, fullname %a, filename %a", + properties.format or "unknown", id or "-", properties.name, parameters.size, + properties.fullname, basename(properties.filename)) + end + statistics.stoptiming(fonts) + return tfmdata +end + +function font.getfont(id) + return fontdata[id] -- otherwise issues +end diff --git a/tex/context/base/mkxl/font-enh.lmt b/tex/context/base/mkxl/font-enh.lmt new file mode 100644 index 000000000..1e532d4ad --- /dev/null +++ b/tex/context/base/mkxl/font-enh.lmt @@ -0,0 +1,90 @@ +if not modules then modules = { } end modules ['font-enh'] = { + 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 module is already stripped from old stuff but more might go away +-- in lmtx. Stay tuned. + +local next = next + +local trace_unicoding = false + +trackers.register("fonts.defining", function(v) trace_unicoding = v end) +trackers.register("fonts.unicoding", function(v) trace_unicoding = v end) + +local report_unicoding = logs.reporter("fonts","unicoding") + +local fonts = fonts +local constructors = fonts.constructors + +local afmfeatures = constructors.features.afm +local otffeatures = constructors.features.otf + +local registerafmfeature = afmfeatures.register +local registerotffeature = otffeatures.register + +local function initialize(tfmdata) + local goodies = tfmdata.goodies + local newcoding = nil + for i=1,#goodies do + local remapping = goodies[i].remapping + if remapping and remapping.unicodes then + newcoding = remapping.unicodes -- names to unicodes + end + end + if newcoding then + local characters = tfmdata.characters + local descriptions = tfmdata.descriptions + local oldcoding = tfmdata.resources.unicodes + local originals = { } + for name, newcode in next, newcoding do + local oldcode = oldcoding[name] + if characters[newcode] and not originals[newcode] then + originals[newcode] = { + character = characters [newcode], + description = descriptions[newcode], + } + end + if oldcode then + local original = originals[oldcode] + local character, description + if original then + character = original.character + description = original.description + else + character = characters [oldcode] + description = descriptions[oldcode] + end + characters [newcode] = character + descriptions[newcode] = description + character .unicode = newcode + description.unicode = newcode + else + oldcoding[name] = newcode + end + if trace_unicoding then + if oldcode then + report_unicoding("aliasing glyph %a from %U to %U",name,oldcode,newcode) + else + report_unicoding("aliasing glyph %a to %U",name,newcode) + end + end + end + end +end + +local specification = { + name = "unicoding", + description = "adapt unicode table", + initializers = { + base = initialize, + node = initialize, + }, +} + +registerotffeature(specification) +registerafmfeature(specification) diff --git a/tex/context/base/mkxl/font-fbk.lmt b/tex/context/base/mkxl/font-fbk.lmt new file mode 100644 index 000000000..0e104aca7 --- /dev/null +++ b/tex/context/base/mkxl/font-fbk.lmt @@ -0,0 +1,357 @@ +if not modules then modules = { } end modules ['font-fbk'] = { + 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 cos, tan, rad, format = math.cos, math.tan, math.rad, string.format +local utfbyte, utfchar = utf.byte, utf.char +local next = next + +--[[ldx-- +<p>This is very experimental code!</p> +--ldx]]-- + +local trace_visualize = false trackers.register("fonts.composing.visualize", function(v) trace_visualize = v end) +local trace_define = false trackers.register("fonts.composing.define", function(v) trace_define = v end) + +local report = logs.reporter("fonts","combining") + +local allocate = utilities.storage.allocate + +local fonts = fonts +local handlers = fonts.handlers +local constructors = fonts.constructors +local helpers = fonts.helpers + +local otf = handlers.otf +local afm = handlers.afm +local registerotffeature = otf.features.register +local registerafmfeature = afm.features.register + +local addotffeature = otf.addfeature + +local unicodecharacters = characters.data +local unicodefallbacks = characters.fallbacks + +local vfcommands = helpers.commands +local charcommand = vfcommands.char +local rightcommand = vfcommands.right +local downcommand = vfcommands.down +local upcommand = vfcommands.up +local push = vfcommands.push +local pop = vfcommands.pop + +local force_combining = false -- just for demo purposes (see mk) +local fraction = 0.15 -- 30 units for lucida + +-- todo: we also need to update the feature hashes ... i'll do that when i'm in the mood +-- and/or when i need it + +local function composecharacters(tfmdata) + -- this assumes that slot 1 is self, there will be a proper self some day + local characters = tfmdata.characters + local descriptions = tfmdata.descriptions + local parameters = tfmdata.parameters + local properties = tfmdata.properties + local Xdesc = descriptions[utfbyte("X")] + local xdesc = descriptions[utfbyte("x")] + if Xdesc and xdesc then + local scale = parameters.factor or 1 + local deltaxheight = scale * (Xdesc.boundingbox[4] - xdesc.boundingbox[4]) + local extraxheight = fraction * deltaxheight -- maybe use compose value + local italicfactor = parameters.italicfactor or 0 + local vfspecials = backends.tables.vfspecials --brr + local red, green, blue, black + if trace_visualize then + red = vfspecials.startcolor("red") + green = vfspecials.startcolor("green") + blue = vfspecials.startcolor("blue") + black = vfspecials.stopcolor + end + local compose = fonts.goodies.getcompositions(tfmdata) + if compose and trace_visualize then + report("using compose information from goodies file") + end + local done = false + for i, c in next, unicodecharacters do -- loop over all characters ... not that efficient but a specials hash takes memory + if force_combining or not characters[i] then + local s = c.specials + if s and s[1] == 'char' then + local chr = s[2] + local charschr = characters[chr] + if charschr then + local cc = c.category + if cc == 'll' or cc == 'lu' or cc == 'lt' then -- characters.is_letter[cc] + local acc = s[3] + local t = { } + for k, v in next, charschr do + if k ~= "commands" then + t[k] = v + end + end + local charsacc = characters[acc] + -- local ca = charsacc.category + -- if ca == "mn" then + -- -- mark nonspacing + -- elseif ca == "ms" then + -- -- mark spacing combining + -- elseif ca == "me" then + -- -- mark enclosing + -- else + if not charsacc then -- fallback accents + acc = unicodefallbacks[acc] + charsacc = acc and characters[acc] + end + local chr_t = charcommand[chr] + if charsacc then + if trace_define then + report("composed %C, base %C, accent %C",i,chr,acc) + end + local acc_t = charcommand[acc] + local cb = descriptions[chr].boundingbox + local ab = descriptions[acc].boundingbox + -- todo: adapt height + if cb and ab then + local c_llx = scale*cb[1] + local c_lly = scale*cb[2] + local c_urx = scale*cb[3] + local c_ury = scale*cb[4] + local a_llx = scale*ab[1] + local a_lly = scale*ab[2] + local a_urx = scale*ab[3] + local a_ury = scale*ab[4] + local done = false + if compose then + local i_compose = compose[i] + local i_anchored = i_compose and i_compose.anchored + if i_anchored then + local c_compose = compose[chr] + local a_compose = compose[acc] + local c_anchors = c_compose and c_compose.anchors + local a_anchors = a_compose and a_compose.anchors + if c_anchors and a_anchors then + local c_anchor = c_anchors[i_anchored] + local a_anchor = a_anchors[i_anchored] + if c_anchor and a_anchor then + local cx = c_anchor.x or 0 + local cy = c_anchor.y or 0 + local ax = a_anchor.x or 0 + local ay = a_anchor.y or 0 + local dx = cx - ax + local dy = cy - ay + if trace_define then + report("building %C from %C and %C",i,chr,acc) + report(" boundingbox:") + report(" chr: %3i %3i %3i %3i",unpack(cb)) + report(" acc: %3i %3i %3i %3i",unpack(ab)) + report(" anchors:") + report(" chr: %3i %3i",cx,cy) + report(" acc: %3i %3i",ax,ay) + report(" delta:") + report(" %s: %3i %3i",i_anchored,dx,dy) + end + local right = rightcommand[scale*dx] + local down = upcommand[scale*dy] + if trace_visualize then + t.commands = { + push, right, down, + green, acc_t, black, + pop, chr_t, + } + else + t.commands = { + push, right, down, + acc_t, pop, chr_t, + } + end + done = true + end + end + end + end + if not done then + -- can be sped up for scale == 1 + local dx = (c_urx - a_urx - a_llx + c_llx)/2 + local dd = (c_urx - c_llx)*italicfactor + if a_ury < 0 then + local right = rightcommand[dx-dd] + if trace_visualize then + t.commands = { + push, right, red, acc_t, + black, pop, chr_t, + } + else + t.commands = { + push, right, acc_t, pop, + chr_t, + } + end +t.depth = a_ury + elseif c_ury > a_lly then -- messy test + local dy + if compose then + -- experimental: we could use sx but all that testing + -- takes time and code + dy = compose[i] + if dy then + dy = dy.dy + end + if not dy then + dy = compose[acc] + if dy then + dy = dy and dy.dy + end + end + if not dy then + dy = compose.dy + end + if not dy then + dy = - deltaxheight + extraxheight + elseif dy > -1.5 and dy < 1.5 then + -- we assume a fraction of (percentage) + dy = - dy * deltaxheight + else + -- we assume fontunits (value smaller than 2 make no sense) + dy = - dy * scale + end + else + dy = - deltaxheight + extraxheight + end +t.height = a_ury-dy + local right = rightcommand[dx+dd] + local down = downcommand[dy] + if trace_visualize then + t.commands = { + push, right, down, green, + acc_t, black, pop, chr_t, + } + else + t.commands = { + push, right, down, acc_t, + pop, chr_t, + } + end + else + local right = rightcommand[dx+dd] + if trace_visualize then + t.commands = { + push, right, blue, acc_t, + black, pop, chr_t, + } + else + t.commands = { + push, right, acc_t, pop, + chr_t, + } + end +t.height = a_ury + end + end + else + t.commands = { + chr_t, -- else index mess + } + end + else + if trace_define then + report("%C becomes simplified %C",i,chr) + end + t.commands = { + chr_t, -- else index mess + } + end + done = true + characters[i] = t + local d = { } + for k, v in next, descriptions[chr] do + d[k] = v + end + descriptions[i] = d + end + end + end + end + end + end +end + +local specification = { + name = "compose", + description = "additional composed characters", + manipulators = { + base = composecharacters, + node = composecharacters, + } +} + +registerotffeature(specification) +registerafmfeature(specification) + +addotffeature { + name = "char-ligatures", + type = "ligature", + data = characters.splits.char, + order = { "char-ligatures" }, + prepend = true, +} + +addotffeature { + name = "compat-ligatures", + type = "ligature", + data = characters.splits.compat, + order = { "compat-ligatures" }, + prepend = true, +} + +registerotffeature { + name = 'char-ligatures', + description = 'unicode char specials to ligatures', +} + +registerotffeature { + name = 'compat-ligatures', + description = 'unicode compat specials to ligatures', +} + +do + + -- This installs the builder into the regular virtual font builder, + -- which only makes sense as demo. + + local vf = handlers.vf + local commands = vf.combiner.commands + + vf.helpers.composecharacters = composecharacters + + commands["compose.trace.enable"] = function() + trace_visualize = true + end + + commands["compose.trace.disable"] = function() + trace_visualize = false + end + + commands["compose.force.enable"] = function() + force_combining = true + end + + commands["compose.force.disable"] = function() + force_combining = false + end + + commands["compose.trace.set"] = function(g,v) + if v[2] == nil then + trace_visualize = true + else + trace_visualize = v[2] + end + end + + commands["compose.apply"] = function(g,v) + composecharacters(g) + end + +end diff --git a/tex/context/base/mkxl/font-fmp.lmt b/tex/context/base/mkxl/font-fmp.lmt new file mode 100644 index 000000000..f35c96f49 --- /dev/null +++ b/tex/context/base/mkxl/font-fmp.lmt @@ -0,0 +1,123 @@ +if not modules then modules = { } end modules ['font-fmp'] = { + 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" +} + +-- We only need to pick up the filename and optionally the enc file as we only use +-- them for old school virtual math fonts. We might as well drop this completely. +-- This used to be a backend module but the code is rather generic so we just put it +-- here now. + +local find, match, splitlines = string.find, string.match, string.splitlines + +local implement = interfaces.implement + +local mappings = { } + +local function setline(n) + if trace_fonts then + report_fonts("mapline: %s",n) + end + local name, fullname, encfile, pfbfile = match(n,"(%S+)%s+(%S+).-<(.-%.enc).-<(.-%.pfb)") + if name then + mappings[name] = { fullname, encfile, pfbfile } + end +end + +local function setfile(n) + local okay, data = resolvers.loadbinfile(n,"map") + if okay and data then + data = splitlines(data) + for i=1,#data do + local d = data[i] + if d ~= "" and not find(d,"^[#%%]") then + setline(d) + end + end + end +end + +local function getentry(n) + local n = file.nameonly(n) + local m = mappings[n] + if m then + local encfile = m[2] + local encoding = fonts.encodings.load(encfile) + if not encoding then + return + end + local pfbfile = resolvers.findfile(m[3],"pfb") + if not pfbfile or pfbfile == "" then + return + end + return encoding, pfbfile, encfile + end +end + +-- soon to be obsolete: + +local mappings = fonts.mappings or { } +fonts.mappings = mappings + +local loaded = { -- prevent loading (happens in cont-sys files) + -- ["original-base.map" ] = true, + -- ["original-ams-base.map" ] = true, + -- ["original-ams-euler.map"] = true, + -- ["original-public-lm.map"] = true, +} + +function mappings.loadfile(name) + name = file.addsuffix(name,"map") + if not loaded[name] then + if trace_mapfiles then + report_mapfiles("loading map file %a",name) + end + setfile(name) + loaded[name] = true + end +end + +local loaded = { -- prevent double loading +} + +function mappings.loadline(how,line) + if line then + how = how .. " " .. line + elseif how == "" then + how = "= " .. line + end + if not loaded[how] then + if trace_mapfiles then + report_mapfiles("processing map line %a",line) + end + setline(how) + loaded[how] = true + end +end + +function mappings.reset() + lpdf.setmapfile("") -- tricky ... backend related +end + +mappings.getentry = getentry + +implement { + name = "loadmapfile", + actions = mappings.loadfile, + arguments = "string" +} + +implement { + name = "loadmapline", + actions = mappings.loadline, + arguments = "string" +} + +implement { + name = "resetmapfiles", + actions = mappings.reset, + arguments = "string" +} diff --git a/tex/context/base/mkxl/font-lib.mklx b/tex/context/base/mkxl/font-lib.mklx index 08c5acbdf..d187d3a05 100644 --- a/tex/context/base/mkxl/font-lib.mklx +++ b/tex/context/base/mkxl/font-lib.mklx @@ -17,9 +17,10 @@ \registerctxluafile{font-ini}{} \registerctxluafile{font-log}{} -\registerctxluafile{font-con}{} -\registerctxluafile{font-cft}{} +\registerctxluafile{font-con}{autosuffix} +%registerctxluafile{font-cft}{} \registerctxluafile{font-enc}{} +\registerctxluafile{font-fmp}{autosuffix} \registerctxluafile{font-agl}{} % if needed we can comment this and delay loading \registerctxluafile{font-cid}{} % cid maps \registerctxluafile{font-map}{optimize} @@ -81,15 +82,15 @@ \registerctxluafile{font-lua}{} \registerctxluafile{font-vir}{} -\registerctxluafile{font-enh}{} +\registerctxluafile{font-enh}{autosuffix} \registerctxluafile{good-ini}{} \registerctxluafile{good-gen}{} \registerctxluafile{good-ctx}{} \registerctxluafile{good-mth}{} -\registerctxluafile{font-def}{} -\registerctxluafile{font-ctx}{} % after def as it overloads +\registerctxluafile{font-def}{autosuffix} +\registerctxluafile{font-ctx}{autosuffix} % after def as it overloads % extensions, order matters @@ -116,7 +117,7 @@ \registerctxluafile{font-imp-tracing}{} % comes last! -\registerctxluafile{font-fbk}{} +\registerctxluafile{font-fbk}{autosuffix} \registerctxluafile{font-aux}{} @@ -151,22 +152,4 @@ \permanent \def\cleanfontname #1{\clf_cleanfontname{#1}} \permanent\protected\def\setfontofid #1{\clf_setfontofid\numexpr#1\relax} -% this is an ugly hack needed for postponed inclusion stuff ... don't try -% to understand this ... these are kind of old mkiv solutions - -\permanent\protected\def\typethreefont#1{\setfontid#1\relax} -\permanent\protected\def\typethreechar#1{\char#1\hskip-\fontcharwd\font#1\relax} -\permanent\protected\def\typethreelast#1{\char#1\relax} -\permanent\protected\def\typethreecode#1{\pdfliteral direct {#1}} - -% This might change ... - -\newtoks \typethreetoks % used at the lua end -\mutable\let\typethreemacro\empty % used at the lua end - -\typethreetoks {% - \setbox\zerocount\hpack{\typethreemacro}% - \setbox\zerocount\hpack{\raise\dp\zerocount\box\zerocount}% -} - \protect \endinput diff --git a/tex/context/base/mkxl/font-ogr.lmt b/tex/context/base/mkxl/font-ogr.lmt index e57e88ed6..df6449ded 100644 --- a/tex/context/base/mkxl/font-ogr.lmt +++ b/tex/context/base/mkxl/font-ogr.lmt @@ -9,12 +9,6 @@ if not modules then modules = { } end modules ['font-ogr'] = { -- Here we deal with graphic variants and for now also color support ends up here -- but that might change. It's lmtx only code. -if not context then - return -elseif CONTEXTLMTXMODE == 0 then - return -end - local tostring, tonumber, next, type = tostring, tonumber, next, type local round, max, mod, div = math.round, math.max, math.mod, math.div local find = string.find @@ -72,7 +66,6 @@ do d_tfmdata.descriptions = d_descriptions d_tfmdata.parentdata = t_tfmdata -- so we can access it if needed d_properties.instance = - droppedin -- will become an extra element in the hash - t_properties.virtualized = true identifiers[droppedin] = d_tfmdata local fonts = t_tfmdata.fonts or { } t_tfmdata.fonts = fonts @@ -358,18 +351,23 @@ end -- This is also somewhat specific. -local sharedpalettes do +local color_direct = function() return false end +local color_indirect = color_direct + +updaters.register("backend.update",function() + color_direct = lpdf.fonts.color_direct + color_indirect = lpdf.fonts.color_indirect +end) - sharedpalettes = { } +local sharedpalettes = { } do - local colors = attributes.list[attributes.private('color')] or { } - local transparencies = attributes.list[attributes.private('transparency')] or { } + local register = attributes.colors.register + + local colors = attributes.list[attributes.private('color')] or { } + local transparencies = attributes.list[attributes.private('transparency')] or { } function otf.registerpalette(name,values) sharedpalettes[name] = values - local color = lpdf.color - local transparency = lpdf.transparency - local register = colors.register for i=1,#values do local v = values[i] if v == "textcolor" then @@ -387,13 +385,7 @@ local sharedpalettes do c = colors[v] t = transparencies[v] end - if c and t then - values[i] = color(1,c) .. " " .. transparency(t) - elseif c then - values[i] = color(1,c) - elseif t then - values[i] = color(1,t) - end + values[i] = color_indirect(c,t) end end end @@ -402,29 +394,11 @@ end local initializeoverlay do - -- we should use the proper interface instead but for now: - - local colors = attributes.colors - local rgbtocmyk = colors.rgbtocmyk - - local f_cmyk = formatters["%.3N %.3f %.3N %.3N k"] - local f_rgb = formatters["%.3N %.3f %.3N rg"] - local f_gray = formatters["%.3N g"] - - local function convert(t,k) + local function autoconvert(t,k) local v = { } - local m = colors.model for i=1,#k do local p = k[i] - local r, g, b = p[1]/255, p[2]/255, p[3]/255 - if r == g and g == b then - p = f_gray(r) - elseif m == "cmyk" then - p = f_cmyk(rgbtocmyk(r,g,b)) - else - p = f_rgb(r,g,b) - end - v[i] = p + v[i] = color_direct(p[1]/255, p[2]/255, p[3]/255) end t[k] = v return v @@ -438,7 +412,7 @@ local initializeoverlay do -- local converted = resources.converted if not converted then - converted = setmetatableindex(convert) + converted = setmetatableindex(autoconvert) resources.converted = converted end local colorvalues = sharedpalettes[value] diff --git a/tex/context/base/mkxl/font-tra.mkxl b/tex/context/base/mkxl/font-tra.mkxl index 72addc5e9..b24eab865 100644 --- a/tex/context/base/mkxl/font-tra.mkxl +++ b/tex/context/base/mkxl/font-tra.mkxl @@ -330,14 +330,14 @@ % new -\permanent\protected\def\savefontdata[#1]% not yet in i-*.xml - {\begingroup - \getdummyparameters[#1]% - \clf_savefont { - filename {\dummyparameter\c!file} - fontname {\dummyparameter\c!name} - method {\dummyparameter\c!method} - }% - \endgroup} +% \permanent\protected\def\savefontdata[#1]% not yet in i-*.xml +% {\begingroup +% \getdummyparameters[#1]% +% \clf_savefont { +% filename {\dummyparameter\c!file} +% fontname {\dummyparameter\c!name} +% method {\dummyparameter\c!method} +% }% +% \endgroup} \protect \endinput diff --git a/tex/context/base/mkxl/lpdf-col.lmt b/tex/context/base/mkxl/lpdf-col.lmt index 5ebd4bd79..13f34fe46 100644 --- a/tex/context/base/mkxl/lpdf-col.lmt +++ b/tex/context/base/mkxl/lpdf-col.lmt @@ -737,47 +737,6 @@ do local f_slant = formatters["q 1 0 %N 1 0 0 cm"] - -- local fillcolors = { - -- red = { "pdf", "page", "1 0 0 rg" }, - -- green = { "pdf", "page", "0 1 0 rg" }, - -- blue = { "pdf", "page", "0 0 1 rg" }, - -- gray = { "pdf", "page", ".5 g" }, - -- black = { "pdf", "page", "0 g" }, - -- palered = { "pdf", "page", "1 .75 .75 rg" }, - -- palegreen = { "pdf", "page", ".75 1 .75 rg" }, - -- paleblue = { "pdf", "page", ".75 .75 1 rg" }, - -- palegray = { "pdf", "page", ".75 g" }, - -- } - -- - -- local strokecolors = { - -- red = { "pdf", "page", "1 0 0 RG" }, - -- green = { "pdf", "page", "0 1 0 RG" }, - -- blue = { "pdf", "page", "0 0 1 RG" }, - -- gray = { "pdf", "page", ".5 G" }, - -- black = { "pdf", "page", "0 G" }, - -- palered = { "pdf", "page", "1 .75 .75 RG" }, - -- palegreen = { "pdf", "page", ".75 1 .75 RG" }, - -- paleblue = { "pdf", "page", ".75 .75 1 RG" }, - -- palegray = { "pdf", "page", ".75 G" }, - -- } - -- - -- backends.pdf.tables.vfspecials = allocate { -- todo: distinguish between glyph and rule color - -- - -- red = { "pdf", "page", "1 0 0 rg 1 0 0 RG" }, - -- green = { "pdf", "page", "0 1 0 rg 0 1 0 RG" }, - -- blue = { "pdf", "page", "0 0 1 rg 0 0 1 RG" }, - -- gray = { "pdf", "page", ".75 g .75 G" }, - -- black = { "pdf", "page", "0 g 0 G" }, - -- - -- -- rulecolors = fillcolors, - -- -- fillcolors = fillcolors, - -- -- strokecolors = strokecolors, - -- - -- startslant = function(a) return { "pdf", "origin", f_slant(a) } end, - -- stopslant = { "pdf", "origin", "Q" }, - -- - -- } - local slants = setmetatableindex(function(t,k) local v = { "pdf", "origin", f_slant(a) } t[k] = v @@ -830,16 +789,147 @@ do -- will experiment first (both engines). Virtual fonts will change -- anyway. - backends.pdf.tables.vfspecials = allocate { -- todo: distinguish between glyph and rule color + local vfspecials = backends.pdf.tables.vfspecials or allocate { } + backends.pdf.tables.vfspecials = vfspecials - startcolor = startcolor, - -- stopcolor = { "pdf", "page", "0 g 0 G Q" }, - stopcolor = { "pdf", "text", "Q" }, + vfspecials.startcolor = startcolor + vfspecials.stopcolor = { "pdf", "text", "Q" } - startslant = startslant, - -- stopslant = { "pdf", "origin", "Q" }, - stopslant = { "pdf", "text", "Q" }, + vfspecials.startslant = startslant + vfspecials.stopslant = { "pdf", "text", "Q" } - } +end + +-- new method: + +do + + updaters.register("backend.update.lpdf",function() + pdfprint = lpdf.print + end) + + -- Is this still used? It's a font property now. + + local f_slant = formatters["q 1 0 %N 1 0 0 cm"] + + local slants = setmetatableindex(function(t,k) + local v = f_slant(a) + t[k] = v + return k + end) + + local function startslant(a) + return pdfprint("origin", slants[a]) + end + + local function stopslant() + pdfprint("text", "Q") + end + + -- We inherit the outer transparency. + + local pdfcolor = lpdf.color + local pdftransparency = lpdf.transparency + + -- local c_cache = setmetatableindex(function(t,m) + -- local v = setmetatableindex(function(t,c) + -- local p = "q " .. pdfcolor(m,c) + -- t[c] = p + -- return p + -- end) + -- t[m] = v + -- return v + -- end) + -- + -- local t_cache = setmetatableindex(function(t,transparency) + -- local p = pdftransparency(transparency) + -- local v = setmetatableindex(function(t,colormodel) + -- local v = setmetatableindex(function(t,color) + -- local v = "q " .. pdfcolor(colormodel,color) .. " " .. p + -- t[color] = v + -- return v + -- end) + -- t[colormodel] = v + -- return v + -- end) + -- t[transparency] = v + -- return v + -- end) + + -- local function startcolor(color) + -- local m, c = colortoattributes(color) + -- local t = transparencytoattribute(color) + -- if t then + -- pdfprint("page", t_cache[t][m][c]) + -- else + -- pdfprint("page", c_cache[m][c]) + -- end + -- end + + local function startcolor(color) + local m, c = colortoattributes(color) + local t = transparencytoattribute(color) + if t and t ~= unsetvalue then + pdfprint("page", "q " .. pdfcolor(m,c) .. " " .. pdftransparency(t)) + else + pdfprint("page", "q " .. pdfcolor(m,c)) + end + end + + local function stopcolor() + pdfprint("text", "Q") + end + + updaters.register("backend.update.lpdf",function() + fonts.vfcommands = { + startslant = startslant, + stopslant = stopslant, + startcolor = startcolor, + stopcolor = stopcolor, + } + end) end + +-- These generate the in-stream color commands: + +do + + local color = lpdf.color + local transparency = lpdf.transparency + + local fonts = { } + lpdf.fonts = fonts + + fonts.color_indirect = function(c,t) + if c and t then + return color(1,c) .. " " .. transparency(t) + elseif c then + return color(1,c) + elseif t then + return transparency(t) + else + return false + end + end + + local colors = attributes.colors + local rgbtocmyk = colors.rgbtocmyk + + local f_cmyk = formatters["%.3N %.3f %.3N %.3N k"] + local f_rgb = formatters["%.3N %.3f %.3N rg"] + local f_gray = formatters["%.3N g"] + + fonts.color_direct = function(r,g,b) + local m = colors.model + if r == g and g == b then + return f_gray(r) + elseif m == "cmyk" then + return f_cmyk(rgbtocmyk(r,g,b)) + else + return f_rgb(r,g,b) + end + end + +end + diff --git a/tex/context/base/mkxl/lpdf-emb.lmt b/tex/context/base/mkxl/lpdf-emb.lmt index 994ae2e07..53bbfe5da 100644 --- a/tex/context/base/mkxl/lpdf-emb.lmt +++ b/tex/context/base/mkxl/lpdf-emb.lmt @@ -78,6 +78,7 @@ local readstring = utilities.files.readstring local openfile = utilities.files.open local closefile = utilities.files.close +local getmapentry = fonts.mappings.getentry -- needs checking: signed vs unsigned @@ -307,63 +308,6 @@ end end --- Map file mess. - -local getmapentry do - - -- We only need to pick up the filename and optionally the enc file - -- as we only use them for old school virtual math fonts. We might as - -- we drop this completely. - - local find, match, splitlines = string.find, string.match, string.splitlines - - - local mappings = { } - - lpdf.loadmapline = function(n) - if trace_fonts then - report_fonts("mapline: %s",n) - end - local name, fullname, encfile, pfbfile = match(n,"(%S+)%s+(%S+).-<(.-%.enc).-<(.-%.pfb)") - if name then - mappings[name] = { fullname, encfile, pfbfile } - end - end - - lpdf.loadmapfile = function(n) - local okay, data = resolvers.loadbinfile(n,"map") - if okay and data then - data = splitlines(data) - for i=1,#data do - local d = data[i] - if d ~= "" and not find(d,"^[#%%]") then - loadmapline(d) - end - end - end - end - - getmapentry = function(n) - local n = file.nameonly(n) - local m = mappings[n] - if m then - local encfile = m[2] - local encoding = fonts.encodings.load(encfile) - if not encoding then - return - end - local pfbfile = resolvers.findfile(m[3],"pfb") - if not pfbfile or pfbfile == "" then - return - end - return encoding, pfbfile, encfile - end - end - - lpdf.getmapentry = getmapentry - -end - -- The three writers: opentype, truetype and type1. local mainwriters = { } @@ -2241,7 +2185,7 @@ end -- local done = false -- todo: --- updaters.register("backend.update.pdf",function() +-- updaters.register("backend.update",function() -- if not done then lpdf.registerdocumentfinalizer(lpdf.flushfonts,1,"wrapping up fonts") -- done = true diff --git a/tex/context/base/mkxl/lpdf-grp.lmt b/tex/context/base/mkxl/lpdf-grp.lmt index 3b45123e3..6adbe8c3c 100644 --- a/tex/context/base/mkxl/lpdf-grp.lmt +++ b/tex/context/base/mkxl/lpdf-grp.lmt @@ -298,3 +298,5 @@ end function lpdf.patternstream(n,width,height) return f_pattern("Pt" .. n,width*basepoints,height*basepoints) end + +backends.pdf.codeinjections.registerpattern = lpdf.registerpattern diff --git a/tex/context/base/mkxl/lpdf-img.lmt b/tex/context/base/mkxl/lpdf-img.lmt index 83d3dfae6..50034c360 100644 --- a/tex/context/base/mkxl/lpdf-img.lmt +++ b/tex/context/base/mkxl/lpdf-img.lmt @@ -1345,3 +1345,7 @@ end -- end -- return true -- end + +backends.pdf.codeinjections.jpg = lpdf.injectors.jpg +backends.pdf.codeinjections.jp2 = lpdf.injectors.jp2 +backends.pdf.codeinjections.png = lpdf.injectors.png diff --git a/tex/context/base/mkxl/lpdf-lmt.lmt b/tex/context/base/mkxl/lpdf-lmt.lmt index d1c5e59f7..c02a35fe1 100644 --- a/tex/context/base/mkxl/lpdf-lmt.lmt +++ b/tex/context/base/mkxl/lpdf-lmt.lmt @@ -176,7 +176,6 @@ local usedcharacters = setmetatableindex("table") local pdfcharacters local horizontalmode = true ------ widefontmode = true local scalefactor = 1 local threshold = 655360 local thresfactor = 100 @@ -193,7 +192,6 @@ local function updatefontstate(font) local designsize = fontparameters.designsize or size pdfcharacters = usedcharacters[font] horizontalmode = fontparameters.writingmode ~= "vertical" - -- widefontmode = fontproperties.encodingbytes == 2 scalefactor = (designsize/size) * tjfactor local fthreshold = fontproperties.threshold threshold = (fthreshold and (size * fthreshold / 100)) or 655360 @@ -945,6 +943,7 @@ local flushrule, flushsimplerule, flushspecialrule, flushimage, flushgroup do local f_b = formatters["%.6N w 0 %.6N %.6N %.6N re f"] local f_x = formatters["[] 0 d 0 J %.6N w %.6N %.6N %.6N %.6N re S"] + local f_y = formatters["[] 0 d 0 J %.6N w %.6N %.6N %.6N %.6N re S %.6N 0 m %.6N 0 l S"] -- Historically the index is an object which is kind of bad. @@ -1361,7 +1360,7 @@ local flushrule, flushsimplerule, flushspecialrule, flushimage, flushgroup do b = b + 1 ; buffer[b] = s_e end - flushspecialrule = function(pos_h,pos_v,pos_r,width,height,depth,line,outline) + flushspecialrule = function(pos_h,pos_v,pos_r,width,height,depth,line,outline,baseline) pdf_goto_pagemode() b = b + 1 ; buffer[b] = s_b @@ -1375,7 +1374,14 @@ local flushrule, flushsimplerule, flushspecialrule, flushimage, flushgroup do local rule if outline then - rule = f_x(line,half,-depth+half,width-line,total-line) + local d = -depth + half + local w = width - line + local t = total - line + if baseline then + rule = f_y(line,half,d,w,t,half,w) + else + rule = f_x(line,half,d,w,t) + end else rule = f_b(line,-depth,width,total) end @@ -2901,6 +2907,11 @@ do -- -- lpdf.registerdocumentfinalizer(wrapup,nil,"wrapping up") -- + statistics.register("result saved in file", function() + local outputfilename = environment.outputfilename or environment.jobname or tex.jobname or "<unset>" + return string.format("%s.%s, compresslevel %s, objectcompresslevel %s",outputfilename,"pdf",lpdf.getcompression()) + end) + -- end converter = drivers.converters.lmtx useddriver = driver diff --git a/tex/context/base/mkxl/lpdf-vfc.lmt b/tex/context/base/mkxl/lpdf-vfc.lmt index 65b863203..6030bb028 100644 --- a/tex/context/base/mkxl/lpdf-vfc.lmt +++ b/tex/context/base/mkxl/lpdf-vfc.lmt @@ -9,7 +9,9 @@ if not modules then modules = { } end modules ['lpdf-vfc'] = { local setmetatableindex = table.setmetatableindex local defaultline = 16384 -local vfspecials = backends.pdf.tables.vfspecials + +local vfspecials = backends.pdf.tables.vfspecials or utilities.storage.allocate { } +backends.pdf.tables.vfspecials = vfspecials vfspecials.backgrounds = setmetatableindex(function(t,h) local v = setmetatableindex(function(t,d) diff --git a/tex/context/base/mkxl/luat-cod.lmt b/tex/context/base/mkxl/luat-cod.lmt index 91aa6592e..8268c9f64 100644 --- a/tex/context/base/mkxl/luat-cod.lmt +++ b/tex/context/base/mkxl/luat-cod.lmt @@ -75,7 +75,7 @@ function lua.registercode(filename,options) end end if barename == filename then - filename = filename .. (opts.autosuffix and CONTEXTLMTXMODE > 0 and ".lmt" or ".lua") + filename = filename .. (opts.autosuffix and ".lmt" or ".lua") end local code = environment.luafilechunk(filename,false,opts.optimize) if code then diff --git a/tex/context/base/mkxl/meta-fnt.lmt b/tex/context/base/mkxl/meta-fnt.lmt new file mode 100644 index 000000000..627dbc746 --- /dev/null +++ b/tex/context/base/mkxl/meta-fnt.lmt @@ -0,0 +1,263 @@ +if not modules then modules = { } end modules ['meta-fnt'] = { + version = 1.001, + comment = "companion to meta-fnt.mkiv", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} + +local next = next +local concat = table.concat +local format = string.format +local formatters = string.formatters +local chardata = characters.data +local fontdata = fonts.hashes.identifiers + +local vffonts = fonts.handlers.vf + +local mpfonts = fonts.mp or { } +fonts.mp = mpfonts + +mpfonts.version = mpfonts.version or 1.20 +mpfonts.inline = true +mpfonts.cache = containers.define("fonts", "mp", mpfonts.version, true) + +metapost.fonts = metapost.fonts or { } + +local function unicodetoactualtext(...) + unicodetoactualtext = backends.codeinjections.unicodetoactualtext + return unicodetoactualtext(...) +end + +-- a few glocals + +local characters, descriptions = { }, { } +local factor, code, slot, width, height, depth, total, variants, bbox, llx, lly, urx, ury = 100, { }, 0, 0, 0, 0, 0, 0, true, 0, 0, 0, 0 + +local flusher = { + startfigure = function(_chr_,_llx_,_lly_,_urx_,_ury_) + code = { } + slot = _chr_ + llx = _llx_ + lly = _lly_ + urx = _urx_ + ury = _ury_ + width = urx - llx + height = ury + depth = -lly + total = total + 1 + inline = mpfonts.inline + end, + flushfigure = function(t) + for i=1,#t do + code[#code+1] = t[i] + end + end, + stopfigure = function() + local cd = chardata[n] + local code = unicodetoactualtext(slot,concat(code," ")) or "" + descriptions[slot] = { + -- unicode = slot, + name = cd and cd.adobename, + width = width * 100, + height = height * 100, + depth = depth * 100, + boundingbox = { llx, lly, urx, ury }, + } + if inline then + characters[slot] = { + commands = { + { "pdf", "origin", code }, + } + } + else + characters[slot] = { + commands = { + { + "image", + { + stream = code, + bbox = { 0, -depth * 65536, width * 65536, height * 65536 } + }, + }, + } + } + end + code = nil -- no need to keep that + end +} + +local function process(mpxformat,name,instances,scalefactor) + local filename = resolvers.findfile(name) + local attributes = filename and lfs.isfile(filename) and lfs.attributes(filename) + if attributes then + statistics.starttiming(metapost.fonts) + scalefactor = scalefactor or 1 + instances = instances or metapost.fonts.instances or 1 -- maybe store in liost too + local fontname = file.removesuffix(file.basename(name)) + local modification = attributes.modification + local filesize = attributes.size + local hash = file.robustname(formatters["%s %05i %03i"](fontname,scalefactor*1000,instances)) + local lists = containers.read(mpfonts.cache,hash) + if not lists or lists.modification ~= modification or lists.filesize ~= filesize or lists.instances ~= instances or lists.scalefactor ~= scalefactor then + statistics.starttiming(flusher) + local data = io.loaddata(filename) + metapost.reset(mpxformat) + metapost.setoutercolor(2) -- no outer color and no reset either + lists = { } + for i=1,instances do + characters = { } + descriptions = { } + metapost.process { + mpx = mpxformat, + flusher = flusher, + askedfig = "all", + -- incontext = false, + data = { + formatters["randomseed := %s ;"](i*10), + formatters["charscale := %s ;"](scalefactor), + data, + }, + } + lists[i] = { + characters = characters, + descriptions = descriptions, + parameters = { + designsize = 655360, + slant = 0, + space = 333 * scalefactor, + space_stretch = 166.5 * scalefactor, + space_shrink = 111 * scalefactor, + x_height = 431 * scalefactor, + quad = 1000 * scalefactor, + extra_space = 0, + }, + properties = { + name = formatters["%s-%03i"](hash,i), + spacer = "space", + } + } + end + lists.version = metapost.variables.fontversion or "1.000" + lists.modification = modification + lists.filesize = filesize + lists.instances = instances + lists.scalefactor = scalefactor + metapost.reset(mpxformat) -- saves memory + lists = containers.write(mpfonts.cache, hash, lists) + statistics.stoptiming(flusher) + end + variants = variants + #lists + statistics.stoptiming(metapost.fonts) + return lists + else + return { } + end +end + +metapost.fonts.flusher = flusher +metapost.fonts.instances = 1 +metapost.fonts.process = process + +local function build(g,v) + local size = g.specification.size + local data = process(v[2],v[3],v[4],size/655360,v[6]) + local list = { } + local t = { } + for d=1,#data do + t = fonts.constructors.scale(data[d],-1000) + -- local id = font.nextid() + -- t.fonts = { { id = id } } + fontdata[id] = t + if v[5] then + vffonts.helpers.composecharacters(t) + end + list[d] = font.define(t) + end + for k, v in next, t do -- last t + g[k] = v -- kind of replace, when not present, make nil + end + g.variants = list +end + +vffonts.combiner.commands.metapost = build +vffonts.combiner.commands.metafont = build + +statistics.register("metapost font generation", function() + if total > 0 then + local time = statistics.elapsedtime(flusher) + if total > 0 then + return format("%i glyphs, %s seconds runtime, %.1f glyphs/second", total, time, total/tonumber(time)) + else + return format("%i glyphs, %s seconds runtime", total, time) + end + end +end) + +statistics.register("metapost font loading",function() + if variants > 0 then + local time = statistics.elapsedtime(metapost.fonts) + if variants > 0 then + return format("%s seconds, %i instances, %.3f instances/second", time, variants, variants/tonumber(time)) + else + return format("%s seconds, %i instances", time, variants) + end + end +end) + +-- fonts.definers.methods.install( "bidi", { +-- { +-- "metapost", -- method +-- "metafun", -- format +-- "fontoeps.mp", -- filename +-- 1, -- instances +-- false, -- compose +-- }, +-- } ) + +local report = logs.reporter("metapost","fonts") + +function metapost.fonts.define(specification) + local fontname = specification.fontname or "" + local filename = specification.filename or "" + local format = specification.format or "metafun" + if fontname == "" then + report("no fontname given") + return + end + if filename == "" then + report("no filename given for %a",fontname) + return + end + local fullname = resolvers.findfile(filename) + if fullname == "" then + report("unable to locate file %a",filename) + return + end + report("generating font %a using format %a and file %a",fontname,format,filename) + fonts.definers.methods.install(fontname, { + { + specification.engine or "metapost", + format, + filename, + specification.instances or 1, + specification.compose or false, + }, + } ) +end + +interfaces.implement { + name = "definemetafont", + actions = metapost.fonts.define, + arguments = { + { + { "fontname" }, + { "filename" }, + } + } +} + +-- metapost.fonts.define { +-- fontname = "bidi", +-- filename = "bidi-symbols.mp", +-- } diff --git a/tex/context/base/mkxl/meta-fnt.mkxl b/tex/context/base/mkxl/meta-fnt.mkxl index 690571c79..9efee0ee5 100644 --- a/tex/context/base/mkxl/meta-fnt.mkxl +++ b/tex/context/base/mkxl/meta-fnt.mkxl @@ -13,7 +13,7 @@ \writestatus{loading}{MetaPost Graphics / Fonts} -\registerctxluafile{meta-fnt}{} +\registerctxluafile{meta-fnt}{autosuffix} \unprotect diff --git a/tex/context/base/mkxl/node-acc.lmt b/tex/context/base/mkxl/node-acc.lmt new file mode 100644 index 000000000..2c3302a3e --- /dev/null +++ b/tex/context/base/mkxl/node-acc.lmt @@ -0,0 +1,112 @@ +if not modules then modules = { } end modules ['node-acc'] = { + version = 1.001, + comment = "companion to node-ini.mkiv", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} + +-- see mkiv lua module for some experimental unused code + +local nodes, node = nodes, node + +local tasks = nodes.tasks + +local nuts = nodes.nuts +local tonut = nodes.tonut +local tonode = nodes.tonode + +local getid = nuts.getid +local getsubtype = nuts.getsubtype +local getattr = nuts.getattr +local getlist = nuts.getlist +local getchar = nuts.getchar +local getnext = nuts.getnext + +local setattr = nuts.setattr +local setlink = nuts.setlink +local setchar = nuts.setchar +local setsubtype = nuts.setsubtype +local getwidth = nuts.getwidth +local setwidth = nuts.setwidth + +local nextglyph = nuts.traversers.glyph +local nextnode = nuts.traversers.node + +local copy_node = nuts.copy +local insert_after = nuts.insert_after + +local nodecodes = nodes.nodecodes +local gluecodes = nodes.gluecodes + +local glue_code = nodecodes.glue +local glyph_code = nodecodes.glyph +local hlist_code = nodecodes.hlist +local vlist_code = nodecodes.vlist + +local userskip_code = gluecodes.user +local spaceskip_code = gluecodes.spaceskip +local xspaceskip_code = gluecodes.xspaceskip + +local a_characters = attributes.private("characters") + +local nofreplaced = 0 + +-- todo: nbsp etc +-- todo: collapse kerns (not needed, backend does this) +-- todo: maybe cache as we now create many nodes +-- todo: check for subtype related to spacing (13/14 but most seems to be user anyway) + +local trace = false trackers.register("backend.spaces", function(v) trace = v end) +local slot = nil + +local function injectspaces(head) + -- This can become two fast loops or we just move this to the backend where we can + -- also check for spaces (it actually is rather old code that relates to tagging + -- and so, which was implemented rather early in the mkiv saga). + local p, p_id + local n = head + while n do + local id = getid(n) + if id == glue_code then + if p and getid(p) == glyph_code then + local s = getsubtype(n) + if s == spaceskip_code or s == xspaceskip_code then + local g = copy_node(p) + local a = getattr(n,a_characters) + setchar(g,slot) + setlink(p,g,n) + setwidth(n,getwidth(n) - getwidth(g)) + if a then + setattr(g,a_characters,a) + end + setattr(n,a_characters,0) + nofreplaced = nofreplaced + 1 + end + end + elseif id == hlist_code or id == vlist_code then + injectspaces(getlist(n),slot) + end + p_id = id + p = n + n = getnext(n) + end + return head +end + +nodes.handlers.accessibility = function(head) + if trace then + if not slot then + slot = fonts.helpers.privateslot("visualspace") + end + else + slot = 32 + end + return injectspaces(head,slot) +end + +statistics.register("inserted spaces in output",function() + if nofreplaced > 0 then + return nofreplaced + end +end) diff --git a/tex/context/base/mkxl/node-ini.mkxl b/tex/context/base/mkxl/node-ini.mkxl index 84f46e546..abdff7b5b 100644 --- a/tex/context/base/mkxl/node-ini.mkxl +++ b/tex/context/base/mkxl/node-ini.mkxl @@ -19,7 +19,7 @@ \registerctxluafile{node-cmp}{autosuffix} \registerctxluafile{node-ini}{autosuffix} -\registerctxluafile{node-met}{} +\registerctxluafile{node-met}{autosuffix} \registerctxluafile{node-nut}{autosuffix} \registerctxluafile{node-res}{autosuffix} %registerctxluafile{node-ppt}{} % experimental, not used so probably useless @@ -34,7 +34,7 @@ \registerctxluafile{node-pro}{} \registerctxluafile{node-ser}{autosuffix} \registerctxluafile{node-ext}{} -\registerctxluafile{node-acc}{} % experimental +\registerctxluafile{node-acc}{autosuffix} % experimental %registerctxluafile{node-prp}{} % makes no sense (yet) \registerctxluafile{node-scn}{autosuffix} \registerctxluafile{node-syn}{} diff --git a/tex/context/base/mkxl/node-met.lmt b/tex/context/base/mkxl/node-met.lmt new file mode 100644 index 000000000..a3f02d709 --- /dev/null +++ b/tex/context/base/mkxl/node-met.lmt @@ -0,0 +1,633 @@ +if not modules then modules = { } end modules ['node-MET'] = { + version = 1.001, + comment = "companion to node-ini.mkiv", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} + +-- This is an experimental module. Don't use nuts for generic code, at least not till +-- the regular code is proven stable. No support otherwise. + +-- luatex: todo: copylist should return h, t +-- todo: see if using insert_before and insert_after makes sense here + +-- This file is a side effect of the \LUATEX\ speed optimization project of Luigi +-- Scarso and me. As \CONTEXT\ spends over half its time in \LUA, we though that +-- using \LUAJIT\ could improve performance. We've published some of our experiences +-- elsewhere, but to summarize: \LUAJITTEX\ benefits a lot from the faster virtual +-- machine, but when jit is turned of we loose some again. We experimented with +-- ffi (without messing up the \CONTEXT\ code too much) but there we also lost more +-- than we gained (mostly due to lack of compatible userdata support: it's all or +-- nothing). This made us decide to look into the \LUA||\TEX\ interfacing and by +-- profiling and careful looking at the (of course then still beta source code) we +-- could come up with some improvements. The first showed up in 0.75 and we've more +-- on the agenda for 0.80. Although some interfaces could be sped up significantly +-- in practice we're only talking of 5||10\% on a \CONTEXT\ run and maybe more when +-- complex and extensive node list manipulations happens (we're talking of hundreds +-- of millions cross boundary calls then for documents of hundreds pages). One of the +-- changes in the \CONTEXT\ code base is that we went from indexed access to nodes to +-- function calls (in principle faster weren't it that the accessors need to do more +-- checking which makes them slower) and from there to optimizing these calls as well +-- as providing fast variants for well defined situations. At first optimizations were +-- put in a separate \type {node.fast} table although some improvements could be +-- ported to the main node functions. Because we got the feeling that more gain was +-- possible (especially when using more complex fonts and \MKIV\ functionality) we +-- eventually abandoned this approach and dropped the \type {fast} table in favour of +-- another hack. In the process we had done lots of profiling and testing so we knew +-- where time was wasted, +-- +-- As lots of testing and experimenting was part of this project, I could not have +-- done without stacks of new \CD s and \DVD s. This time Porcupine Tree, No-Man +-- and Archive were came to rescue. +-- +-- It all started with testing performance of: +-- +-- node.getfield = metatable.__index +-- node.setfield = metatable.__newindex + +local type, select = type, select +local setmetatableindex = table.setmetatableindex + +-- First we get the metatable of a node: + +local metatable = nil + +do + local glyph = node.new("glyph",0) + metatable = getmetatable(glyph) + node.free(glyph) +end + +-- statistics.tracefunction(node, "node", "getfield","setfield") +-- statistics.tracefunction(node.direct,"node.direct","getfield","setfield") + +-- We start with some helpers and provide all relevant basic functions in the +-- node namespace as well. + +nodes = nodes or { } +local nodes = nodes + +local nodecodes = nodes.nodecodes + +nodes.tostring = node.tostring or tostring +nodes.copy = node.copy +nodes.copy_node = node.copy +nodes.copy_list = node.copy_list +nodes.delete = node.delete +nodes.dimensions = node.dimensions +nodes.rangedimensions = node.rangedimensions +nodes.end_of_math = node.end_of_math +nodes.flush = node.flush_node +nodes.flush_node = node.flush_node +nodes.flush_list = node.flush_list +nodes.free = node.free +nodes.insert_after = node.insert_after +nodes.insert_before = node.insert_before +nodes.hpack = node.hpack +nodes.new = node.new +nodes.tail = node.tail +nodes.traverse = node.traverse +nodes.traverse_id = node.traverse_id +nodes.traverse_char = node.traverse_char +nodes.traverse_glyph = node.traverse_glyph +nodes.traverse_list = node.traverse_list +nodes.slide = node.slide +nodes.vpack = node.vpack +nodes.fields = node.fields +nodes.is_node = node.is_node +nodes.setglue = node.setglue +nodes.uses_font = node.uses_font + +nodes.first_glyph = node.first_glyph +nodes.has_glyph = node.has_glyph or node.first_glyph + +nodes.current_attributes = node.current_attributes or node.current_attr +nodes.has_field = node.has_field +nodes.last_node = node.last_node +nodes.usedlist = node.usedlist +nodes.protrusion_skippable = node.protrusion_skippable +nodes.check_discretionaries = node.check_discretionaries +nodes.write = node.write +nodes.flatten_discretionaries = node.flatten_discretionaries + +nodes.count = node.count +nodes.length = node.length + +nodes.has_attribute = node.has_attribute +nodes.set_attribute = node.set_attribute +nodes.find_attribute = node.find_attribute +nodes.unset_attribute = node.unset_attribute + +nodes.protect_glyph = node.protect_glyph +nodes.protect_glyphs = node.protect_glyphs +nodes.unprotect_glyph = node.unprotect_glyph +nodes.unprotect_glyphs = node.unprotect_glyphs +nodes.kerning = node.kerning +nodes.ligaturing = node.ligaturing +nodes.hyphenating = node.hyphenating +nodes.mlist_to_hlist = node.mlist_to_hlist + +nodes.effective_glue = node.effective_glue +nodes.getglue = node.getglue +nodes.setglue = node.setglue +nodes.is_zero_glue = node.is_zero_glue + +nodes.tonode = function(n) return n end +nodes.tonut = function(n) return n end + +-- These are never used in \CONTEXT, only as a gimmick in node operators +-- so we keep them around. +-- +-- Fro nwo I keep them in \LMTX\ but they will go away! + +local n_getfield = node.getfield +local n_getattr = node.get_attribute + +local n_setfield = node.setfield +local n_setattr = n_setfield + +nodes.getfield = n_getfield +nodes.setfield = n_setfield +nodes.getattr = n_getattr +nodes.setattr = n_setattr +nodes.takeattr = nodes.unset_attribute + +local function n_getid (n) return n_getfield(n,"id") end +local function n_getsubtype(n) return n_getfield(n,"subtype") end + +nodes.getid = n_getid +nodes.getsubtype = n_getsubtype + +local function n_getchar(n) return n_getfield(n,"char") end +local function n_setchar(n,c) return n_setfield(n,"char",c) end +local function n_getfont(n) return n_getfield(n,"font") end +local function n_setfont(n,f) return n_setfield(n,"font",f) end + +nodes.getchar = n_getchar +nodes.setchar = n_setchar +nodes.getfont = n_getfont +nodes.setfont = n_setfont + +local function n_getlist (n) return n_getfield(n,"list") end +local function n_setlist (n,l) return n_setfield(n,"list",l) end +local function n_getleader(n) return n_getfield(n,"leader") end +local function n_setleader(n,l) return n_setfield(n,"leader",l) end + +nodes.getlist = n_getlist +nodes.setlist = n_setlist +nodes.getleader = n_getleader +nodes.setleader = n_setleader + +local function n_getnext(n) return n_getfield(n,"next") end +local function n_setnext(n,nn) return n_setfield(n,"next",nn) end +local function n_getprev(n) return n_getfield(n,"prev") end +local function n_setprev(n,pp) return n_setfield(n,"prev",pp) end +local function n_getboth(n) return n_getfield(n,"prev"), n_getfield(n,"next") end +local function n_setboth(n,pp,nn) return n_setfield(n,"prev",pp), n_setfield(n,"next",nn) end + +nodes.getnext = n_getnext +nodes.setnext = n_setnext +nodes.getprev = n_getprev +nodes.setprev = n_setprev +nodes.getboth = n_getboth +nodes.setboth = n_setboth + +local function n_setlink(...) + -- not that fast but not used often anyway + local h = nil + for i=1,select("#",...) do + local n = select(i,...) + if not n then + -- go on + elseif h then + n_setfield(h,"next",n) + n_setfield(n,"prev",h) + else + h = n + end + end + return h +end + +nodes.setlink = n_setlink + +nodes.getbox = node.getbox or tex.getbox +nodes.setbox = node.setbox or tex.setbox + +local n_flush_node = nodes.flush +local n_copy_node = nodes.copy +local n_copy_list = nodes.copy_list +local n_find_tail = nodes.tail +local n_insert_after = nodes.insert_after +local n_insert_before = nodes.insert_before +local n_slide = nodes.slide + +local n_remove_node = node.remove -- not yet nodes.remove + +local function remove(head,current,free_too) + local t = current + head, current = n_remove_node(head,current) + if not t then + -- forget about it + elseif free_too then + n_flush_node(t) + t = nil + else + n_setboth(t) + end + return head, current, t +end + +nodes.remove = remove + +function nodes.delete(head,current) + return remove(head,current,true) +end + +-- local h, c = nodes.replace(head,current,new) +-- local c = nodes.replace(false,current,new) +-- local c = nodes.replace(current,new) +-- +-- todo: check for new.next and find tail + +function nodes.replace(head,current,new) -- no head returned if false + if not new then + head, current, new = false, head, current +-- current, new = head, current + end + local prev = n_getprev(current) + local next = n_getnext(current) + if next then + n_setlink(new,next) + end + if prev then + n_setlink(prev,new) + end + if head then + if head == current then + head = new + end + n_flush_node(current) + return head, new + else + n_flush_node(current) + return new + end +end + +-- nodes.countall : see node-nut.lua + +function nodes.append(head,current,...) + for i=1,select("#",...) do + head, current = n_insert_after(head,current,(select(i,...))) + end + return head, current +end + +function nodes.prepend(head,current,...) + for i=1,select("#",...) do + head, current = n_insert_before(head,current,(select(i,...))) + end + return head, current +end + +function nodes.linked(...) + local head, last + for i=1,select("#",...) do + local next = select(i,...) + if next then + if head then + n_setlink(last,next) + else + head = next + end + last = n_find_tail(next) -- we could skip the last one + end + end + return head +end + +function nodes.concat(list) -- consider tail instead of slide + local head, tail + for i=1,#list do + local li = list[i] + if li then + if head then + n_setlink(tail,li) + else + head = li + end + tail = n_slide(li) + end + end + return head, tail +end + +function nodes.reference(n) + return n and tonut(n) or "<none>" +end + +-- Here starts an experiment with metatables. Of course this only works with nodes +-- wrapped in userdata with a metatable. +-- +-- Nodes are kind of special in the sense that you need to keep an eye on creation +-- and destruction. This is quite natural if you consider that changing the content +-- of a node would also change any copy (or alias). As there are too many pitfalls +-- we don't have this kind of support built in \LUATEX, which means that macro +-- packages are free to provide their own. One can even use local variants. +-- +-- n1 .. n2 : append nodes, no copies +-- n1 * 5 : append 4 copies of nodes +-- 5 + n1 : strip first 5 nodes +-- n1 - 5 : strip last 5 nodes +-- n1 + n2 : inject n2 after first of n1 +-- n1 - n2 : inject n2 before last of n1 +-- n1^2 : two copies of nodes (keep orginal) +-- - n1 : reverse nodes +-- n1/f : apply function to nodes + +-- local s = nodes.typesetters.tonodes +-- +-- local function w(lst) +-- context.dontleavehmode() +-- context(lst) +-- context.par() +-- end +-- +-- local n1 = s("a") +-- local n2 = s("b") +-- local n3 = s("c") +-- local n4 = s("d") +-- local n5 = s("e") +-- local n6 = s("f") +-- local n7 = s("g") +-- +-- local n0 = n1 .. (n2 * 10).. n3 .. (5 * n4) .. n5 .. ( 5 * n6 ) .. n7 / function(n) n.char = string.byte("!") return n end +-- +-- w(#n0) +-- +-- w(n0) +-- +-- local n1 = s("a") * 10 +-- local n2 = s("b") * 10 +-- +-- local n0 = ((5 + n1) .. (n2 - 5) ) +-- local n0 = - n0 +-- +-- local n0 = nil .. n0^3 .. nil +-- +-- w(n0) +-- +-- w ( s("a") + s("b") ) w ( s("a") + 4*s("b") ) w ( 4*s("a") + s("b") ) w ( 4*s("a") + 4*s("b") ) +-- w ( s("a") - s("b") ) w ( s("a") - 4*s("b") ) w ( 4*s("a") - s("b") ) w ( 4*s("a") - 4*s("b") ) + +local n_remove_node = nodes.remove + +metatable.__concat = function(n1,n2) -- todo: accept nut on one end + if not n1 then + return n2 + elseif not n2 then + return n1 + elseif n1 == n2 then + -- or abort + return n2 -- or n2 * 2 + else + local tail = n_find_tail(n1) + n_setlink(tail,n2) + return n1 + end +end + +metatable.__mul = function(n,multiplier) + if type(multiplier) ~= "number" then + n, multiplier = multiplier, n + end + if multiplier <= 1 then + return n + elseif n_getnext(n) then + local head + for i=2,multiplier do + local h = n_copy_list(n) + if head then + local t = n_find_tail(h) + n_setlink(t,head) + end + head = h + end + local t = n_find_tail(n) + n_setlink(t,head) + else + local head + for i=2,multiplier do + local c = n_copy_node(n) + if head then + n_setlink(c,head) + end + head = c + end + n_setlink(n,head) + end + return n +end + +metatable.__sub = function(first,second) + if type(second) == "number" then + local tail = n_find_tail(first) + for i=1,second do + local prev = n_getprev(tail) + n_flush_node(tail) -- can become flushlist/flushnode + if prev then + tail = prev + else + return nil + end + end + if tail then + n_setnext(tail) + return first + else + return nil + end + else + -- aaaaa - bbb => aaaabbba + local firsttail = n_find_tail(first) + local prev = n_getprev(firsttail) + if prev then + local secondtail = n_find_tail(second) + n_setlink(secondtail,firsttail) + n_setlink(prev,second) + return first + else + local secondtail = n_find_tail(second) + n_setlink(secondtail,first) + return second + end + end +end + +metatable.__add = function(first,second) + if type(first) == "number" then + local head = second + for i=1,first do + local second = n_getnext(head) + n_flush_node(head) -- can become flushlist/flushnode + if second then + head = second + else + return nil + end + end + if head then + n_setprev(head) + return head + else + return nil + end + else + -- aaaaa + bbb => abbbaaaa + local next = n_getnext(first) + if next then + local secondtail = n_find_tail(second) + n_setlink(first,second) + n_setlink(secondtail,next) + else + n_setlink(first,second) + end + return first + end +end + +metatable.__len = function(current) + local length = 0 + while current do + current = n_getnext(current) + length = length + 1 + end + return length +end + +metatable.__div = function(list,action) + return action(list) or list -- always a value +end + +metatable.__pow = function(n,multiplier) + local tail = n + local head = nil + if n_getnext(n) then + if multiplier == 1 then + head = n_copy_list(n) + else + for i=1,multiplier do + local h = n_copy_list(n) + if head then + local t = n_find_tail(h) + n_setlink(t,head) + end + head = h + end + end + else + if multiplier == 1 then + head = n_copy_node(n) + else + for i=2,multiplier do + local c = n_copy_node(n) + if head then + n_setlink(head,c) + end + head = c + end + end + end + -- todo: tracing + return head +end + +metatable.__unm = function(head) + local last = head + local first = head + local current = n_getnext(head) + while current do + local next = n_getnext(current) + n_setlink(current,first) + first = current + current = next + end + n_setprev(first) + n_setnext(last) + return first +end + +-- see node-nut.lua for more info on going nuts + +-- if not gonuts then +-- +-- local nuts = { } +-- nodes.nuts = nuts +-- +-- local function dummy(f) return f end +-- +-- nodes.vianuts = dummy +-- nodes.vianodes = dummy +-- +-- for k, v in next, nodes do +-- if type(v) == "function" then +-- nuts[k] = v +-- end +-- end +-- +-- end + +-- also handy + +local tonode = nodes.tonode +local whatsit_code = nodecodes.whatsit +local getfields = node.fields +local sort = table.sort +local whatsitkeys = { } +local keys = { whatsit = whatsitkeys } +local messyhack = table.tohash { -- temporary solution + nodecodes.attributelist, + nodecodes.attribute, + nodecodes.action, -- hm +} + +setmetatableindex(keys,function(t,k) + local v = (k == "attributelist" or k == nodecodes.attributelist) and { } or getfields(k) + if messyhack[k] then + for i=1,#v do + if v[i] == "subtype" then + remove(v,i) + break + end + end + end + if v[ 0] then v[#v+1] = "next" v[ 0] = nil end + if v[-1] then v[#v+1] = "prev" v[-1] = nil end + sort(v) + t[k] = v + return v +end) + +setmetatableindex(whatsitkeys,function(t,k) + local v = getfields(whatsit_code,k) + if v[ 0] then v[#v+1] = "next" v[ 0] = nil end + if v[-1] then v[#v+1] = "prev" v[-1] = nil end + sort(v) + t[k] = v + return v +end) + +local function nodefields(n) + n = tonode(n) + local id = n.id + if id == whatsit_code then + return whatsitkeys[n.subtype] + else + return keys[id] + end +end + +nodes.keys = keys -- [id][subtype] +nodes.fields = nodefields -- (n) diff --git a/tex/context/base/mkxl/node-ref.lmt b/tex/context/base/mkxl/node-ref.lmt index 06c9981a9..c02a37dd1 100644 --- a/tex/context/base/mkxl/node-ref.lmt +++ b/tex/context/base/mkxl/node-ref.lmt @@ -55,7 +55,6 @@ local nodepool = nuts.pool local tonode = nuts.tonode local tonut = nuts.tonut -local getfield = nuts.getfield local setlink = nuts.setlink local setnext = nuts.setnext local setprev = nuts.setprev diff --git a/tex/context/base/mkxl/node-res.lmt b/tex/context/base/mkxl/node-res.lmt index c57e5cfd0..7a37b1b9e 100644 --- a/tex/context/base/mkxl/node-res.lmt +++ b/tex/context/base/mkxl/node-res.lmt @@ -578,28 +578,6 @@ end) lua.registerfinalizer(cleanup, "cleanup reserved nodes") --- experiment - -do - - local glyph = tonode(glyph) - local traverse_id = nodes.traverse_id - - local traversers = table.setmetatableindex(function(t,k) - local v = traverse_id(type(k) == "number" and k or nodecodes[k],glyph) - t[k] = v - return v - end) - - traversers.node = nodes.traverse (glyph) - traversers.char = nodes.traverse_char (glyph) - if nuts.traverse_glyph then traversers.glyph = nodes.traverse_glyph (glyph) end - if nuts.traverse_list then traversers.list = nodes.traverse_list (glyph) end - - nodes.traversers = traversers - -end - do local glyph = glyph @@ -611,11 +589,13 @@ do return v end) - traversers.node = nuts.traverse (glyph) - traversers.char = nuts.traverse_char (glyph) - if nuts.traverse_glyph then traversers.glyph = nuts.traverse_glyph (glyph) end - if nuts.traverse_list then traversers.list = nuts.traverse_list (glyph) end - if nuts.traverse_content then traversers.content = nuts.traverse_content(glyph) end + -- these are special: + + traversers.node = nuts.traverse (glyph) + traversers.char = nuts.traverse_char (glyph) + traversers.glyph = nuts.traverse_glyph (glyph) + traversers.list = nuts.traverse_list (glyph) + traversers.content = nuts.traverse_content(glyph) nuts.traversers = traversers diff --git a/tex/context/base/mkxl/node-rul.lmt b/tex/context/base/mkxl/node-rul.lmt index 99c5d98a4..a95c5272d 100644 --- a/tex/context/base/mkxl/node-rul.lmt +++ b/tex/context/base/mkxl/node-rul.lmt @@ -51,9 +51,6 @@ local getwidth = nuts.getwidth local setwidth = nuts.setwidth local setoffsets = nuts.setoffsets local setfield = nuts.setfield - ------ getfield = nuts.getfield ------ getdata = nuts.getdata local getruledata = nuts.getruledata local isglyph = nuts.isglyph diff --git a/tex/context/base/mkxl/node-ser.lmt b/tex/context/base/mkxl/node-ser.lmt index 0d7f3ccad..8fcbb31e4 100644 --- a/tex/context/base/mkxl/node-ser.lmt +++ b/tex/context/base/mkxl/node-ser.lmt @@ -18,7 +18,6 @@ local node = node local getfields = node.fields -local traverse = nodes.traverse local is_node = nodes.is_node local nodecodes = nodes.nodecodes diff --git a/tex/context/base/mkxl/node-tra.lmt b/tex/context/base/mkxl/node-tra.lmt index 7b401361f..f985652c5 100644 --- a/tex/context/base/mkxl/node-tra.lmt +++ b/tex/context/base/mkxl/node-tra.lmt @@ -181,18 +181,8 @@ end nodes.tosequence = tosequence nuts .tosequence = tosequence -if CONTEXTLMTXMODE > 0 then - - function nodes.report(t) - report_nodes("output %a, %s nodes",tex.getoutputactive(),count_nodes(t)) - end - -else - - function nodes.report(t) - report_nodes("output %a, %s nodes",status.output_active,count_nodes(t)) - end - +function nodes.report(t) + report_nodes("output %a, %s nodes",tex.getoutputactive(),count_nodes(t)) end function nodes.packlist(head) diff --git a/tex/context/base/mkxl/strc-tag.lmt b/tex/context/base/mkxl/strc-tag.lmt index 11049abe0..96510f24e 100644 --- a/tex/context/base/mkxl/strc-tag.lmt +++ b/tex/context/base/mkxl/strc-tag.lmt @@ -263,11 +263,11 @@ function tags.locatedtag(tag) return false -- handy as bogus index end -function structures.atlocation(str) +function structures.atlocation(str) -- not used local specification = taglist[texgetattribute(a_tagged)] if specification then + local list = specification.taglist if list then - local taglist = specification.taglist local pattern = patterns[str] for i=#list,1,-1 do if find(list[i],pattern) then diff --git a/tex/context/base/mkxl/supp-box.lmt b/tex/context/base/mkxl/supp-box.lmt index 53ad3ae31..b4b0d44cc 100644 --- a/tex/context/base/mkxl/supp-box.lmt +++ b/tex/context/base/mkxl/supp-box.lmt @@ -39,7 +39,6 @@ local nuts = nodes.nuts local tonut = nuts.tonut local tonode = nuts.tonode ------ getfield = nuts.getfield local getnext = nuts.getnext local getprev = nuts.getprev local getboth = nuts.getboth @@ -56,7 +55,6 @@ local getdepth = nuts.getdepth local getwhd = nuts.getwhd local takebox = nuts.takebox ------ setfield = nuts.setfield local setlink = nuts.setlink local setboth = nuts.setboth local setnext = nuts.setnext @@ -153,19 +151,6 @@ implement { end } --- local function hyphenatedhack(head,pre) --- pre = tonut(pre) --- for n in nextdisc, tonut(head) do --- local hyphen = getfield(n,"pre") --- if hyphen then --- flush_list(hyphen) --- end --- setfield(n,"pre",copy_list(pre)) --- end --- end --- --- commands.hyphenatedhack = hyphenatedhack - local function checkedlist(list) if type(list) == "number" then return getlist(getbox(tonut(list))) diff --git a/tex/context/base/mkxl/syst-aux.mkxl b/tex/context/base/mkxl/syst-aux.mkxl index 7a60e63bb..7b1b889c3 100644 --- a/tex/context/base/mkxl/syst-aux.mkxl +++ b/tex/context/base/mkxl/syst-aux.mkxl @@ -5216,8 +5216,8 @@ %D \stripspaces\from\one\to\two %D \stoptyping %D -%D Both the old string \type{\one} and the new one \type{\two} -%D are expanded. This command is a special case of: +%D Both the old string \type {\one} and the new one \type {\two} are expanded. This +%D command is a special case of: %D %D \starttyping %D \stripcharacter\char\from\one\to\two @@ -5745,9 +5745,8 @@ %D \macros %D {twodigitrounding} %D -%D When using \type {\special}s or \type {\pdfliteral}s, it sometimes makes sense to -%D limit the precission. The next macro rounds a real number to two digits. It takes -%D one argument and only works in \ETEX. +%D The next macro rounds a real number to two digits. They are probably no longer needed +%D but we keep them around for a while. \permanent\def\integerrounding #1{\clf_rounded\zerocount\numexpr#1\relax} \permanent\def\onedigitrounding #1{\clf_rounded\plusone \numexpr#1\relax} diff --git a/tex/context/base/mkxl/toks-scn.lmt b/tex/context/base/mkxl/toks-scn.lmt index 93e0af09a..73eedbba7 100644 --- a/tex/context/base/mkxl/toks-scn.lmt +++ b/tex/context/base/mkxl/toks-scn.lmt @@ -22,7 +22,7 @@ local tokenbits = tokens.bits local scanstring = scanners.string local scanargument = scanners.argument -local scandelimited = scanners.delimited -- lmtx +local scandelimited = scanners.delimited local scanverbatim = scanners.verbatim local scantokenlist = scanners.tokenlist local scantoks = scanners.toks @@ -176,8 +176,6 @@ function scanners.whd() end end --- begin lmtx - local l = utf.byte("[") local r = utf.byte("]") @@ -209,8 +207,6 @@ scanners.optional = scanoptional scanners.bracketedasis = scanbracketedasis scanners.argumentasis = scanargumentasis --- end lmtx - local shortcuts = { tokens = tokens, bits = tokenbits, @@ -247,21 +243,19 @@ local shortcuts = { scanclose = scanclose, scanlist = scanlist, scancsname = scancsname, - todimen = todimen, - tonumber = tonumber, - tostring = tostring, - toboolean = toboolean, - inspect = inspect, - report = report_scan, - -- lmtx scandelimited = scandelimited, -- not directly useable scanbracketed = scanbracketed, scanoptional = scanoptional, scanbracketedasis = scanbracketedasis, scanargumentasis = scanargumentasis, - -- scanintegerargument = scanintegerargument, scandimenargument = scandimenargument, + todimen = todimen, + tonumber = tonumber, + tostring = tostring, + toboolean = toboolean, + inspect = inspect, + report = report_scan, } tokens.shortcuts = shortcuts @@ -299,10 +293,8 @@ local f_elseif = formatters[" elseif scankeywordcs('%s') then data['%s'] = sc ----- f_if_x = formatters[ " if not data['%s'] and scankeywordcs('%s') then data['%s'] = scan%s()"] ----- f_elseif_x = formatters[" elseif not data['%s'] and scankeywordcs('%s') then data['%s'] = scan%s()"] --- if CONTEXTLMTXMODE > 0 then --- f_if = formatters[" local key = scanletters() if key == '' then break elseif key == '%s' then data['%s'] = scan%s()"] --- f_elseif = formatters[" elseif key == '%s' then data['%s'] = scan%s()"] --- end +----- f_if = formatters[" local key = scanletters() if key == '' then break elseif key == '%s' then data['%s'] = scan%s()"] +----- f_elseif = formatters[" elseif key == '%s' then data['%s'] = scan%s()"] local f_local = formatters["local scan%s = scanners.%s"] local f_scan = formatters["scan%s()"] @@ -314,10 +306,8 @@ local f_scan_c = formatters["%s(scan%s())"] -- see above --- if CONTEXTLMTXMODE > 0 then --- f_if_c = formatters[" local key = scanletters() if key == '' then break elseif key == '%s' then data['%s'] = %s(scan%s())"] --- f_elseif_c = formatters[" elseif k == '%s' then data['%s'] = %s(scan%s())"] --- end +----- f_if_c = formatters[" local key = scanletters() if key == '' then break elseif key == '%s' then data['%s'] = %s(scan%s())"] +----- f_elseif_c = formatters[" elseif k == '%s' then data['%s'] = %s(scan%s())"] local f_any = formatters[" else local key = scanword(true) if key then data[key] = scan%s() else break end end"] local f_any_c = formatters[" else local key = scanword(true) if key then data[key] = %s(scan%s()) else break end end"] diff --git a/tex/context/base/mkxl/typo-chr.lmt b/tex/context/base/mkxl/typo-chr.lmt new file mode 100644 index 000000000..bb11f54a6 --- /dev/null +++ b/tex/context/base/mkxl/typo-chr.lmt @@ -0,0 +1,291 @@ +if not modules then modules = { } end modules ['typo-chr'] = { + version = 1.001, + comment = "companion to typo-bld.mkiv", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} + +-- A historic intermediate version can be found in the mkiv variant. The code +-- can probably be improved but performance etc is not really an issue here. + +local insert, remove = table.insert, table.remove + +local context = context +local ctx_doifelse = commands.doifelse + +local nodecodes = nodes.nodecodes +local boundarycodes = nodes.boundarycodes +local subtypes = nodes.subtypes + +local glyph_code = nodecodes.glyph +local par_code = nodecodes.par +local boundary_code = nodecodes.boundary + +local wordboundary_code = boundarycodes.word + +local texgetnest = tex.getnest -- to be used +local texsetcount = tex.setcount + +local flush_node = nodes.flush_node +local flush_list = nodes.flush_list + +local settexattribute = tex.setattribute +local punctuation = characters.is_punctuation + +local variables = interfaces.variables +local v_all = variables.all +local v_reset = variables.reset + +local stack = { } + +local a_marked = attributes.numbers['marked'] +local lastmarked = 0 +local marked = { + [v_all] = 1, + [""] = 1, + [v_reset] = attributes.unsetvalue, +} + +local function pickup() + local list = texgetnest() + if list then + local tail = list.tail + if tail and tail.id == glyph_code and punctuation[tail.char] then + local prev = tail.prev + list.tail = prev + if prev then + prev.next = nil + end + list.tail = prev + tail.prev = nil + return tail + end + end +end + +local actions = { + remove = function(specification) + local n = pickup() + if n then + flush_node(n) + end + end, + push = function(specification) + local n = pickup() + if n then + insert(stack,n or false) + end + end, + pop = function(specification) + local n = remove(stack) + if n then + context(n) + end + end, +} + +local function pickuppunctuation(specification) + local action = actions[specification.action or "remove"] + if action then + action(specification) + end +end + +-- I played with nested marked content but it makes no sense and gives +-- complex code. Also, it's never needed so why bother. + +local function pickup(head,tail,str) + local attr = marked[str] + local last = tail + if last[a_marked] == attr then + local first = last + while true do + local prev = first.prev + if prev and prev[a_marked] == attr then + if prev.id == par_code then -- and start_of_par(prev) + break + else + first = prev + end + else + break + end + end + return first, last + end +end + +local function found(str) + local list = texgetnest() + if list then + local tail = list.tail + return tail and tail[a_marked] == marked[str] + end +end + +local actions = { + remove = function(specification) + local list = texgetnest() + if list then + local head = list.head + local tail = list.tail + local first, last = pickup(head,tail,specification.mark) + if first then + if first == head then + list.head = nil + list.tail = nil + else + local prev = first.prev + list.tail = prev + prev.next = nil + end + flush_list(first) + end + end + end, +} + +local function pickupmarkedcontent(specification) + local action = actions[specification.action or "remove"] + if action then + action(specification) + end +end + +local function markcontent(str) + local currentmarked = marked[str or v_all] + if not currentmarked then + lastmarked = lastmarked + 1 + currentmarked = lastmarked + marked[str] = currentmarked + end + settexattribute(a_marked,currentmarked) +end + +interfaces.implement { + name = "pickuppunctuation", + actions = pickuppunctuation, + arguments = { + { + { "action" } + } + } +} + +interfaces.implement { + name = "pickupmarkedcontent", + actions = pickupmarkedcontent, + arguments = { + { + { "action" }, + { "mark" } + } + } +} + +interfaces.implement { + name = "markcontent", + actions = markcontent, + arguments = "string", +} + +interfaces.implement { + name = "doifelsemarkedcontent", + actions = function(str) ctx_doifelse(found(str)) end, + arguments = "string", +} + +-- We just put these here. + +interfaces.implement { + name = "lastnodeidstring", + public = true, + actions = function() + local list = texgetnest() -- "top" + local okay = false + if list then + local tail = list.tail + if tail then + okay = nodecodes[tail.id] + end + end + context(okay or "") + end, +} + +-- local t_lastnodeid = token.create("c_syst_last_node_id") +-- +-- interfaces.implement { +-- name = "lastnodeid", +-- public = true, +-- actions = function() +-- ... +-- tex.setcount("c_syst_last_node_id",okay) +-- context.sprint(t_lastnodeid) +-- end, +-- } + +-- not needed in lmtx ... + +interfaces.implement { + name = "lastnodeid", + actions = function() + local list = texgetnest() -- "top" + local okay = -1 + if list then + local tail = list.tail + if tail then + okay = tail.id + end + end + texsetcount("c_syst_last_node_id",okay) + end, +} + +interfaces.implement { + name = "lastnodesubtypestring", + public = true, + actions = function() + local list = texgetnest() -- "top" + local okay = false + if list then + local tail = list.tail + if head then + okay = subtypes[tail.id][tail.subtype] + end + end + context(okay or "") + end, +} + +local function lastnodeequals(id,subtype) + local list = texgetnest() -- "top" + local okay = false + if list then + local tail = list.tail + if tail then + local i = tail.id + okay = i == id or i == nodecodes[id] + if subtype then + local s = tail.subtype + okay = s == subtype or s == subtypes[i][subtype] + end + end + end + ctx_doifelse(okay) +end + +interfaces.implement { + name = "lastnodeequals", + arguments = "2 strings", + actions = lastnodeequals, +} + +interfaces.implement { + name = "atwordboundary", + actions = function() + lastnodeequals(boundary_code,wordboundary_code) + end, +} + diff --git a/tex/context/base/mkxl/typo-chr.mkxl b/tex/context/base/mkxl/typo-chr.mkxl index c4fbeba17..1c459cfcf 100644 --- a/tex/context/base/mkxl/typo-chr.mkxl +++ b/tex/context/base/mkxl/typo-chr.mkxl @@ -33,7 +33,7 @@ %D for instance when combining bit and pieces where keeping a state is complex compared %D to cleaning up unwanted stuff. -\registerctxluafile{typo-chr}{} +\registerctxluafile{typo-chr}{autosuffix} \definesystemattribute[marked][public] diff --git a/tex/context/base/mkxl/typo-drp.lmt b/tex/context/base/mkxl/typo-drp.lmt index 8741376b6..2c53cc111 100644 --- a/tex/context/base/mkxl/typo-drp.lmt +++ b/tex/context/base/mkxl/typo-drp.lmt @@ -310,13 +310,8 @@ actions[v_default] = function(head,setting) if trace_initials then report_initials("setting hangafter to %i and hangindent to %p",hangafter,hangindent) end - if CONTEXTLMTXMODE > 0 then - texset("hangafter",hangafter,true) - texset("hangindent",hangindent,true) - else - texset("hangafter",hangafter) - texset("hangindent",hangindent) - end + texset("hangafter",hangafter,true) + texset("hangindent",hangindent,true) end if indent then insert_after(first,first,new_kern(-parindent)) diff --git a/tex/context/base/mkxl/typo-ovl.lmt b/tex/context/base/mkxl/typo-ovl.lmt new file mode 100644 index 000000000..5834f9ff0 --- /dev/null +++ b/tex/context/base/mkxl/typo-ovl.lmt @@ -0,0 +1,184 @@ +if not modules then modules = { } end modules ['typo-ovl'] = { + version = 1.001, + comment = "companion to typo-ovl.mkiv", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} + +-- This is dubious code. If you needed it your source is probably bad. We only used +-- in when we had to mark bad content but when cleaning up some project code I decided +-- that it is easier to maintain in the distribution then in a project style. After all, +-- we have hardly any private code. For convenience I hooked it into the existing +-- replacement module (as it used the same code anyway). I did some cleanup. + +local next, type = next, type + +local context = context + +local nuts = nodes.nuts +local tonut = nodes.tonut +local tonode = nodes.tonode + +local nodecodes = nodes.nodecodes +local glyph_code = nodecodes.glyph +local disc_code = nodecodes.disc + +local getnext = nuts.getnext +local getid = nuts.getid +local getdisc = nuts.getdisc +local getattr = nuts.getattr +local setattr = nuts.setattr +local getattrlist = nuts.getattrlist +local setattrlist = nuts.setattrlist +local getdata = nuts.getdata +local setfont = nuts.setfont + +local nextnode = nuts.traversers.node + +local unsetvalue = attributes.unsetvalue +local prvattribute = attributes.private + +local texgetbox = tex.getbox +local currentfont = font.current + +local a_overloads = attributes.private("overloads") +local n_overloads = 0 +local t_overloads = { } + +local overloaded = { } + +local function markasoverload(a) + local n = prvattribute(a) + if n then + overloaded[n] = a + end +end + +attributes.markasoverload = markasoverload + +markasoverload("color") +markasoverload("colormodel") +markasoverload("transparency") +markasoverload("case") +markasoverload("negative") +markasoverload("effect") +markasoverload("ruled") +markasoverload("shifted") +markasoverload("kernchars") +markasoverload("kern") +markasoverload("noligature") +markasoverload("viewerlayer") + +local function tooverloads(n) + local current = tonut(n) + local a = getattrlist(current) + local s = { } + while a do + local index, value = getdata(a) + local o = overloaded[n] + if o and value ~= unsetvalue then + -- It is actually very unlikely that we have unsetvalue. + s[index] = value + end + a = getnext(a) + end + return s +end + +attributes.tooverloads = tooverloads + +function attributes.applyoverloads(specification,start,stop) + local start = tonut(start) + local processor = specification.processor + local overloads = specification.processor or getattr(start,a_overloads) + if overloads and overloads ~= unsetvalue then + overloads = t_overloads[overloads] + if not overloads then + return + end + else + return + end + + local last = stop and tonut(stop) + local oldlist = nil + local newlist = nil + local newfont = overloads.font + + local function apply(current) + local a = getattrlist(current) + if a == oldlist then + setattrlist(current,newlist) + else + oldlist = getattrlist(current) + for k, v in next, overloads do + if type(v) == "number" then + setattr(current,k,v) + else + -- can be: ["font"] = number + end + end + newlist = current -- getattrlist(current) + end + if newfont then + setfont(current,newfont) + end + end + + for current, id in nextnode, start do + if id == glyph_code then + apply(current) + elseif id == disc_code then + apply(current) + if pre then + while pre do + if getid(pre) == glyph_code then + apply() + end + pre = getnext(pre) + end + end + if post then + while post do + if getid(post) == glyph_code then + apply() + end + post = getnext(post) + end + end + if replace then + while replace do + if getid(replace) == glyph_code then + apply() + end + replace = getnext(replace) + end + end + end + if current == last then + break + end + end +end + +-- we assume the same highlight so we're global + +interfaces.implement { + name = "overloadsattribute", + arguments = { "string", "integer", "integer" }, + actions = function(name,font,box) + local samplebox = texgetbox(box) + local sample = samplebox and samplebox.list + local overloads = sample and tooverloads(sample) + if overloads then + overloads.font = font > 0 and font or false + n_overloads = n_overloads + 1 + t_overloads[n_overloads] = overloads + t_overloads[name] = overloads + context(n_overloads) + else + context(unsetvalue) + end + end +} diff --git a/tex/context/base/mkxl/typo-ovl.mkxl b/tex/context/base/mkxl/typo-ovl.mkxl index 8ad4cce26..b3ba2e783 100644 --- a/tex/context/base/mkxl/typo-ovl.mkxl +++ b/tex/context/base/mkxl/typo-ovl.mkxl @@ -22,7 +22,7 @@ %D replaced as otherwise normal attributes make more sense. Using this otherwise %D is weird but one never knows what users come up with. -\registerctxluafile{typo-ovl}{} +\registerctxluafile{typo-ovl}{autosuffix} \definesystemattribute[overloads][public,global] diff --git a/tex/generic/context/luatex/luatex-fonts-merged.lua b/tex/generic/context/luatex/luatex-fonts-merged.lua index 7fb03eb1e..d54dc082a 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 : c:/data/develop/context/sources/luatex-fonts-merged.lua -- parent file : c:/data/develop/context/sources/luatex-fonts.lua --- merge date : 2020-12-06 18:12 +-- merge date : 2020-12-08 11:06 do -- begin closure to overcome local limits and interference @@ -21141,7 +21141,6 @@ local function copytotfm(data,cache_id) parameters.units=units parameters.vheight=metadata.defaultvheight properties.space=spacer - properties.encodingbytes=2 properties.format=data.format or formats.otf properties.filename=filename properties.fontname=fontname @@ -21149,6 +21148,9 @@ local function copytotfm(data,cache_id) properties.psname=psname properties.name=filename or fullname properties.subfont=subfont +if not CONTEXTLMTXMODE or CONTEXTLMTXMODE==0 then + properties.encodingbytes=2 +end properties.private=properties.private or data.private or privateoffset return { characters=characters, @@ -34711,7 +34713,6 @@ local function copytotfm(data) parameters.descender=abs(metadata.descender or 0) parameters.units=1000 properties.spacer=spacer - properties.encodingbytes=2 properties.format=fonts.formats[filename] or "type1" properties.filename=filename properties.fontname=fontname @@ -34719,6 +34720,9 @@ local function copytotfm(data) properties.psname=fullname properties.name=filename or fullname or fontname properties.private=properties.private or data.private or privateoffset +if not CONTEXTLMTXMODE or CONTEXTLMTXMODE==0 then + properties.encodingbytes=2 +end if next(characters) then return { characters=characters, |