summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/context/documents/general/manuals/luametatex.pdfbin1212507 -> 1212412 bytes
-rw-r--r--tex/context/base/mkii/cont-new.mkii2
-rw-r--r--tex/context/base/mkii/context.mkii2
-rw-r--r--tex/context/base/mkii/mult-cs.mkii1
-rw-r--r--tex/context/base/mkiv/cont-new.mkiv2
-rw-r--r--tex/context/base/mkiv/context.mkiv2
-rw-r--r--tex/context/base/mkiv/font-lib.mkvi2
-rw-r--r--tex/context/base/mkiv/font-phb-imp-binary.lua119
-rw-r--r--tex/context/base/mkiv/font-phb.lua728
-rw-r--r--tex/context/base/mkiv/font-txt.lua549
-rw-r--r--tex/context/base/mkiv/m-fonts-plugins.mkiv64
-rw-r--r--tex/context/base/mkiv/status-files.pdfbin23999 -> 23923 bytes
-rw-r--r--tex/context/base/mkiv/status-lua.pdfbin224934 -> 224973 bytes
-rw-r--r--tex/context/base/mkxl/cont-new.mkxl2
-rw-r--r--tex/context/base/mkxl/context.mkxl2
-rw-r--r--tex/context/base/mkxl/font-lib.mklx12
-rw-r--r--tex/context/base/mkxl/font-phb-imp-binary.lmt114
-rw-r--r--tex/context/base/mkxl/font-phb-imp-internal.lmt160
-rw-r--r--tex/context/base/mkxl/font-phb.lmt560
-rw-r--r--tex/context/base/mkxl/font-txt.lmt583
-rw-r--r--tex/context/base/mkxl/lpdf-lmt.lmt2
-rw-r--r--tex/context/interface/mkii/keys-cs.xml1
-rw-r--r--tex/generic/context/luatex/luatex-fonts-merged.lua2
23 files changed, 2896 insertions, 13 deletions
diff --git a/doc/context/documents/general/manuals/luametatex.pdf b/doc/context/documents/general/manuals/luametatex.pdf
index a9e16374a..bd3db8ffc 100644
--- a/doc/context/documents/general/manuals/luametatex.pdf
+++ b/doc/context/documents/general/manuals/luametatex.pdf
Binary files differ
diff --git a/tex/context/base/mkii/cont-new.mkii b/tex/context/base/mkii/cont-new.mkii
index c0f77fb71..3a4d4cdcc 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{2021.04.26 00:51}
+\newcontextversion{2021.04.26 20:39}
%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 693531af4..6539eea05 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{2021.04.26 00:51}
+\edef\contextversion{2021.04.26 20:39}
%D For those who want to use this:
diff --git a/tex/context/base/mkii/mult-cs.mkii b/tex/context/base/mkii/mult-cs.mkii
index 0f2336d65..72369ad89 100644
--- a/tex/context/base/mkii/mult-cs.mkii
+++ b/tex/context/base/mkii/mult-cs.mkii
@@ -761,6 +761,7 @@
\setinterfaceconstant{coupling}{propojeni}
\setinterfaceconstant{couplingway}{zpusobpropojeni}
\setinterfaceconstant{criterium}{kriterium}
+\setinterfaceconstant{crop}{crop}
\setinterfaceconstant{cropoffset}{cropoffset}
\setinterfaceconstant{crossreference}{crossreference}
\setinterfaceconstant{cssfile}{cssfile}
diff --git a/tex/context/base/mkiv/cont-new.mkiv b/tex/context/base/mkiv/cont-new.mkiv
index 223405dc1..be48bbe8d 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{2021.04.26 00:51}
+\newcontextversion{2021.04.26 20:39}
%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 c44775e59..87793ce03 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{2021.04.26 00:51}
+\edef\contextversion{2021.04.26 20:39}
%D Kind of special:
diff --git a/tex/context/base/mkiv/font-lib.mkvi b/tex/context/base/mkiv/font-lib.mkvi
index db9df49cd..d05872653 100644
--- a/tex/context/base/mkiv/font-lib.mkvi
+++ b/tex/context/base/mkiv/font-lib.mkvi
@@ -63,6 +63,8 @@
\registerctxluafile{font-one}{optimize}
\registerctxluafile{font-afk}{}
+\registerctxluafile{font-txt}{}
+
% tfm
\registerctxluafile{font-tpk}{optimize}
diff --git a/tex/context/base/mkiv/font-phb-imp-binary.lua b/tex/context/base/mkiv/font-phb-imp-binary.lua
new file mode 100644
index 000000000..39ac6ec04
--- /dev/null
+++ b/tex/context/base/mkiv/font-phb-imp-binary.lua
@@ -0,0 +1,119 @@
+if not modules then modules = { } end modules ['font-phb-imp-binary'] = {
+ version = 1.000, -- 2016.10.10,
+ comment = "companion to font-txt.mkiv",
+ author = "Hans Hagen",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files",
+}
+
+-- The hb library comes in versions and the one I tested in 2016 was part of the inkscape
+-- suite. In principle one can have incompatibilities due to updates but that is the nature
+-- of a library. When a library ie expected one has better use the system version, if only
+-- to make sure that different programs behave the same.
+--
+-- The main reason for testing this approach was that when Idris was working on his fonts,
+-- we wanted to know how different shapers deal with it and the hb command line program
+-- could provide uniscribe output. For the context shaper uniscribe is the reference, also
+-- because Idris started out with Volt a decade ago.
+--
+-- This file uses the indirect approach by calling the executable. This file uses context
+-- features and is not generic.
+
+local next, tonumber, pcall = next, tonumber, pcall
+
+local concat = table.concat
+local reverse = table.reverse
+local formatters = string.formatters
+local removefile = os.remove
+local resultof = os.resultof
+local savedata = io.savedata
+
+local report = utilities.hb.report or print
+local packtoutf8 = utilities.hb.helpers.packtoutf8
+
+if not context then
+ report("the binary runner is only supported in context")
+ return
+end
+
+-- output : [index=cluster@x_offset,y_offset+x_advance,y_advance|...]
+-- result : { index, cluster, x_offset, y_offset, x_advance, y_advance }
+
+local P, Ct, Cc = lpeg.P, lpeg.Ct, lpeg.Cc
+local lpegmatch = lpeg.match
+
+local zero = Cc(0)
+local number = lpeg.patterns.integer / tonumber + zero
+local index = lpeg.patterns.cardinal / tonumber
+local cluster = index
+local offset = (P("@") * number * (P(",") * number + zero)) + zero * zero
+local advance = (P("+") * number * (P(",") * number + zero)) + zero * zero
+local glyph = Ct(index * P("=") * cluster * offset * advance)
+local pattern = Ct(P("[") * (glyph * P("|")^-1)^0 * P("]"))
+
+local shapers = {
+ native = "ot,uniscribe,fallback",
+ uniscribe = "uniscribe,ot,fallback",
+ fallback = "fallback"
+}
+
+local runner = sandbox.registerrunner {
+ method = "resultof",
+ name = "harfbuzz",
+ -- program = {
+ -- windows = "hb-shape.exe",
+ -- unix = "hb-shape"
+ -- },
+ program = "hb-shape",
+ checkers = {
+ shaper = "string",
+ features = "string",
+ script = "string",
+ language = "string",
+ direction = "string",
+ textfile = "writable",
+ fontfile = "readable",
+ },
+ template = string.longtostring [[
+ --shaper=%shaper%
+ --output-format=text
+ --no-glyph-names
+ --features="%features%"
+ --script=%script%
+ --language=%language%
+ --direction=%direction%
+ --text-file=%textfile%
+ --font-file=%fontfile%
+ ]],
+}
+
+local tempfile = "font-phb.tmp"
+local reported = false
+
+function utilities.hb.methods.binary(font,data,rlmode,text,leading,trailing)
+ if runner then
+ savedata(tempfile,packtoutf8(text,leading,trailing))
+ local result = runner {
+ shaper = shapers[data.shaper] or shapers.native,
+ features = data.features,
+ script = data.script or "dflt",
+ language = data.language or "dflt",
+ direction = rlmode < 0 and "rtl" or "ltr",
+ textfile = tempfile,
+ fontfile = data.filename,
+ }
+ removefile(tempfile)
+ if result then
+ -- return jsontolua(result)
+ result = lpegmatch(pattern,result) -- { index cluster xo yo xa ya }
+ if rlmode < 0 then
+ return reverse(result) -- we can avoid this
+ else
+ return result
+ end
+ end
+ elseif reported then
+ report("no runner available")
+ reported = true
+ end
+end
diff --git a/tex/context/base/mkiv/font-phb.lua b/tex/context/base/mkiv/font-phb.lua
new file mode 100644
index 000000000..42ebfcbc6
--- /dev/null
+++ b/tex/context/base/mkiv/font-phb.lua
@@ -0,0 +1,728 @@
+if not modules then modules = { } end modules ['font-phb'] = {
+ version = 1.000, -- 2016.10.10,
+ comment = "companion to font-txt.mkiv",
+ original = "derived from a prototype by Kai Eigner",
+ author = "Hans Hagen", -- so don't blame KE
+ copyright = "TAT Zetwerk / PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files",
+}
+
+-- The next code is a rewrite of Kai's prototype. Here we forget about components
+-- and assume some sane data structures. Clusters are handled on the fly. This is
+-- probably one of the places where generic and context code is (to be) different
+-- anyway. All errors in the logic below are mine (Hans). The optimizations probably
+-- make less sense in luajittex because there the interpreter does some optimization
+-- but we may end up with a non-jit version some day.
+--
+-- For testing I used the commandline tool as this code is not that critital and not
+-- used in context for production (maybe for testing). I noticed some issues with
+-- r2l shaping of latin but the uniscribe shaper seems better with that but as it's
+-- a library we're supposed to treat it as a magic black box and not look into it. In
+-- the end all will be sorted out I guess so we don't need to worry about it. Also, I
+-- can always improve the code below if really needed.
+--
+-- We create intermediate tables which might look inefficient. For instance we could
+-- just return two tables or an iterator but in the end this is not the bottleneck.
+-- In fact, speed is hard to measure anyway, as it depends on the font, complexity
+-- of the text, etc. Sometimes the library is faster, sometimes the context Lua one
+-- (which is interesting as it does a bit more, i.e. supports additional features,
+-- which also makes it even harder to check). When we compare context mkiv runs with
+-- mkii runs using pdftex or xetex (which uses harfbuzz) the performance of luatex
+-- on (simple) font demos normally is significant less compared with pdftex (8 bit
+-- and no unicode) but a bit better than xetex. It looks like the interface that gets
+-- implemented here suits that pattern (keep in mind that especially discretionary
+-- handling is quite complex and similar to the context mkiv variant).
+--
+-- The main motivations for supporting this are (1) the fact that Kai spent time on
+-- it, and (2) that we can compare the Lua variant with uniscribe, which is kind of
+-- a reference. We started a decade ago (2006) with the Lua implementation and had
+-- to rely on MSWord for comparison. On the other hand, the command line version is
+-- also useable for that. Don't blame the library or its (maybe wrong) use (here)
+-- for side effects.
+--
+-- Currently there are two methods: (1) binary, which is slow and uses the command
+-- line shaper and (2) the ffi binding. In the meantime I redid the feed-back-into-
+-- the-node-list method. This way tracing is easier, performance better, and there
+-- is no need to mess so much with spacing. I have no clue if I lost functionality
+-- and as this is not production code issues probably will go unnoticed for a while.
+-- We'll see.
+--
+-- Usage: see m-fonts-plugins.mkiv as that is the interface.
+--
+-- Remark: It looks like the library sets up some features by default. Passing them
+-- somehow doesn't work (yet) so I must miss something here. There is something fishy
+-- here with enabling features like init, medi, fina etc because when we turn them on
+-- they aren't applied. Also some features are not processed.
+--
+-- Remark: Because utf32 is fragile I append a couple of zero slots which seems to
+-- work out ok. In fact, after some experiment I figured out that utf32 needs a list
+-- of 4 byte cardinals. From the fact that Kai used the utf8 method I assumed that
+-- there was a utf32 too and indeed that worked but I have no time to look into it
+-- more deeply. It seems to work ok though.
+--
+-- The plugin itself has plugins and we do it the same as with (my)sql support, i.e.
+-- we provide methods. The specific methods are implemented in the imp files. We
+-- follow that model with other libraries too.
+--
+-- Somehow the command line version does uniscribe (usp10.dll) but not the library
+-- so when I can get motivated I might write a binding for uniscribe. (Problem: I
+-- don't look forward to decipher complex (c++) library api's so in the end it might
+-- never happen. A quick glance at the usp10 api gives me the impression that the
+-- apis don't differ that much, but still.)
+--
+-- Warning: This is rather old code, cooked up in the second half of 2016. I'm not
+-- sure if it will keep working because it's not used in production and therefore
+-- doesn't get tested. It was written as part of some comparison tests for Idris,
+-- who wanted to compare the ConTeXt handler, uniscribe and hb, for which there are
+-- also some special modules (that show results alongside). It has never been tested
+-- in regular documents. As it runs independent of the normal font processors there
+-- is probably not that much risk of interference but of course one looses all the
+-- goodies that have been around for a while (or will show up in the future). The
+-- code can probably be optimized a bit.
+
+-- There are three implementation specific files:
+--
+-- 1 font-phb-imp-binary.lua : calls the command line version of hb
+-- 2 font-phb-imp-library.lua : uses ffi to interface to hb
+-- 3 font-phb-imp-internal.lua : uses a small library to interface to hb
+--
+-- Variants 1 and 2 should work with mkiv and were used when playing with these
+-- things, when writing the articles, and when running some tests for Idris font
+-- development. Variant 3 (and maybe 1 also works) is meant for lmtx and has not
+-- been used (read: tested) so far. The 1 and 2 variants are kind of old, but 3 is
+-- an adaptation of 2 so not hip and modern either.
+
+if not context then
+ return
+end
+
+local next, tonumber, pcall, rawget = next, tonumber, pcall, rawget
+
+local concat = table.concat
+local sortedhash = table.sortedhash
+local formatters = string.formatters
+
+local fonts = fonts
+local otf = fonts.handlers.otf
+local texthandler = otf.texthandler
+
+local fontdata = fonts.hashes.identifiers
+
+local nuts = nodes.nuts
+local tonode = nuts.tonode
+local tonut = nuts.tonut
+
+local remove_node = nuts.remove
+
+local getboth = nuts.getboth
+local getnext = nuts.getnext
+local setnext = nuts.setnext
+local getprev = nuts.getprev
+local setprev = nuts.setprev
+local getid = nuts.getid
+local getchar = nuts.getchar
+local setchar = nuts.setchar
+local setlink = nuts.setlink
+local setoffsets = nuts.setoffsets
+----- getcomponents = nuts.getcomponents
+----- setcomponents = nuts.setcomponents
+local getwidth = nuts.getwidth
+local setwidth = nuts.setwidth
+
+local copy_node = nuts.copy
+local find_tail = nuts.tail
+
+local nodepool = nuts.pool
+local new_kern = nodepool.fontkern
+local new_glyph = nodepool.glyph
+
+local nodecodes = nodes.nodecodes
+local glyph_code = nodecodes.glyph
+local glue_code = nodecodes.glue
+
+local skipped = {
+ -- we assume that only valid features are set but maybe we need a list
+ -- of valid hb features as there can be many context specific ones
+ mode = true,
+ features = true,
+ language = true,
+ script = true,
+}
+
+local seenspaces = {
+ [0x0020] = true,
+ [0x00A0] = true,
+ [0x0009] = true, -- indeed
+ [0x000A] = true, -- indeed
+ [0x000D] = true, -- indeed
+}
+
+-- helpers
+
+local helpers = { }
+local methods = { }
+local initialized = { } -- we don't polute the shared table
+
+local method = "library"
+local shaper = "native" -- "uniscribe"
+local report = logs.reporter("font plugin","hb")
+
+utilities.hb = {
+ methods = methods,
+ helpers = helpers,
+ report = report,
+}
+
+do
+
+ local toutf8 = utf.char
+ local space = toutf8(0x20)
+
+ -- we can move this to the internal lib .. just pass a table .. but it is not faster
+
+ function helpers.packtoutf8(text,leading,trailing)
+ local size = #text
+ for i=1,size do
+ text[i] = toutf8(text[i])
+ end
+ if leading then
+ text[0] = space
+ end
+ if trailing then
+ text[size+1] = space
+ end
+ return concat(text,"",leading and 0 or 1,trailing and (size + 1) or size)
+ end
+
+ local toutf32 = utf.toutf32string
+ local space = toutf32(0x20)
+
+ function helpers.packtoutf32(text,leading,trailing)
+ local size = #text
+ for i=1,size do
+ text[i] = toutf32(text[i])
+ end
+ if leading then
+ text[0] = space
+ end
+ if trailing then
+ text[size+1] = space
+ end
+ return concat(text,"",leading and 0 or 1,trailing and (size + 1) or size)
+ end
+
+end
+
+local function initialize(font)
+
+ local tfmdata = fontdata[font]
+ local resources = tfmdata.resources
+ local shared = tfmdata.shared
+ local filename = resources.filename
+ local features = shared.features
+ local descriptions = shared.rawdata.descriptions
+ local characters = tfmdata.characters
+ local featureset = { }
+ local copytochar = shared.copytochar -- indextounicode
+ local spacewidth = nil -- unscaled
+ local factor = tfmdata.parameters.factor
+ local marks = resources.marks or { }
+
+ -- could be shared but why care about a few extra tables
+
+ if not copytochar then
+ copytochar = { }
+ -- let's make sure that we have an indexed table and not a hash
+ local max = 0
+ for k, v in next, descriptions do
+ if v.index > max then
+ max = v.index
+ end
+ end
+ for i=0,max do
+ copytochar[i] = i
+ end
+ -- the normal mapper
+ for k, v in next, descriptions do
+ copytochar[v.index] = k
+ end
+ shared.copytochar = copytochar
+ end
+
+ -- independent from loop as we have unordered hashes
+
+ if descriptions[0x0020] then
+ spacewidth = descriptions[0x0020].width
+ elseif descriptions[0x00A0] then
+ spacewidth = descriptions[0x00A0].width
+ end
+
+ for k, v in sortedhash(features) do
+ if #k > 4 then
+ -- unknown ones are ignored anyway but we can assume that the current
+ -- (and future) extra context features use more verbose names
+ elseif skipped[k] then
+ -- we don't want to pass language and such so we block a few features
+ -- explicitly
+ elseif v == "yes" or v == true then
+ featureset[#featureset+1] = k .. "=1" -- cf command line (false)
+ elseif v == "no" or v == false then
+ featureset[#featureset+1] = k .. "=0" -- cf command line (true)
+ elseif type(v) == "number" then
+ featureset[#featureset+1] = k .. "=" .. v -- cf command line (alternate)
+ else
+ -- unset
+ end
+ end
+
+ local data = {
+ language = features.language, -- do we need to uppercase and padd to 4 ?
+ script = features.script, -- do we need to uppercase and padd to 4 ?
+ features = #featureset > 0 and concat(featureset,",") or "", -- hash
+ featureset = #featureset > 0 and featureset or nil,
+ copytochar = copytochar,
+ spacewidth = spacewidth,
+ filename = filename,
+ marks = marks,
+ factor = factor,
+ characters = characters, -- the loaded font (we use its metrics which is more accurate)
+ method = features.method or method,
+ shaper = features.shaper or shaper,
+ }
+ initialized[font] = data
+ return data
+end
+
+-- In many cases this gives compatible output but especially with respect to spacing and user
+-- discretionaries that mix fonts there can be different outcomes. We also have no possibility
+-- to tweak and cheat. Of course one can always run a normal node mode pass with specific
+-- features first but then one can as well do all in node mode. So .. after a bit of playing
+-- around I redid this one from scratch and also added tracing.
+
+local trace_colors = false trackers.register("fonts.plugins.hb.colors", function(v) trace_colors = v end)
+local trace_details = false trackers.register("fonts.plugins.hb.details",function(v) trace_details = v end)
+local check_id = false
+----- components = false -- we have no need for them
+
+local setcolor = function() end
+local resetcolor = function() end
+
+if context then
+ setcolor = nodes.tracers.colors.set
+ resetcolor = nodes.tracers.colors.reset
+end
+
+table.setmetatableindex(methods,function(t,k)
+ local l = "font-phb-imp-" .. k .. ".lua"
+ report("start loading method %a from %a",k,l)
+ dofile(resolvers.findfile(l))
+ local v = rawget(t,k)
+ if v then
+ report("loading method %a succeeded",k)
+ else
+ report("loading method %a failed",k)
+ v = function() return { } end
+ end
+ t[k] = v
+ return v
+end)
+
+local inandout do
+
+ local utfbyte = utf.byte
+ local utfchar = utf.char
+ local utf3208 = utf.utf32_to_utf8_le
+
+ inandout = function(text,result,first,last,copytochar)
+ local s = { }
+ local t = { }
+ local r = { }
+ local f = formatters["%05U"]
+ for i=1,#text do
+ local c = text[i]
+ -- t[#t+1] = f(utfbyte(utf3208(c)))
+ s[#s+1] = utfchar(c)
+ t[#t+1] = f(c)
+ end
+ for i=first,last do
+ r[#r+1] = f(copytochar[result[i][1]])
+ end
+ return s, t, r
+ end
+
+end
+
+local function harfbuzz(head,font,attr,rlmode,start,stop,text,leading,trailing)
+ local data = initialized[font]
+
+ if not data then
+ data = initialize(font)
+ end
+
+ if check_id then
+ if getid(start) ~= glyph_code then
+ report("error: start is not a glyph")
+ return head
+ elseif getid(stop) ~= glyph_code then
+ report("error: stop is not a glyph")
+ return head
+ end
+ end
+ local size = #text -- original text, without spaces
+ local result = methods[data.method](font,data,rlmode,text,leading,trailing)
+ local length = result and #result or 0
+
+ if length == 0 then
+ -- report("warning: no result")
+ return head
+ end
+
+ local factor = data.factor
+ local marks = data.marks
+ local spacewidth = data.spacewidth
+ local copytochar = data.copytochar
+ local characters = data.characters
+
+ -- the text analyzer is only partially clever so we must assume that we get
+ -- inconsistent lists
+
+ -- we could check if something has been done (replacement or kern or so) but
+ -- then we pass around more information and need to check a lot and spaces
+ -- are kind of spoiling that game (we need a different table then) .. more
+ -- pain than gain
+
+ -- we could play with 0xFFFE as boundary
+
+ local current = start
+ local prev = nil
+ local glyph = nil
+
+ local first = 1
+ local last = length
+ local next = nil -- todo: keep track of them
+ local prev = nil -- todo: keep track of them
+
+ if leading then
+ first = first + 1
+ end
+ if trailing then
+ last = last - 1
+ end
+
+ local position = first
+ local cluster = 0
+ local glyph = nil
+ local index = 0
+ local count = 1
+ -- local runner = nil
+ local saved = nil
+
+ if trace_details then
+ report("start run, original size: %i, result index: %i upto %i",size,first,last)
+ local s, t, r = inandout(text,result,first,last,copytochar)
+ report("method : %s",data.method)
+ report("shaper : %s",data.shaper)
+ report("string : %t",s)
+ report("text : % t",t)
+ report("result : % t",r)
+ end
+
+ -- okay, after some experiments, it became clear that more complex code aimed at
+ -- optimization doesn't pay off as complexity also demands more testing
+
+ for i=first,last do
+ local r = result[i]
+ local unicode = copytochar[r[1]] -- can be private of course
+ --
+ cluster = r[2] + 1 -- starts at zero
+ --
+ if position == cluster then
+ if i == first then
+ index = 1
+ if trace_details then
+ report("[%i] position: %i, cluster: %i, index: %i, starting",i,position,cluster,index)
+ end
+ else
+ index = index + 1
+ if trace_details then
+ report("[%i] position: %i, cluster: %i, index: %i, next step",i,position,cluster,index)
+ end
+ end
+ elseif position < cluster then
+ -- a new cluster
+ current = getnext(current)
+ position = position + 1
+ size = size - 1
+ -- if runner then
+ -- local h, t
+ -- if saved then
+ -- h = copy_node(runner)
+ -- if trace_colors then
+ -- resetcolor(h)
+ -- end
+ -- setchar(h,saved)
+ -- t = h
+ -- if trace_details then
+ -- report("[%i] position: %i, cluster: %i, index: -, initializing components",i,position,cluster)
+ -- end
+ -- else
+ -- h = getcomponents(runner)
+ -- t = find_tail(h)
+ -- end
+ -- for p=position,cluster-1 do
+ -- local n
+ -- head, current, n = remove_node(head,current)
+ -- setlink(t,n)
+ -- t = n
+ -- if trace_details then
+ -- report("[%i] position: %i, cluster: %i, index: -, moving node to components",i,p,cluster)
+ -- end
+ -- size = size - 1
+ -- end
+ -- if saved then
+ -- setcomponents(runner,h)
+ -- saved = false
+ -- end
+ -- else
+ for p=position,cluster-1 do
+ head, current = remove_node(head,current,true)
+ if trace_details then
+ report("[%i] position: %i, cluster: %i, index: -, removing node",i,p,cluster)
+ end
+ size = size - 1
+ end
+ -- end
+ position = cluster
+ index = 1
+ glyph = nil
+ if trace_details then
+ report("[%i] position: %i, cluster: %i, index: %i, arriving",i,cluster,position,index)
+ end
+ else -- maybe a space got properties
+ if trace_details then
+ report("position: %i, cluster: %i, index: %i, quitting due to fatal inconsistency",position,cluster,index)
+ end
+ return head
+ end
+ local copied = false
+ if glyph then
+ if trace_details then
+ report("[%i] position: %i, cluster: %i, index: %i, copying glyph, unicode %U",i,position,cluster,index,unicode)
+ end
+ local g = copy_node(glyph)
+ if trace_colors then
+ resetcolor(g)
+ end
+ setlink(current,g,getnext(current)) -- insert_before
+ current = g
+ copied = true
+ else
+ if trace_details then
+ report("[%i] position: %i, cluster: %i, index: %i, using glyph, unicode %U",i,position,cluster,index,unicode)
+ end
+ glyph = current
+ end
+ --
+ if not current then
+ if trace_details then
+ report("quitting due to unexpected end of node list")
+ end
+ return head
+ end
+ --
+ local id = getid(current)
+ if id ~= glyph_code then
+ if trace_details then
+ report("glyph expected in node list")
+ end
+ return head
+ end
+ --
+ -- really, we can get a tab (9), lf (10), or cr(13) back in cambria .. don't ask me why
+ --
+ local prev, next = getboth(current)
+ --
+ -- assign glyph: first in run
+ --
+ -- if components and index == 1 then
+ -- runner = current
+ -- saved = getchar(current)
+ -- if saved ~= unicode then
+ -- setchar(current,unicode) -- small optimization
+ -- if trace_colors then
+ -- count = (count == 8) and 1 or count + 1
+ -- setcolor(current,"trace:"..count)
+ -- end
+ -- end
+ -- else
+ setchar(current,unicode)
+ if trace_colors then
+ count = (count == 8) and 1 or count + 1
+ setcolor(current,"trace:"..count)
+ end
+ -- end
+ --
+ local x_offset = r[3] -- r.dx
+ local y_offset = r[4] -- r.dy
+ local x_advance = r[5] -- r.ax
+ ----- y_advance = r[6] -- r.ay
+ local left = 0
+ local right = 0
+ local dx = 0
+ local dy = 0
+ if trace_details then
+ if x_offset ~= 0 or y_offset ~= 0 or x_advance ~= 0 then -- or y_advance ~= 0
+ report("[%i] position: %i, cluster: %i, index: %i, old, xoffset: %p, yoffset: %p, xadvance: %p, width: %p",
+ i,position,cluster,index,x_offset*factor,y_offset*factor,x_advance*factor,characters[unicode].width)
+ end
+ end
+ if y_offset ~= 0 then
+ dy = y_offset * factor
+ end
+ if rlmode >= 0 then
+ -- l2r marks and rest
+ if x_offset ~= 0 then
+ dx = x_offset * factor
+ end
+ local width = characters[unicode].width
+ local delta = x_advance * factor
+ if delta ~= width then
+ -- right = -(delta - width)
+ right = delta - width
+ end
+ elseif marks[unicode] then -- why not just the next loop
+ -- r2l marks
+ if x_offset ~= 0 then
+ dx = -x_offset * factor
+ end
+ else
+ -- r2l rest
+ local width = characters[unicode].width
+ local delta = (x_advance - x_offset) * factor
+ if delta ~= width then
+ left = delta - width
+ end
+ if x_offset ~= 0 then
+ right = x_offset * factor
+ end
+ end
+ if copied or dx ~= 0 or dy ~= 0 then
+ setoffsets(current,dx,dy)
+ end
+ if left ~= 0 then
+ setlink(prev,new_kern(left),current) -- insertbefore
+ if current == head then
+ head = prev
+ end
+ end
+ if right ~= 0 then
+ local kern = new_kern(right)
+ setlink(current,kern,next)
+ current = kern
+ end
+ if trace_details then
+ if dy ~= 0 or dx ~= 0 or left ~= 0 or right ~= 0 then
+ report("[%i] position: %i, cluster: %i, index: %i, new, xoffset: %p, yoffset: %p, left: %p, right: %p",i,position,cluster,index,dx,dy,left,right)
+ end
+ end
+ end
+ --
+ if trace_details then
+ report("[-] position: %i, cluster: %i, index: -, at end",position,cluster)
+ end
+ if size > 1 then
+ current = getnext(current)
+ -- if runner then
+ -- local h, t
+ -- if saved then
+ -- h = copy_node(runner)
+ -- if trace_colors then
+ -- resetcolor(h)
+ -- end
+ -- setchar(h,saved)
+ -- t = h
+ -- if trace_details then
+ -- report("[-] position: %i, cluster: -, index: -, initializing components",position)
+ -- end
+ -- else
+ -- h = getcomponents(runner)
+ -- t = find_tail(h)
+ -- end
+ -- for i=1,size-1 do
+ -- if trace_details then
+ -- report("[-] position: %i + %i, cluster: -, index: -, moving node to components",position,i)
+ -- end
+ -- local n
+ -- head, current, n = remove_node(head,current,true)
+ -- setlink(t,n)
+ -- t = n
+ -- end
+ -- if saved then
+ -- setcomponents(runner,h)
+ -- saved = false
+ -- end
+ -- else
+ for i=1,size-1 do
+ if trace_details then
+ report("[-] position: %i + %i, cluster: -, index: -, removing node",position,i)
+ end
+ head, current = remove_node(head,current,true)
+ end
+ -- end
+ end
+ --
+ -- We see all kind of interesting spaces come back (like tabs in cambria) so we do a bit of
+ -- extra testing here.
+ --
+ if leading then
+ local r = result[1]
+ local unicode = copytochar[r[1]]
+ if seenspaces[unicode] then
+ local x_advance = r[5]
+ local delta = x_advance - spacewidth
+ if delta ~= 0 then
+ -- nothing to do but jump one slot ahead
+ local prev = getprev(start)
+ if getid(prev) == glue_code then
+ local dx = delta * factor
+ setwidth(prev,getwidth(prev) + dx)
+ if trace_details then
+ report("compensating leading glue by %p due to codepoint %U",dx,unicode)
+ end
+ else
+ report("no valid leading glue node")
+ end
+ end
+ end
+ end
+ --
+ if trailing then
+ local r = result[length]
+ local unicode = copytochar[r[1]]
+ if seenspaces[unicode] then
+ local x_advance = r[5]
+ local delta = x_advance - spacewidth
+ if delta ~= 0 then
+ local next = getnext(stop)
+ if getid(next) == glue_code then
+ local dx = delta * factor
+ setwidth(next,getwidth(next) + dx)
+ if trace_details then
+ report("compensating trailing glue by %p due to codepoint %U",dx,unicode)
+ end
+ else
+ report("no valid trailing glue node")
+ end
+ end
+ end
+ end
+ --
+ if trace_details then
+ report("run done")
+ end
+ return head
+end
+
+otf.registerplugin("harfbuzz",function(head,font,attr,direction)
+ return texthandler(head,font,attr,direction,harfbuzz)
+end)
diff --git a/tex/context/base/mkiv/font-txt.lua b/tex/context/base/mkiv/font-txt.lua
new file mode 100644
index 000000000..20f290ddb
--- /dev/null
+++ b/tex/context/base/mkiv/font-txt.lua
@@ -0,0 +1,549 @@
+if not modules then modules = { } end modules ['font-txt'] = {
+ version = 1.001,
+ comment = "companion to font-ini.mkiv",
+ original = "derived from a prototype by Kai Eigner",
+ author = "Hans Hagen", -- so don't blame KE
+ copyright = "TAT Zetwerk / PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+-- The next code is derived from a snippet handler prototype by Kai Eigner and
+-- resembles the main loop of the Lua font handler but I decided use a more generic
+-- (and pluggable) approach and not hook it into the already present opentype
+-- handler. This is cleaner as it cannot interfere with the Lua font processor
+-- (which does some more things) and is also better performance wise. It also makes
+-- it possible to support other handlers as history has proven that there are no
+-- universal solution in computer land. Most of the disc logic is kept but done
+-- slightly different.
+--
+-- The code is heavily optimized and generalized so there can be errors. As
+-- mentioned, the plug mode can be used for alternative font handlers. A font is
+-- still loaded but the node and base mode handlers are ignored. Plugins are
+-- unlikely to work well in context as they can mess up attribute driven subsystem,
+-- so they are not officially supported. The language and script options are
+-- available in the usual way.
+--
+-- The code collects snippets, either or not with spacing around them and partially
+-- running over disc nodes. The r2l "don't assume disc and collect larger chunks" is
+-- not robust so I got rid of that branch. This is somewhat similar to the Lua font
+-- handler.
+--
+-- An alternative is to run over longer strings with dummy chars (unicode objects) as
+-- markers for whatever is in the list but that becomes tricky with mixed fonts and
+-- reconstruction becomes a bit of a mess then, especially because disc nodes force
+-- us to backtrack and look at several solutions. It also has a larger memory
+-- footprint. Some tests demonstrated that it has no gain and only adds complexity.
+--
+-- This (derived) variant is better suited for context and seems to work ok in the
+-- generic variant. I also added some context specific tracing to the code. This
+-- variant uses the plug model provided in the context font system. So, in context,
+-- using the built in Lua handler is the better alternative, also because it has
+-- extensive tracing features. Context users would loose additional functionality
+-- that has been provided for a decade and therefore plugins are not officially
+-- supported (at least not by me, unless I use them myself).
+--
+-- There is no checking here for already processed characters so best not mix this
+-- variant with code that does similar things. If this code evolves depends on the
+-- useability. Kai's code can now be found on github where it is used with a harfbuzz
+-- library. We add this kind of stuff because occasionally we compare engines and
+-- Kai sends me examples and I then need to check context.
+--
+-- One important difference between Kai's approach and the one used in ConTeXt is
+-- that we use utf-32 instead of utf-8. Once I figured out that clusters were just
+-- indices into the original text that made more sense. The first implementation
+-- used the command line tool (binary), then I went for ffi (library).
+--
+-- Beware: this file only implements the framework for plugins. Plugins themselves
+-- are in other files (e.g. font-phb*.lua). On the todo list is a uniscribe plugin
+-- because that is after all the reference for opentype support, but that interface
+-- needs a bit more work (so it might never happen).
+--
+-- Usage: see m-fonts-plugins.mkiv. As it's a nice test for ffi support that file
+-- migth be added to the distribution somewhere in the middle of 2017 when the ffi
+-- interface has been tested a bit more. Okay, it's 2012 now and we're way past that
+-- date but we never had a reason for adding it to the ConTeXt distribution. It
+-- should still work okay because I occasionally checked it against progress made in
+-- the engines and used newer helpers.
+--
+-- Here is an example of usage:
+--
+-- \starttext
+-- \definefontfeature[test][mode=plug,features=text]
+-- \start
+-- \showfontkerns
+-- \definedfont[Serif*test]
+-- \input tufte \par
+-- \stop
+-- \stoptext
+
+local fonts = fonts
+local otf = fonts.handlers.otf
+local nodes = nodes
+
+local utfchar = utf.char
+
+local nuts = nodes.nuts
+
+local getnext = nuts.getnext
+local setnext = nuts.setnext
+local getprev = nuts.getprev
+local setprev = nuts.setprev
+local getid = nuts.getid
+local getsubtype = nuts.getsubtype
+local getfont = nuts.getfont
+local getchar = nuts.getchar
+local getdisc = nuts.getdisc
+local setdisc = nuts.setdisc
+local getboth = nuts.getboth
+local setlink = nuts.setlink
+local getkern = nuts.getkern
+local getwidth = nuts.getwidth
+
+local ischar = nuts.ischar
+local isglyph = nuts.isglyph
+local traverse_id = nuts.traverse_id
+local usesfont = nuts.uses_font
+
+local copy_node_list = nuts.copy_list
+local find_node_tail = nuts.tail
+local flush_list = nuts.flush_list
+local free_node = nuts.free
+local end_of_math = nuts.end_of_math
+local start_of_par = nuts.start_of_par
+
+local nodecodes = nodes.nodecodes
+
+local glyph_code = nodecodes.glyph
+local glue_code = nodecodes.glue
+local disc_code = nodecodes.disc
+local kern_code = nodecodes.kern
+local math_code = nodecodes.math
+local dir_code = nodecodes.dir
+local par_code = nodecodes.par
+
+local righttoleft_code = nodes.dirvalues.righttoleft
+
+local txtdirstate = otf.helpers.txtdirstate
+local pardirstate = otf.helpers.pardirstate
+
+local fonthashes = fonts.hashes
+local fontdata = fonthashes.identifiers
+
+local function deletedisc(head)
+ local current = head
+ local next = nil
+ while current do
+ next = getnext(current)
+ if getid(current) == disc_code then
+ local pre, post, replace, pre_tail, post_tail, replace_tail = getdisc(current,true)
+ setdisc(current)
+ if pre then
+ flush_list(pre)
+ end
+ if post then
+ flush_list(post)
+ end
+ local p, n = getboth(current)
+ if replace then
+ if current == head then
+ head = replace
+ setprev(replace) -- already nil
+ else
+ setlink(p,replace)
+ end
+ setlink(replace_tail,n) -- was: setlink(n,replace_tail)
+ elseif current == head then
+ head = n
+ setprev(n)
+ else
+ setlink(p,n)
+ end
+ free_node(current)
+ end
+ current = next
+ end
+ return head
+end
+
+-- As we know that we have the same font we can probably optimize this a bit more.
+-- Although we can have more in disc nodes than characters and kerns we only support
+-- those two types.
+
+local function eqnode(n,m) -- no real improvement in speed
+ local n_char = isglyph(n)
+ if n_char then
+ return n_char == ischar(m,getfont(n))
+ elseif n_id == kern_code then
+ return getkern(n) == getkern(m)
+ end
+end
+
+local function equalnode(n,m)
+ if not n then
+ return not m
+ elseif not m then
+ return false
+ end
+ local n_char, n_id = isglyph(n)
+ if n_char then
+ return n_char == ischar(m,n_id) -- n_id == n_font
+ elseif n_id == whatsit_code then
+ return false
+ elseif n_id == glue_code then
+ return true
+ elseif n_id == kern_code then
+ return getkern(n) == getkern(m)
+ elseif n_id == disc_code then
+ local n_pre, n_post, n_replace = getdisc(n)
+ local m_pre, m_post, m_replace = getdisc(m)
+ while n_pre and m_pre do
+ if not eqnode(n_pre,m_pre) then
+ return false
+ end
+ n_pre = getnext(n_pre)
+ m_pre = getnext(m_pre)
+ end
+ if n_pre or m_pre then
+ return false
+ end
+ while n_post and m_post do
+ if not eqnode(n_post,m_post) then
+ return false
+ end
+ n_post = getnext(n_post)
+ m_post = getnext(m_post)
+ end
+ if n_post or m_post then
+ return false
+ end
+ while n_replace and m_replace do
+ if not eqnode(n_replace,m_replace) then
+ return false
+ end
+ n_replace = getnext(n_replace)
+ m_replace = getnext(m_replace)
+ end
+ if n_replace or m_replace then
+ return false
+ end
+ return true
+ end
+ return false
+end
+
+-- The spacing hackery is not nice. The text can get leading and trailing spaces
+-- and even mid spaces while the start and stop nodes not always are glues then
+-- so the plugin really needs to do some testing there. We could pass more context
+-- but it doesn't become much better.
+--
+-- The attribute gets passed for tracing purposes. We could support it (not that
+-- hard to do) but as we don't test strickly for fonts (in disc nodes) we are not
+-- compatible anyway. It would also mean more testing. So, don't use this mixed
+-- with node and base mode in context.
+--
+-- We don't distinguish between modes in treatment (so no r2l assumptions) and
+-- no cheats for scripts that might not use discretionaries. Such hacks can work
+-- in predictable cases but in context one can use a mix all kind of things and
+-- users do that. On the other hand, we do support longer glyph runs in both modes
+-- so there we gain a bit.
+
+local function texthandler(head,font,attr,rlmode,handler,startspacing,stopspacing,nesting)
+ if not head then
+ return
+ end
+ if startspacing == nil then
+ startspacing = false
+ end
+ if stopspacing == nil then
+ stopspacing = false
+ end
+
+ if getid(head) == par_code and start_of_par(head) then
+ rlmode = pardirstate(head)
+ elseif rlmode == righttoleft_code then
+ rlmode = -1
+ else
+ rlmode = 0
+ end
+
+ local dirstack = { }
+ local rlparmode = 0
+ local topstack = 0
+ local text = { }
+ local size = 0
+ local current = head
+ local start = nil
+ local stop = nil
+ local startrlmode = rlmode
+
+ local function handle(leading,trailing) -- what gets passed can become configureable: e.g. utf 8
+ local stop = current or start -- hm, what with outer stop
+ if getid(stop) ~= glyph_code then
+ stop = getprev(stop)
+ end
+ head = handler(head,font,attr,rlmode,start,stop,text,leading,trailing) -- handler can adapt text
+ size = 0
+ text = { }
+ start = nil
+ end
+
+ while current do
+ local char, id = ischar(current,font)
+ if char then
+ if not start then
+ start = current
+ startrlmode = rlmode
+ end
+ local char = getchar(current)
+ size = size + 1
+ text[size] = char
+ current = getnext(current)
+ elseif char == false then
+ -- so a mixed font
+ if start and size > 0 then
+ handle(startspacing,false)
+ end
+ startspacing = false
+ current = getnext(current)
+ elseif id == glue_code then
+ -- making this branch optional i.e. always use the else doesn't really
+ -- make a difference in performance (in hb) .. tricky anyway as we can
+ local width = getwidth(current)
+ if width > 0 then
+ if start and size > 0 then
+ handle(startspacing,true)
+ end
+ startspacing = true
+ stopspacing = false
+ else
+ if start and size > 0 then
+ head = handle(startspacing)
+ end
+ startspacing = false
+ stopspacing = false
+ end
+ current = getnext(current)
+ elseif id == disc_code and usesfont(current,font) then -- foo|-|bar : has hbox
+ -- This looks much like the original code but I don't see a need to optimize
+ -- for e.g. deva or r2l fonts. If there are no disc nodes then we won't see
+ -- this branch anyway and if there are, we should just deal with them.
+ --
+ -- There is still some weird code here ... start/stop and such. When I'm in
+ -- the mood (or see a need) I'll rewrite this bit.
+
+ -- bug: disc in last word moves to end (in practice not an issue as one
+ -- doesn't want a break there)
+
+ local pre = nil
+ local post = nil
+ local currentnext = getnext(current)
+ local current_pre, current_post, current_replace = getdisc(current)
+ setdisc(current) -- why, we set it later
+ if start then
+ pre = copy_node_list(start,current)
+ stop = getprev(current)
+ -- why also current and not:
+ -- pre = copy_node_list(start,stop)
+ if start == head then
+ head = current
+ end
+ setlink(getprev(start),current)
+ setlink(stop,current_pre)
+ current_pre = start
+ setprev(current_pre)
+ start = nil
+ stop = nil
+ startrlmode = rlmode
+ end
+ while currentnext do
+ local char, id = ischar(currentnext,font)
+ if char or id == disc_code then
+ stop = currentnext
+ currentnext = getnext(currentnext)
+ elseif id == glue_code then
+ local width = getwidth(currentnext)
+ if width and width > 0 then
+ stopspacing = true
+ else
+ stopspacing = false
+ end
+ break
+ else
+ break
+ end
+ end
+ if stop then
+ local currentnext = getnext(current)
+ local stopnext = getnext(stop)
+ post = copy_node_list(currentnext,stopnext)
+ if current_post then
+ setlink(find_node_tail(current_post),currentnext)
+ else
+ setprev(currentnext)
+ current_post = currentnext
+ end
+ setlink(current,stopnext)
+ setnext(stop)
+ stop = nil
+ end
+ if pre then
+ setlink(find_node_tail(pre),current_replace)
+ current_replace = pre
+ pre = nil
+ end
+ if post then
+ if current_replace then
+ setlink(find_node_tail(current_replace),post)
+ else
+ current_replace = post
+ end
+ post = nil
+ end
+ size = 0 -- hm, ok, start is also nil now
+ text = { }
+ if current_pre then
+ current_pre = texthandler(current_pre,font,attr,rlmode,handler,startspacing,false,"pre")
+ end
+ if current_post then
+ current_post = texthandler(current_post,font,attr,rlmode,handler,false,stopspacing,"post")
+ end
+ if current_replace then
+ current_replace = texthandler(current_replace,font,attr,rlmode,handler,startspacing,stopspacing,"replace")
+ end
+ startspacing = false
+ stopspacing = false
+ local cpost = current_post and find_node_tail(current_post)
+ local creplace = current_replace and find_node_tail(current_replace)
+ local cpostnew = nil
+ local creplacenew = nil
+ local newcurrent = nil
+ while cpost and equalnode(cpost,creplace) do
+ cpostnew = cpost
+ creplacenew = creplace
+ if creplace then
+ creplace = getprev(creplace)
+ end
+ cpost = getprev(cpost)
+ end
+ if cpostnew then
+ if cpostnew == current_post then
+ current_post = nil
+ else
+ setnext(getprev(cpostnew))
+ end
+ flush_list(cpostnew)
+ if creplacenew == current_replace then
+ current_replace = nil
+ else
+ setnext(getprev(creplacenew))
+ end
+ local c = getnext(current)
+ setlink(current,creplacenew)
+ local creplacenewtail = find_node_tail(creplacenew)
+ setlink(creplacenewtail,c)
+ newcurrent = creplacenewtail
+ end
+ current_post = current_post and deletedisc(current_post)
+ current_replace = current_replace and deletedisc(current_replace)
+ local cpre = current_pre
+ local creplace = current_replace
+ local cprenew = nil
+ local creplacenew = nil
+ while cpre and equalnode(cpre, creplace) do
+ cprenew = cpre
+ creplacenew = creplace
+ if creplace then
+ creplace = getnext(creplace)
+ end
+ cpre = getnext(cpre)
+ end
+ if cprenew then
+ cpre = current_pre
+ current_pre = getnext(cprenew)
+ if current_pre then
+ setprev(current_pre)
+ end
+ setnext(cprenew)
+ flush_list(cpre)
+ creplace = current_replace
+ current_replace = getnext(creplacenew)
+ if current_replace then
+ setprev(current_replace)
+ end
+ setlink(getprev(current),creplace)
+ if current == head then
+ head = creplace
+ end
+ setlink(creplacenew,current)
+ end
+ setdisc(current,current_pre,current_post,current_replace)
+ current = currentnext
+ else
+ if start and size > 0 then
+ handle(startspacing,stopspacing)
+ end
+ startspacing = false
+ stopspacing = false
+ if id == math_code then
+ current = getnext(end_of_math(current))
+ elseif id == dir_code then
+ startspacing = false
+ topstack, rlmode = txtdirstate(current,dirstack,topstack,rlparmode)
+ current = getnext(current)
+ -- elseif id == par_code and start_of_par(current) then
+ -- startspacing = false
+ -- rlparmode, rlmode = pardirstate(current)
+ -- current = getnext(current)
+ else
+ current = getnext(current)
+ end
+ end
+ end
+ if start and size > 0 then
+ handle(startspacing,stopspacing)
+ end
+ return head, true
+end
+
+function fonts.handlers.otf.texthandler(head,font,attr,direction,action)
+ if action then
+ return texthandler(head,font,attr,direction == righttoleft_code and -1 or 0,action)
+ else
+ return head, false
+ end
+end
+
+-- Next comes a tracer plug into context.
+
+local texthandler = fonts.handlers.otf.texthandler
+local report_text = logs.reporter("otf plugin","text")
+local nofruns = 0
+local nofsnippets = 0
+local f_unicode = string.formatters["%U"]
+
+local function showtext(head,font,attr,rlmode,start,stop,list,before,after)
+ if list then
+ nofsnippets = nofsnippets + 1
+ local plus = { }
+ for i=1,#list do
+ local u = list[i]
+ list[i] = utfchar(u)
+ plus[i] = f_unicode(u)
+ end
+ report_text("%03i : [%s] %t [%s]-> % t", nofsnippets, before and "+" or "-", list, after and "+" or "-", plus)
+ else
+ report_text()
+ report_text("invalid list")
+ report_text()
+ end
+ return head, false
+end
+
+fonts.handlers.otf.registerplugin("text",function(head,font,attr,direction)
+ nofruns = nofruns + 1
+ nofsnippets = 0
+ report_text("start run %i",nofruns)
+ local h, d = texthandler(head,font,attr,direction,showtext)
+ report_text("stop run %i",nofruns)
+ return h, d
+end)
diff --git a/tex/context/base/mkiv/m-fonts-plugins.mkiv b/tex/context/base/mkiv/m-fonts-plugins.mkiv
index a2b06fb8c..00174d13e 100644
--- a/tex/context/base/mkiv/m-fonts-plugins.mkiv
+++ b/tex/context/base/mkiv/m-fonts-plugins.mkiv
@@ -22,11 +22,65 @@
%D
%D I'm not sure if Idris will ever need this but the code has been used for some
%D articles so that's why it's eventually shipped.
-
-% \enabletrackers[resolvers.ffilib]
-
-\registerctxluafile{font-txt}{} % generic text handler
-\registerctxluafile{font-phb}{} % harfbuzz plugin: binary or library (ffi/optional)
+%D
+%D The library is supposed to be present in
+%D
+%D \starttyping
+%D .../tex/texmf-win64/bin/lib/luatatex/harfbuzz/libharfbuzz-0.dll
+%D .../tex/texmf-win64/bin/lib/luametatex/harfbuzz/libharfbuzz-0.dll
+%D
+%D .../tex/texmf-*/bin/lib/luatatex/harfbuzz/libharfbuzz.so
+%D .../tex/texmf-*/bin/lib/luametatex/harfbuzz/libharfbuzz.so
+%D \stoptyping
+%D
+%D It might be found on the system if paths are set but with libraries that render
+%D (font) stuff it's best to rely on what you explictly installed. When you install
+%D files there make sure to run \type {mtxrun --generate}. Keep in mind that we don't
+%D officially support such dependencies (bug-wise).
+%D
+%D In mkiv we support binary and library, and in lmtx binary and internal where we
+%D default to library or internal as binary is not that fast (more meant as a
+%D playground I guess). You can track loading with:
+%D
+%D \starttyping
+%D \enabletrackers[resolvers.ffilib]
+%D \stoptyping
+%D
+%D This file has always been in the distribution but per end april 2021 the font-txt,
+%D font-phb and font-phb-imp-* files are in the distrinbution too. It's a side effect
+%D of some cleanup of the (luametatex) source tree (where the optional module was not
+%D yet in sync with the other optional ones). This module is mostly for myself and
+%D Idris if only because it is not tested for interferences with all kind of other
+%D font code (and it's unlikely to happen), so don't depend on this!
+
+% \starttext
+%
+% \definefontfeature [native] [default]
+% [mode=plug,
+% features=harfbuzz,
+% shaper=native]
+%
+% \definefontfeature [uniscribe] [default]
+% [mode=plug,
+% features=harfbuzz,
+% shaper=uniscribe]
+%
+% \definefontfeature [binary] [default]
+% [mode=plug,
+% method=binary,
+% features=harfbuzz,
+% shaper=uniscribe]
+%
+% % \nohyphens
+% \definedfont[Serif*default] fiets \par % \input tufte \par
+% \definedfont[Serif*native] fiets \par % \input tufte \par
+% \definedfont[Serif*uniscribe] fiets \par % \input tufte \par
+% \definedfont[Serif*binary] fiets \par % \input tufte \par
+%
+% \stoptext
+
+
+\registerctxluafile{font-phb}{autosuffix}
\startluacode
diff --git a/tex/context/base/mkiv/status-files.pdf b/tex/context/base/mkiv/status-files.pdf
index 18d4edc32..ae6464bfa 100644
--- a/tex/context/base/mkiv/status-files.pdf
+++ b/tex/context/base/mkiv/status-files.pdf
Binary files differ
diff --git a/tex/context/base/mkiv/status-lua.pdf b/tex/context/base/mkiv/status-lua.pdf
index c7c27b61c..ff552cb3c 100644
--- a/tex/context/base/mkiv/status-lua.pdf
+++ b/tex/context/base/mkiv/status-lua.pdf
Binary files differ
diff --git a/tex/context/base/mkxl/cont-new.mkxl b/tex/context/base/mkxl/cont-new.mkxl
index 9fb153df4..9b2fb738e 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{2021.04.26 00:51}
+\newcontextversion{2021.04.26 20:39}
%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 210b1b25c..35103e9b4 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{2021.04.26 00:51}
+\immutable\edef\contextversion{2021.04.26 20:39}
%overloadmode 1 % check frozen / warning
%overloadmode 2 % check frozen / error
diff --git a/tex/context/base/mkxl/font-lib.mklx b/tex/context/base/mkxl/font-lib.mklx
index 72c2d4ba9..f1f7f5ce0 100644
--- a/tex/context/base/mkxl/font-lib.mklx
+++ b/tex/context/base/mkxl/font-lib.mklx
@@ -59,6 +59,8 @@
\registerctxluafile{font-one}{optimize}
\registerctxluafile{font-afk}{}
+\registerctxluafile{font-txt}{autosuffix}
+
% tfm
\registerctxluafile{font-tpk}{optimize}
@@ -153,4 +155,14 @@
\permanent \def\cleanfontname #1{\clf_cleanfontname{#1}}
\permanent\protected\def\setfontofid #1{\clf_setfontofid\numexpr#1\relax}
+%D I have to put it someplace, so here:
+%D
+%D \starttyping
+%D \definefontfeature[test][mode=plug,features=text]
+%D \definefont[FontA][Serif*test]
+%D \definefont[FontB][Serif*test]
+%D {\FontA aaa}{\FontB\glyphscale 1200 bbb}
+%D {\FontA aaa}{\FontB\glyphscale 1200 bbb}
+%D \stoptyping
+
\protect \endinput
diff --git a/tex/context/base/mkxl/font-phb-imp-binary.lmt b/tex/context/base/mkxl/font-phb-imp-binary.lmt
new file mode 100644
index 000000000..39a6057ea
--- /dev/null
+++ b/tex/context/base/mkxl/font-phb-imp-binary.lmt
@@ -0,0 +1,114 @@
+if not modules then modules = { } end modules ['font-phb-imp-binary'] = {
+ version = 1.000, -- 2016.10.10,
+ comment = "companion to font-txt.mkiv",
+ author = "Hans Hagen",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files",
+}
+
+-- The hb library comes in versions and the one I tested in 2016 was part of the inkscape
+-- suite. In principle one can have incompatibilities due to updates but that is the nature
+-- of a library. When a library ie expected one has better use the system version, if only
+-- to make sure that different programs behave the same.
+--
+-- The main reason for testing this approach was that when Idris was working on his fonts,
+-- we wanted to know how different shapers deal with it and the hb command line program
+-- could provide uniscribe output. For the context shaper uniscribe is the reference, also
+-- because Idris started out with Volt a decade ago.
+--
+-- This file uses the indirect approach by calling the executable. This file uses context
+-- features and is not generic.
+
+local next, tonumber, pcall = next, tonumber, pcall
+
+local concat = table.concat
+local reverse = table.reverse
+local formatters = string.formatters
+local removefile = os.remove
+local resultof = os.resultof
+local savedata = io.savedata
+
+local report = utilities.hb.report or print
+local packtoutf8 = utilities.hb.helpers.packtoutf8
+
+-- output : [index=cluster@x_offset,y_offset+x_advance,y_advance|...]
+-- result : { index, cluster, x_offset, y_offset, x_advance, y_advance }
+
+local P, Ct, Cc = lpeg.P, lpeg.Ct, lpeg.Cc
+local lpegmatch = lpeg.match
+
+local zero = Cc(0)
+local number = lpeg.patterns.integer / tonumber + zero
+local index = lpeg.patterns.cardinal / tonumber
+local cluster = index
+local offset = (P("@") * number * (P(",") * number + zero)) + zero * zero
+local advance = (P("+") * number * (P(",") * number + zero)) + zero * zero
+local glyph = Ct(index * P("=") * cluster * offset * advance)
+local pattern = Ct(P("[") * (glyph * P("|")^-1)^0 * P("]"))
+
+local shapers = {
+ native = "ot,uniscribe,fallback",
+ uniscribe = "uniscribe,ot,fallback",
+ fallback = "fallback"
+}
+
+local runner = sandbox.registerrunner {
+ method = "resultof",
+ name = "harfbuzz",
+ -- program = {
+ -- windows = "hb-shape.exe",
+ -- unix = "hb-shape"
+ -- },
+ program = "hb-shape",
+ checkers = {
+ shaper = "string",
+ features = "string",
+ script = "string",
+ language = "string",
+ direction = "string",
+ textfile = "writable",
+ fontfile = "readable",
+ },
+ template = string.longtostring [[
+ --shaper=%shaper%
+ --output-format=text
+ --no-glyph-names
+ --features="%features%"
+ --script=%script%
+ --language=%language%
+ --direction=%direction%
+ --text-file=%textfile%
+ --font-file=%fontfile%
+ ]],
+}
+
+local tempfile = "font-phb.tmp"
+local reported = false
+
+function utilities.hb.methods.binary(font,data,rlmode,text,leading,trailing)
+ if runner then
+ savedata(tempfile,packtoutf8(text,leading,trailing))
+ local result = runner {
+ shaper = shapers[data.shaper] or shapers.native,
+ features = data.features,
+ script = data.script or "dflt",
+ language = data.language or "dflt",
+ direction = rlmode < 0 and "rtl" or "ltr",
+ textfile = tempfile,
+ fontfile = data.filename,
+ }
+ removefile(tempfile)
+ if result then
+ -- return jsontolua(result)
+ result = lpegmatch(pattern,result) -- { index cluster xo yo xa ya }
+ if rlmode < 0 then
+ return reverse(result) -- we can avoid this
+ else
+ return result
+ end
+ end
+ elseif reported then
+ report("no runner available")
+ reported = true
+ end
+end
diff --git a/tex/context/base/mkxl/font-phb-imp-internal.lmt b/tex/context/base/mkxl/font-phb-imp-internal.lmt
new file mode 100644
index 000000000..fb2251abe
--- /dev/null
+++ b/tex/context/base/mkxl/font-phb-imp-internal.lmt
@@ -0,0 +1,160 @@
+if not modules then modules = { } end modules ['font-phb-imp-internal'] = {
+ version = 1.000, -- 2016.10.10,
+ comment = "companion to font-txt.mkiv",
+ original = "derived from font-phb-imp-library",
+ author = "Hans Hagen",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files",
+}
+
+-- The hb library comes in versions and the one I tested in 2016 was part of the inkscape
+-- suite. In principle one can have incompatibilities due to updates but that is the nature
+-- of a library. When a library ie expected one has better use the system version, if only
+-- to make sure that different programs behave the same.
+--
+-- The main reason for testing this approach was that when Idris was working on his fonts,
+-- we wanted to know how different shapers deal with it and the hb command line program
+-- could provide uniscribe output. For the context shaper uniscribe is the reference, also
+-- because Idris started out with Volt a decade ago.
+--
+-- We treat the lib as a black box as it should be. At some point Kai Eigner made an ffi
+-- binding and that one was adapted to the plugin approach of context. It saved me the
+-- trouble of looking at source files to figure it all out. Below is the adapted code.
+--
+-- This is basically the ffi variant but with the hb function calls delegated to a simple
+-- runtime library. That library was a side effect of playing a day with delayed loading
+-- like ffi does in luametatex, which seems to work ok for what we call optional libraries
+-- in lmtx. I didn't really test the next code well (and will probably do that when Idris
+-- needs some comparison with uniscribe etc). There are nowadays probably other ways to do
+-- this but this is what we had and so we can keep the test code that has been around for
+-- a while (which is needed because some old articles need it.)
+--
+-- The following setup doesn't really fit into the way we set up internal libraries
+-- but if isn't used in the same sense anyway so we stick to what we already had in
+-- the ffi variant (also because it uses helpers here and we want to keep the client
+-- variant too). We don't need to be generic as other macro packages follow a different
+-- route.
+--
+-- Last time I checked "fiets" got no ligature with the "ot" shaper but it did get one
+-- with the "uniscribe" shaper ... somewhat puzzling .. but "effe" worked okay. Maybe
+-- there is some built-in heuristic interfering? When Idris an I tested fonts we had
+-- similar differences with arabic so maybe we miss a point here.
+--
+-- native font plugin > hb > string : fi-
+-- font plugin > hb > text : U+00066 U+00069 U+0002D
+-- font plugin > hb > result : U+00066 U+00069 U+0002D
+--
+-- uniscribe font plugin > hb > string : fi-
+-- font plugin > hb > text : U+00066 U+00069 U+0002D
+-- font plugin > hb > result : U+0FB01 U+0002D
+--
+-- native font plugin > hb > string : ets
+-- font plugin > hb > text : U+00065 U+00074 U+00073
+-- font plugin > hb > result : U+00065 U+00074 U+00073
+--
+-- uniscribe font plugin > hb > string : ets
+-- font plugin > hb > text : U+00065 U+00074 U+00073
+-- font plugin > hb > result : U+00065 U+00074 U+00073
+--
+-- native font plugin > hb > string : fiets
+-- font plugin > hb > text : U+00066 U+00069 U+00065 U+00074 U+00073
+-- font plugin > hb > result : U+00066 U+00069 U+00065 U+00074 U+00073
+--
+-- uniscribe font plugin > hb > string : fiets
+-- font plugin > hb > text : U+00066 U+00069 U+00065 U+00074 U+00073
+-- font plugin > hb > result : U+0FB01 U+00065 U+00074 U+00073
+
+local report = utilities.hb.report or print
+
+local hblib = optional and (optional.hb or optional.test)
+
+if not hblib then
+ report("no hblib found, you can try the ffi variant")
+ return
+end
+
+local hb_initialize = hblib.initialize
+local hb_getversion = hblib.getversion
+local hb_getshapers = hblib.getshapers
+local hb_loadfont = hblib.loadfont
+local hb_shapestring = hblib.shapestring
+
+if not hb_initialize then
+ report("no functions in hblib found, you can try the ffi variant")
+ return
+end
+
+local loaddata = io.loaddata
+local findlib = resolvers.findlib
+local concat = table.concat
+local utfchar = utf.char
+local packtoutf8 = utilities.hb.helpers.packtoutf8
+local packtoutf32 = utilities.hb.helpers.packtoutf32
+local report = utilities.hb.report or print
+local fontdata = fonts.hashes.identifiers
+local initialized = nil
+local loaded = { }
+local shared = { }
+local libname = os.name == "windows" and "libharfbuzz-0" or "libharfbuzz"
+
+local shapers = {
+ native = { "ot", "uniscribe", "fallback" },
+ uniscribe = { "uniscribe", "ot", "fallback" },
+ -- uniscribe = { "uniscribe", "fallback" }, -- stalls without fallback when no uniscribe present
+ fallback = { "fallback" },
+}
+
+local mode = 32
+
+function utilities.hb.methods.internal(font,data,rlmode,text,leading,trailing)
+ if initialized == nil then
+ local filename = findlib(libname)
+ initialized = hb_initialize(filename)
+ if initialized then
+ report("using hb library version %a, supported shapers: %,t",hb_getversion(),hb_getshapers())
+ else
+ report("unable to locate hb library")
+ initialize = false
+ end
+ end
+ if initialized then
+ local instance = loaded[font]
+ if instance == nil then
+ local tfmdata = fontdata[font]
+ local resources = tfmdata.resources
+ local filename = resources.filename
+ instance = shared[filename]
+ if instance == nil then
+ local wholefont = loaddata(filename)
+ if wholefont then
+ instance = hb_loadfont(font,wholefont)
+ end
+ if not instance then
+ instance = false
+ end
+ shared[filename] = instance
+ end
+ loaded[font] = instance
+ end
+ if instance then
+ if mode ==32 then
+ text = packtoutf32(text,leading,trailing)
+ else
+ text = packtoutf8(text,leading,trailing) -- doesn't work ok (no time not to figure it out)
+ end
+ local result = hb_shapestring (
+ instance,
+ data.script or "dflt",
+ data.language or "dflt",
+ rlmode < 0 and "rtl" or "ltr",
+ shapers[data.shaper] or shapers.native,
+ data.featureset or { },
+ text,
+ rlmode < 0,
+ mode
+ )
+ -- inspect(result)
+ return result
+ end
+ end
+end
diff --git a/tex/context/base/mkxl/font-phb.lmt b/tex/context/base/mkxl/font-phb.lmt
new file mode 100644
index 000000000..43edfd33f
--- /dev/null
+++ b/tex/context/base/mkxl/font-phb.lmt
@@ -0,0 +1,560 @@
+if not modules then modules = { } end modules ['font-phb'] = {
+ version = 1.000, -- 2016.10.10,
+ comment = "companion to font-txt.mkiv",
+ original = "derived from a prototype by Kai Eigner",
+ author = "Hans Hagen", -- so don't blame KE
+ copyright = "TAT Zetwerk / PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files",
+}
+
+-- Some (historic) explanation can be found in the font-phb.lua file. To summarize:
+-- this code kind of old and originates from the times that Idris was making a font
+-- that should work with context and uniscribe. When we started with mkiv there were
+-- no libraries, but at some point Kai Eigner made an ffi interface to the harfbuzz
+-- library that showed up. His code was adapted to ConTeXt so that we could test
+-- Idris fonts (the library could use uniscribe which served as refeence for his
+-- fonts). Some experiences were was wrapped up in articles. Interesting was that
+-- sometimes context, uniscribe and/or native hb could not agree on how to interpret
+-- font features and subtle differences could occur.
+--
+-- This file is made from font-phb.lua and I stripped the components code because
+-- it made no sense. The files were eventually added because I did some cleanup and
+-- didn't want to carry old stuff around without also sort of maintaining it. I can
+-- probably strip away even more code. I might pick up this thread when Idris picks
+-- up his font making.
+--
+-- Todo: use the new (context) advance and offset features.
+
+local next, tonumber, pcall, rawget = next, tonumber, pcall, rawget
+
+local concat = table.concat
+local sortedhash = table.sortedhash
+local formatters = string.formatters
+
+local fonts = fonts
+local otf = fonts.handlers.otf
+local texthandler = otf.texthandler
+
+local fontdata = fonts.hashes.identifiers
+
+local nuts = nodes.nuts
+local tonode = nuts.tonode
+local tonut = nuts.tonut
+
+local remove_node = nuts.remove
+
+local getboth = nuts.getboth
+local getnext = nuts.getnext
+local setnext = nuts.setnext
+local getprev = nuts.getprev
+local setprev = nuts.setprev
+local getid = nuts.getid
+local getchar = nuts.getchar
+local setchar = nuts.setchar
+local setlink = nuts.setlink
+local setoffsets = nuts.setoffsets
+local getwidth = nuts.getwidth
+local setwidth = nuts.setwidth
+
+local copy_node = nuts.copy
+local find_tail = nuts.tail
+
+local nodepool = nuts.pool
+local new_kern = nodepool.fontkern
+local new_glyph = nodepool.glyph
+
+local nodecodes = nodes.nodecodes
+local glyph_code = nodecodes.glyph
+local glue_code = nodecodes.glue
+
+local skipped = {
+ -- we assume that only valid features are set but maybe we need a list
+ -- of valid hb features as there can be many context specific ones
+ mode = true,
+ features = true,
+ language = true,
+ script = true,
+}
+
+local seenspaces = {
+ [0x0020] = true,
+ [0x00A0] = true,
+ [0x0009] = true, -- indeed
+ [0x000A] = true, -- indeed
+ [0x000D] = true, -- indeed
+}
+
+-- helpers
+
+local helpers = { }
+local methods = { }
+local initialized = { } -- we don't polute the shared table
+
+local method = "internal" -- a bit misleading name: it's the optional module
+local shaper = "native" -- "uniscribe"
+local report = logs.reporter("font plugin","hb")
+
+utilities.hb = {
+ methods = methods,
+ helpers = helpers,
+ report = report,
+}
+
+do
+
+ local toutf8 = string.toutf8
+ local toutf32 = string.toutf32
+
+ function helpers.packtoutf8(text,leading,trailing)
+ if leading then
+ text[0] = 32
+ end
+ if trailing then
+ text[#text+1] = 32
+ end
+ return toutf8(text)
+ end
+
+ function helpers.packtoutf32(text,leading,trailing)
+ if leading then
+ text[0] = 32
+ end
+ if trailing then
+ text[#text+1] = 32
+ end
+ return toutf32(text)
+ end
+
+end
+
+local function initialize(font)
+
+ local tfmdata = fontdata[font]
+ local resources = tfmdata.resources
+ local shared = tfmdata.shared
+ local filename = resources.filename
+ local features = shared.features
+ local descriptions = shared.rawdata.descriptions
+ local characters = tfmdata.characters
+ local featureset = { }
+ local copytochar = shared.copytochar -- indextounicode
+ local spacewidth = nil -- unscaled
+ local factor = tfmdata.parameters.factor
+ local marks = resources.marks or { }
+
+ -- could be shared but why care about a few extra tables
+
+ if not copytochar then
+ copytochar = { }
+ -- let's make sure that we have an indexed table and not a hash
+ local max = 0
+ for k, v in next, descriptions do
+ if v.index > max then
+ max = v.index
+ end
+ end
+ for i=0,max do
+ copytochar[i] = i
+ end
+ -- the normal mapper
+ for k, v in next, descriptions do
+ copytochar[v.index] = k
+ end
+ shared.copytochar = copytochar
+ end
+
+ -- independent from loop as we have unordered hashes
+
+ if descriptions[0x0020] then
+ spacewidth = descriptions[0x0020].width
+ elseif descriptions[0x00A0] then
+ spacewidth = descriptions[0x00A0].width
+ end
+
+ for k, v in sortedhash(features) do
+ if #k > 4 then
+ -- unknown ones are ignored anyway but we can assume that the current
+ -- (and future) extra context features use more verbose names
+ elseif skipped[k] then
+ -- we don't want to pass language and such so we block a few features
+ -- explicitly
+ elseif v == "yes" or v == true then
+ featureset[#featureset+1] = k .. "=1" -- cf command line (false)
+ elseif v == "no" or v == false then
+ featureset[#featureset+1] = k .. "=0" -- cf command line (true)
+ elseif type(v) == "number" then
+ featureset[#featureset+1] = k .. "=" .. v -- cf command line (alternate)
+ else
+ -- unset
+ end
+ end
+
+ local data = {
+ language = features.language, -- do we need to uppercase and padd to 4 ?
+ script = features.script, -- do we need to uppercase and padd to 4 ?
+ features = #featureset > 0 and concat(featureset,",") or "", -- hash
+ featureset = #featureset > 0 and featureset or nil,
+ copytochar = copytochar,
+ spacewidth = spacewidth,
+ filename = filename,
+ marks = marks,
+ factor = factor,
+ characters = characters, -- the loaded font (we use its metrics which is more accurate)
+ method = features.method or method,
+ shaper = features.shaper or shaper,
+ }
+ initialized[font] = data
+ return data
+end
+
+-- In many cases this gives compatible output but especially with respect to spacing and user
+-- discretionaries that mix fonts there can be different outcomes. We also have no possibility
+-- to tweak and cheat. Of course one can always run a normal node mode pass with specific
+-- features first but then one can as well do all in node mode. So .. after a bit of playing
+-- around I redid this one from scratch and also added tracing.
+
+local trace_colors = false trackers.register("fonts.plugins.hb.colors", function(v) trace_colors = v end)
+local trace_details = false trackers.register("fonts.plugins.hb.details",function(v) trace_details = v end)
+local check_id = false
+
+local setcolor = nodes.tracers.colors.set
+local resetcolor = nodes.tracers.colors.reset
+
+table.setmetatableindex(methods,function(t,k)
+ local l = "font-phb-imp-" .. k .. ".lmt"
+ report("start loading method %a from %a",k,l)
+ dofile(resolvers.findfile(l))
+ local v = rawget(t,k)
+ if v then
+ report("loading method %a succeeded",k)
+ else
+ report("loading method %a failed",k)
+ v = function() return { } end
+ end
+ t[k] = v
+ return v
+end)
+
+local inandout do
+
+ local utfbyte = utf.byte
+ local utfchar = utf.char
+ local utf3208 = utf.utf32_to_utf8_le
+
+ inandout = function(text,result,first,last,copytochar)
+ local s = { }
+ local t = { }
+ local r = { }
+ local f = formatters["%05U"]
+ for i=1,#text do
+ local c = text[i]
+ -- t[#t+1] = f(utfbyte(utf3208(c)))
+ s[#s+1] = utfchar(c)
+ t[#t+1] = f(c)
+ end
+ for i=first,last do
+ r[#r+1] = f(copytochar[result[i][1]])
+ end
+ return s, t, r
+ end
+
+end
+
+local function harfbuzz(head,font,dynamic,rlmode,start,stop,text,leading,trailing)
+ local data = initialized[font]
+
+ if not data then
+ data = initialize(font)
+ end
+
+ if check_id then
+ if getid(start) ~= glyph_code then
+ report("error: start is not a glyph")
+ return head
+ elseif getid(stop) ~= glyph_code then
+ report("error: stop is not a glyph")
+ return head
+ end
+ end
+ local size = #text -- original text, without spaces
+ local result = methods[data.method](font,data,rlmode,text,leading,trailing)
+ local length = result and #result or 0
+
+ if length == 0 then
+ -- report("warning: no result")
+ return head
+ end
+
+ local factor = data.factor
+ local marks = data.marks
+ local spacewidth = data.spacewidth
+ local copytochar = data.copytochar
+ local characters = data.characters
+
+ -- the text analyzer is only partially clever so we must assume that we get
+ -- inconsistent lists
+
+ -- we could check if something has been done (replacement or kern or so) but
+ -- then we pass around more information and need to check a lot and spaces
+ -- are kind of spoiling that game (we need a different table then) .. more
+ -- pain than gain
+
+ -- we could play with 0xFFFE as boundary
+
+ local current = start
+ local prev = nil
+ local glyph = nil
+
+ local first = 1
+ local last = length
+ local next = nil -- todo: keep track of them
+ local prev = nil -- todo: keep track of them
+
+ if leading then
+ first = first + 1
+ end
+ if trailing then
+ last = last - 1
+ end
+
+ local position = first
+ local cluster = 0
+ local glyph = nil
+ local index = 0
+ local count = 1
+ local saved = nil
+
+ if trace_details then
+ report("start run, original size: %i, result index: %i upto %i",size,first,last)
+ local s, t, r = inandout(text,result,first,last,copytochar)
+ report("method : %s",data.method)
+ report("shaper : %s",data.shaper)
+ report("string : %t",s)
+ report("text : % t",t)
+ report("result : % t",r)
+ end
+
+ -- okay, after some experiments, it became clear that more complex code aimed at
+ -- optimization doesn't pay off as complexity also demands more testing
+
+ for i=first,last do
+ local r = result[i]
+ local unicode = copytochar[r[1]] -- can be private of course
+ --
+ cluster = r[2] + 1 -- starts at zero
+ --
+ if position == cluster then
+ if i == first then
+ index = 1
+ if trace_details then
+ report("[%i] position: %i, cluster: %i, index: %i, starting",i,position,cluster,index)
+ end
+ else
+ index = index + 1
+ if trace_details then
+ report("[%i] position: %i, cluster: %i, index: %i, next step",i,position,cluster,index)
+ end
+ end
+ elseif position < cluster then
+ -- a new cluster
+ current = getnext(current)
+ position = position + 1
+ size = size - 1
+ for p=position,cluster-1 do
+ head, current = remove_node(head,current,true)
+ if trace_details then
+ report("[%i] position: %i, cluster: %i, index: -, removing node",i,p,cluster)
+ end
+ size = size - 1
+ end
+ position = cluster
+ index = 1
+ glyph = nil
+ if trace_details then
+ report("[%i] position: %i, cluster: %i, index: %i, arriving",i,cluster,position,index)
+ end
+ else -- maybe a space got properties
+ if trace_details then
+ report("position: %i, cluster: %i, index: %i, quitting due to fatal inconsistency",position,cluster,index)
+ end
+ return head
+ end
+ local copied = false
+ if glyph then
+ if trace_details then
+ report("[%i] position: %i, cluster: %i, index: %i, copying glyph, unicode %U",i,position,cluster,index,unicode)
+ end
+ local g = copy_node(glyph)
+ if trace_colors then
+ resetcolor(g)
+ end
+ setlink(current,g,getnext(current)) -- insert_before
+ current = g
+ copied = true
+ else
+ if trace_details then
+ report("[%i] position: %i, cluster: %i, index: %i, using glyph, unicode %U",i,position,cluster,index,unicode)
+ end
+ glyph = current
+ end
+ --
+ if not current then
+ if trace_details then
+ report("quitting due to unexpected end of node list")
+ end
+ return head
+ end
+ --
+ local id = getid(current)
+ if id ~= glyph_code then
+ if trace_details then
+ report("glyph expected in node list")
+ end
+ return head
+ end
+ --
+ -- really, we can get a tab (9), lf (10), or cr(13) back in cambria .. don't ask me why
+ --
+ local prev, next = getboth(current)
+ --
+ -- assign glyph: first in run
+ --
+ setchar(current,unicode)
+ if trace_colors then
+ count = (count == 8) and 1 or count + 1
+ setcolor(current,"trace:"..count)
+ end
+ --
+ local x_offset = r[3] -- r.dx
+ local y_offset = r[4] -- r.dy
+ local x_advance = r[5] -- r.ax
+ ----- y_advance = r[6] -- r.ay
+ local left = 0
+ local right = 0
+ local dx = 0
+ local dy = 0
+ if trace_details then
+ if x_offset ~= 0 or y_offset ~= 0 or x_advance ~= 0 then -- or y_advance ~= 0
+ report("[%i] position: %i, cluster: %i, index: %i, old, xoffset: %p, yoffset: %p, xadvance: %p, width: %p",
+ i,position,cluster,index,x_offset*factor,y_offset*factor,x_advance*factor,characters[unicode].width)
+ end
+ end
+ if y_offset ~= 0 then
+ dy = y_offset * factor
+ end
+ if rlmode >= 0 then
+ -- l2r marks and rest
+ if x_offset ~= 0 then
+ dx = x_offset * factor
+ end
+ local width = characters[unicode].width
+ local delta = x_advance * factor
+ if delta ~= width then
+ -- right = -(delta - width)
+ right = delta - width
+ end
+ elseif marks[unicode] then -- why not just the next loop
+ -- r2l marks
+ if x_offset ~= 0 then
+ dx = -x_offset * factor
+ end
+ else
+ -- r2l rest
+ local width = characters[unicode].width
+ local delta = (x_advance - x_offset) * factor
+ if delta ~= width then
+ left = delta - width
+ end
+ if x_offset ~= 0 then
+ right = x_offset * factor
+ end
+ end
+ if copied or dx ~= 0 or dy ~= 0 then
+ setoffsets(current,dx,dy)
+ end
+ if left ~= 0 then
+ setlink(prev,new_kern(left),current) -- insertbefore
+ if current == head then
+ head = prev
+ end
+ end
+ if right ~= 0 then
+ local kern = new_kern(right)
+ setlink(current,kern,next)
+ current = kern
+ end
+ if trace_details then
+ if dy ~= 0 or dx ~= 0 or left ~= 0 or right ~= 0 then
+ report("[%i] position: %i, cluster: %i, index: %i, new, xoffset: %p, yoffset: %p, left: %p, right: %p",i,position,cluster,index,dx,dy,left,right)
+ end
+ end
+ end
+ --
+ if trace_details then
+ report("[-] position: %i, cluster: %i, index: -, at end",position,cluster)
+ end
+ if size > 1 then
+ current = getnext(current)
+ for i=1,size-1 do
+ if trace_details then
+ report("[-] position: %i + %i, cluster: -, index: -, removing node",position,i)
+ end
+ head, current = remove_node(head,current,true)
+ end
+ end
+ --
+ -- We see all kind of interesting spaces come back (like tabs in cambria) so we do a bit of
+ -- extra testing here.
+ --
+ if leading then
+ local r = result[1]
+ local unicode = copytochar[r[1]]
+ if seenspaces[unicode] then
+ local x_advance = r[5]
+ local delta = x_advance - spacewidth
+ if delta ~= 0 then
+ -- nothing to do but jump one slot ahead
+ local prev = getprev(start)
+ if getid(prev) == glue_code then
+ local dx = delta * factor
+ setwidth(prev,getwidth(prev) + dx)
+ if trace_details then
+ report("compensating leading glue by %p due to codepoint %U",dx,unicode)
+ end
+ else
+ report("no valid leading glue node")
+ end
+ end
+ end
+ end
+ --
+ if trailing then
+ local r = result[length]
+ local unicode = copytochar[r[1]]
+ if seenspaces[unicode] then
+ local x_advance = r[5]
+ local delta = x_advance - spacewidth
+ if delta ~= 0 then
+ local next = getnext(stop)
+ if getid(next) == glue_code then
+ local dx = delta * factor
+ setwidth(next,getwidth(next) + dx)
+ if trace_details then
+ report("compensating trailing glue by %p due to codepoint %U",dx,unicode)
+ end
+ else
+ report("no valid trailing glue node")
+ end
+ end
+ end
+ end
+ --
+ if trace_details then
+ report("run done")
+ end
+ return head
+end
+
+otf.registerplugin("harfbuzz",function(head,font,dynamic,direction)
+ return texthandler(head,font,dynamic,direction,harfbuzz)
+end)
diff --git a/tex/context/base/mkxl/font-txt.lmt b/tex/context/base/mkxl/font-txt.lmt
new file mode 100644
index 000000000..ce177e114
--- /dev/null
+++ b/tex/context/base/mkxl/font-txt.lmt
@@ -0,0 +1,583 @@
+if not modules then modules = { } end modules ['font-txt'] = {
+ version = 1.001,
+ comment = "companion to font-ini.mkiv",
+ original = "derived from a prototype by Kai Eigner",
+ author = "Hans Hagen", -- so don't blame KE
+ copyright = "TAT Zetwerk / PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+-- The next code is derived from a snippet handler prototype by Kai Eigner and
+-- resembles the main loop of the Lua font handler but I decided use a more generic
+-- (and pluggable) approach and not hook it into the already present opentype
+-- handler. This is cleaner as it cannot interfere with the Lua font processor
+-- (which does some more things) and is also better performance wise. It also makes
+-- it possible to support other handlers as history has proven that there are no
+-- universal solution in computer land. Most of the disc logic is kept but done
+-- slightly different.
+--
+-- The code is heavily optimized and generalized so there can be errors. As
+-- mentioned, the plug mode can be used for alternative font handlers. A font is
+-- still loaded but the node and base mode handlers are ignored. Plugins are
+-- unlikely to work well in context as they can mess up attribute driven subsystem,
+-- so they are not officially supported. The language and script options are
+-- available in the usual way.
+--
+-- The code collects snippets, either or not with spacing around them and partially
+-- running over disc nodes. The r2l "don't assume disc and collect larger chunks" is
+-- not robust so I got rid of that branch. This is somewhat similar to the Lua font
+-- handler.
+--
+-- An alternative is to run over longer strings with dummy chars (unicode objects) as
+-- markers for whatever is in the list but that becomes tricky with mixed fonts and
+-- reconstruction becomes a bit of a mess then, especially because disc nodes force
+-- us to backtrack and look at several solutions. It also has a larger memory
+-- footprint. Some tests demonstrated that it has no gain and only adds complexity.
+--
+-- This (derived) variant is better suited for context and seems to work ok in the
+-- generic variant. I also added some context specific tracing to the code. This
+-- variant uses the plug model provided in the context font system. So, in context,
+-- using the built in Lua handler is the better alternative, also because it has
+-- extensive tracing features. Context users would loose additional functionality
+-- that has been provided for a decade and therefore plugins are not officially
+-- supported (at least not by me, unless I use them myself).
+--
+-- There is no checking here for already processed characters so best not mix this
+-- variant with code that does similar things. If this code evolves depends on the
+-- useability. Kai's code can now be found on github where it is used with a harfbuzz
+-- library. We add this kind of stuff because occasionally we compare engines and
+-- Kai sends me examples and I then need to check context.
+--
+-- One important difference between Kai's approach and the one used in ConTeXt is
+-- that we use utf-32 instead of utf-8. Once I figured out that clusters were just
+-- indices into the original text that made more sense. The first implementation
+-- used the command line tool (binary), then I went for ffi (library).
+--
+-- Beware: this file only implements the framework for plugins. Plugins themselves
+-- are in other files (e.g. font-phb*.lua). On the todo list is a uniscribe plugin
+-- because that is after all the reference for opentype support, but that interface
+-- needs a bit more work (so it might never happen).
+--
+-- Usage: see m-fonts-plugins.mkiv. As it's a nice test for ffi support that file
+-- migth be added to the distribution somewhere in the middle of 2017 when the ffi
+-- interface has been tested a bit more. Okay, it's 2012 now and we're way past that
+-- date but we never had a reason for adding it to the ConTeXt distribution. It
+-- should still work okay because I occasionally checked it against progress made in
+-- the engines and used newer helpers.
+--
+-- Here is an example of usage:
+--
+-- \starttext
+-- \definefontfeature[test][mode=plug,features=text]
+-- \start
+-- \showfontkerns
+-- \definedfont[Serif*test]
+-- \input tufte \par
+-- \stop
+-- \stoptext
+
+local fonts = fonts
+local otf = fonts.handlers.otf
+local nodes = nodes
+
+local utfchar = utf.char
+
+local nuts = nodes.nuts
+
+local getnext = nuts.getnext
+local setnext = nuts.setnext
+local getprev = nuts.getprev
+local setprev = nuts.setprev
+local getid = nuts.getid
+local getsubtype = nuts.getsubtype
+local getfont = nuts.getfont
+local getchar = nuts.getchar
+local getdisc = nuts.getdisc
+local setdisc = nuts.setdisc
+local getboth = nuts.getboth
+local getscales = nuts.getscales
+local setlink = nuts.setlink
+local getkern = nuts.getkern
+local getwidth = nuts.getwidth
+
+local ischar = nuts.ischar
+local isnextchar = nuts.isnextchar
+local isglyph = nuts.isglyph
+local traverse_id = nuts.traverse_id
+local usesfont = nuts.uses_font
+
+local copy_node_list = nuts.copy_list
+local find_node_tail = nuts.tail
+local flush_list = nuts.flush_list
+local free_node = nuts.free
+local end_of_math = nuts.end_of_math
+local start_of_par = nuts.start_of_par
+
+local nodecodes = nodes.nodecodes
+
+local glyph_code = nodecodes.glyph
+local glue_code = nodecodes.glue
+local disc_code = nodecodes.disc
+local kern_code = nodecodes.kern
+local math_code = nodecodes.math
+local dir_code = nodecodes.dir
+local par_code = nodecodes.par
+
+local righttoleft_code = nodes.dirvalues.righttoleft
+
+local txtdirstate = otf.helpers.txtdirstate
+local pardirstate = otf.helpers.pardirstate
+
+local fonthashes = fonts.hashes
+local fontdata = fonthashes.identifiers
+
+local function deletedisc(head)
+ local current = head
+ local next = nil
+ while current do
+ next = getnext(current)
+ if getid(current) == disc_code then
+ local pre, post, replace, pre_tail, post_tail, replace_tail = getdisc(current,true)
+ setdisc(current)
+ if pre then
+ flush_list(pre)
+ end
+ if post then
+ flush_list(post)
+ end
+ local p, n = getboth(current)
+ if replace then
+ if current == head then
+ head = replace
+ setprev(replace) -- already nil
+ else
+ setlink(p,replace)
+ end
+ setlink(replace_tail,n) -- was: setlink(n,replace_tail)
+ elseif current == head then
+ head = n
+ setprev(n)
+ else
+ setlink(p,n)
+ end
+ free_node(current)
+ end
+ current = next
+ end
+ return head
+end
+
+-- As we know that we have the same font we can probably optimize this a bit more.
+-- Although we can have more in disc nodes than characters and kerns we only support
+-- those two types.
+
+local function eqnode(n,m) -- no real improvement in speed
+ local n_char = isglyph(n)
+ if n_char then
+ return n_char == ischar(m,getfont(n))
+ elseif n_id == kern_code then
+ return getkern(n) == getkern(m)
+ end
+end
+
+local function equalnode(n,m)
+ if not n then
+ return not m
+ elseif not m then
+ return false
+ end
+ local n_char, n_id = isglyph(n)
+ if n_char then
+ return n_char == ischar(m,n_id) -- n_id == n_font
+ elseif n_id == whatsit_code then
+ return false
+ elseif n_id == glue_code then
+ return true
+ elseif n_id == kern_code then
+ return getkern(n) == getkern(m)
+ elseif n_id == disc_code then
+ local n_pre, n_post, n_replace = getdisc(n)
+ local m_pre, m_post, m_replace = getdisc(m)
+ while n_pre and m_pre do
+ if not eqnode(n_pre,m_pre) then
+ return false
+ end
+ n_pre = getnext(n_pre)
+ m_pre = getnext(m_pre)
+ end
+ if n_pre or m_pre then
+ return false
+ end
+ while n_post and m_post do
+ if not eqnode(n_post,m_post) then
+ return false
+ end
+ n_post = getnext(n_post)
+ m_post = getnext(m_post)
+ end
+ if n_post or m_post then
+ return false
+ end
+ while n_replace and m_replace do
+ if not eqnode(n_replace,m_replace) then
+ return false
+ end
+ n_replace = getnext(n_replace)
+ m_replace = getnext(m_replace)
+ end
+ if n_replace or m_replace then
+ return false
+ end
+ return true
+ end
+ return false
+end
+
+-- The spacing hackery is not nice. The text can get leading and trailing spaces
+-- and even mid spaces while the start and stop nodes not always are glues then
+-- so the plugin really needs to do some testing there. We could pass more context
+-- but it doesn't become much better.
+--
+-- The attribute gets passed for tracing purposes. We could support it (not that
+-- hard to do) but as we don't test strickly for fonts (in disc nodes) we are not
+-- compatible anyway. It would also mean more testing. So, don't use this mixed
+-- with node and base mode in context.
+--
+-- We don't distinguish between modes in treatment (so no r2l assumptions) and
+-- no cheats for scripts that might not use discretionaries. Such hacks can work
+-- in predictable cases but in context one can use a mix all kind of things and
+-- users do that. On the other hand, we do support longer glyph runs in both modes
+-- so there we gain a bit.
+
+
+do
+
+ local currentscale, currentxscale, currentyscale
+
+ local function texthandler(head,font,dynamic,rlmode,handler,startspacing,stopspacing,nesting)
+ if not head then
+ return
+ end
+ if startspacing == nil then
+ startspacing = false
+ end
+ if stopspacing == nil then
+ stopspacing = false
+ end
+
+ if getid(head) == par_code and start_of_par(head) then
+ rlmode = pardirstate(head)
+ elseif rlmode == righttoleft_code then
+ rlmode = -1
+ else
+ rlmode = 0
+ end
+
+ local dirstack = { }
+ local rlparmode = 0
+ local topstack = 0
+ local text = { }
+ local size = 0
+ local current = head
+ local start = nil
+ local stop = nil
+ local startrlmode = rlmode
+
+ local function handle(leading,trailing) -- what gets passed can become configureable: e.g. utf 8
+ local stop = current or start -- hm, what with outer stop
+ if getid(stop) ~= glyph_code then
+ stop = getprev(stop)
+ end
+ head = handler(head,font,dynamic,rlmode,start,stop,text,leading,trailing) -- handler can adapt text
+ size = 0
+ text = { }
+ start = nil
+ end
+
+ -- maybe: isnextchar
+
+ while current do
+ -- local char, id = ischar(current,font,dynamic)
+ local char, id = ischar(current,font,dynamic,currentscale,currentxscale,currentyscale)
+ if char then
+ -- local s, sx, sy = getscales(current)
+ -- if s ~= currentscale or sx ~= currentxscale or sy ~= currentyscale then
+ -- if start and size > 0 then
+ -- handle(startspacing,false)
+ -- end
+ -- startspacing = false
+ -- currentscale, currentxscale, currentyscale = s, sx, sy
+ -- end
+ if not start then
+ start = current
+ startrlmode = rlmode
+ end
+ local char = getchar(current)
+ size = size + 1
+ text[size] = char
+ current = getnext(current)
+ elseif char == false then
+ -- so a mixed font
+ if start and size > 0 then
+ handle(startspacing,false)
+ end
+ startspacing = false
+ local s, sx, sy = getscales(current)
+ if s ~= currentscale or sx ~= currentxscale or sy ~= currentyscale then
+ if start and size > 0 then
+ handle(startspacing,false)
+ end
+ startspacing = false
+ currentscale, currentxscale, currentyscale = s, sx, sy
+ -- todo: safeguard against a loop
+ else
+ current = getnext(current)
+ currentscale, currentxscale, currentyscale = false, false, false
+ end
+ elseif id == glue_code then
+ -- making this branch optional i.e. always use the else doesn't really
+ -- make a difference in performance (in hb) .. tricky anyway as we can
+ local width = getwidth(current)
+ if width > 0 then
+ if start and size > 0 then
+ handle(startspacing,true)
+ end
+ startspacing = true
+ stopspacing = false
+ else
+ if start and size > 0 then
+ head = handle(startspacing)
+ end
+ startspacing = false
+ stopspacing = false
+ end
+ current = getnext(current)
+ elseif id == disc_code and usesfont(current,font) then -- foo|-|bar : has hbox
+ -- This looks much like the original code but I don't see a need to optimize
+ -- for e.g. deva or r2l fonts. If there are no disc nodes then we won't see
+ -- this branch anyway and if there are, we should just deal with them.
+ --
+ -- There is still some weird code here ... start/stop and such. When I'm in
+ -- the mood (or see a need) I'll rewrite this bit.
+
+ -- bug: disc in last word moves to end (in practice not an issue as one
+ -- doesn't want a break there)
+
+ local pre = nil
+ local post = nil
+ local currentnext = getnext(current)
+ local current_pre, current_post, current_replace = getdisc(current)
+ setdisc(current) -- why, we set it later
+ if start then
+ pre = copy_node_list(start,current)
+ stop = getprev(current)
+ -- why also current and not:
+ -- pre = copy_node_list(start,stop)
+ if start == head then
+ head = current
+ end
+ setlink(getprev(start),current)
+ setlink(stop,current_pre)
+ current_pre = start
+ setprev(current_pre)
+ start = nil
+ stop = nil
+ startrlmode = rlmode
+ end
+ while currentnext do
+ local char, id = ischar(currentnext,font)
+ if char or id == disc_code then
+ stop = currentnext
+ currentnext = getnext(currentnext)
+ elseif id == glue_code then
+ local width = getwidth(currentnext)
+ if width and width > 0 then
+ stopspacing = true
+ else
+ stopspacing = false
+ end
+ break
+ else
+ break
+ end
+ end
+ if stop then
+ local currentnext = getnext(current)
+ local stopnext = getnext(stop)
+ post = copy_node_list(currentnext,stopnext)
+ if current_post then
+ setlink(find_node_tail(current_post),currentnext)
+ else
+ setprev(currentnext)
+ current_post = currentnext
+ end
+ setlink(current,stopnext)
+ setnext(stop)
+ stop = nil
+ end
+ if pre then
+ setlink(find_node_tail(pre),current_replace)
+ current_replace = pre
+ pre = nil
+ end
+ if post then
+ if current_replace then
+ setlink(find_node_tail(current_replace),post)
+ else
+ current_replace = post
+ end
+ post = nil
+ end
+ size = 0 -- hm, ok, start is also nil now
+ text = { }
+ if current_pre then
+ current_pre = texthandler(current_pre,font,dynamic,rlmode,handler,startspacing,false,"pre")
+ end
+ if current_post then
+ current_post = texthandler(current_post,font,dynamic,rlmode,handler,false,stopspacing,"post")
+ end
+ if current_replace then
+ current_replace = texthandler(current_replace,font,dynamic,rlmode,handler,startspacing,stopspacing,"replace")
+ end
+ startspacing = false
+ stopspacing = false
+ local cpost = current_post and find_node_tail(current_post)
+ local creplace = current_replace and find_node_tail(current_replace)
+ local cpostnew = nil
+ local creplacenew = nil
+ local newcurrent = nil
+ while cpost and equalnode(cpost,creplace) do
+ cpostnew = cpost
+ creplacenew = creplace
+ if creplace then
+ creplace = getprev(creplace)
+ end
+ cpost = getprev(cpost)
+ end
+ if cpostnew then
+ if cpostnew == current_post then
+ current_post = nil
+ else
+ setnext(getprev(cpostnew))
+ end
+ flush_list(cpostnew)
+ if creplacenew == current_replace then
+ current_replace = nil
+ else
+ setnext(getprev(creplacenew))
+ end
+ local c = getnext(current)
+ setlink(current,creplacenew)
+ local creplacenewtail = find_node_tail(creplacenew)
+ setlink(creplacenewtail,c)
+ newcurrent = creplacenewtail
+ end
+ current_post = current_post and deletedisc(current_post)
+ current_replace = current_replace and deletedisc(current_replace)
+ local cpre = current_pre
+ local creplace = current_replace
+ local cprenew = nil
+ local creplacenew = nil
+ while cpre and equalnode(cpre, creplace) do
+ cprenew = cpre
+ creplacenew = creplace
+ if creplace then
+ creplace = getnext(creplace)
+ end
+ cpre = getnext(cpre)
+ end
+ if cprenew then
+ cpre = current_pre
+ current_pre = getnext(cprenew)
+ if current_pre then
+ setprev(current_pre)
+ end
+ setnext(cprenew)
+ flush_list(cpre)
+ creplace = current_replace
+ current_replace = getnext(creplacenew)
+ if current_replace then
+ setprev(current_replace)
+ end
+ setlink(getprev(current),creplace)
+ if current == head then
+ head = creplace
+ end
+ setlink(creplacenew,current)
+ end
+ setdisc(current,current_pre,current_post,current_replace)
+ current = currentnext
+ else
+ if start and size > 0 then
+ handle(startspacing,stopspacing)
+ end
+ startspacing = false
+ stopspacing = false
+ if id == math_code then
+ current = getnext(end_of_math(current))
+ elseif id == dir_code then
+ startspacing = false
+ topstack, rlmode = txtdirstate(current,dirstack,topstack,rlparmode)
+ current = getnext(current)
+ -- elseif id == par_code and start_of_par(current) then
+ -- startspacing = false
+ -- rlparmode, rlmode = pardirstate(current)
+ -- current = getnext(current)
+ else
+ current = getnext(current)
+ end
+ end
+ end
+ if start and size > 0 then
+ handle(startspacing,stopspacing)
+ end
+ return head, true
+ end
+
+ function fonts.handlers.otf.texthandler(head,font,dynamic,direction,action)
+ currentscale = false
+ currentxscale = false
+ currentyscale = false
+ if action then
+ return texthandler(head,font,dynamic,direction == righttoleft_code and -1 or 0,action)
+ else
+ return head, false
+ end
+ end
+
+ -- Next comes a tracer plug into context.
+
+ ----- texthandler = fonts.handlers.otf.texthandler
+ local report_text = logs.reporter("otf plugin","text")
+ local nofruns = 0
+ local nofsnippets = 0
+ local f_unicode = string.formatters["%U"]
+
+ local function showtext(head,font,dynamic,rlmode,start,stop,list,before,after)
+ if list then
+ nofsnippets = nofsnippets + 1
+ local plus = { }
+ for i=1,#list do
+ local u = list[i]
+ list[i] = utfchar(u)
+ plus[i] = f_unicode(u)
+ end
+ report_text("%03i : [%s] %t [%s]-> % t", nofsnippets, before and "+" or "-", list, after and "+" or "-", plus)
+ else
+ report_text()
+ report_text("invalid list")
+ report_text()
+ end
+ return head, false
+ end
+
+ fonts.handlers.otf.registerplugin("text",function(head,font,dynamic,direction)
+ nofruns = nofruns + 1
+ nofsnippets = 0
+ report_text("start run %i",nofruns)
+ local h, d = texthandler(head,font,dynamic,direction,showtext)
+ report_text("stop run %i",nofruns)
+ return h, d
+ end)
+
+end
diff --git a/tex/context/base/mkxl/lpdf-lmt.lmt b/tex/context/base/mkxl/lpdf-lmt.lmt
index b7db8e50d..c0f02d75b 100644
--- a/tex/context/base/mkxl/lpdf-lmt.lmt
+++ b/tex/context/base/mkxl/lpdf-lmt.lmt
@@ -2291,7 +2291,7 @@ local function flushstreamobj(data,n,dict,comp,nolength)
if done then
b = dict and f_stream_b_d_c(n,dict,size) or f_stream_b_n_c(n,size)
else
- b = dict and f_stream_b_d_u(n,dict,size) or f_stream_b_d_r(n,size)
+ b = dict and f_stream_b_d_u(n,dict,size) or f_stream_b_n_u(n,size)
end
end
flush(f,b)
diff --git a/tex/context/interface/mkii/keys-cs.xml b/tex/context/interface/mkii/keys-cs.xml
index 3006aa9b0..eee6b834e 100644
--- a/tex/context/interface/mkii/keys-cs.xml
+++ b/tex/context/interface/mkii/keys-cs.xml
@@ -767,6 +767,7 @@
<cd:constant name='coupling' value='propojeni'/>
<cd:constant name='couplingway' value='zpusobpropojeni'/>
<cd:constant name='criterium' value='kriterium'/>
+ <cd:constant name='crop' value='crop'/>
<cd:constant name='cropoffset' value='cropoffset'/>
<cd:constant name='crossreference' value='crossreference'/>
<cd:constant name='cssfile' value='cssfile'/>
diff --git a/tex/generic/context/luatex/luatex-fonts-merged.lua b/tex/generic/context/luatex/luatex-fonts-merged.lua
index 453ee0018..1a0cf868e 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 : 2021-04-26 00:51
+-- merge date : 2021-04-26 20:39
do -- begin closure to overcome local limits and interference