diff options
-rw-r--r-- | luaotfload-extralibs.lua | 402 | ||||
-rw-r--r-- | luaotfload-typo-krn.lua | 335 | ||||
-rw-r--r-- | luaotfload.dtx | 17 |
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 |