diff options
author | Philipp Gesang <phg42.2a@gmail.com> | 2013-05-20 02:18:33 -0700 |
---|---|---|
committer | Philipp Gesang <phg42.2a@gmail.com> | 2013-05-20 02:18:33 -0700 |
commit | bbf301d77998b97a4bfbb9644bcaaea4a77c62a6 (patch) | |
tree | f28f8efd36505342fcd49c2538beb7054cb9bca2 | |
parent | 145f556ee90d4e46b897d5fac5a3d7387416f056 (diff) | |
parent | ddf5bd917a05f5826e47f87d128d1948d0c4e49c (diff) | |
download | luaotfload-bbf301d77998b97a4bfbb9644bcaaea4a77c62a6.tar.gz |
Merge pull request #82 from phi-gamma/master
exceptions for xetex-style feature spec
-rw-r--r-- | luaotfload-extralibs.lua | 164 | ||||
-rw-r--r-- | luaotfload-features.lua | 97 | ||||
-rw-r--r-- | luaotfload-letterspace.lua | 270 |
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 + |