[latex3-commits] [git/LaTeX3-latex3-luaotfload] dev: Semisplit color and transparency handling (e823c6e)

Marcel Fabian Krüger tex at 2krueger.de
Fri Feb 21 17:32:23 CET 2020


Repository : https://github.com/latex3/luaotfload
On branch  : dev
Link       : https://github.com/latex3/luaotfload/commit/e823c6eaeac6ced09feb48886200cad0886c3ac9

>---------------------------------------------------------------

commit e823c6eaeac6ced09feb48886200cad0886c3ac9
Author: Marcel Fabian Krüger <tex at 2krueger.de>
Date:   Fri Feb 21 17:32:03 2020 +0100

    Semisplit color and transparency handling
    
    First step toward compatibility with luacolor and maybe at some point
    also transparency


>---------------------------------------------------------------

e823c6eaeac6ced09feb48886200cad0886c3ac9
 src/luaotfload-colors.lua | 242 ++++++++++++++++++----------------------------
 1 file changed, 92 insertions(+), 150 deletions(-)

diff --git a/src/luaotfload-colors.lua b/src/luaotfload-colors.lua
index b8091b2..e39dc64 100644
--- a/src/luaotfload-colors.lua
+++ b/src/luaotfload-colors.lua
@@ -59,22 +59,7 @@ local identifiers           = fonts.hashes.identifiers
 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 function hex_to_dec (hex,one)
-    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.
+Color string parser.
 --doc]]--
 
 local lpeg           = require"lpeg"
@@ -83,31 +68,14 @@ 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 opaque         = S("fF") * S("fF")
-local octet          = C(digit16 * digit16)
-
-local p_rgb          = octet * octet * octet
-local p_rgba         = p_rgb * (octet - opaque)
-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 function sanitize_color_expression (digits)
-    digits = tostring(digits)
-    local sanitized = lpegmatch(valid_digits, digits)
-    if not sanitized then
-        logreport("both", 0, "color",
-                  "%q is not a valid rgb[a] color expression",
-                  digits)
-        return nil
-    end
-    return sanitized
+local octet          = digit16 * digit16 / function(s)
+    return tonumber(s, 16) / 255
 end
 
+local extract_color  = octet * octet * octet / function(r,g,b)
+                         return stringformat("%.3g %.3g %.3g rg", r, g, b)
+                       end * (octet - opaque + opaque)^-1 * -1
+
 --- something is carried around in ``res``
 --- for later use by color_handler() --- but what?
 
@@ -116,47 +84,37 @@ local res = nil
 --- float -> unit
 local function pageresources(alpha)
     res = res or {}
-    res[alpha] = true
+    local f = res[alpha]
+        or stringformat("/TransGs%.3g gs", alpha, alpha)
+    res[alpha] = f
+    return f
 end
 
---- 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 function hex_to_rgba (digits)
-    if not digits then
+--- string -> (string | nil)
+local function sanitize_color_expression (digits)
+    digits = tostring(digits)
+    local rgb, a = lpegmatch(extract_color, digits)
+    if not rgb then
+        logreport("both", 0, "color",
+                  "%q is not a valid rgb[a] color expression",
+                  digits)
         return
     end
+    return rgb, (a and pageresources(a))
+end
 
-    --- 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 cached[1], cached[2]
+local color_stack = 0
+-- Beside maybe allowing {transpareny} package compatibility at some
+-- point, this ensures that the stack is only created if it is actually
+-- needed. Especially important because it adds /TransGs1 gs to every page
+local function transparent_stack()
+    -- if token.is_defined'TRP at colorstack' then -- transparency
+        -- transparent_stack = tonumber(token.get_macro'TRP at colorstack')
+    -- else
+        res[1] = true
+        transparent_stack = pdf.newcolorstack("/TransGs1 gs","direct",true)
+    -- end
+    return transparent_stack
 end
 
 --- Luatex internal types
@@ -167,49 +125,43 @@ local hlist_t           = nodetype("hlist")
 local vlist_t           = nodetype("vlist")
 local whatsit_t         = nodetype("whatsit")
 local disc_t            = nodetype("disc")
-local pdfliteral_t      = node.subtype("pdf_literal")
 local colorstack_t      = node.subtype("pdf_colorstack")
 local mlist_to_hlist    = node.mlist_to_hlist
 
 local color_callback
 local color_attr        = luatexbase.new_attribute("luaotfload_color_attribute")
 
--- (node * node * string * bool * (bool | nil)) -> (node * node * (string | nil))
-local function color_whatsit (head, curr, color, push, tail)
-    local pushdata  = hex_to_rgba(color)
+-- Pass nil for new_color or old_color to indicate no color
+-- If color is nil, pass tail to decide where to add whatsit
+local function color_whatsit (head, curr, stack, old_color, new_color, tail)
+    if new_color == old_color then
+        return head, curr, old_color
+    end
     local colornode = newnode(whatsit_t, colorstack_t)
