3

This question is a continuation of How to create an overhanging label for parts of a sentence?. The question has already an accepted answer by jakun. I wish to have a further improvement to his solution.

First improvement Is it possible to have the exact same behavior with the exception that it doesn't influence the space between lines - i.e. the labels should overlap with normal text? In order to read the labels clearly the labels should have a white background (or, more general, the same background color of the page background).

Second improvement Is it possible to, as optional argument, change the position of the label in a way that it is defined as horizontal position in the page? In this way should be easier to put all labels aligned horizontally and maybe all the labels on the margin of the page.

Why other packages doesn't satisfy me Usually, when in draft mode, I use todonotes or soul (with some margin note) to keep trace of comments. For what concern the latter, the problem is that the highlighted text and the margin note are two different things. The former is more semantically correct IMO but it isn't clear what the comment is referring to since it is inserted in a point while the solution by jakun provide a label that take as argument a portion of text. Also, the solution by jakun is nicer from a pure aesthetic point of view.

2
  1. I have added the option smash which is a boolean value. If true the label (and the line) do not contribute to the height of the tikzpicture. (Now text ysep does not influence the height anymore.)

    The implementation is different from what I suggested in my comment to my answer to the original question because this is required for absolute positioning. The downside of this implementation is that it requires two runs.

    (The name is derived from the macro \smash.)

  2. I have added new values to the pos key ending on abs for absolute (horizontal) position. These will try to position the left/right side of the label relative to the right/left border of the text.

    These require smash to be true and therefore set it implicitly.

    I am using a rather naive algorithm to calculate the size of the margins. It will fail if (\oddsidemargin and \evensidemargin are unequal and) the label appears after a page break in the same paragraph (it will use the margin of the previous page because it does not know that it has been broken to the next page). If that happens you can set _offset x manually to one of the margin commands I have defined (possibly with a minus in front).

    A more robust but more difficult implementation (requiring two runs) using \label and \pageref would be possible.

    If you don't want to worry about this you can set _offset x to zero, flip _anchor label horizontally, negate shift x and position the labels relative to the border of the page.

  3. I have added some additional new functionality:

    • the left counter parts for the pos key values
    • the line style suggested in my edit of the answer to the original question
    • a label style to which values like text width, align, draw, fill, rounded corners and so on can be applied (see TikZ documentation)
    • instead of pos = above left abs you can write above left abs
\documentclass[twoside]{article}
\usepackage{blindtext}

\usepackage{tikz}


% --------------- margins ---------------

\newcommand{\oddmarginleft}{(1in+\hoffset+\oddsidemargin)}
\newcommand{\evenmarginleft}{(1in+\hoffset+\evensidemargin)}
\newcommand{\marginleft}{\ifodd\thepage\oddmarginleft\else\evenmarginleft\fi}

\newcommand{\oddmarginright}{(\paperwidth-\textwidth-\oddmarginleft)}
\newcommand{\evenmarginright}{(\paperwidth-\textwidth-\evenmarginleft)}
\newcommand{\marginright}{\ifodd\thepage\oddmarginright\else\evenmarginright\fi}


% --------------- def options ---------------

\newif\ifsmashmylabel

