diff options
| author | Philipp Gesang <phg42.2a@gmail.com> | 2013-05-20 02:18:33 -0700 | 
|---|---|---|
| committer | Philipp Gesang <phg42.2a@gmail.com> | 2013-05-20 02:18:33 -0700 | 
| commit | bbf301d77998b97a4bfbb9644bcaaea4a77c62a6 (patch) | |
| tree | f28f8efd36505342fcd49c2538beb7054cb9bca2 | |
| parent | 145f556ee90d4e46b897d5fac5a3d7387416f056 (diff) | |
| parent | ddf5bd917a05f5826e47f87d128d1948d0c4e49c (diff) | |
| download | luaotfload-bbf301d77998b97a4bfbb9644bcaaea4a77c62a6.tar.gz | |
Merge pull request #82 from phi-gamma/master
exceptions for xetex-style feature spec
| -rw-r--r-- | luaotfload-extralibs.lua | 164 | ||||
| -rw-r--r-- | luaotfload-features.lua | 97 | ||||
| -rw-r--r-- | luaotfload-letterspace.lua | 270 | 
3 files changed, 463 insertions, 68 deletions
diff --git a/luaotfload-extralibs.lua b/luaotfload-extralibs.lua index 618808f..1e0d85a 100644 --- a/luaotfload-extralibs.lua +++ b/luaotfload-extralibs.lua @@ -23,6 +23,7 @@ local texattribute    = tex.attribute  local new_node        = node.new  local copy_node       = node.copy +local otffeatures     = fonts.constructors.newfeatures "otf"  -----------------------------------------------------------------------  --- namespace @@ -38,8 +39,10 @@ local kerns              = typesetters.kerns  kerns.mapping            = kerns.mapping or { }  kerns.factors            = kerns.factors or { } -local namespace          = "kern" --- <= attribute <= plugin.name -local callback_name      = "typesetters.kerncharacters" +local kern_callback      = "typesetters.kerncharacters" + +typesetters.kernfont     = typesetters.kernfont or { } +local kernfont           = typesetters.kernfont  -----------------------------------------------------------------------  --- node-ini @@ -207,6 +210,7 @@ if not markdata then    fonthashes.marks = markdata  end +--- next stems from the multilingual interface  interfaces                = interfaces or { }  interfaces.variables      = interfaces.variables or { }  interfaces.variables.max  = "max" @@ -281,9 +285,10 @@ commands = commands or { }  ---                              LOAD  --===================================================================-- ---- we should be ready at this moment to insert the library +--- we should be ready at this moment to insert the libraries -require "luaotfload-typo-krn" +require "luaotfload-typo-krn"    --- typesetters.kerns +require "luaotfload-letterspace" --- typesetters.kernfont  --===================================================================--  ---                              CLEAN @@ -296,67 +301,146 @@ local mapping           = kerns.mapping  local unsetvalue        = attributes.unset_value  local process_kerns     = plugin_store.kern -local kerncharacters = function (head) -  return process_kerns("kerns", hidden.a_kerns, head) -end +--- kern_callback     : normal +--- · callback:     process_kerns +--- · enabler:      enablecharacterkerning +--- · disabler:     disablecharacterkerning +--- · interface:    kerns.set + +--- kernfont_callback : fontwise +--- · callback:     kernfont.handler +--- · enabler:      enablefontkerning +--- · disabler:     disablefontkerning  --- callback wrappers -local add_kern_processor = function (...) -  for i=1, select("#", ...) do -    luatexbase.add_to_callback( -      select(i, ...), kerncharacters, callback_name -    ) + +--- (node_t -> node_t) -> string -> string list -> bool +local registered_as = { } --- procname -> callbacks +local add_processor = function (processor, name, ...) +  local callbacks = { ... } +  for i=1, #callbacks do +    luatexbase.add_to_callback(callbacks[i], processor, name)    end +  registered_as[name] = callbacks --- for removal +  return true  end -local remove_kern_processor = function (...) -  for i=1, select("#", ...) do -    luatexbase.remove_from_callback( -      select(i, ...), kerncharacters, callback_name -    ) + +--- string -> bool +local remove_processor = function (name) +  local callbacks = registered_as[name] +  if callbacks then +    for i=1, #callbacks do +      luatexbase.remove_from_callback(callbacks[i], name) +    end +    return true    end +  return false --> unregistered  end  --- we use the same callbacks as a node processor in Context -kerns.enablecharacterkerning = function ( ) -  add_kern_processor("pre_linebreak_filter", "hpack_filter") +--- unit -> bool +local enablecharacterkerning = function ( ) +  return add_processor(function (head) +      return process_kerns("kerns", hidden.a_kerns, head) +    end, +    "typesetters.kerncharacters", +    "pre_linebreak_filter", "hpack_filter" +  )  end -kerns.disablecharacterkerning = function ( ) -  remove_kern_processor("pre_linebreak_filter", "hpack_filter") +--- unit -> bool +local disablecharacterkerning = function ( ) +  return remove_processor "typesetters.kerncharacters"  end -local enabled = false --- callback state +kerns.enablecharacterkerning     = enablecharacterkerning +kerns.disablecharacterkerning    = disablecharacterkerning + +--- now for the simplistic variant +--- unit -> bool +local enablefontkerning = function ( ) +  return add_processor( +    kernfont.handler, +    "typesetters.kernfont", +    "pre_linebreak_filter", "hpack_filter" +  ) +end ---- we just replace the kern enabler with our modified version -kerns.set = function (factor) -  if factor ~= v_max then +--- unit -> bool +local disablefontkerning = function ( ) +  return remove_processor "typesetters.kernfont" +end + +--- fontwise kerning uses a font property for passing along the +--- letterspacing factor + +local fontkerning_enabled = false --- callback state + +--- fontobj -> float -> unit +local initializefontkerning = function (tfmdata, factor) +  if factor ~= "max" then      factor = tonumber(factor) or 0    end -  if factor == v_max or factor ~= 0 then -    if not enabled then -      kerns.enablecharacterkerning() -      enabled = true +  if factor == "max" or factor ~= 0 then +    local fontproperties = tfmdata.properties +    if fontproperties then +      --- hopefully this field stays unused otherwise +      fontproperties.kerncharacters = factor      end -    local a = factors[factor] -    if not a then -      a = #mapping + 1 -      factors[factors], mapping[a] = a, factor +    if not fontkerning_enabled then +      fontkerning_enabled = enablefontkerning()      end -    factor = a -  else -    factor = unsetvalue    end -  texattribute[hidden.a_kerns] = factor -  return factor  end +--- like the font colorization, fontwise kerning is hooked into the +--- feature mechanism + +otffeatures.register { +  name        = "letterspace", --"kerncharacters", +  description = "letterspace", --"kerncharacters", +  initializers = { +    base = initializefontkerning, +    node = initializefontkerning, +  } +} + +kerns.set = nil + +local characterkerning_enabled = false + +kerns.set = function (factor) +    if factor ~= "max" then +        factor = tonumber(factor) or 0 +    end +    if factor == "max" or factor ~= 0 then +        if not characterkerning_enabled then +            enablecharacterkerning() +            characterkerning_enabled = true +        end +        local a = factors[factor] +        if not a then +            a = #mapping + 1 +            factors[factors], mapping[a] = a, factor +        end +        factor = a +    else +        factor = unsetvalue +    end +    texattribute[hidden.a_kerns] = factor +    return factor +end + +  -----------------------------------------------------------------------  --- options  ----------------------------------------------------------------------- -kerns.keepligature = false --- supposed to be of type function -kerns.keeptogether = false --- supposed to be of type function +kerns   .keepligature     = false --- supposed to be of type function +kerns   .keeptogether     = false --- supposed to be of type function +kernfont.keepligature     = false --- supposed to be of type function +kernfont.keeptogether     = false --- supposed to be of type function  -----------------------------------------------------------------------  --- erase fake Context layer diff --git a/luaotfload-features.lua b/luaotfload-features.lua index 7ae035b..415d2ac 100644 --- a/luaotfload-features.lua +++ b/luaotfload-features.lua @@ -6,8 +6,9 @@ if not modules then modules = { } end modules ["features"] = {      license   = "see context related readme files"  } -local format, insert    = string.format, table.insert  local type, next        = type, next +local tonumber          = tonumber +local tostring          = tostring  local lpegmatch         = lpeg.match  ---[[ begin included font-ltx.lua ]] @@ -28,15 +29,15 @@ function fonts.definers.getspecification(str)      return "", str, "", ":", str  end -local old_feature_list = { } -  local report = logs.names_report  local stringfind       = string.find  local stringlower      = string.lower  local stringgsub       = string.gsub  local stringsub        = string.sub +local stringformat     = string.format  local stringis_empty   = string.is_empty +local mathceil         = math.ceil  --- TODO an option to dump the default features for a script would make  ---      a nice addition to luaotfload-tool @@ -753,6 +754,7 @@ local support_incomplete = table.tohash({  --- (string, string) dict -> (string, string) dict  local set_default_features = function (speclist)      speclist = speclist or { } +    speclist[""] = nil --- invalid options stub      --- handle language tag      local language = speclist.language @@ -877,22 +879,53 @@ end  --doc]]-- -local strip_leading_sign = function (s) -    --- handle option list keys -    local first = stringsub(s, 1, 1) -    if first == "+" or first == "-" then --- Xetex style -        return stringsub(s, 2) +local handle_normal_option = function (key, val) +    val = stringlower(val) +    --- the former “toboolean()” handler +    if val == "true"  then +        val = true +    elseif val == "false" then +        val = false +    end +    return key, val +end + +--[[doc-- + +    Xetex style indexing begins at zero which we just increment before +    passing it along to the font loader.  Ymmv. + +--doc]]-- + +local handle_xetex_option = function (key, val) +    val = stringlower(val) +    local numeric = tonumber(val) --- decimal only; keeps colors intact +    if numeric then --- ugh +        if  mathceil(numeric) == numeric then -- integer, possible index +            val = tostring(numeric + 1) +        end +    elseif val == "true"  then +        val = true +    elseif val == "false" then +        val = false      end -    return s +    return key, val  end -local toboolean = function (s) -    --- handle option list values -    if s == "true"  then return true  end -    if s == "false" then return false end -    --if s == "yes"   then return true  end --- Context style -    --if s == "no"    then return false end -    return stringlower(s) +--[[doc-- + +    Instead of silently ignoring invalid options we emit a warning to +    the log. + +    Note that we have to return a pair to please rawset(). This creates +    an entry on the resulting features hash which will later be removed +    during set_default_features(). + +--doc]]-- + +local handle_invalid_option = function (opt) +    report("log", 0, "load", "font option “%s” unknown.", opt) +    return "", false  end  --[[doc-- @@ -975,19 +1008,28 @@ local unprefixed        = Cg(fontname, "anon")  local path_lookup       = lbrk * Cg(C((1-rbrk)^1), "path") * rbrk  --- features ---------------------------------------------------------- -local field             = (anum + S"+-.")^1 --- sic! +local field_char        = anum + S"+-." --- sic! +local field             = field_char^1  --- assignments are “lhs=rhs” +---              or “+lhs=rhs” (Xetex-style)  --- switches    are “+key” | “-key” -local assignment        = (field / strip_leading_sign) * ws -                        * equals * ws -                        * (field / toboolean) +local normal_option     = C(field) * ws * equals * ws * C(field) * ws +local xetex_option      = P"+" * ws * normal_option +local assignment        = xetex_option  / handle_xetex_option +                        + normal_option / handle_normal_option +----- assignment        = (field / strip_leading_sign) * ws +-----                   * equals * ws +-----                   * (field / toboolean)  local switch            = P"+" * ws * C(field) * Cc(true)                          + P"-" * ws * C(field) * Cc(false) -                        +             C(field) * Cc(true) -- catch crap +--                      +             C(field) * Cc(true) -- catch crap +local ignore            = (1 - featuresep)^1     --- ignores one option +                        / handle_invalid_option  local feature_expr      = ws * Cg(assignment + switch) * ws +local option            = feature_expr + ignore  local feature_list      = Cf(Ct"" -                           * feature_expr -                           * (featuresep * feature_expr)^0 +                           * option +                           * (featuresep * option)^0                             , rawset)                          * featuresep^-1 @@ -1002,7 +1044,6 @@ local feature_list      = Cf(Ct""  --- string; I won’t mess with it though until someone reports a  --- problem.)  --- local subvalue   = P("(") * (C(P(1-S("()"))^1)/issub) * P(")") -- for Kim ----                                                                 Who’s Kim?  --- Note to self: subfonts apparently start at index 0. Tested with  --- Cambria.ttc that includes “Cambria Math” at 0 and “Cambria” at 1.  --- Other values cause luatex to segfault. @@ -1113,8 +1154,8 @@ local handle_request = function (specification)          specification.lookup = "path"          return specification      end -    local lookup, name = select_lookup(request) -    request.features  = set_default_features(request.features) +    local lookup, name  = select_lookup(request) +    request.features    = set_default_features(request.features)      if name then          specification.name    = name @@ -1231,11 +1272,11 @@ local function addfeature(data,feature,specifications)                  local featuretype   = types[specification.type or "substitution"]                  local featureflags  = specification.flags or noflags                  local added         = false -                local featurename   = format("ctx_%s_%s",feature,s) +                local featurename   = stringformat("ctx_%s_%s",feature,s)                  local st = { }                  for t=1,#subtables do                      local list = subtables[t] -                    local full = format("%s_%s",featurename,t) +                    local full = stringformat("%s_%s",featurename,t)                      st[t] = full                      if featuretype == "gsub_ligature" then                          lookuptypes[full] = "ligature" diff --git a/luaotfload-letterspace.lua b/luaotfload-letterspace.lua new file mode 100644 index 0000000..ef41bb6 --- /dev/null +++ b/luaotfload-letterspace.lua @@ -0,0 +1,270 @@ +if not modules then modules = { } end modules ['letterspace'] = { +    version   = 2.200, +    comment   = "companion to luaotfload.lua", +    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL; adapted by Philipp Gesang", +    copyright = "PRAGMA ADE / ConTeXt Development Team", +    license   = "see context related readme files" +} + +local next               = next +local nodes, node, fonts = nodes, node, fonts + +local find_node_tail     = node.tail or node.slide +local free_node          = node.free +local free_nodelist      = node.flush_list +local copy_node          = node.copy +local copy_nodelist      = node.copy_list +local insert_node_before = node.insert_before +local insert_node_after  = node.insert_after + +local nodepool           = nodes.pool +local tasks              = nodes.tasks + +local new_kern           = nodepool.kern +local new_glue           = nodepool.glue + +local nodecodes          = nodes.nodecodes +local kerncodes          = nodes.kerncodes +local skipcodes          = nodes.skipcodes + +local glyph_code         = nodecodes.glyph +local kern_code          = nodecodes.kern +local disc_code          = nodecodes.disc +local glue_code          = nodecodes.glue +local hlist_code         = nodecodes.hlist +local vlist_code         = nodecodes.vlist +local math_code          = nodecodes.math + +local kerning_code       = kerncodes.kerning +local userkern_code      = kerncodes.userkern + +local fonthashes         = fonts.hashes +local chardata           = fonthashes.characters +local quaddata           = fonthashes.quads + +typesetters              = typesetters or { } +local typesetters        = typesetters + +typesetters.kernfont     = typesetters.kernfont or { } +local kernfont           = typesetters.kernfont + +kernfont.keepligature    = false +kernfont.keeptogether    = false + +local kern_injector = function (fillup,kern) +  if fillup then +    local g = new_glue(kern) +    local s = g.spec +    s.stretch = kern +    s.stretch_order = 1 +    return g +  else +    return new_kern(kern) +  end +end + +--[[doc-- + +    Caveat lector. +    This is a preliminary, makeshift adaptation of the Context +    character kerning mechanism that emulates XeTeX-style fontwise +    letterspacing. Note that in its present state it is far inferior to +    the original, which is attribute-based and ignores font-boundaries. +    Nevertheless, due to popular demand the following callback has been +    added. It should not be relied upon to be present in future +    versions. + +--doc]]-- + +local kernfactors = { } --- fontid -> factor + +local kerncharacters +kerncharacters = function (head) +  local start, done   = head, false +  local lastfont      = nil +  local keepligature  = kernfont.keepligature --- function +  local keeptogether  = kernfont.keeptogether --- function +  local fillup        = false + +  local identifiers   = fonthashes.identifiers +  local kernfactors   = kernfactors + +  while start do +    local attr = start[attribute] +    local id = start.id +    if id == glyph_code then + +      --- 1) look up kern factor (slow, but cached rudimentarily) +      local krn +      local fontid = start.font +      do +        krn = kernfactors[fontid] +        if not krn then +          local tfmdata = identifiers[fontid] +          if not tfmdata then -- unsafe +            tfmdata = font.fonts[fontid] +          end +          if tfmdata then +            fontproperties = tfmdata.properties +            if fontproperties then +              krn = fontproperties.kerncharacters +            end +          end +          kernfactors[fontid] = krn +        end +        if not krn or krn == 0 then +          goto nextnode +        end +      end + +      if krn == "max" then +        krn = .25 +        fillup = true +      else +        fillup = false +      end + +      lastfont = fontid + +      --- 2) resolve ligatures +      local c = start.components +      if c then +        if keepligature and keepligature(start) then +          -- keep 'm +        else +          c = kerncharacters (c) +          local s = start +          local p, n = s.prev, s.next +          local tail = find_node_tail(c) +          if p then +            p.next = c +            c.prev = p +          else +            head = c +          end +          if n then +            n.prev = tail +          end +          tail.next = n +          start = c +          s.components = nil +          -- we now leak nodes ! +          --  free_node(s) +          done = true +        end +      end -- kern ligature + +      --- 3) apply the extra kerning +      local prev = start.prev +      if prev then +        local pid = prev.id + +        if not pid then +          -- nothing + +        elseif pid == kern_code then +          if prev.subtype == kerning_code or prev[a_fontkern] then +            if keeptogether and prev.prev.id == glyph_code and keeptogether(prev.prev,start) then +              -- keep +            else +              prev.subtype = userkern_code +              prev.kern = prev.kern + quaddata[lastfont]*krn -- here +              done = true +            end +          end + +        elseif pid == glyph_code then +          if prev.font == lastfont then +            local prevchar, lastchar = prev.char, start.char +            if keeptogether and keeptogether(prev,start) then +              -- keep 'm +            elseif identifiers[lastfont] then +              local kerns = chardata[lastfont][prevchar].kerns +              local kern = kerns and kerns[lastchar] or 0 +              krn = kern + quaddata[lastfont]*krn -- here +              insert_node_before(head,start,kern_injector(fillup,krn)) +              done = true +            end +          else +            krn = quaddata[lastfont]*krn -- here +            insert_node_before(head,start,kern_injector(fillup,krn)) +            done = true +          end + +        elseif pid == disc_code then +          -- a bit too complicated, we can best not copy and just calculate +          -- but we could have multiple glyphs involved so ... +          local disc = prev -- disc +          local pre, post, replace = disc.pre, disc.post, disc.replace +          local prv, nxt = disc.prev, disc.next + +          if pre and prv then -- must pair with start.prev +            -- this one happens in most cases +            local before = copy_node(prv) +            pre.prev = before +            before.next = pre +            before.prev = nil +            pre = kerncharacters (before) +            pre = pre.next +            pre.prev = nil +            disc.pre = pre +            free_node(before) +          end + +          if post and nxt then  -- must pair with start +            local after = copy_node(nxt) +            local tail = find_node_tail(post) +            tail.next = after +            after.prev = tail +            after.next = nil +            post = kerncharacters (post) +            tail.next = nil +            disc.post = post +            free_node(after) +          end + +          if replace and prv and nxt then -- must pair with start and start.prev +            local before = copy_node(prv) +            local after = copy_node(nxt) +            local tail = find_node_tail(replace) +            replace.prev = before +            before.next = replace +            before.prev = nil +            tail.next = after +            after.prev = tail +            after.next = nil +            replace = kerncharacters (before) +            replace = replace.next +            replace.prev = nil +            after.prev.next = nil +            disc.replace = replace +            free_node(after) +            free_node(before) +          elseif identifiers[lastfont] then +            if prv and prv.id == glyph_code and prv.font == lastfont then +              local prevchar, lastchar = prv.char, start.char +              local kerns = chardata[lastfont][prevchar].kerns +              local kern = kerns and kerns[lastchar] or 0 +              krn = kern + quaddata[lastfont]*krn -- here +            else +              krn = quaddata[lastfont]*krn -- here +            end +            disc.replace = kern_injector(false,krn) -- only kerns permitted, no glue +          end + +        end +      end +    end + +    ::nextnode:: +    if start then +      start = start.next +    end +  end +  return head, done +end + +kernfont.handler = kerncharacters + +--- vim:sw=2:ts=2:expandtab:tw=71 +  | 
