summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilipp Gesang <phg42.2a@gmail.com>2013-05-17 16:05:03 +0200
committerPhilipp Gesang <phg42.2a@gmail.com>2013-05-17 16:05:03 +0200
commit3be0018a711851ab1ddcf65af0f9b3d7584f2234 (patch)
tree3dd32cbe12845e37bb5e958af74a88a45b4dcd38
parentaceb8a22bd3e7c53de492d3e806ae265942ccfa6 (diff)
parenta7393f50fcc4f9bb8b46cfb3b270e71b07dbb5ba (diff)
downloadluaotfload-3be0018a711851ab1ddcf65af0f9b3d7584f2234.tar.gz
Merge branch 'characterkerning'
-rw-r--r--luaotfload-extralibs.lua402
-rw-r--r--luaotfload-typo-krn.lua335
-rw-r--r--luaotfload.dtx17
3 files changed, 746 insertions, 8 deletions
diff --git a/luaotfload-extralibs.lua b/luaotfload-extralibs.lua
new file mode 100644
index 0000000..2236631
--- /dev/null
+++ b/luaotfload-extralibs.lua
@@ -0,0 +1,402 @@
+if not modules then modules = { } end modules ["extralibs"] = {
+ version = 2.200,
+ 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
+
+-----------------------------------------------------------------------
+--- 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 namespace = "kern" --- <= attribute <= plugin.name
+local callback_name = "typesetters.kerncharacters"
+
+-----------------------------------------------------------------------
+--- node-ini
+-----------------------------------------------------------------------
+
+nodes = nodes or { } --- should be present with luaotfload
+local bothways = function (t) return table.swapped (t, t) end
+
+nodes.kerncodes = bothways({
+ [0] = "fontkern",
+ [1] = "userkern",
+ [2] = "accentkern",
+})
+
+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 characters = identifiers[k].characters
+ t[k] = characters
+ return characters
+ 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 = identifiers[k].resources or { }
+ local marks = resources.marks or { }
+ t[k] = marks
+ return marks
+ end
+ end)
+ fonthashes.marks = markdata
+end
+
+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
+
+attributes.unsetvalue = luatexbase.get_unset_value()
+
+-----------------------------------------------------------------------
+--- 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 library
+
+require "luaotfload-typo-krn"
+
+--===================================================================--
+--- CLEAN
+--===================================================================--
+--- interface
+-----------------------------------------------------------------------
+
+local factors = kerns.factors
+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
+
+--- callback wrappers
+local add_kern_processor = function (...)
+ for i=1, select("#", ...) do
+ luatexbase.add_to_callback(
+ select(i, ...), kerncharacters, callback_name
+ )
+ end
+end
+local remove_kern_processor = function (...)
+ for i=1, select("#", ...) do
+ luatexbase.remove_from_callback(
+ select(i, ...), kerncharacters, callback_name
+ )
+ end
+end
+
+--- we use the same callbacks as a node processor in Context
+kerns.enablecharacterkerning = function ( )
+ add_kern_processor("pre_linebreak_filter", "hpack_filter")
+end
+
+kerns.disablecharacterkerning = function ( )
+ remove_kern_processor("pre_linebreak_filter", "hpack_filter")
+end
+
+local enabled = false --- callback state
+
+--- we just replace the kern enabler with our modified version
+kerns.set = function (factor)
+ if factor ~= v_max then
+ factor = tonumber(factor) or 0
+ end
+ if factor == v_max or factor ~= 0 then
+ if not enabled then
+ kerns.enablecharacterkerning()
+ 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
+
+-----------------------------------------------------------------------
+--- 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-typo-krn.lua b/luaotfload-typo-krn.lua
new file mode 100644
index 0000000..fb28d3b
--- /dev/null
+++ b/luaotfload-typo-krn.lua
@@ -0,0 +1,335 @@
+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 texattribute = tex.attribute
+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
+ texattribute[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 88cc599..2fd77e3 100644
--- a/luaotfload.dtx
+++ b/luaotfload.dtx
@@ -1815,14 +1815,14 @@ local resolvefile = fonts.names.crude_file_lookup
--local resolvefile = fonts.names.crude_file_lookup_verbose
function request_resolvers.file(specification)
- local name = resolvefile(specification.name)
- local suffix = file.suffix(name)
- if formats[suffix] then
- specification.forced = suffix
- specification.name = file.removesuffix(name)
- else
- specification.name = name
- end
+ local name = resolvefile(specification.name)
+ local suffix = file.suffix(name)
+ if formats[suffix] then
+ specification.forced = suffix
+ specification.name = file.removesuffix(name)
+ else
+ specification.name = name
+ end
end
@@ -1963,6 +1963,7 @@ elseif font_definer == "patch" then
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)
-- vim:tw=71:sw=4:ts=4:expandtab