-    setfield(colornode, "stack", 0)
-    setfield(colornode, "command", push and 1 or 2) -- 1: push, 2: pop
-    setfield(colornode, "data", push and pushdata or nil)
+    setfield(colornode, "stack", tonumber(stack) or stack())
+    setfield(colornode, "command", new_color and (old_color and 0 or 1) or 2) -- 1: push, 2: pop
+    setfield(colornode, "data", new_color) -- Is nil for pop
     if tail then
         head, curr = insert_node_after (head, curr, colornode)
     else
         head = insert_node_before(head, curr, colornode)
     end
-    if not push and color:len() > 6 then
-        local colornode = newnode(whatsit_t, pdfliteral_t)
-        setfield(colornode, "mode", 2)
-        setfield(colornode, "data", "/TransGs1 gs")
-        if tail then
-            head, curr = insert_node_after (head, curr, colornode)
-        else
-            head = insert_node_before(head, curr, colornode)
-        end
-    end
-    color = push and color or nil
-    return head, curr, color
+    return head, curr, new_color
 end
 
 -- number -> string | nil
 local function get_glyph_color (font_id, char)
     local tfmdata    = identifiers[font_id]
-    local font_color = tfmdata and tfmdata.properties and tfmdata.properties.color
-    if type(font_color) == "table" then
+    local properties = tfmdata and tfmdata.properties
+    local font_color = properties and properties.color_rgb
+    local font_transparent = properties and properties.color_a
+    if type(font_color) == "table" and type(font_transparent) == "table" then
         local char_tbl = tfmdata.characters[char]
         char = char_tbl and (char_tbl.index or char)
-        return char and font_color[char] or font_color.default
+        font_color = char and font_color[char] or font_color.default
+        font_transparent = char and font_transparent[char] or font_transparent.default
     end
-    return font_color
+    return font_color, font_transparent
 end
 
 --[[doc--
@@ -219,7 +171,7 @@ values during the node list traversal.
 --doc]]--
 
 --- (node * (string | nil)) -> (node * (string | nil))
-local function node_colorize (head, toplevel, current_color)
+local function node_colorize (head, toplevel, current_color, current_transparent)
     local n = head
     while n do
         local n_id = getid(n)
@@ -227,70 +179,52 @@ local function node_colorize (head, toplevel, current_color)
         if n_id == hlist_t or n_id == vlist_t then
             local n_list = getlist(n)
             if getattribute(n_list, color_attr) then
-                if current_color then
-                    head, n, current_color = color_whatsit(head, n, current_color, false)
-                end
+                head, n, current_color = color_whatsit(head, n, color_stack, current_color, nil)
+                head, n, current_transparent = color_whatsit(head, n, transparent_stack, current_transparent, nil)
             else
-                n_list, current_color = node_colorize(n_list, false, current_color)
-                if current_color and getsubtype(n) == 1 then -- created by linebreak
-                    n_list, _, current_color = color_whatsit(n_list, nodetail(n_list), current_color, false, true)
+                n_list, current_color, current_transparent = node_colorize(n_list, false, current_color, current_transparent)
+                if getsubtype(n) == 1 then -- created by linebreak
+                    local nn = nodetail(n_list)
+                    n_list, nn, current_color = color_whatsit(n_list, nn, color_stack, current_color, nil, true)
+                    n_list, nn, current_transparent = color_whatsit(n_list, nn, transparent_stack, current_transparent, nil, true)
                 end
                 setfield(n, "head", n_list)
             end
 
         elseif n_id == disc_t then
             local n_pre, n_post, n_replace = getdisc(n)
-            n_replace, current_color = node_colorize(n_replace, false, current_color)
+            n_replace, current_color, current_transparent = node_colorize(n_replace, false, current_color, current_transparent)
             setdisc(n, n_pre, n_post, n_replace)
 
         elseif n_id == glyph_t then
             --- colorization is restricted to those fonts
             --- that received the “color” property upon
             --- loading (see ``setcolor()`` above)
-            local glyph_color = get_glyph_color(getfont(n), getchar(n))
-            if glyph_color ~= current_color then
-                if current_color then
-                    head, n, current_color = color_whatsit(head, n, current_color, false)
-                end
-                if glyph_color then
-                    head, n, current_color = color_whatsit(head, n, glyph_color, true)
-                end
-            end
-
-            if current_color and color_callback == "pre_linebreak_filter" then
-                local nn = getnext(n)
-                while nn and getid(nn) == glyph_t do
-                    local glyph_color = get_glyph_color(getfont(nn), getchar(nn))
-                    if glyph_color == current_color then
-                        n = nn
-                    else
-                        break
-                    end
-                    nn = getnext(nn)
-                end
-                if getid(nn) == disc_t then
-                    head, n, current_color = color_whatsit(head, nn, current_color, false, true)
-                else
-                    head, n, current_color = color_whatsit(head, n, current_color, false, true)
-                end
-            end
+            local glyph_color, glyph_transparent = get_glyph_color(getfont(n), getchar(n))
+            head, n, current_color = color_whatsit(head, n, color_stack, current_color, glyph_color)
+            head, n, current_transparent = color_whatsit(head, n, transparent_stack, current_transparent, glyph_transparent)
 
         elseif n_id == whatsit_t then
