diff options
| author | Philipp Gesang <phg42.2a@gmail.com> | 2013-05-17 16:05:03 +0200 | 
|---|---|---|
| committer | Philipp Gesang <phg42.2a@gmail.com> | 2013-05-17 16:05:03 +0200 | 
| commit | 3be0018a711851ab1ddcf65af0f9b3d7584f2234 (patch) | |
| tree | 3dd32cbe12845e37bb5e958af74a88a45b4dcd38 | |
| parent | aceb8a22bd3e7c53de492d3e806ae265942ccfa6 (diff) | |
| parent | a7393f50fcc4f9bb8b46cfb3b270e71b07dbb5ba (diff) | |
| download | luaotfload-3be0018a711851ab1ddcf65af0f9b3d7584f2234.tar.gz | |
Merge branch 'characterkerning'
| -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  | 
