[latex3-commits] [git/LaTeX3-latex3-luaotfload] dev: Switch to callback based interface for new color options and document it (d0b298c)

Marcel Fabian Krüger tex at 2krueger.de
Thu Jun 9 17:24:17 CEST 2022


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

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

commit d0b298c8320de0b54932465243cf8bc5383d882f
Author: Marcel Fabian Krüger <tex at 2krueger.de>
Date:   Thu Jun 9 17:00:10 2022 +0200

    Switch to callback based interface for new color options and document it


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

d0b298c8320de0b54932465243cf8bc5383d882f
 doc/luaotfload-main.tex    | 102 +++++++++++++++++++++++++++++++++++++++++++
 src/luaotfload-colors.lua  | 106 +++++++++++++++++++++------------------------
 src/luaotfload-l3color.lua |  61 +++++++++++++++++---------
 3 files changed, 192 insertions(+), 77 deletions(-)

diff --git a/doc/luaotfload-main.tex b/doc/luaotfload-main.tex
index 48b300e..3352fcf 100644
--- a/doc/luaotfload-main.tex
+++ b/doc/luaotfload-main.tex
@@ -2406,6 +2406,108 @@ are defined for which scripts.
 
 \endsubsubsection
 
+\endsubsection
+
+\beginsubsection {Format Author’s Interface}
+
+Additionally some functions are provided which should only be needed for
+format authors trying to integrate \identifier{luaotfload}'s color handling
+with the conventions of a specific format.
+
+End users should never have to work with these.
+
+\endsubsubsection
+
+\beginsubsubsection{Color setting}
+
+\identifier{luaotfload}'s \inlinecode{color} feature can apply color in two
+different ways: By default it inserts \inlinecode{pdf_colorstack} whatsits
+containing the PDF code to change the color.
+
+Alternatively a function can be provided which gets called on every glyph
+node to be colored. This function can then e.g. apply an attribute which gets
+interpreted later.
+
+This can be enabled separately for colors and transparency by passing such
+callback functions:
+
+\beginfunctionlist
+
+  \beginaltitem  {luaotfload.set_colorhandler((head : node, n : node, color :
+      string) -> (head : node, n : node)}
+            Mark the node \luaident{n} in the list starting with \luaident{head}
+            to be colored with color \luaident{color}.
+
+            By default the color is represented by literal PDF code setting the
+            color.
+  \endaltitem
+
+  \beginaltitem  {luaotfload.set_transparenthandler((head : node, n : node,
+      level : string) -> (head : node, n : node)}
+            Mark the node \luaident{n} in the list starting with \luaident{head}
+            to be set transparently with transparency level \luaident{level}.
+
+            By default the transparency level is represented by literal PDF code setting the
+            transparency.
+  \endaltitem
+
+\endfunctionlist
+
+When these functions aren't used, then the color is set based on colorstack 0.
+By default a new colorstack is allocated for transparency, but alternatively an
+existsing colorstack for this prpose can be set:
+
+\beginfunctionlist
+
+  \beginaltitem  {luaotfload.set_transparent_colorstack(stack : int)}
+            Use colorstack \luaident{stack} for setting transparency.
+  \endaltitem
+
+\endfunctionlist
+
+\endsubsubsection
+
+\beginsubsubsection{Color selection}
+
+Additionally the translation of the argument to \inlinecode{color} to an
+actual PDF color can be customized though three \luaident{exclusive} callbacks:
+
+Since there is only a single \inlinecode{color} feature which sets both the
+color and the transparency, the first callback
+\luaident{luaotfload.split_color} gets called with a single string representing
+the feature value and is supposed to return two values representing the color
+and the transparency part.
+
+The default implementation splits the string the feature value at a comma and
+strips outer spaces for both result values, except the HTML style 8 hexdigit
+RGBA values get split into the first 6 and the last two digits.
+
+Afterwards the return values get passed to the
+\luaident{luaotfload.parse_color} respectively
+\luaident{luaotfload.parse_transparent} callbacks (except that \luaident{false}
+and \luaident{nil} skip the corresponding callback).
+
+These callbacks should translate these strings components into valid PDF
+commands applyind the described color or transparency.
+
+All error handling and reporting should be done in the callbacks.
+
+\endfunctionlist
+
+When these functions aren't used, then the color is set based on colorstack 0.
+By default a new colorstack is allocated for transparency, but alternatively an
+existsing colorstack for this prpose can be set:
+
+\beginfunctionlist
+
+  \beginaltitem  {luaotfload.set_transparent_colorstack(stack : int)}
+            Use colorstack \luaident{stack} for setting transparency.
+  \endaltitem
+
+\endfunctionlist
+
+\endsubsubsection
+
 \endsubsection
 \endsection
 
diff --git a/src/luaotfload-colors.lua b/src/luaotfload-colors.lua
index 59b0ab1..1253ae2 100644
--- a/src/luaotfload-colors.lua
+++ b/src/luaotfload-colors.lua
@@ -48,13 +48,14 @@ local nodetail              = nodedirect.tail
 local getattribute          = nodedirect.has_attribute
 local setattribute          = nodedirect.set_attribute
 
+local call_callback         = luatexbase.call_callback
+
 local stringformat          = string.format
 local identifiers           = fonts.hashes.identifiers
 
 local add_color_callback --[[ this used to be a global‽ ]]
 
-local custom_setcolor, custom_settransparent, custom_splitcolor,
-      custom_parsecolor, custom_parsetransparent
+local custom_setcolor, custom_settransparent
 
 --[[doc--
 Color string parser.
@@ -62,7 +63,7 @@ Color string parser.
 
 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 C, Cc, P, R, S = lpeg.C, lpeg.Cc, lpeg.P, lpeg.R, lpeg.S
 
 local spaces         = S"\t "^0
 local digit16        = R("09", "af", "AF")
@@ -80,13 +81,31 @@ local function lpeg_repeat(patt, count)
     return result
 end
 
-local split_color     = spaces * C(lpeg_repeat(digit16, 6)) * (opaque + C(lpeg_repeat(digit16, 2)))^-1 * spaces * -1;
+local split_color     = spaces * C(lpeg_repeat(digit16, 6)) * (opaque + C(lpeg_repeat(digit16, 2)))^-1 * spaces * -1
+                      + spaces * (C((spaces * (1 - R', ')^1)^1) + Cc(nil)) * spaces * (',' * spaces * C((spaces * (1 - R' ,')^1)^1)^-1 * spaces)^-1 * -1
+
+luatexbase.create_callback('luaotfload.split_color', 'exclusive', function(value)
+    local rgb, a = lpegmatch(split_color, value)
+    if not rgb and not a then
+        logreport("both", 0, "color",
+                  "%q is not a valid rgb[a] color expression",
+                  digits)
+    end
+    return rgb, a
+end)
 
 local extract_color  = octet * octet * octet / function(r,g,b)
                          return stringformat("%.3g %.3g %.3g rg", r, g, b)
                        end * -1
-
-local extract_transparent = octet * -1
+luatexbase.create_callback('luaotfload.parse_color', 'exclusive', function(value)
+    local rgb = lpegmatch(extract_color, value)
+    if not rgb then
+        logreport("both", 0, "color",
+                  "Invalid color part in color expression %q",
+                  value)
+    end
+    return rgb
+end)
 
 -- Keep the currently collected page resources needed for the current
 -- colors in `res`.
@@ -102,51 +121,35 @@ local function pageresources(alpha)
     return f
 end
 
---- string -> (string | nil)
-local function sanitize_color_expression (digits)
-    digits = tostring(digits)
-    local rgb, a
-    if custom_splitcolor then
-        rgb, a = custom_splitcolor (digits)
-    else
-        rgb, a = lpegmatch(split_color, digits)
-        if not rgb and not a then
+local extract_transparent = octet * -1
+luatexbase.create_callback('luaotfload.parse_transparent', 'exclusive', function(value)
+    local a
+    if type(value) == 'string' then
+        a = lpegmatch(extract_transparent, value)
+        if not a then
             logreport("both", 0, "color",
-                      "%q is not a valid rgb[a] color expression",
-                      digits)
-            return
+                      "Invalid transparency part in color expression %q",
+                      value)
         end
+    else
+        a = value
+    end
+    if a then
+        a = pageresources(a)
     end
+    return a
+end)
+
+
+--- string -> (string | nil)
+local function sanitize_color_expression (digits)
+    digits = tostring(digits)
+    local rgb, a = call_callback('luaotfload.split_color', digits)
     if rgb then
-        if custom_parsecolor then
-            rgb = custom_parsecolor(rgb)
-        else
-            rgb = lpegmatch(extract_color, rgb)
-            if not rgb then
-                logreport("both", 0, "color",
-                          "Invalid color part in color expression %q",
-                          digits)
-                return
-            end
-        end
+        rgb = call_callback('luaotfload.parse_color', rgb)
     end
     if a then
-        if custom_parsetransparent then
-            a = custom_parsetransparent(a)
-        else
-            if type(a) == 'string' then
-                a = lpegmatch(extract_transparent, a)
-                if not a then
-                    logreport("both", 0, "color",
-                              "Invalid transparency part in color expression %q",
-                              digits)
-                    return
-                end
-            end
-            if a then
-                a = pageresources(a)
-            end
-        end
+        a = call_callback('luaotfload.parse_transparent', a)
     end
     return rgb, a
 end
@@ -444,16 +447,7 @@ end
 function luaotfload.set_transparenthandler(cb)
   custom_settransparent = cb
 end
-function luaotfload.set_colorsplitter(cb)
-  custom_splitcolor = cb
-end
-function luaotfload.set_colorparser(cb)
-  custom_parsecolor = cb
-end
-function luaotfload.set_transparentparser(cb)
-  custom_parsetransparent = cb
-end
-function luaotfload.set_transparentstack(stack)
+function luaotfload.set_transparent_colorstack(stack)
   if type(transparent_stack) == 'number' then
     tex.error"luaotfload's transparency stack can't be changed after it has been used"
   else
@@ -468,7 +462,7 @@ function luaotfload.set_transparentstack(stack)
         return transparent_stack
       end
     else
-      tex.error("Invalid argument in luaotfload.set_transparentstack")
+      tex.error("Invalid argument in luaotfload.set_transparent_colorstack")
     end
   end
 end
diff --git a/src/luaotfload-l3color.lua b/src/luaotfload-l3color.lua
index 13468e2..1d4eab4 100644
--- a/src/luaotfload-l3color.lua
+++ b/src/luaotfload-l3color.lua
@@ -1,26 +1,10 @@
-if not luaotfload.set_colorsplitter then return end
+if not luaotfload.set_transparent_colorstack then return end
 local l = lpeg
 local spaces = l.P' '^0
 local digit16 = l.R('09', 'af', 'AF')
-local function rep(patt, count)
-  local result = patt
-  for i=2, count do
-    result = result * patt
-  end
-  return result
-end
-local traditional = spaces * l.C(rep(digit16, 6)) * (rep(l.S'fF', 2) + l.C(rep(digit16, 2)))^-1 * spaces * -1
-local field = l.C((1 - l.S' ,')^1)
-local new_syntax = spaces * field * (spaces * ',' * spaces * field)^-1 * spaces * -1
-local split_patt = traditional + new_syntax
-
-luaotfload.set_colorsplitter(function (value)
-  local rgb, a = split_patt:match(value)
-  return split_patt:match(value)
-end)
 
-local octet = rep(digit16, 2) / function(s) return string.format('%.3g ', tonumber(s, 16) / 255) end
-local htmlcolor = l.Cs(rep(octet, 3) * -1 * l.Cc'rg')
+local octet = digit16 * digit16 / function(s) return string.format('%.3g ', tonumber(s, 16) / 255) end
+local htmlcolor = l.Cs(octet * octet * octet * -1 * l.Cc'rg')
 local color_export = {
   token.create'endlocalcontrol',
   token.create'tex_hpack:D',
@@ -40,7 +24,7 @@ local color_export = {
 }
 local group_end = token.create'group_end:'
 local value = (1 - l.P'}')^0
-luaotfload.set_colorparser(function (value)
+luatexbase.add_to_callback('luaotfload.parse_color', function (value)
   local html = htmlcolor:match(value)
   if html then return html end
 
@@ -56,4 +40,39 @@ luaotfload.set_colorparser(function (value)
   local cmd = list.head.data
   node.free(list)
   return cmd
-end)
+end, 'l3color')
+
+-- Let's also integrate l3opacity
+
+luaotfload.set_transparent_colorstack(token.create'c__opacity_backend_stack_int'.index)
+
+local transparent_register = {
+  token.create'pdfmanagement_add:nnn',
+  token.new(0, 1),
+    'Page/Resources/ExtGState',
+  token.new(0, 2),
+  token.new(0, 1),
+    '',
+  token.new(0, 2),
+  token.new(0, 1),
+    '<</ca ',
+    '',
+    '/CA ',
+    '',
+    '>>',
+  token.new(0, 2),
+}
+luatexbase.add_to_callback('luaotfload.parse_transparent', function(value)
+  value = (octet * -1):match(value)
+  if not value then
+    tex.error'Invalid transparency value'
+    return
+  end
+  value = value:sub(1, -2)
+  local result = 'opacity' .. value
+  tex.runtoks(function()
+    transparent_register[6], transparent_register[10], transparent_register[12] = result, value, value
+    tex.sprint(-2, transparent_register)
+  end)
+  return '/' .. result .. ' gs'
+end, 'l3opacity')





More information about the latex3-commits mailing list.