[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.