diff options
author | Philipp Gesang <phg42.2a@gmail.com> | 2013-12-28 07:19:10 -0800 |
---|---|---|
committer | Philipp Gesang <phg42.2a@gmail.com> | 2013-12-28 07:19:10 -0800 |
commit | c0bb510a8a5bd18094043777cb7c320caa8283ce (patch) | |
tree | 696a6ae6aba988bce950b9173c8d52ecdb148a68 | |
parent | c8a6d4cfba7a0d5d53917f709a973292931e0d3e (diff) | |
parent | 3371bb25ce3d7cd703b7b25dec6aafd305b4833a (diff) | |
download | luaotfload-c0bb510a8a5bd18094043777cb7c320caa8283ce.tar.gz |
Merge pull request #159 from phi-gamma/master
make fontwise letterspacing an official supported feature
-rw-r--r-- | NEWS | 4 | ||||
-rw-r--r-- | filegraph.dot | 20 | ||||
-rw-r--r-- | luaotfload-extralibs.lua | 536 | ||||
-rw-r--r-- | luaotfload-letterspace.lua | 316 | ||||
-rw-r--r-- | luaotfload-typo-krn.lua | 335 | ||||
-rw-r--r-- | luaotfload.dtx | 74 | ||||
-rwxr-xr-x | mkstatus | 2 |
7 files changed, 360 insertions, 927 deletions
@@ -16,6 +16,10 @@ Change History to ``luaotfload-fontloader.lua`` * Treat arguments of the ``letterspace`` option as percentages; add ``kernfactor`` option that behaves as before. + * Remove imported version of typo-krn.lua. Xetex-style per-font + letterspacing is now the canonical method. + * Merge functionality from extralibs (fake Context layer) into + luaotfload-letterspace.lua as it is only needed there anyways. 2013/07/10, luaotfload v2.3a * Detect LuaJIT interpreter (LuaJITTeX) diff --git a/filegraph.dot b/filegraph.dot index 0c64e58..9eccd80 100644 --- a/filegraph.dot +++ b/filegraph.dot @@ -61,7 +61,6 @@ strict digraph luaotfload_files { //looks weird with circo ... ltail=cluster_merged] luaotfload_libs -> font_names [label="luaotfload-database.lua"] - luaotfload_libs -> typo_krn [label="luaotfload-extralibs.lua"] mkstatus -> status [label="generates from distribution files", style=dashed] @@ -83,7 +82,7 @@ strict digraph luaotfload_files { //looks weird with circo ... * main files * ································································· */ - fontdbutil [label = "luaotfload-tool.lua\nmkluatexfontdb.lua", + fontdbutil [label = "luaotfload-tool.lua", shape = rect, width = "3.2cm", height = "1.2cm", @@ -147,19 +146,10 @@ strict digraph luaotfload_files { //looks weird with circo ... style = "filled,rounded", penwidth=2] - typo_krn [label = "luaotfload-typo-krn.lua", - shape = rect, - width = "3.2cm", - height = "1.2cm", - color = "#01012222", - style = "filled,rounded", - penwidth=2] - /* ···································································· * luaotfload files * ································································· */ - characters [style = "filled,dashed", shape = rect, width = "3.2cm", @@ -208,10 +198,10 @@ strict digraph luaotfload_files { //looks weird with circo ... label = < <table cellborder="0" bgcolor="#FFFFFFAA"> <th> <td colspan="2"> <font point-size="12" face="Iwona Italic">Luaotfload Libraries</font> </td> </th> - <tr> <td>luaotfload-auxiliary.lua</td> <td>luaotfload-features.lua</td> </tr> - <tr> <td>luaotfload-override.lua</td> <td>luaotfload-loaders.lua</td> </tr> - <tr> <td>luaotfload-database.lua</td> <td>luaotfload-color.lua</td> </tr> - <tr> <td>luaotfload-extralibs.lua</td> <td>luaotfload-letterspace.lua</td> </tr> + <tr> <td>luaotfload-auxiliary.lua</td> <td>luaotfload-features.lua</td> </tr> + <tr> <td>luaotfload-override.lua</td> <td>luaotfload-loaders.lua</td> </tr> + <tr> <td>luaotfload-database.lua</td> <td>luaotfload-color.lua</td> </tr> + <tr> <td>luaotfload-letterspace.lua</td> </tr> </table> >, ] diff --git a/luaotfload-extralibs.lua b/luaotfload-extralibs.lua deleted file mode 100644 index 21f738c..0000000 --- a/luaotfload-extralibs.lua +++ /dev/null @@ -1,536 +0,0 @@ -if not modules then modules = { } end modules ["extralibs"] = { - version = "2.4", - comment = "companion to luaotfload.lua", - author = "Hans Hagen, Philipp Gesang", - copyright = "PRAGMA ADE / ConTeXt Development Team", - license = "GPL v.2.0", -} - --- extralibs: set up an emulation layer to load additional Context --- libraries - ---===================================================================-- ---- PREPARE ---===================================================================-- - -local getmetatable = getmetatable -local require = require -local select = select -local setmetatable = setmetatable -local tonumber = tonumber - -local texattribute = tex.attribute - -local new_node = node.new -local copy_node = node.copy -local otffeatures = fonts.constructors.newfeatures "otf" - ------------------------------------------------------------------------ ---- namespace ------------------------------------------------------------------------ - ---- The “typesetters” namespace isn’t bad at all; there is no need ---- to remove it after loading. - -typesetters = typesetters or { } -local typesetters = typesetters -typesetters.kerns = typesetters.kerns or { } -local kerns = typesetters.kerns -kerns.mapping = kerns.mapping or { } -kerns.factors = kerns.factors or { } - -local kern_callback = "typesetters.kerncharacters" - -typesetters.kernfont = typesetters.kernfont or { } -local kernfont = typesetters.kernfont - ------------------------------------------------------------------------ ---- node-ini ------------------------------------------------------------------------ - -nodes = nodes or { } --- should be present with luaotfload -local bothways = function (t) return table.swapped (t, t) end - -local kerncodes = bothways({ - [0] = "fontkern", - [1] = "userkern", - [2] = "accentkern", -}) - -kerncodes.kerning = kerncodes.fontkern --- idiosyncrasy - -nodes.kerncodes = kerncodes - -nodes.skipcodes = bothways({ - [ 0] = "userskip", - [ 1] = "lineskip", - [ 2] = "baselineskip", - [ 3] = "parskip", - [ 4] = "abovedisplayskip", - [ 5] = "belowdisplayskip", - [ 6] = "abovedisplayshortskip", - [ 7] = "belowdisplayshortskip", - [ 8] = "leftskip", - [ 9] = "rightskip", - [ 10] = "topskip", - [ 11] = "splittopskip", - [ 12] = "tabskip", - [ 13] = "spaceskip", - [ 14] = "xspaceskip", - [ 15] = "parfillskip", - [ 16] = "thinmuskip", - [ 17] = "medmuskip", - [ 18] = "thickmuskip", - [100] = "leaders", - [101] = "cleaders", - [102] = "xleaders", - [103] = "gleaders", -}) - ------------------------------------------------------------------------ ---- node-res ------------------------------------------------------------------------ - -nodes.pool = nodes.pool or { } -local pool = nodes.pool - -local kern = new_node("kern", nodes.kerncodes.userkern) -local glue_spec = new_node "glue_spec" - -pool.kern = function (k) - local n = copy_node(kern) - n.kern = k - return n -end - -pool.gluespec = function (width, stretch, shrink, - stretch_order, shrink_order) - local s = copy_node(glue_spec) - if width then s.width = width end - if stretch then s.stretch = stretch end - if shrink then s.shrink = shrink end - if stretch_order then s.stretch_order = stretch_order end - if shrink_order then s.shrink_order = shrink_order end - return s -end - -pool.glue = function (width, stretch, shrink, - stretch_order, shrink_order) - local n = new_node"glue" - if not width then - -- no spec - elseif width == false or tonumber(width) then - local s = copy_node(glue_spec) - if width then s.width = width end - if stretch then s.stretch = stretch end - if shrink then s.shrink = shrink end - if stretch_order then s.stretch_order = stretch_order end - if shrink_order then s.shrink_order = shrink_order end - n.spec = s - else - -- shared - 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 markdata = fonthashes.marks -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 - -if not markdata then - markdata = { } - setmetatableindex(markdata, function(t, k) - if k == true then - return marks[currentfont()] - else - local resources = { } - - if identifiers[k] then - resources = identifiers[k].resources or { } - end - - local marks = resources.marks or { } - t[k] = marks - return marks - end - end) - fonthashes.marks = markdata -end - ---- next stems from the multilingual interface -interfaces = interfaces or { } -interfaces.variables = interfaces.variables or { } -interfaces.variables.max = "max" - ------------------------------------------------------------------------ ---- attr-ini ------------------------------------------------------------------------ - -attributes = attributes or { } --- to be removed with cleanup - -local hidden = { - a_kerns = luatexbase.new_attribute("typo-krn:a_kerns", true), - a_fontkern = luatexbase.new_attribute("typo-krn:a_fontkern", true), -} - -attributes.private = attributes.private or function (attr_name) - local res = hidden[attr_name] - if not res then - res = luatexbase.new_attribute(attr_name) - end - return res -end - -if luatexbase.get_unset_value then - attributes.unsetvalue = luatexbase.get_unset_value() -else -- old luatexbase - attributes.unsetvalue = (luatexbase.luatexversion < 37) and -1 - or -2147483647 -end - ------------------------------------------------------------------------ ---- luat-sto ------------------------------------------------------------------------ - ---- Storage is so ridiculously well designed in Context it’s a pity ---- we can’t just force every package author to use it. - -storage = storage or { } -storage.register = storage.register or function (...) - local t = { ... } - --- sorry - return t -end - ------------------------------------------------------------------------ ---- node-fin ------------------------------------------------------------------------ - -local plugin_store = { } - -local installattributehandler = function (plugin) - --- Context has some load() magic here. - plugin_store[plugin.name] = plugin.processor -end - -nodes.installattributehandler = installattributehandler - ------------------------------------------------------------------------ ---- node-tsk ------------------------------------------------------------------------ - -nodes.tasks = nodes.tasks or { } -nodes.tasks.enableaction = function () end - ------------------------------------------------------------------------ ---- core-ctx ------------------------------------------------------------------------ - -commands = commands or { } - ---===================================================================-- ---- LOAD ---===================================================================-- - ---- we should be ready at this moment to insert the libraries - -require "luaotfload-typo-krn" --- typesetters.kerns -require "luaotfload-letterspace" --- typesetters.kernfont - ---===================================================================-- ---- CLEAN ---===================================================================-- ---- interface ------------------------------------------------------------------------ - -local factors = kerns.factors -local mapping = kerns.mapping -local unsetvalue = attributes.unset_value -local process_kerns = plugin_store.kern - ---- kern_callback : normal ---- · callback: process_kerns ---- · enabler: enablecharacterkerning ---- · disabler: disablecharacterkerning ---- · interface: kerns.set - ---- kernfont_callback : fontwise ---- · callback: kernfont.handler ---- · 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 - ---- we use the same callbacks as a node processor in Context ---- unit -> bool -local enablecharacterkerning = function ( ) - return add_processor(function (head) - return process_kerns("kerns", hidden.a_kerns, head) - end, - "typesetters.kerncharacters", - "pre_linebreak_filter", "hpack_filter" - ) -end - ---- unit -> bool -local disablecharacterkerning = function ( ) - return remove_processor "typesetters.kerncharacters" -end - -kerns.enablecharacterkerning = enablecharacterkerning -kerns.disablecharacterkerning = disablecharacterkerning - ---- now for the simplistic variant ---- unit -> bool -local enablefontkerning = function ( ) - return add_processor( kernfont.handler - , "typesetters.kernfont" - , "pre_linebreak_filter" - , "hpack_filter") -end - ---- unit -> bool -local disablefontkerning = function ( ) - return remove_processor "typesetters.kernfont" -end - ---- fontwise kerning uses a font property for passing along the ---- letterspacing factor - -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 - logs.names_report ("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, - } -} - -kerns.set = nil - -local characterkerning_enabled = false - -kerns.set = function (factor) - if factor ~= "max" then - factor = tonumber(factor) or 0 - end - if factor == "max" or factor ~= 0 then - if not characterkerning_enabled then - enablecharacterkerning() - characterkerning_enabled = true - end - local a = factors[factor] - if not a then - a = #mapping + 1 - factors[factors], mapping[a] = a, factor - end - factor = a - else - factor = unsetvalue - end - texattribute[hidden.a_kerns] = factor - return factor -end - - - ------------------------------------------------------------------------ ---- options ------------------------------------------------------------------------ - -kerns .keepligature = false --- supposed to be of type function -kerns .keeptogether = false --- supposed to be of type function -kernfont.keepligature = false --- supposed to be of type function -kernfont.keeptogether = false --- supposed to be of type function - ------------------------------------------------------------------------ ---- erase fake Context layer ------------------------------------------------------------------------ - -attributes = nil ---commands = nil --- used in lualibs -storage = nil --- not to confuse with utilities.storage -nodes.tasks = nil - -collectgarbage"collect" - ---[[example-- - -\input luaotfload.sty -\def\setcharacterkerning#1{% #1 factor : float - \directlua{typesetters.kerns.set(0.618)}% -} -%directlua{typesetters.kerns.enablecharacterkerning()} - -\font\iwona = "name:Iwona:mode=node" at 42pt -\font\lmregular = "name:Latin Modern Roman:mode=node" at 42pt - -{\iwona - foo - {\setcharacterkerning{0.618}% - bar} - baz} - -{\lmregular - foo {\setcharacterkerning{0.125}ff fi ffi fl Th} baz} - -{\lmregular - \directlua{ %% I’m not exactly sure how those work - typesetters.kerns.keepligature = function (start) - print("[liga]", start) - return true - end - typesetters.kerns.keeptogether = function (start) - print("[keeptogether]", start) - return true - end}% - foo {\setcharacterkerning{0.125}ff fi ffi fl Th} baz} - -\bye ---example]]-- - --- vim:ts=2:sw=2:expandtab diff --git a/luaotfload-letterspace.lua b/luaotfload-letterspace.lua index b8bd36d..7c5a967 100644 --- a/luaotfload-letterspace.lua +++ b/luaotfload-letterspace.lua @@ -6,52 +6,182 @@ if not modules then modules = { } end modules ['letterspace'] = { license = "see context related readme files" } +local getmetatable = getmetatable +local require = require +local setmetatable = setmetatable +local tonumber = tonumber + local next = next local nodes, node, fonts = nodes, node, fonts local find_node_tail = node.tail or node.slide local free_node = node.free -local free_nodelist = node.flush_list local copy_node = node.copy -local copy_nodelist = node.copy_list +local new_node = node.new local insert_node_before = node.insert_before -local insert_node_after = node.insert_after local nodepool = nodes.pool -local tasks = nodes.tasks local new_kern = nodepool.kern local new_glue = nodepool.glue local nodecodes = nodes.nodecodes -local kerncodes = nodes.kerncodes -local skipcodes = nodes.skipcodes local glyph_code = nodecodes.glyph local kern_code = nodecodes.kern local disc_code = nodecodes.disc -local glue_code = nodecodes.glue -local hlist_code = nodecodes.hlist -local vlist_code = nodecodes.vlist local math_code = nodecodes.math -local kerning_code = kerncodes.kerning -local userkern_code = kerncodes.userkern +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" + } + +kerncodes.kerning = kerncodes.fontkern --- idiosyncrasy +local kerning_code = kerncodes.kerning +local userkern_code = kerncodes.userkern + + +----------------------------------------------------------------------- +--- node-res +----------------------------------------------------------------------- + +nodes.pool = nodes.pool or { } +local pool = nodes.pool + +local kern = new_node ("kern", kerncodes.userkern) +local glue_spec = new_node "glue_spec" + +pool.kern = function (k) + local n = copy_node (kern) + n.kern = k + return n +end + +pool.glue = function (width, stretch, shrink, + stretch_order, shrink_order) + local n = new_node"glue" + if not width then + -- no spec + elseif width == false or tonumber(width) then + local s = copy_node(glue_spec) + if width then s.width = width end + if stretch then s.stretch = stretch end + if shrink then s.shrink = shrink end + if stretch_order then s.stretch_order = stretch_order end + if shrink_order then s.shrink_order = shrink_order end + n.spec = s + else + -- shared + 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 -typesetters = typesetters or { } -local typesetters = typesetters +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 -typesetters.kernfont = typesetters.kernfont or { } -local kernfont = typesetters.kernfont +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 -kernfont.keepligature = false -kernfont.keeptogether = false +---=================================================================--- +--- character kerning functionality +---=================================================================--- -local kern_injector = function (fillup,kern) +local kern_injector = function (fillup, kern) if fillup then local g = new_glue(kern) local s = g.spec @@ -66,13 +196,11 @@ end --[[doc-- Caveat lector. - This is a preliminary, makeshift 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. It should not be relied upon to be present in future - versions. + 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]]-- @@ -82,8 +210,8 @@ local kerncharacters kerncharacters = function (head) local start, done = head, false local lastfont = nil - local keepligature = kernfont.keepligature --- function - local keeptogether = kernfont.keeptogether --- function + local keepligature = letterspace.keepligature --- function + local keeptogether = letterspace.keeptogether --- function local fillup = false local identifiers = fonthashes.identifiers @@ -275,7 +403,139 @@ kerncharacters = function (head) return head, done end -kernfont.handler = kerncharacters +---=================================================================--- +--- 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 + +--- now for the simplistic variant +--- unit -> bool +local enablefontkerning = function ( ) + return add_processor( kerncharacters + , "luaotfload.letterspace" + , "pre_linebreak_filter" + , "hpack_filter") +end + +--- unit -> bool +local disablefontkerning = function ( ) + return remove_processor "luaotfload.letterspace" +end + +--[[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 + logs.names_report ("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 diff --git a/luaotfload-typo-krn.lua b/luaotfload-typo-krn.lua deleted file mode 100644 index fb39404..0000000 --- a/luaotfload-typo-krn.lua +++ /dev/null @@ -1,335 +0,0 @@ -if not modules then modules = { } end modules ['typo-krn'] = { - version = 1.001, - comment = "companion to typo-krn.mkiv", - author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", - copyright = "PRAGMA ADE / ConTeXt Development Team", - license = "see context related readme files" -} - -local next, type, tonumber = next, type, tonumber -local utfchar = utf.char - -local nodes, node, fonts = nodes, node, fonts - -local find_node_tail = node.tail or node.slide -local free_node = node.free -local free_nodelist = node.flush_list -local copy_node = node.copy -local copy_nodelist = node.copy_list -local insert_node_before = node.insert_before -local insert_node_after = node.insert_after -local end_of_math = node.end_of_math - -local texsetattribute = tex.setattribute -local unsetvalue = attributes.unsetvalue - -local nodepool = nodes.pool -local tasks = nodes.tasks - -local new_gluespec = nodepool.gluespec -local new_kern = nodepool.kern -local new_glue = nodepool.glue - -local nodecodes = nodes.nodecodes -local kerncodes = nodes.kerncodes -local skipcodes = nodes.skipcodes - -local glyph_code = nodecodes.glyph -local kern_code = nodecodes.kern -local disc_code = nodecodes.disc -local glue_code = nodecodes.glue -local hlist_code = nodecodes.hlist -local vlist_code = nodecodes.vlist -local math_code = nodecodes.math - -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 - -local fonthashes = fonts.hashes -local fontdata = fonthashes.identifiers -local chardata = fonthashes.characters -local quaddata = fonthashes.quads -local markdata = fonthashes.marks - -local v_max = interfaces.variables.max - -typesetters = typesetters or { } -local typesetters = typesetters - -typesetters.kerns = typesetters.kerns or { } -local kerns = typesetters.kerns - -kerns.mapping = kerns.mapping or { } -kerns.factors = kerns.factors or { } -local a_kerns = attributes.private("kern") -local a_fontkern = attributes.private('fontkern') -kerns.attribute = kerns.attribute - -storage.register("typesetters/kerns/mapping", kerns.mapping, "typesetters.kerns.mapping") -storage.register("typesetters/kerns/factors", kerns.factors, "typesetters.kerns.factors") - -local mapping = kerns.mapping -local factors = kerns.factors - --- one must use liga=no and mode=base and kern=yes --- use more helpers --- make sure it runs after all others --- there will be a width adaptor field in nodes so this will change --- todo: interchar kerns / disc nodes / can be made faster - -local gluefactor = 4 -- assumes quad = .5 enspace - -kerns.keepligature = false -- just for fun (todo: control setting with key/value) -kerns.keeptogether = false -- just for fun (todo: control setting with key/value) - --- can be optimized .. the prev thing .. but hardly worth the effort - -local function kern_injector(fillup,kern) - if fillup then - local g = new_glue(kern) - local s = g.spec - s.stretch = kern - s.stretch_order = 1 - return g - else - return new_kern(kern) - end -end - -local function spec_injector(fillup,width,stretch,shrink) - if fillup then - local s = new_gluespec(width,2*stretch,2*shrink) - s.stretch_order = 1 - return s - else - return new_gluespec(width,stretch,shrink) - end -end - --- needs checking ... base mode / node mode - -local function do_process(namespace,attribute,head,force) -- todo: glue so that we can fully stretch - local start, done, lastfont = head, false, nil - local keepligature = kerns.keepligature - local keeptogether = kerns.keeptogether - local fillup = false - while start do - -- faster to test for attr first - local attr = force or start[attribute] - if attr and attr > 0 then - start[attribute] = unsetvalue - local krn = mapping[attr] - if krn == v_max then - krn = .25 - fillup = true - else - fillup = false - end - if krn and krn ~= 0 then - local id = start.id - if id == glyph_code then - lastfont = start.font - local c = start.components - if c then - if keepligature and keepligature(start) then - -- keep 'm - else - c = do_process(namespace,attribute,c,attr) - local s = start - local p, n = s.prev, s.next - local tail = find_node_tail(c) - if p then - p.next = c - c.prev = p - else - head = c - end - if n then - n.prev = tail - end - tail.next = n - start = c - s.components = nil - -- we now leak nodes ! - -- free_node(s) - done = true - end - end - local prev = start.prev - if not prev then - -- skip - elseif markdata[lastfont][start.char] then - -- skip - else - local pid = prev.id - if not pid then - -- nothing - elseif pid == kern_code then - if prev.subtype == kerning_code or prev[a_fontkern] then - if keeptogether and prev.prev.id == glyph_code and keeptogether(prev.prev,start) then -- we could also pass start - -- keep 'm - else - -- not yet ok, as injected kerns can be overlays (from node-inj.lua) - prev.subtype = userkern_code - prev.kern = prev.kern + quaddata[lastfont]*krn -- here - done = true - end - end - elseif pid == glyph_code then - if prev.font == lastfont then - local prevchar, lastchar = prev.char, start.char - if keeptogether and keeptogether(prev,start) then - -- keep 'm - else - local kerns = chardata[lastfont][prevchar].kerns - local kern = kerns and kerns[lastchar] or 0 - krn = kern + quaddata[lastfont]*krn -- here - insert_node_before(head,start,kern_injector(fillup,krn)) - done = true - end - else - krn = quaddata[lastfont]*krn -- here - insert_node_before(head,start,kern_injector(fillup,krn)) - done = true - end - elseif pid == disc_code then - -- a bit too complicated, we can best not copy and just calculate - -- but we could have multiple glyphs involved so ... - local disc = prev -- disc - local pre, post, replace = disc.pre, disc.post, disc.replace - local prv, nxt = disc.prev, disc.next - if pre and prv then -- must pair with start.prev - -- this one happens in most cases - local before = copy_node(prv) - pre.prev = before - before.next = pre - before.prev = nil - pre = do_process(namespace,attribute,before,attr) - pre = pre.next - pre.prev = nil - 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) - tail.next = after - after.prev = tail - after.next = nil - post = do_process(namespace,attribute,post,attr) - tail.next = nil - 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) - replace.prev = before - before.next = replace - before.prev = nil - tail.next = after - after.prev = tail - after.next = nil - replace = do_process(namespace,attribute,before,attr) - replace = replace.next - replace.prev = nil - after.prev.next = nil - disc.replace = replace - free_node(after) - free_node(before) - else - if prv and prv.id == glyph_code and prv.font == lastfont then - local prevchar, lastchar = prv.char, start.char - local kerns = chardata[lastfont][prevchar].kerns - local kern = kerns and kerns[lastchar] or 0 - krn = kern + quaddata[lastfont]*krn -- here - else - krn = quaddata[lastfont]*krn -- here - end - disc.replace = kern_injector(false,krn) -- only kerns permitted, no glue - end - end - end - elseif id == glue_code then - local subtype = start.subtype - if subtype == userskip_code or subtype == xspaceskip_code or subtype == spaceskip_code then - local s = start.spec - local w = s.width - if w > 0 then - local width, stretch, shrink = w+gluefactor*w*krn, s.stretch, s.shrink - start.spec = spec_injector(fillup,width,stretch*width/w,shrink*width/w) - done = true - end - end - elseif id == kern_code then - -- if start.subtype == kerning_code then -- handle with glyphs - -- local sk = start.kern - -- if sk > 0 then - -- start.kern = sk*krn - -- done = true - -- end - -- end - elseif lastfont and (id == hlist_code or id == vlist_code) then -- todo: lookahead - local p = start.prev - if p and p.id ~= glue_code then - insert_node_before(head,start,kern_injector(fillup,quaddata[lastfont]*krn)) - done = true - end - local n = start.next - if n and n.id ~= glue_code then - insert_node_after(head,start,kern_injector(fillup,quaddata[lastfont]*krn)) - done = true - end - elseif id == math_code then - start = end_of_math(start) - end - end - end - if start then - start = start.next - end - end - return head, done -end - -local enabled = false - -function kerns.set(factor) - if factor ~= v_max then - factor = tonumber(factor) or 0 - end - if factor == v_max or factor ~= 0 then - if not enabled then - tasks.enableaction("processors","typesetters.kerns.handler") - enabled = true - end - local a = factors[factor] - if not a then - a = #mapping + 1 - factors[factors], mapping[a] = a, factor - end - factor = a - else - factor = unsetvalue - end - texsetattribute(a_kerns,factor) - return factor -end - -local function process(namespace,attribute,head) - return do_process(namespace,attribute,head) -- no direct map, because else fourth argument is tail == true -end - -kerns.handler = nodes.installattributehandler { - name = "kern", - namespace = kerns, - processor = process, -} - --- interface - -commands.setcharacterkerning = kerns.set diff --git a/luaotfload.dtx b/luaotfload.dtx index 4107185..e6e9523 100644 --- a/luaotfload.dtx +++ b/luaotfload.dtx @@ -755,10 +755,64 @@ and the derived files % % \begin{quote} % \begin{verbatim} -% \font\test={Latin Modern Roman}:color=FF0000BB +% \font\test={Latin Modern Roman}:color=FF0000BB % \end{verbatim} % \end{quote} % +% \item [kernfactor \& letterspace] \hfill \\ +% Define a font with letterspacing (tracking) enabled. +% In \identifier{luaotfload}, letterspacing is implemented by +% inserting additional kerning between glyphs. +% +% This approach is derived from and still quite similar to the +% \emphasis{character kerning} (\texmacro{setcharacterkerning} / +% \texmacro{definecharacterkerning} \& al.) functionality of +% Context, see the file \fileent{typo-krn.lua} there. +% The main difference is that \identifier{luaotfload} does not +% use \LUATEX attributes to assign letterspacing to regions, +% but defines virtual letterspaced versions of a font. +% +% The option \identifier{kernfactor} accepts a numeric value that +% determines the letterspacing factor to be applied to the font +% size. +% E.~g. a kern factor of $0.42$ applied to a $10$ pt font +% results in $4.2$ pt of additional kerning applied to each +% pair of glyphs. +% Ligatures are split into their component glyphs unless +% explicitly ignored (see below). +% +% For compatibility with \XETEX an alternative +% \identifier{letterspace} option is supplied that interprets the +% supplied value as a \emphasis{percentage} of the font size but +% is otherwise identical to \identifier{kernfactor}. +% Consequently, both definitions in below snippet yield the same +% letterspacing width: +% +% \begin{quote} +% \begin{verbatim} +% \font\iwonakernedA="file:Iwona-Regular.otf:kernfactor=0.125" +% \font\iwonakernedB="file:Iwona-Regular.otf:letterspace=12.5" +% \end{verbatim} +% \end{quote} +% +% Specific pairs of letters and ligatures may be exempt from +% letterspacing by defining the \LUA functions +% \luafunction{keeptogether} and \luafunction{keepligature}, +% respectively, inside the namespace \verb|luaotfload.letterspace|. +% Both functions are called whenever the letterspacing callback +% encounters an appropriate node or set of nodes. +% If they return a true-ish value, no extra kern is inserted at +% the current position. +% \luafunction{keeptogether} receives a pair of consecutive +% glyph nodes in order of their appearance in the node list. +% \luafunction{keepligature} receives a single node which can be +% analyzed into components. +% (For details refer to the \emphasis{glyph nodes} section in the +% \LUATEX reference manual.) +% The implementation of both functions is left entirely to the +% user. +% +% % \item [protrusion \& expansion] \hfill \\ % These keys control microtypographic features of the font, % namely \emphasis{character protrusion} and \emphasis{font @@ -771,14 +825,15 @@ and the derived files % Alternatively and with loss of information, you can dump % those tables into your terminal by issuing % \begin{verbatim} -% \directlua{inspect(fonts.protrusions.setups.default) -% inspect(fonts.expansions.setups.default)} +% \directlua{inspect(fonts.protrusions.setups.default) +% inspect(fonts.expansions.setups.default)} % \end{verbatim} % at some point after loading \fileent{luaotfload.sty}. % } % For both, only the set \identifier{default} is predefined. % -% For example, to enable default protrusion\footnote{% +% For example, to define a font with the default +% protrusion vector applied\footnote{% % You also need to set % \verb|pdfprotrudechars=2| and % \verb|pdfadjustspacing=2| @@ -791,7 +846,7 @@ and the derived files % % \begin{quote} % \begin{verbatim} -% \font\test=LatinModernRoman:protrusion=default +% \font\test=LatinModernRoman:protrusion=default % \end{verbatim} % \end{quote} % \end{description} @@ -1213,10 +1268,7 @@ and the derived files % \ouritem {luaotfload-auxiliary.lua} access to internal functionality % for package authors % (proposals for additions welcome). -% \ouritem {luaotfload-extralibs.lua} layer for loading of further -% Context libraries. % \ouritem {luaotfload-letterspace.lua} font-based letterspacing. -% \ouritem {luaotfload-typo-krn.lua} attribute-based letterspacing. % \end{itemize} % % \begin{figure}[b] @@ -2149,9 +2201,9 @@ elseif font_definer == "patch" then 1) end -loadmodule"features.lua" --- contains what was “font-ltx” and “font-otc” -loadmodule"extralibs.lua" --- load additional Context libraries -loadmodule"auxiliary.lua" --- additionaly high-level functionality (new) +loadmodule"features.lua" --- contains what was “font-ltx” and “font-otc” +loadmodule"letterspace.lua" --- extra character kerning +loadmodule"auxiliary.lua" --- additionaly high-level functionality (new) luaotfload.aux.start_rewrite_fontname () --- to be migrated to fontspec @@ -40,7 +40,6 @@ local names = { "luaotfload-colors.lua", "luaotfload-database.lua", "luaotfload-diagnostics.lua", - "luaotfload-extralibs.lua", "luaotfload-features.lua", "luaotfload-fonts-cbk.lua", "luaotfload-fonts-def.lua", @@ -55,7 +54,6 @@ local names = { "luaotfload-fontloader.lua", "luaotfload-override.lua", "luaotfload-tool.lua", - "luaotfload-typo-krn.lua", "mkcharacters", "mkglyphlist", "mkstatus", |