\pgfqkeys{/mylabel}{%
    % geometry
    shift x/.initial = 1.5em,
    shift y/.initial = .2em,
    slope width/.initial = .3em,
    text xsep/.initial  = .1em,
    text ysep/.initial  = .1em,
    label xsep/.initial = .3333em,
    label ysep/.initial = .3333em,
    smash/.is if = smashmylabel,
    xref/.initial = inline,
    %
    % colors
    line color/.initial  = black,
    text color/.initial  = black,
    label color/.initial = black,
    color/.style = {line color=#1, label color=#1},
    %
    % styles
    line style/.initial =,
    label style/.initial =,
    %
    % label position shortcuts
    /mylabel/pos/.is choice,
    /mylabel/pos/above/.style = {pos=above right},
    /mylabel/pos/below/.style = {pos=below right},
    %
    % label positions relative x
    /mylabel/pos/below right/.style = {%
        _anchor label  = north west,
        _sign x        =  ,
        _sign y        = -,
        _line pos      = south,
        xref           = inline,
        _offset x      = 0pt,
    },
    /mylabel/pos/above right/.style = {%
        _anchor label  = south west,
        _sign x        =  ,
        _sign y        =  ,
        _line pos      = north,
        xref           = inline,
        _offset x      = 0pt,
    },
    %
    /mylabel/pos/below left/.style = {%
        _anchor label  = north east,
        _sign x        = -,
        _sign y        = -,
        _line pos      = south,
        xref           = inline,
        _offset x      = 0pt,
    },
    /mylabel/pos/above left/.style = {%
        _anchor label  = south east,
        _sign x        = -,
        _sign y        =  ,
        _line pos      = north,
        xref           = inline,
        _offset x      = 0pt,
    },
    %
    % label positions absolute x
    /mylabel/pos/below right abs/.style = {%
        _anchor label  = north west,
        _sign x        =  ,
        _sign y        = -,
        _line pos      = south,
        xref           = current page.east,
        _offset x      = -\marginright,
        smash          = true,
    },
    /mylabel/pos/above right abs/.style = {%
        _anchor label  = south west,
        _sign x        =  ,
        _sign y        =  ,
        _line pos      = north,
        xref           = current page.east,
        _offset x      = -\marginright,
        smash          = true,
    },
    %
    /mylabel/pos/below left abs/.style = {%
        _anchor label  = north east,
        _sign x        = -,
        _sign y        = -,
        _line pos      = south,
        xref           = current page.west,
        _offset x      = \marginleft,
        smash          = true,
    },
    /mylabel/pos/above left abs/.style = {%
        _anchor label  = south east,
        _sign x        = -,
        _sign y        =  ,
        _line pos      = north,
        xref           = current page.west,
        _offset x      = \marginleft,
        smash          = true,
    },
    %
    % internal
    _anchor label/.initial,
    _direction/.initial,
    _line pos/.initial,
    _sign x/.initial,
    _sign y/.initial,
    _offset x/.initial,
    %
    % struts
    inline strut/.initial=\vphantom{Ap},
    label strut/.initial=\strut,
    %
    % try pos if key is unknown
    % based on pgfmanual page 900
    % I don't know why `.search also` does not work instead
    .unknown/.code = {%
        \let\searchname=\pgfkeyscurrentname
        \pgfkeysalso{/mylabel/pos/\searchname/.try}%
        \ifpgfkeyssuccess
        \else
            \let\pgfkeyscurrentkey=\searchname
            \def\pgfkeyscurrentvalue{\pgfkeysnovalue}%
            \def\pgfutilnext{\pgfkeysvalueof{/handlers/.unknown/.@cmd}\searchname\pgfeov}%
            \pgfutilnext
        \fi
    },
}

\newcommand{\setmylabel}[1]{%
    \pgfqkeys{/mylabel}{#1}%
}


% --------------- init options ---------------

\setmylabel{%
    pos = below,
}


% --------------- \mylabel command ---------------

\newcommand{\mylabel}[3][]{% [#1: options], #2: inline text, #3: label text
    \begingroup
    % process options
    \setmylabel{#1}
    %
    \begin{tikzpicture}[remember picture, baseline=(inline.base)]
        % draw inline text
        \node[%
            inner xsep = \pgfkeysvalueof{/mylabel/text xsep},
            inner ysep = 0pt,
            text = \pgfkeysvalueof{/mylabel/text color},
        ] (inline) {\pgfkeysvalueof{/mylabel/inline strut}#2};
        \ifsmashmylabel
    \end{tikzpicture}%
    \begin{tikzpicture}[remember picture, overlay]
        \fi
        % draw label
        \pgfkeys{/mylabel/label style/.get=\tmpLabelStyle}
        \node (label) [
            xshift = \pgfkeysvalueof{/mylabel/_sign x}\pgfkeysvalueof{/mylabel/shift x} + \pgfkeysvalueof{/mylabel/_offset x},
            yshift = \pgfkeysvalueof{/mylabel/_sign y}\pgfkeysvalueof{/mylabel/shift y},
            anchor = \pgfkeysvalueof{/mylabel/_anchor label},
            inner xsep = \pgfkeysvalueof{/mylabel/label xsep},
            inner ysep = \pgfkeysvalueof{/mylabel/label ysep},
            text = \pgfkeysvalueof{/mylabel/label color},
            /utils/exec = \expandafter \pgfkeysalso \expandafter {\tmpLabelStyle},
        ] at (\pgfkeysvalueof{/mylabel/xref} |- inline.\pgfkeysvalueof{/mylabel/_line pos})
        {\pgfkeysvalueof{/mylabel/label strut}#3};

        % draw lines
        \coordinate (line-w) at ([yshift = \pgfkeysvalueof{/mylabel/_sign y}\pgfkeysvalueof{/mylabel/text ysep}] inline.\pgfkeysvalueof{/mylabel/_line pos} west);
        \coordinate (line-e) at ([yshift = \pgfkeysvalueof{/mylabel/_sign y}\pgfkeysvalueof{/mylabel/text ysep}] inline.\pgfkeysvalueof{/mylabel/_line pos} east);
        \coordinate (line-c) at ([yshift = \pgfkeysvalueof{/mylabel/_sign y}\pgfkeysvalueof{/mylabel/text ysep}] inline.\pgfkeysvalueof{/mylabel/_line pos});

        \pgfkeys{/mylabel/line style/.get=\tmpLineStyle}%
        \expandafter \draw \expandafter [\tmpLineStyle, draw=\pgfkeysvalueof{/mylabel/line color}]
            (line-w) -- (line-e)
            (line-c) -- ([xshift=\pgfkeysvalueof{/mylabel/_sign x}\pgfkeysvalueof{/mylabel/slope width}] line-c |- label) -- (label);
        ;

        % set bounding box
        \ifsmashmylabel
        \else
            \pgfresetboundingbox
            \useasboundingbox 
                (inline.south west) rectangle (inline.north east)
                (inline |- label.south) rectangle (inline |- label.north)
            ;
        \fi
    \end{tikzpicture}%
    \endgroup
}


% --------------- test document ---------------

\setmylabel{%
    line color  = orange,
    text color  = blue,
    label color = green,
    pos = below right abs,
}

\begin{document}
    \blindtext
    text \mylabel[above left abs]{part}{label~1} of a \mylabel{sentence}{label~2}
    \blindtext
    \newpage
    \blindtext
    text \mylabel[above left abs]{part}{label~1} of a \mylabel{sentence}{label~2}
    \blindtext
\end{document}

enter image description here


Example how the label style could be used:

\setmylabel{%
    label strut = ,
    label style = {text width=5em, draw=gray, fill=yellow!10!white, rounded corners,},
    pos/above left abs/.append style = {label style/.append={align=flush right}},
    pos/above right abs/.append style = {label style/.append={align=flush left}},
}

enter image description here


I have noticed that some key names may be confusing:

  • In TikZ it's called xshift, I named it shift x.
  • shift x is more like a distance than a shift, it's value is negated automatically when inverting the direction. (Unlike _offset x where a positive value means right and a negative value means left.)

But I do not want to break backward compatability (or maybe I am just to lazy to change that...)

  • This has the pontential to became a better package than todonotes. :) I can't check your answer but I'll try it tomorrow with some of my documents. – gvgramazio May 5 '18 at 0:03
  • I checked your code and it's really what I wanted, thanks. In the future I'll try to make some changes to the code you provided to add other options and to better fit my needs. – gvgramazio May 5 '18 at 10:48

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.