summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/doc/luatex/fontloader-luaotfload/luaotfload-letterspace.luax
diff options
context:
space:
mode:
Diffstat (limited to 'Master/texmf-dist/doc/luatex/fontloader-luaotfload/luaotfload-letterspace.luax')
-rw-r--r--Master/texmf-dist/doc/luatex/fontloader-luaotfload/luaotfload-letterspace.luax683
1 files changed, 683 insertions, 0 deletions
diff --git a/Master/texmf-dist/doc/luatex/fontloader-luaotfload/luaotfload-letterspace.luax b/Master/texmf-dist/doc/luatex/fontloader-luaotfload/luaotfload-letterspace.luax
new file mode 100644
index 00000000000..df93dbe1b70
--- /dev/null
+++ b/Master/texmf-dist/doc/luatex/fontloader-luaotfload/luaotfload-letterspace.luax
@@ -0,0 +1,683 @@
+if not modules then modules = { } end modules ['letterspace'] = {
+ version = "2.8",
+ comment = "companion to luaotfload-main.lua",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL; adapted by Philipp Gesang",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+--- This code diverged quite a bit from its origin in Context. Please
+--- do *not* report bugs on the Context list.
+
+local log = luaotfload.log
+local logreport = log.report
+
+-- UF
+texio.write_nl ("------------------")
+texio.write_nl ("!!!!! UF: Using CHANGED luaotfload-letterspace 2017-07-14 !!!!!")
+texio.write_nl ("------------------")
+-- /UF
+
+local getmetatable = getmetatable
+local require = require
+local setmetatable = setmetatable
+local tonumber = tonumber
+
+local next = next
+local nodes, node, fonts = nodes, node, fonts
+
+local nodedirect = nodes.nuts
+
+local getfield = nodedirect.getfield
+local setfield = nodedirect.setfield
+
+local field_setter = function (name) return function (n, ...) setfield (n, name, ...) end end
+local field_getter = function (name) return function (n, ...) getfield (n, name, ...) end end
+
+local getfont = nodedirect.getfont
+local getid = nodedirect.getid
+
+local getnext = nodedirect.getnext or field_getter "next"
+local setnext = nodedirect.setnext or field_setter "next"
+
+local getprev = nodedirect.getprev or field_getter "prev"
+local setprev = nodedirect.setprev or field_setter "prev"
+
+--- since r5336
+local getboth = nodedirect.getboth or function (n)
+ return getprev (n), getnext (n)
+end
+
+local setlink = nodedirect.setlink or function (a, b)
+ setnext (a, b)
+ setprev (b, a)
+end
+
+local getdisc = nodedirect.getdisc or field_getter "disc"
+local setdisc = nodedirect.setdisc or field_setter "disc"
+
+local getsubtype = nodedirect.getsubtype or field_getter "subtype"
+local setsubtype = nodedirect.setsubtype or field_setter "subtype"
+
+local getchar = nodedirect.getchar or field_getter "subtype"
+local setchar = nodedirect.setchar or field_setter "subtype"
+
+local find_node_tail = nodedirect.tail
+local todirect = nodedirect.tonut
+local tonode = nodedirect.tonode
+
+local insert_node_before = nodedirect.insert_before
+local free_node = nodedirect.free -- may cause double free
+local free_node = function (n)
+ logreport ("term", 5, "letterspace", "not calling free_node(%d)", n)
+ -- free_node (n)
+end
+local copy_node = nodedirect.copy
+local new_node = nodedirect.new
+
+local nodepool = nodedirect.pool
+-- local new_kern = nodepool.kern -- UF removed 2017-07-14
+
+local nodecodes = nodes.nodecodes
+
+local glyph_code = nodecodes.glyph
+local kern_code = nodecodes.kern
+local disc_code = nodecodes.disc
+local math_code = nodecodes.math
+local glue_code = nodecodes.glue
+
+local fonthashes = fonts.hashes
+local chardata = fonthashes.characters
+local quaddata = fonthashes.quads
+local otffeatures = fonts.constructors.newfeatures "otf"
+
+--[[doc--
+
+ Since the letterspacing method was derived initially from Context’s
+ typo-krn.lua we keep the sub-namespace “letterspace” inside the
+ “luaotfload” table.
+
+--doc]]--
+
+luaotfload.letterspace = luaotfload.letterspace or { }
+local letterspace = luaotfload.letterspace
+
+letterspace.keepligature = false
+letterspace.keeptogether = false
+
+---=================================================================---
+--- preliminary definitions
+---=================================================================---
+-- We set up a layer emulating some Context internals that are needed
+-- for the letterspacing callback.
+-----------------------------------------------------------------------
+--- node-ini
+-----------------------------------------------------------------------
+
+local bothways = function (t) return table.swapped (t, t) end
+local kerncodes = bothways { [0] = "fontkern"
+ , [1] = "userkern"
+ , [2] = "accentkern"
+ }
+local skipcodes = bothways { [0] = "userskip"
+ , [13] = "spaceskip"
+ , [14] = "xspaceskip"
+ }
+
+kerncodes.kerning = kerncodes.fontkern --- idiosyncrasy
+local kerning_code = kerncodes.kerning
+local userkern_code = kerncodes.userkern
+local userskip_code = skipcodes.userskip
+local spaceskip_code = skipcodes.spaceskip
+local xspaceskip_code = skipcodes.xspaceskip
+
+-----------------------------------------------------------------------
+--- node-res
+-----------------------------------------------------------------------
+
+local glue_spec = new_node "glue_spec"
+
+local new_gluespec = function (width,
+ stretch, shrink,
+ stretch_order, shrink_order)
+ local spec = copy_node(glue_spec)
+ if width then setfield(spec, "width" , width ) end
+ if stretch then setfield(spec, "stretch" , stretch ) end
+ if shrink then setfield(spec, "shrink" , shrink ) end
+ if stretch_order then setfield(spec, "stretch_order", stretch_order) end
+ if shrink_order then setfield(spec, "shrink_order" , shrink_order ) end
+ return spec
+end
+
+local new_glue = function (width, stretch, shrink,
+ stretch_order, shrink_order)
+ local n = new_node "glue"
+ if not width then return n end
+ -- no spec
+ if width == false then
+ local width = tonumber(width)
+ if width then
+ setfield(n, "spec",
+ new_gluespec(width, stretch, shrink,
+ stretch_order, shrink_order))
+ end
+ else
+ -- shared
+ setfield(n, "spec", copy_node(width))
+ end
+ return n
+end
+
+-----------------------------------------------------------------------
+--- font-hsh
+-----------------------------------------------------------------------
+--- some initialization resembling font-hsh
+local fonthashes = fonts.hashes
+local identifiers = fonthashes.identifiers --- was: fontdata
+local chardata = fonthashes.characters
+local quaddata = fonthashes.quads
+local parameters = fonthashes.parameters
+
+--- ('a, 'a) hash -> (('a, 'a) hash -> 'a -> 'a) -> ('a, 'a) hash
+local setmetatableindex = function (t, f)
+ local mt = getmetatable(t)
+ if mt then
+ mt.__index = f
+ else
+ setmetatable(t, { __index = f })
+ end
+ return t
+end
+
+if not parameters then
+ parameters = { }
+ setmetatableindex(parameters, function(t, k)
+ if k == true then
+ return parameters[currentfont()]
+ else
+ local parameters = identifiers[k].parameters
+ t[k] = parameters
+ return parameters
+ end
+ end)
+ --fonthashes.parameters = parameters
+end
+
+if not chardata then
+ chardata = { }
+ setmetatableindex(chardata, function(t, k)
+ if k == true then
+ return chardata[currentfont()]
+ else
+ local tfmdata = identifiers[k]
+ if not tfmdata then --- unsafe
+ tfmdata = font.fonts[k]
+ end
+ if tfmdata then
+ local characters = tfmdata.characters
+ t[k] = characters
+ return characters
+ end
+ end
+ end)
+ fonthashes.characters = chardata
+end
+
+if not quaddata then
+ quaddata = { }
+ setmetatableindex(quaddata, function(t, k)
+ if k == true then
+ return quads[currentfont()]
+ else
+ local parameters = parameters[k]
+ local quad = parameters and parameters.quad or 0
+ t[k] = quad
+ return quad
+ end
+ end)
+ --fonthashes.quads = quaddata
+end
+
+---=================================================================---
+--- character kerning functionality
+---=================================================================---
+
+-- UF changed 2017-07-14
+local newUF = node.direct.new
+
+local kern_injector = function (fillup, kern)
+ if fillup then
+ local g = newUF("glue")
+ setfield(g, "stretch", kern)
+ setfield(g, "stretch_order", 1)
+ return g
+ end
+ local g = newUF("kern")
+ setfield(g,"kern",kern)
+ return g
+end
+-- /UF
+
+local kernable_skip = function (n)
+ local st = getsubtype (n)
+ return st == userskip_code
+ or st == spaceskip_code
+ or st == xspaceskip_code
+end
+
+local function spec_injector (fillup, width, stretch, shrink)
+ if fillup then
+ local spec = new_gluespec(width, 2 * stretch, 2 * shrink)
+ setfield(spec, "stretch_order", 1)
+ return spec
+ end
+ return new_gluespec(width,stretch,shrink)
+end
+
+--[[doc--
+
+ Caveat lector.
+ This is an adaptation of the Context character kerning mechanism
+ that emulates XeTeX-style fontwise letterspacing. Note that in its
+ present state it is far inferior to the original, which is
+ attribute-based and ignores font-boundaries. Nevertheless, due to
+ popular demand the following callback has been added.
+
+--doc]]--
+
+local kernfactors = { } --- fontid -> factor
+
+local kerncharacters
+kerncharacters = function (head)
+ local start = head
+ local lastfont = nil
+ local keepligature = letterspace.keepligature --- function
+ local keeptogether = letterspace.keeptogether --- function
+ local fillup = false
+
+ local identifiers = fonthashes.identifiers
+ local kernfactors = kernfactors
+ local firstkern = true
+
+ while start do
+ local id = getid(start)
+ if id == glyph_code then
+ --- 1) look up kern factor (slow, but cached rudimentarily)
+ local krn
+ local fontid = getfont(start)
+ do
+ krn = kernfactors[fontid]
+ if not krn then
+ local tfmdata = identifiers[fontid]
+ if not tfmdata then -- unsafe
+ tfmdata = font.fonts[fontid]
+ end
+ if tfmdata then
+ fontproperties = tfmdata.properties
+ if fontproperties then
+ krn = fontproperties.kerncharacters
+ end
+ end
+ kernfactors[fontid] = krn
+ end
+ if not krn or krn == 0 then
+ firstkern = true
+ goto nextnode
+ elseif firstkern then
+ firstkern = false
+ if (id ~= disc_code) and (not getfield(start, "components")) then
+ --- not a ligature, skip node
+ goto nextnode
+ end
+ end
+ end
+
+ if krn == "max" then
+ krn = .25
+ fillup = true
+ else
+ fillup = false
+ end
+
+ lastfont = fontid
+
+ --- 2) resolve ligatures
+ local c = getfield(start, "components")
+
+ if c then
+ if keepligature and keepligature(start) then
+ -- keep 'm
+ c = nil
+ else
+ while c do
+ local s = start
+ local p, n = getboth (s)
+ if p then
+ setlink (p, c)
+ else
+ head = c
+ end
+ if n then
+ local tail = find_node_tail(c)
+ setlink (tail, n)
+ end
+ start = c
+ setfield(s, "components", nil)
+ free_node(s) --> double free with multipart components
+ c = getfield (start, "components")
+ end
+ end
+ end -- kern ligature
+
+ --- 3) apply the extra kerning
+ local prev = getprev(start)
+ if prev then
+ local pid = getid(prev)
+
+ if not pid then
+ -- nothing
+
+ elseif pid == glue_code and kernable_skip(prev) then
+ local spec = getfield(prev, "spec")
+ local wd = getfield(spec, "width")
+ if wd > 0 then
+ --- formula taken from Context
+ --- existing_width extended by four times the
+ --- width times the font’s kernfactor
+ local newwd = wd + --[[two en to a quad]] 4 * wd * krn
+ local stretched = (getfield(spec,"stretch") * newwd) / wd
+ local shrunk = (getfield(spec,"shrink") * newwd) / wd
+ setfield(prev, "spec",
+ spec_injector(fillup, newwd, stretched, shrunk))
+ end
+
+ elseif pid == kern_code then
+ local prev_subtype = getsubtype(prev)
+ if prev_subtype == kerning_code --- context does this by means of an
+ or prev_subtype == userkern_code --- attribute; we may need a test
+ then
+
+ local pprev = getprev(prev)
+ local pprev_id = getid(pprev)
+
+ if keeptogether
+ and pprev_id == glyph_code
+ and keeptogether(pprev, start)
+ then
+ -- keep
+ else
+ setsubtype (prev, userkern_code)
+ local prev_kern = getfield(prev, "kern")
+ prev_kern = prev_kern + quaddata[lastfont] * krn
+ setfield (prev, "kern", prev_kern)
+ end
+ end
+
+ elseif pid == glyph_code then
+ if getfont(prev) == lastfont then
+ local prevchar = getchar(prev)
+ local lastchar = getchar(start)
+ if keeptogether and keeptogether(prev, start) then
+ -- keep 'm
+ elseif identifiers[lastfont] then
+ local lastfontchars = chardata[lastfont]
+ if lastfontchars then
+ local prevchardata = lastfontchars[prevchar]
+ if not prevchardata then
+ --- font doesn’t contain the glyph
+ else
+ local kern = 0
+ local kerns = prevchardata.kerns
+ if kerns then kern = kerns[lastchar] or kern end
+ krn = kern + quaddata[lastfont]*krn -- here
+ insert_node_before(head,start,kern_injector(fillup,krn))
+ end
+ end
+ end
+ else
+ krn = quaddata[lastfont]*krn -- here
+ insert_node_before(head,start,kern_injector(fillup,krn))
+ end
+
+ elseif pid == disc_code then
+ local disc = prev -- disc
+ local pre, post, replace = getdisc (disc)
+ local prv = getprev(disc)
+ local nxt = getnext(disc)
+
+ if pre and prv then -- must pair with start.prev
+ -- this one happens in most cases
+ local before = copy_node(prv)
+ setprev(pre, before)
+ setnext(before, pre)
+ setprev(before, nil)
+ pre = kerncharacters (before)
+ pre = getnext(pre)
+ setprev(pre, nil)
+ setfield(disc, "pre", pre)
+ free_node(before)
+ end
+
+ if post and nxt then -- must pair with start
+ local after = copy_node(nxt)
+ local tail = find_node_tail(post)
+ setnext(tail, after)
+ setprev(after, tail)
+ setnext(after, nil)
+ post = kerncharacters (post)
+ setnext(tail, nil)
+ setfield(disc, "post", post)
+ free_node(after)
+ end
+
+ if replace and prv and nxt then -- must pair with start and start.prev
+ local before = copy_node(prv)
+ local after = copy_node(nxt)
+ local tail = find_node_tail(replace)
+ setprev(replace, before)
+ setnext(before, replace)
+ setprev(before, nil)
+ setnext(tail, after)
+ setprev(after, tail)
+ setnext(after, nil)
+ replace = kerncharacters (before)
+ replace = getnext(replace)
+ setprev(replace, nil)
+ setnext(getprev(after), nil)
+ setfield(disc, "replace", replace)
+ free_node(after)
+ free_node(before)
+
+ elseif identifiers[lastfont] then
+ if prv
+ and getid(prv) == glyph_code
+ and getfont(prv) == lastfont
+ then
+ local kern = 0
+ local prevchar = getchar(prv)
+ local lastchar = getchar(start)
+ local lastfontchars = chardata[lastfont]
+ if lastfontchars then
+ local prevchardata = lastfontchars[prevchar]
+ if not prevchardata then
+ --- font doesn’t contain the glyph
+ else
+ local kerns = prevchardata.kerns
+ if kerns then kern = kerns[lastchar] or kern end
+ end
+ end
+ krn = kern + quaddata[lastfont]*krn -- here
+ else
+ krn = quaddata[lastfont]*krn -- here
+ end
+ setfield(disc, "replace", kern_injector(false, krn))
+ end --[[if replace and prv and nxt]]
+ end --[[if not pid]]
+ end --[[if prev]]
+ end --[[if id == glyph_code]]
+
+ ::nextnode::
+ if start then
+ start = getnext(start)
+ end
+ end
+ return head
+end
+
+---=================================================================---
+--- integration
+---=================================================================---
+
+--- · callback: kerncharacters
+--- · enabler: enablefontkerning
+--- · disabler: disablefontkerning
+
+--- callback wrappers
+
+--- (node_t -> node_t) -> string -> string list -> bool
+local registered_as = { } --- procname -> callbacks
+local add_processor = function (processor, name, ...)
+ local callbacks = { ... }
+ for i=1, #callbacks do
+ luatexbase.add_to_callback(callbacks[i], processor, name)
+ end
+ registered_as[name] = callbacks --- for removal
+ return true
+end
+
+--- string -> bool
+local remove_processor = function (name)
+ local callbacks = registered_as[name]
+ if callbacks then
+ for i=1, #callbacks do
+ luatexbase.remove_from_callback(callbacks[i], name)
+ end
+ return true
+ end
+ return false --> unregistered
+end
+
+--- When font kerning is requested, usually by defining a font with the
+--- ``letterspace`` parameter, we inject a wrapper for the
+--- ``kerncharacters()`` node processor in the relevant callbacks. This
+--- wrapper initially converts the received head node into its “direct”
+--- counterpart. Likewise, the callback result is converted back to an
+--- ordinary node prior to returning. Internally, ``kerncharacters()``
+--- performs all node operations on direct nodes.
+
+--- unit -> bool
+local enablefontkerning = function ( )
+
+ local handler = function (hd)
+ local direct_hd = todirect (hd)
+ logreport ("term", 5, "letterspace",
+ "kerncharacters() invoked with node.direct interface \z
+ (``%s`` -> ``%s``)", tostring (hd), tostring (direct_hd))
+ local direct_hd = kerncharacters (direct_hd)
+ if not direct_hd then --- bad
+ logreport ("both", 0, "letterspace",
+ "kerncharacters() failed to return a valid new head")
+ end
+ return tonode (direct_hd)
+ end
+
+ return add_processor( handler
+ , "luaotfload.letterspace"
+ , "pre_linebreak_filter"
+ , "hpack_filter")
+end
+
+--- unit -> bool
+---al disablefontkerning = function ( )
+---eturn remove_processor "luaotfload.letterspace"
+---
+
+--[[doc--
+
+ Fontwise kerning is enabled via the “kernfactor” option at font
+ definition time. Unlike the Context implementation which relies on
+ Luatex attributes, it uses a font property for passing along the
+ letterspacing factor of a node.
+
+ The callback is activated the first time a letterspaced font is
+ requested and stays active until the end of the run. Since the font
+ is a property of individual glyphs, every glyph in the entire
+ document must be checked for the kern property. This is quite
+ inefficient compared to Context’s attribute based approach, but Xetex
+ compatibility reduces our options significantly.
+
+--doc]]--
+
+
+local fontkerning_enabled = false --- callback state
+
+--- fontobj -> float -> unit
+local initializefontkerning = function (tfmdata, factor)
+ if factor ~= "max" then
+ factor = tonumber (factor) or 0
+ end
+ if factor == "max" or factor ~= 0 then
+ local fontproperties = tfmdata.properties
+ if fontproperties then
+ --- hopefully this field stays unused otherwise
+ fontproperties.kerncharacters = factor
+ end
+ if not fontkerning_enabled then
+ fontkerning_enabled = enablefontkerning ()
+ end
+ end
+end
+
+--- like the font colorization, fontwise kerning is hooked into the
+--- feature mechanism
+
+otffeatures.register {
+ name = "kernfactor",
+ description = "kernfactor",
+ initializers = {
+ base = initializefontkerning,
+ node = initializefontkerning,
+ }
+}
+
+--[[doc--
+
+ The “letterspace” feature is essentially identical with the above
+ “kernfactor” method, but scales the factor to percentages to match
+ Xetex’s behavior. (See the Xetex reference, page 5, section 1.2.2.)
+
+ Since Xetex doesn’t appear to have a (documented) “max” keyword, we
+ assume all input values are numeric.
+
+--doc]]--
+
+local initializecompatfontkerning = function (tfmdata, percentage)
+ local factor = tonumber (percentage)
+ if not factor then
+ logreport ("both", 0, "letterspace",
+ "Invalid argument to letterspace: %s (type %q), " ..
+ "was expecting percentage as Lua number instead.",
+ percentage, type (percentage))
+ return
+ end
+ return initializefontkerning (tfmdata, factor * 0.01)
+end
+
+otffeatures.register {
+ name = "letterspace",
+ description = "letterspace",
+ initializers = {
+ base = initializecompatfontkerning,
+ node = initializecompatfontkerning,
+ }
+}
+
+--[[example--
+
+See https://bitbucket.org/phg/lua-la-tex-tests/src/tip/pln-letterspace-8-compare.tex
+for an example.
+
+--example]]--
+
+--- vim:sw=2:ts=2:expandtab:tw=71
+