7

Background

Using MetaPost, I'd like to account for a \framed environment being nested inside of a \startnarrower environment. When outside the narrower environment, the MetaPost code renders as desired:

framed 1

Problem

When the \framed is wrapped inside a \startnarrower, unwanted padding is added to the right of the title (a.k.a. legend):

framed 2

The space occupied by the blue rectangle is the problem. This space is caused by the indentation, which is represented by the red rectangle. The blue and red rectangles are the same width. (The green rectangle shows the spacing at the start of a paragraph and isn't relevant to the problem.)

I'd like to subtract the red rectangle width (indentation) from the blue rectangle width (extraneous padding caused by shifting too far).

Code

There many technical details, but the problem is this snippet:

string legend;
legend := "\ConcurrentTextGet";

% For new text blocks, write the title.
picture p;
p := textext.rt( legend ) 
  shifted ulcorner multipars[ index ] 
  shifted (1cm, 0);

% Draw the horizontal rule only for the initial text block.
draw
  ulcorner multipars[ index ] shifted (xpart lrcorner p + 1mm, 0) --
  urcorner multipars[ index ];

% Draw the vertical rule for the initial text block.
draw
  llcorner multipars[ index ] --
  ulcorner multipars[ index ] --
  ulcorner multipars[ index ] shifted (9mm, 0);

draw p;

Specifically, this calculation:

xpart lrcorner p + 1mm

It needs to account for the amount of paragraph indentation. For example:

xpart lrcorner p + 1mm - \the\parindent

Question

How would you account for the current paragraph's indentation amount within an MP graphic being used as a text background?

Addendum

Although probably irrelevant, here's the framed setup, where GraphicConcurrent refers to the given code above:

\definetextbackground[TextConcurrentFrame][
  mp=GraphicConcurrent,
  frame=off,
  topoffset=1em,
  leftoffset=1em,
  before=\blank[2*big],
  after=\blank,
  location=paragraph,
]

Here are links to the code:

--

A complete, working example to show the problem (sorry, it's a bit long):

\definenumber[ConcurrentTextSetCounter][prefix=no]
\definenumber[ConcurrentTextGetCounter][prefix=no]

\setnumber[ConcurrentTextSetCounter][0]
\setnumber[ConcurrentTextGetCounter][0]

% Map each label as global key/value pairs.
\def\ConcurrentTextSet#1{%
  \incrementnumber[ConcurrentTextSetCounter]%
  \setxvariable
    {concurrent}
    {text:\rawcountervalue[ConcurrentTextSetCounter]}
    {#1}}

% Account for the counter incrementing twice and the index being 1-based.
\def\ConcurrentTextGet{%
  \incrementnumber[ConcurrentTextGetCounter]%
  \getvariable
    {concurrent}
    {text:\number\numexpr1+\rawcountervalue[ConcurrentTextGetCounter]/2\relax}}

\startuseMPgraphic{GraphicConcurrent}
  numeric index;
  index := 1;

  % Differentiate between new text blocks and those crossing pages.
  if (multikind[ index ] = "single") or (multikind[ index ] = "first"):
    string legend;
    legend := "\ConcurrentTextGet";

    % For new text blocks, write the title.
    picture p;
    p := textext.rt( legend )
      shifted ulcorner multipars[ index ]
      shifted (1cm, 0);

    % Draw the horizontal rule only for the initial text block.
    draw
      ulcorner multipars[ index ] shifted (1mm + xpart lrcorner p, 0) --
      urcorner multipars[ index ];

    % Draw the vertical rule for the initial text block.
    draw
      llcorner multipars[ index ] --
      ulcorner multipars[ index ] --
      ulcorner multipars[ index ] shifted (9mm, 0);

    draw p;
  else:
    % Draw only the vertical rule only when crossing page boundaries.
    draw
      llcorner multipars[ index ] --
      ulcorner multipars[ index ];
  fi
\stopuseMPgraphic

\definetextbackground[TextConcurrentFrame][
  mp=GraphicConcurrent,
  frame=off,
  topoffset=1em,
  leftoffset=1em,
  before=\blank[2*big],
  after=\blank,
  location=paragraph,
]

\startsetups concurrent:before
  \ConcurrentTextSet{Legend Title}
  \startTextConcurrentFrame
\stopsetups

\startsetups concurrent:after
  \stopTextConcurrentFrame
\stopsetups

\definestartstop[concurrent][
  before=\directsetup{concurrent:before},
  after=\directsetup{concurrent:after}
]

\starttext
  \startconcurrent
    as desired, padding is just right
  \stopconcurrent

  \startnarrower
    \startconcurrent
      text goes here, padding is too long
    \stopconcurrent
  \stopnarrower
\stoptext
1
  • Without compiling the code, two potential/untested solutions: (1) don't break the line under the text, just fill a white rectangle under it; and (2), add \dontleavehmode before \startTextConcurrentFrame. Commented Aug 29, 2024 at 5:15

2 Answers 2

5

For drawing the horizontal rule, instead of adding the position of the end of the text box, we should add the width of the text box:

\definenumber[ConcurrentTextSetCounter][prefix=no]
\definenumber[ConcurrentTextGetCounter][prefix=no]

\setnumber[ConcurrentTextSetCounter][0]
\setnumber[ConcurrentTextGetCounter][0]

% Map each label as global key/value pairs.
\def\ConcurrentTextSet#1{%
  \incrementnumber[ConcurrentTextSetCounter]%
  \setxvariable
    {concurrent}
    {text:\rawcountervalue[ConcurrentTextSetCounter]}
    {#1}}

% Account for the counter incrementing twice and the index being 1-based.
\def\ConcurrentTextGet{%
  \incrementnumber[ConcurrentTextGetCounter]%
  \getvariable
    {concurrent}
    {text:\number\numexpr1+\rawcountervalue[ConcurrentTextGetCounter]/2\relax}}

\startuseMPgraphic{GraphicConcurrent}
  numeric index;
  index := 1;

  % Differentiate between new text blocks and those crossing pages.
  if (multikind[ index ] = "single") or (multikind[ index ] = "first"):
    string legend;
    legend := "\ConcurrentTextGet";

    % For new text blocks, write the title.
    picture p;
    p := textext.rt( legend )
      shifted ulcorner multipars[ index ]
      shifted (1cm, 0);

    % Draw the horizontal rule only for the initial text block.
    draw
      ulcorner multipars[ index ] shifted (1mm + bbwidth p + 1cm, 0) -- %% CHANGED!
      urcorner multipars[ index ];

    % Draw the vertical rule for the initial text block.
    draw
      llcorner multipars[ index ] --
      ulcorner multipars[ index ] --
      ulcorner multipars[ index ] shifted (9mm, 0);

    draw p;
  else:
    % Draw only the vertical rule only when crossing page boundaries.
    draw
      llcorner multipars[ index ] --
      ulcorner multipars[ index ];
  fi
\stopuseMPgraphic

\definetextbackground[TextConcurrentFrame][
  mp=GraphicConcurrent,
  frame=off,
  topoffset=1em,
  leftoffset=1em,
  before=\blank[2*big],
  after=\blank,
  location=paragraph,
]

\startsetups concurrent:before
  \ConcurrentTextSet{Legend Title}
  \startTextConcurrentFrame
\stopsetups

\startsetups concurrent:after
  \stopTextConcurrentFrame
\stopsetups

\definestartstop[concurrent][
  before=\directsetup{concurrent:before},
  after=\directsetup{concurrent:after}
]

\starttext
  \startconcurrent
    as desired, padding is just right
  \stopconcurrent

  \startnarrower
    \startconcurrent
      text goes here, padding is too long
    \stopconcurrent
  \stopnarrower
\stoptext

output

5

As an alternative solution, instead of drawing the horizontal line in two separate segments, we can clip the legend texts out of a single horizontal line. We can also smuggle the legend text inside the backgroundcolor key, removing the need to define separate variables. Together, this gives a much shorter solution:

\unprotect
%% Define the (internal) text background
\definetextbackground[_concurrent][
    mp=_concurrent,
    topoffset=1em,
    leftoffset=1em,
    before=\blank[1ex],
    after=\blank[1ex],
    location=paragraph,
]

%% Define our custom environment
\define[1]\startconcurrent{
    %% We smuggle the "legend text" inside the "backgroundcolor" key
    \start_concurrent[backgroundcolor={#1}]
}

\let\stopconcurrent\stop_concurrent

%% Define the MetaPost graphic
\startuniqueMPgraphic{_concurrent}
    %% Variables
    numeric thickness; thickness := 1pt;
    numeric legend_indent; legend_indent := 2 EmWidth;
    numeric legend_offset; legend_offset := 0.33 EmWidth;

    %% We need a known pen thickness to offset the clip path correctly
    pickup pencircle scaled thickness;

    %% Draw the frame around the background
    path background; background := multipars[1];
    path frame; frame := llcorner background
        -- ulcorner background
        -- urcorner background
    ;
    draw frame;

    %% Save the legend text into a picture
    string text; text := boxfillcolor;
    picture text_pic; text_pic := textext.rt(text)
        shifted ulcorner frame
        xshifted legend_indent
    ;
    path bbox_text; bbox_text := bbox text_pic enlarged legend_offset;

    %% Remove the text's bounding box from the drawn frame
    path clip_frame; clip_frame := (frame enlarged thickness)
        && reverse (bbox_text enlarged - thickness)
        && cycle
    ;
    clip currentpicture to clip_frame;

    %% Draw the legend text
    draw text_pic;
\stopuniqueMPgraphic
\protect

%% Demonstration
\setupheader[state=stop]
\define\test{This is a test paragraph. \par}

\starttext
    \test
    \startconcurrent{First}
        \test
    \stopconcurrent
    \test
    \startnarrower
        \test
        \startconcurrent{Second}
            \test
        \stopconcurrent
        \test
    \stopnarrower
    \test
\stoptext

output

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.