-            if current_color then
-                head, n, current_color = color_whatsit(head, n, current_color, false)
-            end
+            head, n, current_color = color_whatsit(head, n, color_stack, current_color, nil)
+            head, n, current_transparent = color_whatsit(head, n, transparent_stack, current_transparent, nil)
 
         end
 
         n = getnext(n)
     end
 
-    if toplevel and current_color then
-        head, _, current_color = color_whatsit(head, nodetail(head), current_color, false, true)
+    if toplevel then
+        local nn = nodetail(n_list)
+        if current_color then
+            head, nn, current_color = color_whatsit(head, nn, color_stack, false, true)
+        end
+        if current_transparent then
+            head, nn, current_transparent = color_whatsit(head, nn, transparent_stack, false, true)
+        end
     end
 
     setattribute(head, color_attr, 1)
-    return head, current_color
+    return head, current_color, current_transparent
 end
 
 local getpageres = pdf.getpageresources or function() return pdf.pageresources end
@@ -307,16 +241,16 @@ local function color_handler (head)
 
     -- now append our page resources
     if res then
-        res["1"]  = true
-        if scantoks and pgf.bye and not pgf.loaded then
+        if scantoks and nil == pgf.loaded then
             pgf.loaded = token.create(pgf.bye).cmdname == "assign_toks"
-            pgf.bye    = pgf.loaded and pgf.bye
         end
-        local tpr = pgf.loaded and gettoks(pgf.bye) or getpageres() or ""
+        local tpr = pgf.loaded                 and gettoks(pgf.bye) or -- PGF
+                    -- token.is_defined'TRP at list' and token.get_macro'TRP at list' or -- transparency
+                                                   getpageres() or ""
 
         local t   = ""
         for k in pairs(res) do
-            local str = stringformat("/TransGs%s<</ca %s>>", k, k) -- don't touch stroking elements
+            local str = stringformat("/TransGs%.3g<</ca %.3g>>", k, k) -- don't touch stroking elements
             if not tpr:find(str) then
                 t = t .. str
             end
@@ -324,6 +258,8 @@ local function color_handler (head)
         if t ~= "" then
             if pgf.loaded then
                 scantoks("global", pgf.bye, catat11, stringformat("%s{%s}%s", pgf.extgs, t, tpr))
+            -- elseif token.is_defined'TRP at list' then
+            --     token.set_macro('TRP at list', t .. tpr, 'global')
             else
                 local tpr, n = tpr:gsub("/ExtGState<<", "%1"..t)
                 if n == 0 then
@@ -401,10 +337,10 @@ local glyph_color_tables = { }
 -- and node and difficulties with getting the mapped unicode value for
 -- a GID.
 local function setcolor (tfmdata, value)
-    local sanitized
+    local sanitized_rgb, sanitized_a
     local color_table = glyph_color_tables[tonumber(value) or value]
     if color_table then
-        sanitized = {}
+        sanitized_rgb = {}
         local unicodes = tfmdata.resources.unicodes
         local gid_mapping = {}
         local descriptions = tfmdata.descriptions or tfmdata.characters
@@ -417,7 +353,13 @@ local function setcolor (tfmdata, value)
                     gid = desc and (desc.index or unicode)
                 end
                 if gid then
-                    sanitized[gid] = sanitize_color_expression(color)
+                    local a
+                    sanitized_rgb[gid], a
+                        = sanitize_color_expression(color)
+                    if a then
+                        sanitized_a = sanitized_a or {}
+                        sanitized_a[gid] = a
+                    end
                 else
                     -- TODO: ??? Error out, warn or just ignore? Ignore
                     -- makes sense because we have to ignore for GIDs
@@ -426,12 +368,12 @@ local function setcolor (tfmdata, value)
             end
         end
     else
-        sanitized = sanitize_color_expression(value)
+        sanitized_rgb, sanitized_a = sanitize_color_expression(value)
     end
     local properties = tfmdata.properties
 
-    if sanitized then
-        properties.color = sanitized
+    if sanitized_rgb then
+        properties.color_rgb, properties.color_a = sanitized_rgb, sanitized_a
         add_color_callback()
     end
 end





More information about the latex3-commits mailing list.