summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilipp Gesang <phg42.2a@gmail.com>2013-12-28 07:19:10 -0800
committerPhilipp Gesang <phg42.2a@gmail.com>2013-12-28 07:19:10 -0800
commitc0bb510a8a5bd18094043777cb7c320caa8283ce (patch)
tree696a6ae6aba988bce950b9173c8d52ecdb148a68
parentc8a6d4cfba7a0d5d53917f709a973292931e0d3e (diff)
parent3371bb25ce3d7cd703b7b25dec6aafd305b4833a (diff)
downloadluaotfload-c0bb510a8a5bd18094043777cb7c320caa8283ce.tar.gz
Merge pull request #159 from phi-gamma/master
make fontwise letterspacing an official supported feature
-rw-r--r--NEWS4
-rw-r--r--filegraph.dot20
-rw-r--r--luaotfload-extralibs.lua536
-rw-r--r--luaotfload-letterspace.lua316
-rw-r--r--luaotfload-typo-krn.lua335
-rw-r--r--luaotfload.dtx74
-rwxr-xr-xmkstatus2
7 files changed, 360 insertions, 927 deletions
diff --git a/NEWS b/NEWS
index 526d32d..3665153 100644
--- a/NEWS
+++ b/NEWS
@@ -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
diff --git a/mkstatus b/mkstatus
index 8ca4237..9940970 100755
--- a/mkstatus
+++ b/mkstatus
@@ -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",