19

I want to draw a rounded box around a piece of text like that:

Mockup

The left and right borders should be half circles and line breaking should be supported.

This my code so far:

\setuppapersize[A7]
\setupwhitespace[medium]
\setupinterlinespace[line=3.5ex]

\definecolor[PlaceholderBGColor][h=65a6ff]
\definecolor[PlaceholderBorderColor][h=0051be]

\definetextbackground[PlaceholderBackground]
  [location=text,
   background=color,backgroundcolor=PlaceholderBGColor,
   frame=on,rulethickness=1pt,framecolor=PlaceholderBorderColor]

\def\Placeholder#1{\starttextbackground[PlaceholderBackground]#1\stoptextbackground{}}

\def\PlaceholderFramed#1{\inframed[
    background=color,backgroundcolor=PlaceholderBGColor,
    frame=on,rulethickness=1pt,framecolor=PlaceholderBorderColor,corner=00]{#1}}

\starttext
/foo/\Placeholder{placeholder}/bar/

/foo/\PlaceholderFramed{placeholder}/bar/
\stoptext

Code result

But the boxes don't look like drawn in the first picture: They are too high, line breaking does not work and corner=00 is not round enough. :)

How to define a command that creates boxes like the ones in the first picture?


EDIT:

Here are two variants for multiline output:

1) Single rows (probably better):
multiline variant 1

2) Merged rows:
multiline variant 2

But I think variant 1) might be easier to implement and additionally good looking regardless of inter-line space.

7
  • 1
    Regarding the height of the box, try adding \inframed[...,, strut=no, ...]{...}.
    – Aditya
    Jul 23, 2013 at 19:07
  • @Aditya Great, this decreases box height to optimum. :) But this also has the negative effect, that the complete \inframe together with the text is shifted down (~2mm).
    – hiddenbit
    Jul 23, 2013 at 19:23
  • What should be the output if the placeholder is split over multiple lines?
    – Aditya
    Jul 24, 2013 at 14:46
  • @Aditya: The question is updated with images, how multiline placeholders should look like.
    – hiddenbit
    Jul 24, 2013 at 16:47
  • OK. I'll try to see if I can use textbackgrounds to achieve this result.
    – Aditya
    Jul 24, 2013 at 18:14

3 Answers 3

11

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

enter image description here

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

enter image description here

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
6
  • Thank you! This looks quite good. I think placeholders that span over three or more lines are rather unusual, so this is probably no serious problem. Most of them contain only a few words. The only thing that can happen is a placeholder starting at the bottom of one page and ending at the top of the next one. In this case the starting and ending background are drawn on both pages. I don't know how complicated the handling of this case is; otherwise an alternative solution for this is text rephrasing. ;-)
    – hiddenbit
    Jul 26, 2013 at 10:41
  • 1
    When the placeholder is split across multiple pages you need to keep track of the page using '\MPp. and RealPage. I edit the answer to handle that
    – Aditya
    Jul 26, 2013 at 13:04
  • @Radon: See the updated version
    – Aditya
    Jul 26, 2013 at 19:59
  • 3
    @Aditya It's unfortunate that there's so little ConTeXt traffic on this site and your answer only got two upvotes despite all the work you spend on coding it. It's a well-done and creative solution.
    – Marco
    Jul 26, 2013 at 23:25
  • 2
    @Marco: Thanks for the sentiments. Don't worry, eventually I'll get upvotes from all ConTeXt users that visit this site---all five of them :)
    – Aditya
    Jul 26, 2013 at 23:39
12

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

enter image description here

This does not behave well when it is split across only one line.

enter image description here

5
  • Another quirk: There's a vertical bar on both sides. Do you have an idea where it's coming from?
    – Marco
    Jul 24, 2013 at 19:18
  • The relevant code is in metapost/context/base/mp-abck.mkiv. Another option is to use bars, but AFAIK, they done implement rounded corners.
    – Aditya
    Jul 24, 2013 at 20:23
  • @Marco afaics it’s not a bar but the end of the opposite rounded corner. Its size increases with the radius parameter (try a value of 1.5). Jul 24, 2013 at 20:47
  • @phg Indeed. With radii small than .5\lineheight it's fine, larger ones are problematic.
    – Marco
    Jul 24, 2013 at 21:01
  • I think it may be a consequence of how the rounded macro in Metafun is implemented.
    – Aditya
    Jul 24, 2013 at 22:03
5

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}

enter image description here

3
  • 3
    The question is about ConTeXt
    – egreg
    Jul 24, 2013 at 21:30
  • 2
    But LaTeX solution are welcome
    – Aditya
    Jul 25, 2013 at 2:18
  • @egreg Oops... I hadn't seen that tag on the question. Jul 25, 2013 at 9:44

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .