summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilipp Gesang <phg42.2a@gmail.com>2013-05-20 02:18:33 -0700
committerPhilipp Gesang <phg42.2a@gmail.com>2013-05-20 02:18:33 -0700
commitbbf301d77998b97a4bfbb9644bcaaea4a77c62a6 (patch)
treef28f8efd36505342fcd49c2538beb7054cb9bca2
parent145f556ee90d4e46b897d5fac5a3d7387416f056 (diff)
parentddf5bd917a05f5826e47f87d128d1948d0c4e49c (diff)
downloadluaotfload-bbf301d77998b97a4bfbb9644bcaaea4a77c62a6.tar.gz
Merge pull request #82 from phi-gamma/master
exceptions for xetex-style feature spec
-rw-r--r--luaotfload-extralibs.lua164
-rw-r--r--luaotfload-features.lua97
-rw-r--r--luaotfload-letterspace.lua270
3 files changed, 463 insertions, 68 deletions
diff --git a/luaotfload-extralibs.lua b/luaotfload-extralibs.lua
index 618808f..1e0d85a 100644
--- a/luaotfload-extralibs.lua
+++ b/luaotfload-extralibs.lua
@@ -23,6 +23,7 @@ local texattribute = tex.attribute
local new_node = node.new
local copy_node = node.copy
+local otffeatures = fonts.constructors.newfeatures "otf"
-----------------------------------------------------------------------
--- namespace
@@ -38,8 +39,10 @@ local kerns = typesetters.kerns
kerns.mapping = kerns.mapping or { }
kerns.factors = kerns.factors or { }
-local namespace = "kern" --- <= attribute <= plugin.name
-local callback_name = "typesetters.kerncharacters"
+local kern_callback = "typesetters.kerncharacters"
+
+typesetters.kernfont = typesetters.kernfont or { }
+local kernfont = typesetters.kernfont
-----------------------------------------------------------------------
--- node-ini
@@ -207,6 +210,7 @@ if not markdata then
fonthashes.marks = markdata
end
+--- next stems from the multilingual interface
interfaces = interfaces or { }
interfaces.variables = interfaces.variables or { }
interfaces.variables.max = "max"
@@ -281,9 +285,10 @@ commands = commands or { }
--- LOAD
--===================================================================--
---- we should be ready at this moment to insert the library
+--- we should be ready at this moment to insert the libraries
-require "luaotfload-typo-krn"
+require "luaotfload-typo-krn" --- typesetters.kerns
+require "luaotfload-letterspace" --- typesetters.kernfont
--===================================================================--
--- CLEAN
@@ -296,67 +301,146 @@ local mapping = kerns.mapping
local unsetvalue = attributes.unset_value
local process_kerns = plugin_store.kern
-local kerncharacters = function (head)
- return process_kerns("kerns", hidden.a_kerns, head)
-end
+--- 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
-local add_kern_processor = function (...)
- for i=1, select("#", ...) do
- luatexbase.add_to_callback(
- select(i, ...), kerncharacters, callback_name
- )
+
+--- (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
-local remove_kern_processor = function (...)
- for i=1, select("#", ...) do
- luatexbase.remove_from_callback(
- select(i, ...), kerncharacters, callback_name
- )
+
+--- 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
-kerns.enablecharacterkerning = function ( )
- add_kern_processor("pre_linebreak_filter", "hpack_filter")
+--- 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
-kerns.disablecharacterkerning = function ( )
- remove_kern_processor("pre_linebreak_filter", "hpack_filter")
+--- unit -> bool
+local disablecharacterkerning = function ( )
+ return remove_processor "typesetters.kerncharacters"
end
-local enabled = false --- callback state
+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
---- we just replace the kern enabler with our modified version
-kerns.set = function (factor)
- if factor ~= v_max then
+--- 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 == v_max or factor ~= 0 then
- if not enabled then
- kerns.enablecharacterkerning()
- enabled = true
+ if factor == "max" or factor ~= 0 then
+ local fontproperties = tfmdata.properties
+ if fontproperties then
+ --- hopefully this field stays unused otherwise
+ fontproperties.kerncharacters = factor
end
- local a = factors[factor]
- if not a then
- a = #mapping + 1
- factors[factors], mapping[a] = a, factor
+ if not fontkerning_enabled then
+ fontkerning_enabled = enablefontkerning()
end
- factor = a
- else
- factor = unsetvalue
end
- texattribute[hidden.a_kerns] = factor
- return factor
end
+--- like the font colorization, fontwise kerning is hooked into the
+--- feature mechanism
+
+otffeatures.register {
+ name = "letterspace", --"kerncharacters",
+ description = "letterspace", --"kerncharacters",
+ initializers = {
+ base = initializefontkerning,
+ node = initializefontkerning,
+ }
+}
+
+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
+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
diff --git a/luaotfload-features.lua b/luaotfload-features.lua
index 7ae035b..415d2ac 100644
--- a/luaotfload-features.lua
+++ b/luaotfload-features.lua
@@ -6,8 +6,9 @@ if not modules then modules = { } end modules ["features"] = {
license = "see context related readme files"
}
-local format, insert = string.format, table.insert
local type, next = type, next
+local tonumber = tonumber
+local tostring = tostring
local lpegmatch = lpeg.match
---[[ begin included font-ltx.lua ]]
@@ -28,15 +29,15 @@ function fonts.definers.getspecification(str)
return "", str, "", ":", str
end
-local old_feature_list = { }
-
local report = logs.names_report
local stringfind = string.find
local stringlower = string.lower
local stringgsub = string.gsub
local stringsub = string.sub
+local stringformat = string.format
local stringis_empty = string.is_empty
+local mathceil = math.ceil
--- TODO an option to dump the default features for a script would make
--- a nice addition to luaotfload-tool
@@ -753,6 +754,7 @@ local support_incomplete = table.tohash({
--- (string, string) dict -> (string, string) dict
local set_default_features = function (speclist)
speclist = speclist or { }
+ speclist[""] = nil --- invalid options stub
--- handle language tag
local language = speclist.language
@@ -877,22 +879,53 @@ end
--doc]]--
-local strip_leading_sign = function (s)
- --- handle option list keys
- local first = stringsub(s, 1, 1)
- if first == "+" or first == "-" then --- Xetex style
- return stringsub(s, 2)
+local handle_normal_option = function (key, val)
+ val = stringlower(val)
+ --- the former “toboolean()” handler
+ if val == "true" then
+ val = true
+ elseif val == "false" then
+ val = false
+ end
+ return key, val
+end
+
+--[[doc--
+
+ Xetex style indexing begins at zero which we just increment before
+ passing it along to the font loader. Ymmv.
+
+--doc]]--
+
+local handle_xetex_option = function (key, val)
+ val = stringlower(val)
+ local numeric = tonumber(val) --- decimal only; keeps colors intact
+ if numeric then --- ugh
+ if mathceil(numeric) == numeric then -- integer, possible index
+ val = tostring(numeric + 1)
+ end
+ elseif val == "true" then
+ val = true
+ elseif val == "false" then
+ val = false
end
- return s
+ return key, val
end
-local toboolean = function (s)
- --- handle option list values
- if s == "true" then return true end
- if s == "false" then return false end
- --if s == "yes" then return true end --- Context style
- --if s == "no" then return false end
- return stringlower(s)
+--[[doc--
+
+ Instead of silently ignoring invalid options we emit a warning to
+ the log.
+
+ Note that we have to return a pair to please rawset(). This creates
+ an entry on the resulting features hash which will later be removed
+ during set_default_features().
+
+--doc]]--
+
+local handle_invalid_option = function (opt)
+ report("log", 0, "load", "font option “%s” unknown.", opt)
+ return "", false
end
--[[doc--
@@ -975,19 +1008,28 @@ local unprefixed = Cg(fontname, "anon")
local path_lookup = lbrk * Cg(C((1-rbrk)^1), "path") * rbrk
--- features ----------------------------------------------------------
-local field = (anum + S"+-.")^1 --- sic!
+local field_char = anum + S"+-." --- sic!
+local field = field_char^1
--- assignments are “lhs=rhs”
+--- or “+lhs=rhs” (Xetex-style)
--- switches are “+key” | “-key”
-local assignment = (field / strip_leading_sign) * ws
- * equals * ws
- * (field / toboolean)
+local normal_option = C(field) * ws * equals * ws * C(field) * ws
+local xetex_option = P"+" * ws * normal_option
+local assignment = xetex_option / handle_xetex_option
+ + normal_option / handle_normal_option
+----- assignment = (field / strip_leading_sign) * ws
+----- * equals * ws
+----- * (field / toboolean)
local switch = P"+" * ws * C(field) * Cc(true)
+ P"-" * ws * C(field) * Cc(false)
- + C(field) * Cc(true) -- catch crap
+-- + C(field) * Cc(true) -- catch crap
+local ignore = (1 - featuresep)^1 --- ignores one option
+ / handle_invalid_option
local feature_expr = ws * Cg(assignment + switch) * ws
+local option = feature_expr + ignore
local feature_list = Cf(Ct""
- * feature_expr
- * (featuresep * feature_expr)^0
+ * option
+ * (featuresep * option)^0
, rawset)
* featuresep^-1
@@ -1002,7 +1044,6 @@ local feature_list = Cf(Ct""
--- string; I won’t mess with it though until someone reports a
--- problem.)
--- local subvalue = P("(") * (C(P(1-S("()"))^1)/issub) * P(")") -- for Kim
---- Who’s Kim?
--- Note to self: subfonts apparently start at index 0. Tested with
--- Cambria.ttc that includes “Cambria Math” at 0 and “Cambria” at 1.
--- Other values cause luatex to segfault.
@@ -1113,8 +1154,8 @@ local handle_request = function (specification)
specification.lookup = "path"
return specification
end
- local lookup, name = select_lookup(request)
- request.features = set_default_features(request.features)
+ local lookup, name = select_lookup(request)
+ request.features = set_default_features(request.features)
if name then
specification.name = name
@@ -1231,11 +1272,11 @@ local function addfeature(data,feature,specifications)
local featuretype = types[specification.type or "substitution"]
local featureflags = specification.flags or noflags
local added = false
- local featurename = format("ctx_%s_%s",feature,s)
+ local featurename = stringformat("ctx_%s_%s",feature,s)
local st = { }
for t=1,#subtables do
local list = subtables[t]
- local full = format("%s_%s",featurename,t)
+ local full = stringformat("%s_%s",featurename,t)
st[t] = full
if featuretype == "gsub_ligature" then
lookuptypes[full] = "ligature"
diff --git a/luaotfload-letterspace.lua b/luaotfload-letterspace.lua
new file mode 100644
index 0000000..ef41bb6
--- /dev/null
+++ b/luaotfload-letterspace.lua
@@ -0,0 +1,270 @@
+if not modules then modules = { } end modules ['letterspace'] = {
+ version = 2.200,
+ comment = "companion to luaotfload.lua",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL; adapted by Philipp Gesang",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+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 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
+
+typesetters = typesetters or { }
+local typesetters = typesetters
+
+typesetters.kernfont = typesetters.kernfont or { }
+local kernfont = typesetters.kernfont
+
+kernfont.keepligature = false
+kernfont.keeptogether = false
+
+local kern_injector = function (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
+
+--[[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.
+
+--doc]]--
+
+local kernfactors = { } --- fontid -> factor
+
+local kerncharacters
+kerncharacters = function (head)
+ local start, done = head, false
+ local lastfont = nil
+ local keepligature = kernfont.keepligature --- function
+ local keeptogether = kernfont.keeptogether --- function
+ local fillup = false
+
+ local identifiers = fonthashes.identifiers
+ local kernfactors = kernfactors
+
+ while start do
+ local attr = start[attribute]
+ local id = start.id
+ if id == glyph_code then
+
+ --- 1) look up kern factor (slow, but cached rudimentarily)
+ local krn
+ local fontid = start.font
+ 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
+ goto nextnode
+ end
+ end
+
+ if krn == "max" then
+ krn = .25
+ fillup = true
+ else
+ fillup = false
+ end
+
+ lastfont = fontid
+
+ --- 2) resolve ligatures
+ local c = start.components
+ if c then
+ if keepligature and keepligature(start) then
+ -- keep 'm
+ else
+ c = kerncharacters (c)
+ 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 -- kern ligature
+
+ --- 3) apply the extra kerning
+ local prev = start.prev
+ if prev then
+ 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
+ -- keep
+ else
+ 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
+ elseif identifiers[lastfont] then
+ 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 = kerncharacters (before)
+ 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 = kerncharacters (post)
+ 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 = kerncharacters (before)
+ replace = replace.next
+ replace.prev = nil
+ after.prev.next = nil
+ disc.replace = replace
+ free_node(after)
+ free_node(before)
+ elseif identifiers[lastfont] then
+ 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
+ end
+
+ ::nextnode::
+ if start then
+ start = start.next
+ end
+ end
+ return head, done
+end
+
+kernfont.handler = kerncharacters
+
+--- vim:sw=2:ts=2:expandtab:tw=71
+