diff options
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.luax | 683 |
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 + |