Skip to content

Instantly share code, notes, and snippets.

@bpj
Last active February 12, 2024 08:11
Show Gist options
  • Save bpj/a10e8373ca82bf5678db80abee2f43ef to your computer and use it in GitHub Desktop.
Save bpj/a10e8373ca82bf5678db80abee2f43ef to your computer and use it in GitHub Desktop.
A Pandoc filter which sets LaTeX text/background/frame color(s) on Span and Div elements based on Pandoc attributes.
--[==============================[
# attr-color.lua
A Pandoc filter which sets LaTeX text/background/frame color(s) on
Span and Div elements based on Pandoc attributes.
## Usage
See https://git.io/JI4yA
## License
This software is Copyright (c) 2020 by Benct Philip Jonsson.
This is free software, licensed under:
The MIT (X11) License
The MIT License
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to
whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice shall
be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT
WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
--]==============================]
-- local dump = require"pl.pretty".dump -- for debugging
-- assert with format string
local function assertf (val, msg, ...)
if val then return val end
error(msg:format(...))
end
local rrggbb_keys = {'rr','gg','bb'}
local rgb_keys = {'r','g','b'}
local pats = {
{ keys = rrggbb_keys, pat = '^%#(%x%x)(%x%x)(%x%x)$' },
{ keys = rgb_keys, pat = '^%#(%x)(%x)(%x)$' },
{ keys = {'name'}, pat = '^(%a%w*)$' },
}
local color_attrs = {'fg','bg','fr'}
local tex_fmt = {
fcolorbox = {
pre = '\\fcolorbox@(fr)@(bg){',
post = '}',
},
colorbox = {
pre = '\\colorbox@(bg){',
post = '}',
},
-- This is a 'pseudocommand',
-- see https://tex.stackexchange.com/a/22928
-- We might wish to define it but then the filter or
-- the user will have to modify header-includes, so no!
colorfbox = {
pre = '{\\colorlet{curcolor}{.}\\color@(fr)\\fbox{\\color{curcolor}',
post = '}}',
},
-- Variation on colorfbox with a custom text/fg color to avoid redundancy
-- although there will be some redundancy if the frame/text colors are
-- the same, but can't check for that because User may say `{fr=blue fg="#00f"}`
-- and knowing the values of named colors is too much work.
textcolorfbox = {
pre = '{\\color@(fr)\\fbox{',
post = '}}',
},
textcolor = {
pre = '\\textcolor@(fg){',
post = '}',
},
}
local function color2tex (name,value)
local color
for _,p in ipairs(pats) do
local match = { value:match(p.pat) }
if #match > 0 then
for i,k in ipairs(p.keys) do
match[k] = match[i]
end
color = match
break
end
end
assertf(
color, 'Bad value for attribute %s: "%s"', name, value
)
if color.r then
for _,x in ipairs(rgb_keys) do
color[x .. x] = color[x] .. color[x]
end
end
if color.rr then
for _,xx in ipairs(rrggbb_keys) do
color[xx] = color[xx]:upper()
end
color.name = color.rr .. color.gg .. color.bb
color.model = '[HTML]'
else
color.model = "" -- named color
end
return color.model .. '{' .. color.name .. '}'
end
-- Interpolate table values into a string by replacing
-- placeholders of the form `@(KEY)` with the value of key KEY
local function interp (str, tab)
local function _subst (k)
return assertf(tab[k], "No value for key: %s", k)
end
return str:gsub('%@%(([^()]+)%)', _subst)
end
-- Generate the xcolor commands to 'wrap' around
-- the span contents according to which color attributes we found
local function make_tex (colors)
-- The return values are two strings of raw LaTeX
-- to put before and after the span contents.
-- We always include a \strut to make sure that
-- the box(es) be one \baselineskip high!
local coms, pre, post = {}, {},{"\\strut"}
if colors.fr then -- if frame
if colors.bg then -- if also bground
coms[#coms+1] = 'fcolorbox'
elseif colors.fg then
coms[#coms+1] = 'textcolorfbox'
else
coms[#coms+1] = 'colorfbox'
end
elseif colors.bg then
coms[#coms+1] = 'colorbox'
end
if colors.fg then
coms[#coms+1] = 'textcolor'
end
for _,com in ipairs(coms) do
local com = assertf(
tex_fmt[com],
"No format for command: %s", com
)
pre[#pre+1] = interp(com.pre, colors)
post[#post+1] = interp(com.post, colors)
end
pre = table.concat(pre)
post = table.concat(post)
return pre, post
end
local function color_span (elem)
local colors
for _,name in ipairs(color_attrs) do
value = elem.attributes[name]
if value then
colors = colors or {}
colors[name] = color2tex(name,value)
end
end
if colors then
local pre, post = make_tex(colors)
return{
pandoc.RawInline('latex', pre),
elem,
pandoc.RawInline('latex', post),
}
end
return nil
end
-- -- for debugging without pandoc
-- local read = require"pl.pretty".read
-- for line in io.lines() do
-- colors = assert(read(line))
-- for _,name in ipairs(color_attrs) do
-- if colors[name] then
-- colors[name] = color2tex(name, colors[name])
-- end
-- end
-- print(line)
-- print( make_tex(colors) )
-- end
return {
{ Span = color_span },
{ Link = color_span },
}

attr-color.lua

A Pandoc filter which sets LaTeX text/background/frame color(s) on Span and Div elements based on Pandoc attributes.

Usage

pandoc -L attr-color.lua -o document.ltx document.md

pandoc -L attr-color.lua -o document.pdf document.md

To colorize a Span or Link set one or more of the following custom attributes on the Span or Link:

Attribute Sets color of
fg foreground (text)
bg background
fr frame

The value of the attributes should be one of

Notation Description
"#rrggbb" RGB color in HTML notation
"#rgb" Short HTML RGB notation
ColorName xcolor color name.

A ColorName should be a color name from one of the predefined xcolor named color sets (described in the section of the xcolor documentation called Colors By Name) loaded by Pandoc's latex template (unless you want to go through the hassle of injecting your own color definitions into the LaTeX preamble!), such as ForestGreen.

In the #rrggbb notation each r/g/b stands for an hexadecimal digit 0-9a-z, two digits for each of the red, green and blue component of the color. The #rgb notation is equivalent to an #rrggbb where both digits in each component are identical, so that for example blue, #00f and #0000ff are equivalent.

As implied in the table Pandoc generally lets you leave color names, which contain only ASCII letters and digits, unquoted as attribute values, but the RGB formats need to be quoted because of the # character.

Examples

Markdown LaTeX
[Foo]{fg=red} \textcolor{red}{Foo\strut}
[Foo]{fg="#f00"} \textcolor[HTML]{FF0000}{Foo\strut}
[Foo]{fg="#ff0000"} \textcolor[HTML]{FF0000}{Foo\strut}
[Foo]{fg="#F00"} \textcolor[HTML]{FF0000}{Foo\strut}
[Foo]{bg=green} \colorbox{green}{Foo\strut}
[Foo]{bg=yellow fr=blue} \fcolorbox{blue}{yellow}{Foo\strut}
[Foo]{fg=red bg=yellow fr=blue} \fcolorbox{blue}{yellow}{\textcolor{red}{Foo\strut}}
[Foo]{fg=red fr=blue} {\color{blue}\fbox{\textcolor{red}{Foo\strut}}}
[Foo]{fr=red} {\colorlet{curcolor}{.}\color{red}\fbox{\color{curcolor}Foo\strut}}

Rendered example

As you can see the LaTeX for frames without background color is a hack. It could certainly be wrapped in a command, but in that case it would be necessary to get that command into the LaTeX preamble, which is non-trivial. Both the filter code and the LaTeX code would be even prettier if the adjustbox package were used, but again that would mean getting the \usepackage{adjustbox} into the preamble.

Links

Note that if you use this filter to color links you should not use the hyperref link coloring feature as triggered by colorlinks and other Pandoc variables since the two methods clash.

Dependencies

  • A version of pandoc which supports Lua filters.
  • The xcolor package. If you produce standalone LaTeX with pandoc, such as when producing PDF with Pandoc/LaTeX, the latex template loads xcolor automatically, along with the dvipsnames, svgnames and xllnames. Some older versions of pandoc may not be fully supportive, but of course it is a good idea to update to the latest anyway.

Todo

  • Add the necessary metadata trickery to at least optionally use adjustbox or at least to inject the code for a \colorfbox macro so that the LaTeX source doesn't look so ugly.

  • Add an HTML mode which translates the attributes to inline CSS style attributes.

Bugs

Report them at the gist

Author and license

This software is Copyright (c) 2020 by Benct Philip Jonsson.

This is free software, licensed under:

The MIT (X11) License

http://www.opensource.org/licenses/mit-license.php

@bpj
Copy link
Author

bpj commented Mar 25, 2021

@lhoupert the filter now supports both LaTeX and HTML and lives at

https://github.com/bpj/pandoc-attr-color

@lhoupert
Copy link

Great thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment