Rounded box around placeholder text that supports line breaking
This is not a perfect solution, but it is close, and you can get there with very little effort. Basically use textbackground
and change the radius using the radius
key. For example:
\definecolor [lightblue] [r=0.5, g=0.5, b=1]
\definetextbackground
[placeholder]
[
location=text,
background=color,
backgroundcolor=lightblue,
frame=on,
corner=round,
radius=0.8\lineheight,
]
\starttext
This is a \placeholder{placeholder text \input ward again} continuing
/foo/\placeholder{placeholder}/bar
\stoptext
which gives
This does not behave well when it is split across only one line.
This answer uses a different mechanism, so I am posting it as a separate answer rather than editing the previous one. This is not a complete answer, but a proof of concept that works if the phrase is split across lines only once. It is possible to extend it so that it works if the phrase is split across multiple lines and/or multiple pages, but that requires more code.
The main idea is to use the anchoring mechanism of ConTeXt (similar to the \Tikzmark
macro), record the beginning and end of the phrase, and use that the draw the background. Since the actual code of drawing the background is a bit messy, let me start by the simple task of underlining the phrase so that the underline splits across lines.
\definecolor[lightblue][b=1,r=0.5,g=0.5]
\unprotect
\newcount\c_placeholder_n
\def\placeholder#1%
{\global\advance\c_placeholder_n\plusone
\startpositionoverlay{text-1}% The layer just below text
\setMPpositiongraphicrange
{b:placeholder:\the\c_placeholder_n}%
{e:placeholder:\the\c_placeholder_n}%
{mpos:placeholder}%
{self=placeholder:\the\c_placeholder_n,
linewidth=1bp,
linecolor=red,
color=lightblue,
radius=0.5\lineheight}%
\stoppositionoverlay
\bpos{placeholder:\the\c_placeholder_n}%
#1%
\epos{placeholder:\the\c_placeholder_n}}
\protect
% This is a simple proof of concept. I assume that there is at most one line
% break, and no page break between the text.
\startMPpositionmethod{mpos:placeholder}
\startMPpositiongraphic{mpos:placeholder}{linecolor,linewidth,color,radius}%
begingroup;
newnumeric x,y, w, a_x, a_y, a_h, a_d, b_x, b_y, b_h, b_d, r ;
(x,y) = \MPxy\textanchor; % The overlay is anchored at (x,y).
% So, we have to subtract (x,y) from each point.
(a_x,a_y) = \MPxy\MPbself; % The localtion of the beginning of the line.
% Height and depth at the beginning of the line
a_h := \MPh\MPbself; a_d := \MPd\MPbself;
(b_x,b_y) = \MPxy\MPeself; % The location of the end of the line
% Height and depth at the end of the line
b_h := \MPh\MPeself; b_d := \MPd\MPeself;
w := \MPw{\textanchor}; % equal to line width for split words.
r := \MPvar{radius};
newpath p;
drawoptions (withpen pencircle scaled \MPvar{linewidth} withcolor \MPvar{linecolor});
% Note that the line is drawn at the baseline of the word.
% If you subtract a_d to the y-coordinate, then the line will
% be drawn so that it is below the depth of the characters (i.e., below the
% 'p' in placeholder).
% If you add a_h to the y-coordinate, then the line will be drawn so
% that it is at the top of the characters.
if a_y = b_y : % Word is not split
p := (-x + a_x, -y + a_y ) -- (-x + b_x, -y + b_y) ;
draw p;
else : % word is split across lines
% Draw the first part
p := (-x + a_x, -y + a_y) -- (w, -y + a_y);
draw p;
% Draw the second part
p := (0, -y + b_y) -- (-x + b_x, -y + b_y);
draw p;
fi
endgroup;
% Now, to complete the solution, all you need to do is to constuct an
% appropriate metapost shape.
\stopMPpositiongraphic
\MPpositiongraphic{mpos:placeholder}{}%
\stopMPpositionmethod
\setuppapersize[A5]
\starttext
A short \placeholder{placeholder} in a line
A very long line that splits across lines at the word \placeholder{place\-holder~word} and continues after that.
\stoptext
which gives
The rest is just a matter of details to tweak the MP position method so that it draws the required shape. For example, if you change it to
\startMPpositionmethod{mpos:placeholder}
\startMPpositiongraphic{mpos:placeholder}{linecolor,linewidth,color,radius}%
begingroup;
newnumeric x,y, w, a_x, a_y, a_h, a_d, b_x, b_y, b_h, b_d, r ;
(x,y) = \MPxy\textanchor; % The overlay is anchored at (x,y).
% So, we have to subtract (x,y) from each point.
(a_x,a_y) = \MPxy\MPbself; % The localtion of the beginning of the line.
% Height and depth at the beginning of the line
a_h := \MPh\MPbself; a_d := \MPd\MPbself;
(b_x,b_y) = \MPxy\MPeself; % The location of the end of the line
% Height and depth at the end of the line
b_h := \MPh\MPeself; b_d := \MPd\MPeself;
w := \MPw{\textanchor}; % equal to line width for split words.
r := \MPvar{radius};
newpath p;
drawoptions (withpen pencircle scaled \MPvar{linewidth} withcolor \MPvar{linecolor});
if a_y = b_y : % Word is not split
p := (-x + a_x + r/2 , -y + a_y - a_d) {left}
.. {up}(-x + a_x - r/2, -y + a_y + (a_h - a_d)/2 ){up}
.. {right}( -x + a_x + r/2, -y + a_y + a_h)
-- (-x + b_x - r/2, -y + b_y + b_h) {right}
.. {down} (-x + b_x + r/2, -y + b_y + (b_h - b_d)/2 ) {down}
.. (-x + b_x - r/2, -y + b_y - b_d)
-- cycle;
fill p withcolor \MPvar{color};
draw p;
else : % word is split across lines
% Draw the first part
p := (-x + a_x + r/2 , -y + a_y - a_d) {left}
.. {up}(-x + a_x - r/2, -y + a_y + (a_h - a_d)/2 ){up}
.. {right}( -x + a_x + r/2, -y + a_y + a_h)
-- (w , -y + a_y + a_h)
-- (w , -y + a_y - a_d)
-- cycle;
fill p withcolor \MPvar{color};
draw p;
% Draw the second part
p := (0, -y + b_y - b_d)
-- (0, -y + b_y + b_h)
-- (-x + b_x - r/2, -y + b_y + b_h) {right}
.. {down} (-x + b_x + r/2, -y + b_y + (b_h - b_d)/2 ) {down}
.. (-x + b_x - r/2, -y + b_y - b_d)
-- cycle;
fill p withcolor \MPvar{color};
draw p;
fi
endgroup;
% Now, to complete the solution, all you need to do is to constuct an
% appropriate metapost shape.
\stopMPpositiongraphic
\MPpositiongraphic{mpos:placeholder}{}%
\stopMPpositionmethod
you get
As I said earlier, it is possible to add support for multiple lines (similar to the MkII version of the underline macro, but that assumes that the interline spacing is constant; an assumption that may not always be true). The underline macro in MkIV uses a completely different attributes based mechanism to draw the underline. It may be possible to tweak that to get rounded corners, but I don't understand that code well enough to suggest how to do that.
EDIT: The version below takes care of the placeholder splitting across pages.
\startMPpositionmethod{mpos:placeholder}
\startMPpositiongraphic{mpos:placeholder}{linecolor,linewidth,color,radius}%
begingroup;
newnumeric x,y, w, a_x, a_y, a_h, a_d, b_x, b_y, b_h, b_d, a_p, p_h, r ;
(x,y) = \MPxy\textanchor; % The overlay is anchored at (x,y).
% So, we have to subtract (x,y) from each point.
(a_x,a_y) = \MPxy\MPbself; % The localtion of the beginning of the line.
% Height and depth at the beginning of the line
a_h := \MPh\MPbself; a_d := \MPd\MPbself;
(b_x,b_y) = \MPxy\MPeself; % The location of the end of the line
% Height and depth at the end of the line
b_h := \MPh\MPeself; b_d := \MPd\MPeself;
% Page number of the anchors
a_p := \MPp\MPbself; b_p := \MPp\MPeself;
w := \MPw{\textanchor}; % equal to line width for split words.
r := \MPvar{radius};
newpath p;
drawoptions (withpen pencircle scaled \MPvar{linewidth} withcolor \MPvar{linecolor});
if a_y = b_y : % Word is not split
p := (-x + a_x + r/2 , -y + a_y - a_d) {left}
.. {up}(-x + a_x - r/2, -y + a_y + (a_h - a_d)/2 ){up}
.. {right}( -x + a_x + r/2, -y + a_y + a_h)
-- (-x + b_x - r/2, -y + b_y + b_h) {right}
.. {down} (-x + b_x + r/2, -y + b_y + (b_h - b_d)/2 ) {down}
.. (-x + b_x - r/2, -y + b_y - b_d)
-- cycle;
fill p withcolor \MPvar{color};
draw p;
else : % word is split across lines
% If the beginning anchor is on the current page, draw the first part
if a_p = RealPageNumber :
p := (-x + a_x + r/2 , -y + a_y - a_d) {left}
.. {up}(-x + a_x - r/2, -y + a_y + (a_h - a_d)/2 ){up}
.. {right}( -x + a_x + r/2, -y + a_y + a_h)
-- (w , -y + a_y + a_h)
-- (w , -y + a_y - a_d)
-- cycle;
fill p withcolor \MPvar{color};
draw p;
fi
% The the end of the anchor is on the current page, draw the second
% part
if b_p = RealPageNumber :
p := (0, -y + b_y - b_d)
-- (0, -y + b_y + b_h)
-- (-x + b_x - r/2, -y + b_y + b_h) {right}
.. {down} (-x + b_x + r/2, -y + b_y + (b_h - b_d)/2 ) {down}
.. (-x + b_x - r/2, -y + b_y - b_d)
-- cycle;
fill p withcolor \MPvar{color};
draw p;
fi
fi
endgroup;
\stopMPpositiongraphic
\MPpositiongraphic{mpos:placeholder}{}%
\stopMPpositionmethod
This solution is based on a modification of my censor
package to replace the strikeout \rule
with the original letter on a blue field. I had to do the endcaps specially, and some of the parameter lengths associated with the covering of "glue" might need tweaking.
I should point out that one of the virtues of this solution (and the censor
package) is that the blue'ing should not change the underlying spacing of the text (except in the case of word-breaking hyphenation, which will not happen). That is to say, it should typeset the same, if the \marktext
macro is removed. But one downside of this is that the endcaps could overlap surrounding text. Thus the \gapmarktext
macro will set the endcaps directly, rather than lapping them to the ends of the textblock.
\documentclass{article}
\usepackage{censor}
\usepackage{stackengine}
\usepackage{xcolor}
\usepackage{scalerel}
\usepackage{bbding}
\makeatletter
\def\mystrut{\rule[-.22\baselineskip]{0pt}{.84\baselineskip}}
\setstackgap{L}{0pt}
\def\stacktype{L}
\def\useanchorwidth{T}
\periodrlap=0pt\relax
\afterperiodlap=0pt\relax
\lletterlap=0pt\relax
\rletterlap=0pt\relax
\afterspacelap=.85ex\relax
\renewcommand\censorrule[1]{
\protect\colorbox{cyan}{\mystrut\rule[\censorruledepth]{#1}{0pt}}}
\renewcommand\@cenword[1]{\colorbox{cyan}{\mystrut#1}}
\def\censordot{\colorbox{cyan}{\mystrut.}}
\newsavebox\HSL
\sbox\HSL{\textcolor{cyan}{\HalfCircleLeft}}
\newsavebox\HSR
\sbox\HSR{\textcolor{cyan}{\HalfCircleRight}}
\def\endleft{\hstretch{.5}{\scalerel*{\usebox{\HSL}}{\mystrut}}}
\def\endright{\hstretch{.5}{\scalerel*{\usebox{\HSR}}{\mystrut}}}
\newcommand\marktext[1]{%
\textcolor{cyan}{\llap{\smash{\endleft}}}%
\xblackout{#1}%
\textcolor{cyan}{\rlap{\smash{\endright}}}%
}
\newcommand\gapmarktext[1]{%
\textcolor{cyan}{\endleft}%
\xblackout{#1}%
\textcolor{cyan}{\endright}%
}
\makeatother
\fboxsep=0pt
\parindent 0in
\parskip 1em
\begin{document}
Path with placeholder\\
/foo/ \marktext{place holder} /bar/
\parbox{2.05in}
{Path that contains a placeholder with a line break:
/foo/ \marktext{place holder} /bar/}
Note however, that auto-hypenation will NOT work with this approach,
which is why I made \marktext{place holder} with a space in the middle.
Also, if the marktext doesn't have a space on either side, an overlap
could occur as in this example:
/foo/\marktext{placeholder}/bar
That can be remedied by using the gapmarktext macro instead:
/foo/\gapmarktext{placeholder}/bar
This shows linebreaking capability: aaa aaa aaa aaa aaa aaa aaa
\marktext{bbb bbb bbb bbb. bbb bbb bbb bbb bbb bbb}
ccc ccc ccc ccc ccc ccc
Can this \marktext{procedure go across paragraphs boundaries?
Why yes} it can.
But gaps can arise if glue is stretched too far.
\marktext{%
This tests marking a multiline block of text. This tests marking a multiline block of text.
This tests marking a multiline block of text. This tests marking a multiline block of text.
This tests marking a multiline block of text.}
\end{document}