diff options
| -rw-r--r-- | luaotfload-colors.lua | 348 | ||||
| -rw-r--r-- | luaotfload.dtx | 11 | ||||
| -rwxr-xr-x | mkglyphlist | 10 | 
3 files changed, 247 insertions, 122 deletions
diff --git a/luaotfload-colors.lua b/luaotfload-colors.lua index 1525214..ec076c2 100644 --- a/luaotfload-colors.lua +++ b/luaotfload-colors.lua @@ -6,39 +6,105 @@ if not modules then modules = { } end modules ['luaotfload-colors'] = {      license   = "GNU GPL v2"  } -local newnode            = node.new -local nodetype           = node.id -local traverse_nodes     = node.traverse -local insert_node_before = node.insert_before -local insert_node_after  = node.insert_after - -local stringformat = string.format -local stringgsub   = string.gsub -local stringfind   = string.find - -local otffeatures        = fonts.constructors.newfeatures("otf") -local ids                = fonts.hashes.identifiers -local registerotffeature = otffeatures.register - -local function setcolor(tfmdata,value) -    local sanitized -    local properties = tfmdata.properties +--[[doc-- +buggy coloring with the pre_output_filter when expansion is enabled +    · tfmdata for different expansion values is split over different objects +    · in ``initializeexpansion()``, chr.expansion_factor is set, and only +      those characters that have it are affected +    · in constructors.scale: chr.expansion_factor = ve*1000 if commented out +      makes the bug vanish +--doc]]-- -    if value then -        value = tostring(value) -        if #value == 6 or #value == 8 then -            sanitized = value -        elseif #value == 7 then -            _, _, sanitized = stringfind(value, "(......)") -        elseif #value > 8 then -            _, _, sanitized = stringfind(value, "(........)") -        else -            -- broken color code ignored, issue a warning? -        end + +local color_callback = config.luaotfload.color_callback +if not color_callback then +    --- maybe this would be better as a method: "early" | "late" +    color_callback = "pre_linebreak_filter" +--  color_callback = "pre_output_filter" --- old behavior, breaks expansion +end + + +local newnode               = node.new +local nodetype              = node.id +local traverse_nodes        = node.traverse +local insert_node_before    = node.insert_before +local insert_node_after     = node.insert_after + +local stringformat          = string.format +local stringgsub            = string.gsub +local stringfind            = string.find +local stringsub             = string.sub + +local otffeatures           = fonts.constructors.newfeatures("otf") +local identifiers           = fonts.hashes.identifiers +local registerotffeature    = otffeatures.register + +local add_color_callback --[[ this used to be a global‽ ]] + +--[[doc-- +This converts a single octet into a decimal with three digits of +precision. The optional second argument limits precision to a single +digit. +--doc]]-- + +--- string -> bool? -> string +local hex_to_dec = function (hex,one) --- one isn’t actually used anywhere ... +    if one then +        return stringformat("%.1g", tonumber(hex, 16)/255) +    else +        return stringformat("%.3g", tonumber(hex, 16)/255) +    end +end + +--[[doc-- +Color string validator / parser. +--doc]]-- + +local lpeg           = require"lpeg" +local lpegmatch      = lpeg.match +local C, Cg, Ct, P, R, S = lpeg.C, lpeg.Cg, lpeg.Ct, lpeg.P, lpeg.R, lpeg.S + +local digit16        = R("09", "af", "AF") +local octet          = C(digit16 * digit16) + +local p_rgb          = octet * octet * octet +local p_rgba         = p_rgb * octet +local valid_digits   = C(p_rgba + p_rgb) -- matches eight or six hex digits + +local p_Crgb         = Cg(octet/hex_to_dec, "red") --- for captures +                     * Cg(octet/hex_to_dec, "green") +                     * Cg(octet/hex_to_dec, "blue") +local p_Crgba        = p_Crgb * Cg(octet/hex_to_dec, "alpha") +local extract_color  = Ct(p_Crgba + p_Crgb) + +--- string -> (string | nil) +local sanitize_color_expression = function (digits) +    digits = tostring(digits) +    local sanitized = lpegmatch(valid_digits, digits) +    if not sanitized then +        luaotfload.warning( +            "“%s” is not a valid rgb[a] color expression", digits) +        return nil      end +    return sanitized +end + +--[[doc-- +``setcolor`` modifies tfmdata.properties.color in place +--doc]]-- + +--- fontobj -> string -> unit +--- +---         (where “string” is a rgb value as three octet +---         hexadecimal, with an optional fourth transparency +---         value) +--- +local setcolor = function (tfmdata, value) +    local sanitized  = sanitize_color_expression(value) +    local properties = tfmdata.properties      if sanitized then -        tfmdata.properties.color = sanitized +        properties.color = sanitized          add_color_callback()      end  end @@ -52,133 +118,187 @@ registerotffeature {      }  } -local function hex2dec(hex,one) -    if one then -        return stringformat("%.1g", tonumber(hex, 16)/255) -    else -        return stringformat("%.3g", tonumber(hex, 16)/255) -    end -end -local res +--- something is carried around in ``res`` +--- for later use by color_handler() --- but what? + +local res --- <- state of what? -local function pageresources(a) +--- float -> unit +local function pageresources(alpha)      local res2      if not res then         res = "/TransGs1<</ca 1/CA 1>>"      end -    res2 = stringformat("/TransGs%s<</ca %s/CA %s>>", a, a, a) -    res  = stringformat("%s%s", res, stringfind(res, res2) and "" or res2) +    res2 = stringformat("/TransGs%s<</ca %s/CA %s>>", +                        alpha, alpha, alpha) +    res  = stringformat("%s%s", +                        res, +                        stringfind(res, res2) and "" or res2)  end -local function hex_to_rgba(hex) -    local r, g, b, a, push, pop, res3 -    if hex then -        if #hex == 6 then -            _, _, r, g, b    = stringfind(hex, '(..)(..)(..)') -        elseif #hex == 8 then -            _, _, r, g, b, a = stringfind(hex, '(..)(..)(..)(..)') -            a                = hex2dec(a,true) -            pageresources(a) -        end -    else -        return nil +--- we store results of below color handler as tuples of +--- push/pop strings +local color_cache = { } --- (string, (string * string)) hash_t + +--- string -> (string * string) +local hex_to_rgba = function (digits) +    if not digits then +        return      end -    r = hex2dec(r) -    g = hex2dec(g) -    b = hex2dec(b) -    if a then -        push = stringformat('/TransGs%g gs %s %s %s rg', a, r, g, b) -        pop  = '0 g /TransGs1 gs' -    else -        push = stringformat('%s %s %s rg', r, g, b) -        pop  = '0 g' + +    --- this is called like a thousand times, so some +    --- memoizing is in order. +    local cached = color_cache[digits] +    if not cached then +        local push, pop +        local rgb = lpegmatch(extract_color, digits) +        if rgb.alpha then +            pageresources(rgb.alpha) +            push = stringformat( +                        "/TransGs%g gs %s %s %s rg", +                        rgb.alpha, +                        rgb.red, +                        rgb.green, +                        rgb.blue) +            pop  = "0 g /TransGs1 gs" +        else +            push = stringformat( +                        "%s %s %s rg", +                        rgb.red, +                        rgb.green, +                        rgb.blue) +            pop  = "0 g" +        end +        color_cache[digits] = { push, pop } +        return push, pop      end -    return push, pop + +    return cached[1], cached[2]  end -local glyph   = nodetype('glyph') -local hlist   = nodetype('hlist') -local vlist   = nodetype('vlist') -local whatsit = nodetype('whatsit') -local pgi     = nodetype('page_insert') -local sbox    = nodetype('sub_box') +--- Luatex internal types -local function lookup_next_color(head) +local glyph_t           = nodetype("glyph") +local hlist_t           = nodetype("hlist") +local vlist_t           = nodetype("vlist") +local whatsit_t         = nodetype("whatsit") +local page_insert_t     = nodetype("page_insert") +local sub_box_t         = nodetype("sub_box") + +--- node -> nil | -1 | color‽ +local lookup_next_color +lookup_next_color = function (head) --- paragraph material      for n in traverse_nodes(head) do -        if n.id == glyph then -            if ids[n.font] and ids[n.font].properties and ids[n.font].properties.color then -                return ids[n.font].properties.color +        local n_id = n.id + +        if n_id == glyph_t then +            local n_font +            if  identifiers[n_font] +            and identifiers[n_font].properties +            and identifiers[n_font].properties.color +            then +                return identifiers[n.font].properties.color              else                  return -1              end -        elseif n.id == vlist or n.id == hlist or n.id == sbox then + +        elseif n_id == vlist_t or n_id == hlist_t or n_id == sub_box_t then              local r = lookup_next_color(n.list) -            if r == -1 then -                return -1 -            elseif r then +            if r then                  return r              end -        elseif n.id == whatsit or n.id == pgi then + +        elseif n_id == whatsit_t or n_id == page_insert_t then              return -1          end      end      return nil  end -local function node_colorize(head, current_color, next_color) +--[[doc-- +While the second argument and second returned value are apparently +always nil when the function is called, they temporarily take string +values during the node list traversal. +--doc]]-- + +local cnt = 0 +--- node -> string -> int -> (node * string) +local node_colorize +node_colorize = function (head, current_color, next_color)      for n in traverse_nodes(head) do -        if n.id == hlist or n.id == vlist or n.id == sbox then -            local next_color_in = lookup_next_color(n.next) or next_color +        local n_id      = n.id +        local nextnode  = n.next + +        if n_id == hlist_t or n_id == vlist_t or n_id == sub_box_t then +            local next_color_in = lookup_next_color(nextnode) or next_color              n.list, current_color = node_colorize(n.list, current_color, next_color_in) -        elseif n.id == glyph then -            local tfmdata = ids[n.font] + +        elseif n_id == glyph_t then +            cnt = cnt + 1 +            local tfmdata = identifiers[n.font] + +            --- colorization is restricted to those fonts +            --- that received the “color” property upon +            --- loading (see ``setcolor()`` above)              if tfmdata and tfmdata.properties  and tfmdata.properties.color then -                if tfmdata.properties.color ~= current_color then -                    local pushcolor = hex_to_rgba(tfmdata.properties.color) -                    local push = newnode(whatsit, 8) -                    push.mode  = 1 -                    push.data  = pushcolor -                    head       = insert_node_before(head, n, push) -                    current_color = tfmdata.properties.color +                local font_color = tfmdata.properties.color +--                luaotfload.info( +--                    "n: %d; %s; %d %s, %s", +--                    cnt, utf.char(n.char), n.font, "<TRUE>", font_color) +                if font_color ~= current_color then +                    local pushcolor = hex_to_rgba(font_color) +                    local push      = newnode(whatsit_t, 8) +                    push.mode       = 1 +                    push.data       = pushcolor +                    head            = insert_node_before(head, n, push) +                    current_color   = font_color                  end -                local next_color_in = lookup_next_color (n.next) or next_color -                if next_color_in ~= tfmdata.properties.color then -                    local _, popcolor = hex_to_rgba(tfmdata.properties.color) -                    local pop  = newnode(whatsit, 8) -                    pop.mode   = 1 -                    pop.data   = popcolor -                    head       = insert_node_after(head, n, pop) -                    current_color = nil +                local next_color_in = lookup_next_color (nextnode) or next_color +                if next_color_in ~= font_color then +                    local _, popcolor = hex_to_rgba(font_color) +                    local pop         = newnode(whatsit_t, 8) +                    pop.mode          = 1 +                    pop.data          = popcolor +                    head              = insert_node_after(head, n, pop) +                    current_color     = nil                  end + +--            else +--                luaotfload.info( +--                    "n: %d; %s; %d %s", +--                    cnt, utf.char(n.char), n.font, "<FALSE>")              end          end      end      return head, current_color  end -local function font_colorize(head) -   -- check if our page resources existed in the previous run -   -- and remove it to avoid duplicating it later -   if res then -      local r = "/ExtGState<<"..res..">>" -      tex.pdfpageresources = stringgsub(tex.pdfpageresources, r, "") -   end -   local h = node_colorize(head, nil, nil) -   -- now append our page resources -   if res and stringfind(res, "%S") then -- test for non-empty string -      local r = "/ExtGState<<"..res..">>" -      tex.pdfpageresources = tex.pdfpageresources..r -   end -   return h +--- node -> node +local color_handler = function (head) +    -- check if our page resources existed in the previous run +    -- and remove it to avoid duplicating it later +    if res then +        local r = "/ExtGState<<" .. res .. ">>" +        tex.pdfpageresources = stringgsub(tex.pdfpageresources, r, "") +    end +    local new_head = node_colorize(head, nil, nil) +    -- now append our page resources +    if res and stringfind(res, "%S") then -- test for non-empty string +        local r = "/ExtGState<<" .. res .. ">>" +        tex.pdfpageresources = tex.pdfpageresources..r +    end +    return new_head  end  local color_callback_activated = 0 -function add_color_callback() +--- unit -> unit +add_color_callback = function ( )      if color_callback_activated == 0 then -        luatexbase.add_to_callback( -          "pre_output_filter", font_colorize, "luaotfload.colorize") +        luatexbase.add_to_callback(color_callback, +                                   color_handler, +                                   "luaotfload.color_handler")          color_callback_activated = 1      end  end diff --git a/luaotfload.dtx b/luaotfload.dtx index fb57462..d8d1863 100644 --- a/luaotfload.dtx +++ b/luaotfload.dtx @@ -1182,11 +1182,12 @@ and the derived files  luaotfload                  = luaotfload or {}  local luaotfload            = luaotfload -config                      = config or { } -config.luaotfload           = config.luaotfload or { } -config.luaotfload.resolver  = config.luaotfload.resolver  or "normal" -config.luaotfload.definer   = config.luaotfload.definer   or "patch" -config.luaotfload.loglevel  = config.luaotfload.loglevel  or 1 +config                            = config or { } +config.luaotfload                 = config.luaotfload or { } +config.luaotfload.resolver        = config.luaotfload.resolver  or "normal" +config.luaotfload.definer         = config.luaotfload.definer   or "patch" +config.luaotfload.loglevel        = config.luaotfload.loglevel  or 1 +config.luaotfload.color_callback  = config.luaotfload.color_callback  or "pre_linebreak_filter"  --luaotfload.prefer_merge     = config.luaotfload.prefer_merge or true  luaotfload.module = { diff --git a/mkglyphlist b/mkglyphlist index d73a608..281c736 100755 --- a/mkglyphlist +++ b/mkglyphlist @@ -27,9 +27,13 @@ require"lpeg"  require"socket"  kpse.set_program_name"luatex" -dofile(kpse.find_file("lualibs-lua.lua",   "lua")) -dofile(kpse.find_file("lualibs-lpeg.lua",  "lua")) -dofile(kpse.find_file("lualibs-table.lua", "lua")) --- for serialization +for _, lib in next, { "lualibs-lua.lua", +                      "lualibs-lpeg.lua", +                      "lualibs-table.lua", } do +  local found = assert(kpse.find_file(lib, "lua"), +                       "Could not locate " .. lib) +  require(found) +end  local C, Cf, Cg, Ct, P, R =    lpeg.C, lpeg.Cf, lpeg.Cg, lpeg.Ct, lpeg.P, lpeg.R  | 
