diff options
| -rw-r--r-- | NEWS | 4 | ||||
| -rw-r--r-- | filegraph.dot | 20 | ||||
| -rw-r--r-- | luaotfload-extralibs.lua | 536 | ||||
| -rw-r--r-- | luaotfload-letterspace.lua | 316 | ||||
| -rw-r--r-- | luaotfload-typo-krn.lua | 335 | ||||
| -rw-r--r-- | luaotfload.dtx | 74 | ||||
| -rwxr-xr-x | mkstatus | 2 | 
7 files changed, 360 insertions, 927 deletions
@@ -16,6 +16,10 @@ Change History        to ``luaotfload-fontloader.lua``      * Treat arguments of the ``letterspace`` option as percentages; add        ``kernfactor`` option that behaves as before. +    * Remove imported version of typo-krn.lua. Xetex-style per-font +      letterspacing is now the canonical method. +    * Merge functionality from extralibs (fake Context layer) into +      luaotfload-letterspace.lua as it is only needed there anyways.  2013/07/10, luaotfload v2.3a      * Detect LuaJIT interpreter (LuaJITTeX) diff --git a/filegraph.dot b/filegraph.dot index 0c64e58..9eccd80 100644 --- a/filegraph.dot +++ b/filegraph.dot @@ -61,7 +61,6 @@ strict digraph luaotfload_files { //looks weird with circo ...                                         ltail=cluster_merged]      luaotfload_libs -> font_names [label="luaotfload-database.lua"] -    luaotfload_libs -> typo_krn   [label="luaotfload-extralibs.lua"]      mkstatus    -> status     [label="generates from distribution files",                                 style=dashed] @@ -83,7 +82,7 @@ strict digraph luaotfload_files { //looks weird with circo ...   * main files   * ································································· */ -    fontdbutil        [label  = "luaotfload-tool.lua\nmkluatexfontdb.lua", +    fontdbutil        [label  = "luaotfload-tool.lua",                         shape  = rect,                         width  = "3.2cm",                         height = "1.2cm", @@ -147,19 +146,10 @@ strict digraph luaotfload_files { //looks weird with circo ...                         style  = "filled,rounded",                         penwidth=2] -    typo_krn [label  = "luaotfload-typo-krn.lua", -              shape  = rect, -              width  = "3.2cm", -              height = "1.2cm", -              color  = "#01012222", -              style  = "filled,rounded", -              penwidth=2] -  /* ····································································   * luaotfload files   * ································································· */ -      characters [style      = "filled,dashed",                  shape      = rect,                  width      = "3.2cm", @@ -208,10 +198,10 @@ strict digraph luaotfload_files { //looks weird with circo ...          label      = <              <table cellborder="0" bgcolor="#FFFFFFAA">                  <th> <td colspan="2"> <font point-size="12" face="Iwona Italic">Luaotfload Libraries</font> </td> </th> -                <tr> <td>luaotfload-auxiliary.lua</td>  <td>luaotfload-features.lua</td>    </tr> -                <tr> <td>luaotfload-override.lua</td>   <td>luaotfload-loaders.lua</td>     </tr> -                <tr> <td>luaotfload-database.lua</td>   <td>luaotfload-color.lua</td>       </tr> -                <tr> <td>luaotfload-extralibs.lua</td>  <td>luaotfload-letterspace.lua</td> </tr> +                <tr> <td>luaotfload-auxiliary.lua</td>    <td>luaotfload-features.lua</td>    </tr> +                <tr> <td>luaotfload-override.lua</td>     <td>luaotfload-loaders.lua</td>     </tr> +                <tr> <td>luaotfload-database.lua</td>     <td>luaotfload-color.lua</td>       </tr> +                <tr> <td>luaotfload-letterspace.lua</td>                                      </tr>              </table>          >,      ] diff --git a/luaotfload-extralibs.lua b/luaotfload-extralibs.lua deleted file mode 100644 index 21f738c..0000000 --- a/luaotfload-extralibs.lua +++ /dev/null @@ -1,536 +0,0 @@ -if not modules then modules = { } end modules ["extralibs"] = { -    version   = "2.4", -    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 -local otffeatures     = fonts.constructors.newfeatures "otf" - ------------------------------------------------------------------------ ---- 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 kern_callback      = "typesetters.kerncharacters" - -typesetters.kernfont     = typesetters.kernfont or { } -local kernfont           = typesetters.kernfont - ------------------------------------------------------------------------ ---- node-ini ------------------------------------------------------------------------ - -nodes              = nodes or { } --- should be present with luaotfload -local bothways     = function (t) return table.swapped (t, t) end - -local kerncodes = bothways({ -  [0] = "fontkern", -  [1] = "userkern", -  [2] = "accentkern", -}) - -kerncodes.kerning = kerncodes.fontkern --- idiosyncrasy - -nodes.kerncodes = kerncodes - -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 tfmdata = identifiers[k] -      if not tfmdata then --- unsafe -        tfmdata = font.fonts[k] -      end -      if tfmdata then -        local characters = tfmdata.characters -        t[k] = characters -        return characters -      end -    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 = { } - -      if identifiers[k] then -        resources = identifiers[k].resources or { } -      end - -      local marks = resources.marks or { } -      t[k] = marks -      return marks -    end -  end) -  fonthashes.marks = markdata -end - ---- next stems from the multilingual interface -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 - -if luatexbase.get_unset_value then -  attributes.unsetvalue = luatexbase.get_unset_value() -else -- old luatexbase -  attributes.unsetvalue = (luatexbase.luatexversion < 37) and -1 -                       or -2147483647 -end - ------------------------------------------------------------------------ ---- 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 libraries - -require "luaotfload-typo-krn"    --- typesetters.kerns -require "luaotfload-letterspace" --- typesetters.kernfont - ---===================================================================-- ----                              CLEAN ---===================================================================-- ---- interface ------------------------------------------------------------------------ - -local factors           = kerns.factors -local mapping           = kerns.mapping -local unsetvalue        = attributes.unset_value -local process_kerns     = plugin_store.kern - ---- 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 - ---- (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 - ---- 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 ---- 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 - ---- unit -> bool -local disablecharacterkerning = function ( ) -  return remove_processor "typesetters.kerncharacters" -end - -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 - ---- 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 == "max" or factor ~= 0 then -    local fontproperties = tfmdata.properties -    if fontproperties then -      --- hopefully this field stays unused otherwise -      fontproperties.kerncharacters = factor -    end -    if not fontkerning_enabled then -      fontkerning_enabled = enablefontkerning() -    end -  end -end - ---- like the font colorization, fontwise kerning is hooked into the ---- feature mechanism - -otffeatures.register { -  name        = "kernfactor", -  description = "kernfactor", -  initializers = { -    base = initializefontkerning, -    node = initializefontkerning, -  } -} - ---[[doc-- - -  The “letterspace” feature is essentially identical with the above -  “kernfactor” method, but scales the factor to percentages to match -  Xetex’s behavior. (See the Xetex reference, page 5, section 1.2.2.) - -  Since Xetex doesn’t appear to have a (documented) “max” keyword, we -  assume all input values are numeric. - ---doc]]-- - -local initializecompatfontkerning = function (tfmdata, percentage) -  local factor = tonumber (percentage) -  if not factor then -    logs.names_report ("both", 0, "letterspace", -                       "Invalid argument to letterspace: %s (type %q), was expecting percentage as Lua number instead.", -                       percentage, type (percentage)) -    return -  end -  return initializefontkerning (tfmdata, factor * 0.01) -end - -otffeatures.register { -  name        = "letterspace", -  description = "letterspace", -  initializers = { -    base = initializecompatfontkerning, -    node = initializecompatfontkerning, -  } -} - -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 -kernfont.keepligature     = false --- supposed to be of type function -kernfont.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-letterspace.lua b/luaotfload-letterspace.lua index b8bd36d..7c5a967 100644 --- a/luaotfload-letterspace.lua +++ b/luaotfload-letterspace.lua @@ -6,52 +6,182 @@ if not modules then modules = { } end modules ['letterspace'] = {      license   = "see context related readme files"  } +local getmetatable       = getmetatable +local require            = require +local setmetatable       = setmetatable +local tonumber           = tonumber +  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 new_node           = node.new  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 +local otffeatures        = fonts.constructors.newfeatures "otf" + +--[[doc-- + +  Since the letterspacing method was derived initially from Context’s +  typo-krn.lua we keep the sub-namespace “letterspace” inside the +  “luaotfload” table. + +--doc]]-- + +luaotfload.letterspace   = luaotfload.letterspace or { } +local letterspace        = luaotfload.letterspace + +letterspace.keepligature = false +letterspace.keeptogether = false +---=================================================================--- +---                     preliminary definitions +---=================================================================--- +-- We set up a layer emulating some Context internals that are needed +-- for the letterspacing callback. +----------------------------------------------------------------------- +--- node-ini +----------------------------------------------------------------------- + +local bothways  = function (t) return table.swapped (t, t) end +local kerncodes = bothways { [0] = "fontkern" +                           , [1] = "userkern" +                           , [2] = "accentkern" +                           } + +kerncodes.kerning    = kerncodes.fontkern --- idiosyncrasy +local kerning_code   = kerncodes.kerning +local userkern_code  = kerncodes.userkern + + +----------------------------------------------------------------------- +--- node-res +----------------------------------------------------------------------- + +nodes.pool        = nodes.pool or { } +local pool        = nodes.pool + +local kern        = new_node ("kern", 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.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 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 -typesetters              = typesetters or { } -local typesetters        = typesetters +if not chardata then +  chardata = { } +  setmetatableindex(chardata, function(t, k) +    if k == true then +      return chardata[currentfont()] +    else +      local tfmdata = identifiers[k] +      if not tfmdata then --- unsafe +        tfmdata = font.fonts[k] +      end +      if tfmdata then +        local characters = tfmdata.characters +        t[k] = characters +        return characters +      end +    end +  end) +  fonthashes.characters = chardata +end -typesetters.kernfont     = typesetters.kernfont or { } -local kernfont           = typesetters.kernfont +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 -kernfont.keepligature    = false -kernfont.keeptogether    = false +---=================================================================--- +---                 character kerning functionality +---=================================================================--- -local kern_injector = function (fillup,kern) +local kern_injector = function (fillup, kern)    if fillup then      local g = new_glue(kern)      local s = g.spec @@ -66,13 +196,11 @@ 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. +    This is an 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.  --doc]]-- @@ -82,8 +210,8 @@ local kerncharacters  kerncharacters = function (head)    local start, done   = head, false    local lastfont      = nil -  local keepligature  = kernfont.keepligature --- function -  local keeptogether  = kernfont.keeptogether --- function +  local keepligature  = letterspace.keepligature --- function +  local keeptogether  = letterspace.keeptogether --- function    local fillup        = false    local identifiers   = fonthashes.identifiers @@ -275,7 +403,139 @@ kerncharacters = function (head)    return head, done  end -kernfont.handler = kerncharacters +---=================================================================--- +---                         integration +---=================================================================--- + +--- · callback:     kerncharacters +--- · enabler:      enablefontkerning +--- · disabler:     disablefontkerning + +--- callback wrappers + +--- (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 + +--- 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 + +--- now for the simplistic variant +--- unit -> bool +local enablefontkerning = function ( ) +  return add_processor( kerncharacters +                      , "luaotfload.letterspace" +                      , "pre_linebreak_filter" +                      , "hpack_filter") +end + +--- unit -> bool +local disablefontkerning = function ( ) +  return remove_processor "luaotfload.letterspace" +end + +--[[doc-- + +  Fontwise kerning is enabled via the “kernfactor” option at font +  definition time. Unlike the Context implementation which relies on +  Luatex attributes, it uses a font property for passing along the +  letterspacing factor of a node. + +  The callback is activated the first time a letterspaced font is +  requested and stays active until the end of the run. Since the font +  is a property of individual glyphs, every glyph in the entire +  document must be checked for the kern property. This is quite +  inefficient compared to Context’s attribute based approach, but Xetex +  compatibility reduces our options significantly. + +--doc]]-- + + +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 == "max" or factor ~= 0 then +    local fontproperties = tfmdata.properties +    if fontproperties then +      --- hopefully this field stays unused otherwise +      fontproperties.kerncharacters = factor +    end +    if not fontkerning_enabled then +      fontkerning_enabled = enablefontkerning () +    end +  end +end + +--- like the font colorization, fontwise kerning is hooked into the +--- feature mechanism + +otffeatures.register { +  name        = "kernfactor", +  description = "kernfactor", +  initializers = { +    base = initializefontkerning, +    node = initializefontkerning, +  } +} + +--[[doc-- + +  The “letterspace” feature is essentially identical with the above +  “kernfactor” method, but scales the factor to percentages to match +  Xetex’s behavior. (See the Xetex reference, page 5, section 1.2.2.) + +  Since Xetex doesn’t appear to have a (documented) “max” keyword, we +  assume all input values are numeric. + +--doc]]-- + +local initializecompatfontkerning = function (tfmdata, percentage) +  local factor = tonumber (percentage) +  if not factor then +    logs.names_report ("both", 0, "letterspace", +                       "Invalid argument to letterspace: %s (type %q), " .. +                       "was expecting percentage as Lua number instead.", +                       percentage, type (percentage)) +    return +  end +  return initializefontkerning (tfmdata, factor * 0.01) +end + +otffeatures.register { +  name        = "letterspace", +  description = "letterspace", +  initializers = { +    base = initializecompatfontkerning, +    node = initializecompatfontkerning, +  } +} + +--[[example-- + +See https://bitbucket.org/phg/lua-la-tex-tests/src/tip/pln-letterspace-8-compare.tex +for an example. + +--example]]--  --- vim:sw=2:ts=2:expandtab:tw=71 diff --git a/luaotfload-typo-krn.lua b/luaotfload-typo-krn.lua deleted file mode 100644 index fb39404..0000000 --- a/luaotfload-typo-krn.lua +++ /dev/null @@ -1,335 +0,0 @@ -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 texsetattribute    = tex.setattribute -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 -    texsetattribute(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 4107185..e6e9523 100644 --- a/luaotfload.dtx +++ b/luaotfload.dtx @@ -755,10 +755,64 @@ and the derived files  %  %        \begin{quote}  %           \begin{verbatim} -%           \font\test={Latin Modern Roman}:color=FF0000BB +%  \font\test={Latin Modern Roman}:color=FF0000BB  %           \end{verbatim}  %        \end{quote}  % +% \item [kernfactor \& letterspace] \hfill \\ +%        Define a font with letterspacing (tracking) enabled. +%        In \identifier{luaotfload}, letterspacing is implemented by +%        inserting additional kerning between glyphs. +% +%        This approach is derived from and still quite similar to the +%        \emphasis{character kerning} (\texmacro{setcharacterkerning} / +%        \texmacro{definecharacterkerning} \& al.) functionality of +%        Context, see the file \fileent{typo-krn.lua} there. +%        The main difference is that \identifier{luaotfload} does not +%        use \LUATEX attributes to assign letterspacing to regions, +%        but defines virtual letterspaced versions of a font. +% +%        The option \identifier{kernfactor} accepts a numeric value that +%        determines the letterspacing factor to be applied to the font +%        size. +%        E.~g. a kern factor of $0.42$ applied to a $10$ pt font +%        results in $4.2$ pt of additional kerning applied to each +%        pair of glyphs. +%        Ligatures are split into their component glyphs unless +%        explicitly ignored (see below). +% +%        For compatibility with \XETEX an alternative +%        \identifier{letterspace} option is supplied that interprets the +%        supplied value as a \emphasis{percentage} of the font size but +%        is otherwise identical to \identifier{kernfactor}. +%        Consequently, both definitions in below snippet yield the same +%        letterspacing width: +% +%        \begin{quote} +%               \begin{verbatim} +%  \font\iwonakernedA="file:Iwona-Regular.otf:kernfactor=0.125" +%  \font\iwonakernedB="file:Iwona-Regular.otf:letterspace=12.5" +%               \end{verbatim} +%        \end{quote} +% +%        Specific pairs of letters and ligatures may be exempt from +%        letterspacing by defining the \LUA functions +%        \luafunction{keeptogether} and \luafunction{keepligature}, +%        respectively, inside the namespace \verb|luaotfload.letterspace|. +%        Both functions are called whenever the letterspacing callback +%        encounters an appropriate node or set of nodes. +%        If they return a true-ish value, no extra kern is inserted at +%        the current position. +%        \luafunction{keeptogether} receives a pair of consecutive +%        glyph nodes in order of their appearance in the node list. +%        \luafunction{keepligature} receives a single node which can be +%        analyzed into components. +%        (For details refer to the \emphasis{glyph nodes} section in the +%        \LUATEX reference manual.) +%        The implementation of both functions is left entirely to the +%        user. +% +%  % \item [protrusion \& expansion] \hfill \\  %        These keys control microtypographic features of the font,  %        namely \emphasis{character protrusion} and \emphasis{font @@ -771,14 +825,15 @@ and the derived files  %           Alternatively and with loss of information, you can dump  %           those tables into your terminal by issuing  %           \begin{verbatim} -%             \directlua{inspect(fonts.protrusions.setups.default) -%                        inspect(fonts.expansions.setups.default)} +%  \directlua{inspect(fonts.protrusions.setups.default) +%             inspect(fonts.expansions.setups.default)}  %           \end{verbatim}  %           at some point after loading \fileent{luaotfload.sty}.  %        }  %        For both, only the set \identifier{default} is predefined.  % -%        For example, to enable default protrusion\footnote{% +%        For example, to define a font with the default +%        protrusion vector applied\footnote{%  %          You also need to set  %              \verb|pdfprotrudechars=2| and  %              \verb|pdfadjustspacing=2| @@ -791,7 +846,7 @@ and the derived files  %  %        \begin{quote}  %           \begin{verbatim} -%           \font\test=LatinModernRoman:protrusion=default +%  \font\test=LatinModernRoman:protrusion=default  %           \end{verbatim}  %        \end{quote}  % \end{description} @@ -1213,10 +1268,7 @@ and the derived files  %     \ouritem {luaotfload-auxiliary.lua}    access to internal functionality  %                                            for package authors  %                                            (proposals for additions welcome). -%     \ouritem {luaotfload-extralibs.lua}    layer for loading of further -%                                            Context libraries.  %     \ouritem {luaotfload-letterspace.lua}  font-based letterspacing. -%     \ouritem {luaotfload-typo-krn.lua}     attribute-based letterspacing.  % \end{itemize}  %  % \begin{figure}[b] @@ -2149,9 +2201,9 @@ elseif font_definer == "patch"  then                    1)  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) +loadmodule"features.lua"        --- contains what was “font-ltx” and “font-otc” +loadmodule"letterspace.lua"     --- extra character kerning +loadmodule"auxiliary.lua"       --- additionaly high-level functionality (new)  luaotfload.aux.start_rewrite_fontname () --- to be migrated to fontspec @@ -40,7 +40,6 @@ local names = {    "luaotfload-colors.lua",    "luaotfload-database.lua",    "luaotfload-diagnostics.lua", -  "luaotfload-extralibs.lua",    "luaotfload-features.lua",    "luaotfload-fonts-cbk.lua",    "luaotfload-fonts-def.lua", @@ -55,7 +54,6 @@ local names = {    "luaotfload-fontloader.lua",    "luaotfload-override.lua",    "luaotfload-tool.lua", -  "luaotfload-typo-krn.lua",    "mkcharacters",    "mkglyphlist",    "mkstatus",  | 
