8

I like the way soul package enables coloring text background because it:

  • adds no padding around \hl{}ed text (unlike \colorbox unless we set \fboxsep=0cm in a surrounding group)
  • enables line wrapping (unlike \colorbox which is unbreakable)

However, it does not support:

  • transparent background coloring
  • nesting highlighted texts (which does makes sense if the background is transparent)

In other words, I am looking for a magical command which would allow me define a custom \mhl highlighting command so that:

\documentclass{standalone}

\newcommand{\mhl}[1]{\whichCommandIsThat{black}{.2}{#1}}

\begin{document}

\begin{minipage}{3.4cm}
here is a \mhl{weird \mhl{nested, wrappable} weird} text
\end{minipage}

\end{document}

would produce:

enter image description here

Any idea?

  • Do you really need transparent background, i.e. seeing background pictures, or would it be enough that the colour in nested highlights becomes darker? – samcarter_is_at_topanswers.xyz Mar 26 '17 at 12:24
  • @samcarter Mmh.. I get the idea, but I guess I need true transparent. Not because of background pictures, but because darkenizing highlight color on each step down the tree would not be sufficient in the case of a \mhl{black}{.5}{wide gray text both \mhl{red}{.2}{reddish} or \mhl{blue}{.2}{blueish sometimes}, see?}.. :\ – iago-lito 'considering leaving Mar 26 '17 at 12:30
  • OK, this might still be doable without true transparency, by mixing the colours and darkening the result - unfortunately this is beyond my colour model knowledge. – samcarter_is_at_topanswers.xyz Mar 26 '17 at 12:35
  • @samcarter yeah, these are quite naughty calculations, uh? ;) Plus, with such a comfortable scripting language, it would be a nightmare XD – iago-lito 'considering leaving Mar 26 '17 at 12:40
  • This should be doable with something like the blend mode in tikz, albeit that dealing with line breaks could be tough. – JPi Mar 26 '17 at 13:49
6

Edit : see the updated full solution at the end.

I do have a partial answer by using the tcolorbox package, where nesting and transparancy are possible:

\documentclass{article}
\usepackage{tcolorbox}
\tcbuselibrary{skins,breakable,xparse}
\newcommand{\mhl}[1]{%
   \tcbox[tcbox raise base, 
  left=0mm,right=0mm,top=0mm,bottom=0mm,boxsep=0.5pt,arc=0mm,
  boxrule=0pt,opacityfill=0.3,enhanced jigsaw,colback=gray!85!white,
   before=\relax,after=\relax]{#1}
}
\begin{document}
I say \mhl{Hello World} !
This box has a no \mhl{defined \mhl{height}} as you can see. 
The total \mhl{colored box is \mhl{shrunk to the 
\mhl{dimensions} of the \mhl{upper} part}. There} should be  no title. 
\end{document}

resulting in: enter image description here Allas it is not breakable across lines. May be would it possible to emulate breakable capability by using xstring to handle text word by word...

EDIT: A better solution uses the rather new listofitems package allowing to split the content of \mhl into words or inner \mhl, an to process them one by one. For this purpose the former macro \mhl calling \tcbox is recast into an internal macro \mhl@int. The line breaking works in the first level of \mhl, but not in the nested one.

New code:

\documentclass{article}
\usepackage{tcolorbox}
\usepackage{trimspaces}
\usepackage{listofitems}
\tcbuselibrary{skins,breakable}
%
\makeatletter
\newcommand{\trim}[1]{\trim@spaces@noexp{#1}}
\newlength{\mhl@spacelen}
\settowidth{\mhl@spacelen}{\ }
\def\mhl@space{\kern\mhl@spacelen}
\def\mhl@backspace{\kern-1.1\mhl@spacelen}
%
\newcommand{\mhl@int}[1]{%
\mhl@backspace%
\tcbox[tcbox raise base,
left=0mm,right=0mm,top=0mm,bottom=0mm,boxsep=0pt,arc=0mm,boxrule=0pt,
opacityfill=0.3,enhanced jigsaw,colback=gray!85!white,
before=\relax,after=\relax]{\trim{#1}}
}
%
\newcommand{\mhl}[1]{%
\setsepchar{ }
\ignoreemptyitems
\readlist\phrase{#1}
\foreachitem\mot\in\phrase{\mhl@int{\strut\mot\mhl@space}}
}
%
\makeatother
%
\begin{document}
I say \mhl{Hello World}! This box has a no \mhl{defined \mhl{height}} 
as you can see. The total colored \mhl{box is shrunk to 
the\mhl{dimensions} of the upper part. \mhl{There} should be 
no lower part and no title.}
\end{document}

New result: enter image description here

Further improvement would likely use recursive calls to mhl with a tracking of the level to get rid of some extra spaces and pushes the breakability into the nested highlighted sentences.

Edit: full solution

After a while, I finaly obtained a full solution of this question. It still rely on tcolorbox for coloring the background and on parsing the text with listofitems. As nested tcolorbox are not breakable, the procedure loop on the sentence, highlighting it word by word, keeping a track of the level, and handling space as carefully as possible.

Here is the MWE (sorry for the French words, phrase=sentence, mot=word):

\documentclass{standalone}
\usepackage{listofitems}
\usepackage{xstring}
\usepackage{tcolorbox}
\tcbuselibrary{skins,breakable,xparse}
%
\makeatletter
\newlength{\mhl@spacelen}
\settowidth{\mhl@spacelen}{\space}
\def\mhl@space{\hspace*{\mhl@spacelen}}
\def\mhl@sep{\hspace{\z@}}
\newcounter{nesthl}
\newif\ifmhlstart \mhlstartfalse
\newif\ifmhlend \mhlendfalse
%

\newcommand{\mhl@int}[2]{%
\ifcase#1\def\col{black!10!white}\or\def\col{black!10!white}\or\def\col{black!50!white}\or\def\col{black!85!white}\fi%
\tcbox[tcbox raise base, spartan, left=0mm,right=0mm,top=0mm,bottom=0mm,boxsep=0pt,arc=0mm,
boxrule=0pt,before=\relax,after=\relax, opacityfill=0.35,colback=\col
]{\strut #2}}

\newcommand{\mhl}[1]{%
\setcounter{nesthl}{1}%
\def\beforeskip{}%
\setsepchar{ }\ignoreemptyitems\readlist*\phrase{#1}%
\foreachitem\mot\in\phrase{%
    \IfBeginWith{\mot}{+}{\mhlstarttrue}{\mhlstartfalse}%
    \IfEndWith{\mot}{+}{\mhlendtrue}{\mhlendfalse}%
    \ifmhlstart \StrBehind{\mot}{+}[\nmot]\else\edef\nmot{\mot}\fi%
    \ifmhlend\StrBefore{\nmot}{+}[\nnmot]\else\edef\nnmot{\nmot}\fi%
    \ifmhlstart\addtocounter{nesthl}{1}\fi%
    \ifmhlend \ifnum\thenesthl>1 \def\beforeskip{\mhl@space}\fi
    \mhl@int{\thenesthl}{\nnmot}\mhl@sep\addtocounter{nesthl}{-1}%
    \else\mhl@int{\thenesthl}{\beforeskip\nnmot\mhl@space}\mhl@sep\def\beforeskip{}\fi%
    }
}
\makeatother

\begin{document}
\framebox{
\begin{minipage}{3.7cm}
here is a  \mhl{weird nested, +wrappable, +dummy blob+ and nice+} text
\end{minipage}
}
\end{document}

and the result: enter image description here

The key idea in this solution consists in not using nested \mhl command that would prevent wrapping, but to use a simple wiki-like markup inside the \mhl command, with + (it works also with *, and likely punctuation character if they are not active). Words starting with a + (without space) initialize a new level, and those finishing with a + close this level. The number of + must be even, and their relative position does matter.

The maximal nesting depth is not really limited, but to use a larger one than the 3 levels used here, one would have to modify the \mhl@int macro to enlarge the number of shades available (first line) and likely the opacityfill parameter of the boxes.

| improve this answer | |
  • These are a few steps right on the way :) Thank you for your patience! – iago-lito 'considering leaving Nov 29 '17 at 10:03
  • I finally propose a full solution, along the lines outlined at the end of my former post. – Jhor Jul 26 '18 at 19:18
  • Nice. No doubt this recent question, tex.stackexchange.com/questions/443156/…, got you thinking about this one again. I tried to upvote, but found I had already done so. – Steven B. Segletes Jul 26 '18 at 19:21
  • @Steven B. Segletes Sorry I was bot aware about this question and your nice solution, even if I don't really understand which was the problem. The great achievement in your answer is the ability to have line breaking, and furthermore keep the right justification. But for the present topic, the key problem was the nesting of successives levels. – Jhor Jul 26 '18 at 19:32
  • That's a big work for a nice solution, thanks :D – iago-lito 'considering leaving Jul 31 '18 at 9:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.