I looked at Cool Text Highlighting in LaTeX, and as my MWE (below) demonstrates:
... the first part, highlighted with soul
's \hl
, fails when [cmyk]{xcolor}
is used. The accepted answer in the link above uses tikz
, and that solution does work with CMYK - but I was quite irritated by the faded text issue, which I managed to only partially solve.
Basically, I would like highlighting that works with CMYK, multiple hyphenated lines in a paragraph, and does not fade the text (the highlight is drawn in the "background"/"behind" the text). If there is an easier way for that, great - else, here is some discussion of the modified tikz
approach in the MWE, in case it can be changed so it works fully.
The first idea is that since that approach also uses soul
's machinery to get syllables upon their way to hyphenation, we could insert \phantom
s for the syllables when soul
runs for a highlight - and at the same time concatenate/append the syllables into a partial sentence, that we'll render on top of the highlight (which necessarily would have to be drawn over the phantoms) - and so avoid the faded text issue.
The problem is: if you give soul
something \box
ed, like a \phantom
, then the rest of the hyphenation/line breaking engine ends up wrong; the same happens if you try to draw white letters, e.g. via \textcolor{white}{#text}
. So, we have to print the syllables in the first run; then the only thing to do is to make the highlight background opaque (so it covers the first run letters), and then print the concatenated sentence on top of it. As seen above, this is not all that bad of an approach - but, the debug version (in a gist) test.tex which uses lua-visual-debug
(and transparent highlight background) reveals a bit more:
Note that the "double" letters on top of each other tend to mess up the selection process in a PDF reader (at least they do in evince
- but somewhat strangely, that is not as pronounced when the highlight is opaque?!)
Basically, the overlay text is placed in a tikz
node, which is "stretched" to cover the tikz
highlight background (a \fill
rectangle
), and the text align=
is then set to justify
to stretch the text inside the node; clearly, this does not always result with the same glue sizes being generated for the respective spaces. Second, when the TeX engine decides to hyphenate and break the end of line, we don't get the respective hyphenation -
in a soul
handler; and so that one is only included in the first-run text, and so is covered by the opaque background.
This MWE uses concatenation of syllable content into a command/macro variable; however, in the debug code there is also a test which concatenates into a box (see also Accessing child contents of LaTeX boxes (append/concatenate TeX box material)?). The problem there is that the boxes always end up with a .\pdfcolorstack 0 pop
at end of the box list, which is a problem because if the partial sentence ends with a space, it will mess up the sizing of the overlay text - and because of the .\pdfcolorstack
, we cannot use \unskip
to remove such trailing glues (spaces) from the box list (when using a "string" "variable", \unskip
at end of the string will help removing a possible trailing space).
Here is the code:
% see also: https://gist.github.com/anonymous/999426bbae600abb9e74
\documentclass{book}
\usepackage[cmyk]{xcolor}
\usepackage{soulutf8}
\usepackage{trace}
\usepackage{tikz}
\usetikzlibrary{calc}
\usetikzlibrary{fit}
\usepackage{etoolbox} % \providerobustcmd
\makeatletter
\gdef\highlight@Tcontent{}
\global\newsavebox{\highlight@box}
\newcommand{\defhighlighter}[3][]{%
\tikzset{every highlighter/.style={color=#2, fill opacity=#3, #1}}%
}
% \defhighlighter{yellow!30}{.5} % transparent, to show the text below
\defhighlighter{yellow!30}{1} % fully solid/opaque, to hide the text below
\newcommand{\highlight@DoHighlight}{
\path let
\p1=($(begin highlight)+(0,8pt)$),
\p2=($(end highlight)+(0,-3pt)$),
\p3=($(\p2)-(\p1)$)
in \pgfextra %
\global\highlight@previous=\x3 %
\endpgfextra %
[fill,%
every highlighter,%
this highlighter,%
]%
(\p1) rectangle (\p2) ;%
\unskip\ignorespaces%
\node%
[anchor=south west,inner sep=0pt, outer sep=0pt, minimum size=0pt,
% hand tuned for best position:
fit={($(begin highlight)+(0pt,0pt)$)($(end highlight)+(0,-2pt)$)},
% align goes after fit;
% https://tex.stackexchange.com/questions/213105/forcing-left-text-alignment-in-a-fit-tikz-node
text width=\highlight@previous, %if it is set, align=left may break, align=justify doesn't
align=justify, %
]%
%at ($(begin highlight)+(0,2.5pt)$) % don't use at() if using fit!
{\ignorespaces\highlight@Tcontent\unskip\ignorespaces}%
;
\gdef\highlight@Tcontent{}% erase here for next round
}
\newcommand{\highlight@BeginHighlight}{
\coordinate (begin highlight) at (0,0) ;
}
\newcommand{\highlight@EndHighlight}{
\coordinate (end highlight) at (0,0) ;
}
\def\highlight@appendTcontent#1{%
% do the append to a "string"
\xdef\highlight@Tcontent{\highlight@Tcontent#1}%
% output the syllable characters:
#1%
% \hbox{#1}, \phantom{#1} makes main line breaking calc wrong ;
% an outer box probably messes up hyphenation algorithm
% (in #1 we get the syllable, but not the '-' at end if it gets hyphenated)
}
\newdimen\highlight@previous %y!
\newdimen\highlight@current %y!s
\DeclareRobustCommand*\highlight[1][]{%
\tikzset{this highlighter/.style={#1}}%
\SOUL@setup%
%
\def\SOUL@preamble{%
\begin{tikzpicture}[overlay, remember picture]%
\highlight@BeginHighlight%
\highlight@EndHighlight%
\end{tikzpicture}%
}%
%
\def\SOUL@postamble{%
\begin{tikzpicture}[overlay, remember picture]%
\highlight@EndHighlight%
\highlight@DoHighlight%
\end{tikzpicture}%
}%
%
\def\SOUL@everyhyphen{%
\discretionary{%
\SOUL@setkern\SOUL@hyphkern%
\SOUL@sethyphenchar%
\tikz[overlay, remember picture] \highlight@EndHighlight ;%
}{%
}{%
\SOUL@setkern\SOUL@charkern%
}%
}%
%
\def\SOUL@everyexhyphen##1{%
\SOUL@setkern\SOUL@hyphkern%
%\hbox{##1}% don't output raw anymore; only append it to the box
\highlight@appendTcontent{\hbox{##1}}%
\discretionary{%
\tikz[overlay, remember picture] \highlight@EndHighlight ;%
}{%
}{%
\SOUL@setkern\SOUL@charkern%
}%
}%
%
\def\SOUL@everysyllable{%
\begin{tikzpicture}[overlay, remember picture]%
\path let \p0 = (begin highlight), \p1 = (0,0)
in \pgfextra
\global\highlight@previous=\y0
\global\highlight@current =\y1
\endpgfextra (0,0);%
\ifdim\highlight@current < \highlight@previous%
\highlight@DoHighlight%
%\gdef\highlight@Tcontent{}%
\highlight@BeginHighlight%
\fi%
\end{tikzpicture}%
%\the\SOUL@syllable% don't output raw; only append it to the box
\highlight@appendTcontent{\the\SOUL@syllable}%
\tikz[overlay, remember picture] \highlight@EndHighlight ;%
}%
% must define for spaces too:
\def\SOUL@everyspace##1{%
%##1\space% "The macro should at least do {#1\space}."% don't output raw; only append it to the box
\highlight@appendTcontent{##1\space}%
}
%
\unskip\ignorespaces% this to avoid doubling spaces at start!
%
\SOUL@%
}
\makeatother
\begin{document}
\noindent\hl{Highlighting} text feels good.
You can draw attention of people to a \hl{word} or perhaps
\hl{even a whole sentence that spans across multiple lines
in such a way that hyphenation etc. are not affected.}
% the highlights will render wrong if there is no space before them!
Lorem ipsum and the again %
\highlight{dolor sit amet, consectetur adipis-icing elit, sed do
eiusmod tempor} incididunt ut labore et dolore magna aliqua. Ut enim ad minim
veniam, quis nostrud exercitation
\highlight[red]{ullamco $laboris$ nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit}
in
voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Lorem ipsum %
\highlight{dolor sit amet, consectetur adipis-icing elit, sed do
eiusmod tempor} incididunt ut labore et dolore magna aliqua. Ut enim ad minim
veniam, quis nostrud exercitation
\highlight[red]{ullamco $laboris$ nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit}
in
voluptate velit esse cillum dolore eu fugiat nulla pariatur.
\end{document}