0

How do I align text in individual boxes to avoid getting results like the following where the second word is shifted up due to parts of the characters stretching below the baseline?

enter image description here

Here's my MWE:

\documentclass{article}
\usepackage{tikz}
\pagestyle{empty}
\usepackage{geometry}
\usepackage{xcolor}
\usetikzlibrary{positioning,calc}

\geometry{papersize={85mm, 15mm}}

\begin{document}

\newcommand\textbox[5]{
\begin{tikzpicture}[remember picture,overlay]
\filldraw [fill=black, draw=black, line width=0.3mm]
     ($(current page.south west)+(#1,#2)$) rectangle 
     ($(current page.south west)+(#3,#4)$)
     node[pos=0.5] {\color{white}#5};
\end{tikzpicture}}

\textbox{10.0mm}{5mm}{40.0mm}{10.0mm}{\bf{Levels}}
\textbox{45.0mm}{5mm}{75.0mm}{10.0mm}{\bf{Gauging}}

\end{document}

I know there is an argument yshift but I don't know how to make this work dependent on the text's baseline. Maybe that argument isn't even the right option.

Edited to say: the placement of the word "Levels" looks natural and "Gauging" should be at the same level, meaning the g's can stick out towards the bottom.

Hansel
  • 417
  • unrelated but \bf is not defined by default in latex, and when it is defined (for compatibility with documents written in the 1980s) the syntax is {\bf Levels} not \bf{Levels} use \textbf{levels} – David Carlisle Jan 25 '20 at 10:11
  • OK, thanks for the hint. – Hansel Jan 25 '20 at 10:15
  • 2
    you could use \strut#5 so they always aligned as if they had a descender – David Carlisle Jan 25 '20 at 10:24
  • If I use \strut%5 then they align. However, both of the words are shifted up. I think it looks more natural if it is placed like the word "Levels". So in case of "Gauging", the g's should be sticking out to the bottom. – Hansel Jan 25 '20 at 10:32
  • In terms of the \strut approach, here is the related question: https://tex.stackexchange.com/questions/133227/how-to-align-text-in-tikz-nodes-by-baseline – Steven B. Segletes Jan 27 '20 at 13:59

1 Answers1

5

First, as David Carlisle said, you should not use \bf in LaTeX (plus, it doesn't take an argument, it is what Leslie Lamport would call a declaration: it acts on whatever follows in the current TeX group). I replaced it with font=\normalfont\bfseries in the node options.

You have texts of different depths, but you presumably want the black rectangles to have exactly the same height. Since you said in a comment that you would like vertical placement to be the same as for Levels, which has no descender, I propose to ignore the depth of the texts passed to your \textbox macro. This can be done either with TikZ options or with box manipulation commands provided by (La)TeX.

TikZy ways

Using TikZ nodes for the black rectangles

Here, we use a TikZ node with rectangle shape to draw each text as well as the black rectangle behind it (in your solution, the black rectangles were drawn with a rectangle operation, not a node). For cleanliness and readability of the code, we define a TikZ style named my special node that contains all options needed for these special nodes. The most important options in this style in relation to your question are:

  • text depth=0pt in order to ignore the depth of the node contents when computing its dimensions;

  • anchor=south west to place the node by giving the coordinates of its lower left corner;

  • font=\normalfont\bfseries to select the font;

  • minimum width and minimum height so that the rectangles are at least as wide and high as mandated by the arguments of your \textbox macro;

  • inner sep=0pt to ensure that no more space is automatically added inside the node, around its contents (with a larger value, the node contents could “push” its border even when not touching it; this can be desirable in some way, but may also easily result in nodes with different heights if the contents of a node is higher than that of other nodes) .

You don't really need line width=0.3mm (you could use 0pt) nor the -0.5\pgflinewidth in the xshift and yshift amounts used for the node. The only reason I include them is to provide an exact match with your example (for the line width) and with the TeXnical method given below. With the following code, the TikZ calc and positioning libraries are not needed at all.

\documentclass{article}
\usepackage[papersize={85mm, 15mm}]{geometry}
\usepackage{xcolor}
\usepackage{tikz}

\pagestyle{empty}

\tikzset{
  my special node/.style n args={4}{
    line width=0.3mm, draw=black, fill=black, text=white,
    inner sep=0pt, text depth=0pt, anchor=south west, font=\normalfont\bfseries,
    minimum width={(#3)-(#1)}, minimum height={(#4)-(#2)},
  }
}

\begin{document}

\newcommand{\textbox}[5]{%
  \begin{tikzpicture}[remember picture, overlay]
  \node[my special node={#1}{#2}{#3}{#4}]
    at ([xshift={(#1)-0.5\pgflinewidth}, yshift={(#2)-0.5\pgflinewidth}]%
        current page.south west) {#5};
  \end{tikzpicture}%
}

\textbox{10.0mm}{5mm}{40.0mm}{10.0mm}{Levels}
\textbox{45.0mm}{5mm}{75.0mm}{10.0mm}{Gauging}

\end{document}

screenshot

Cropped screenshot:

enter image description here

Proof that both texts have the same baseline:

screenshot with added rule

One nice thing when your rectangles are TikZ nodes is that you can use many TikZ tools to work with them: name nodes, place nodes relatively to other ones, connect nodes, drop shadows, etc. For instance, with the above code, you only need to add \usetikzlibrary{shadows} and the drop shadow node option to obtain this:

Example with a “drop shadow”

Using the rectangle operation

@Schrödinger'scat proposed another nice TikZy solution that is very close to your code and produces the exact same output as my two methods given above and below. This method uses the rectangle operation to draw the black rectangles, as opposed to filling the rectangle nodes containing the texts from the fifth argument of \textbox.

\documentclass{article}
\usepackage[papersize={85mm, 15mm}]{geometry}
\usepackage{xcolor}
\usepackage{tikz}

\pagestyle{empty}

\begin{document}

\newcommand*{\textbox}[5]{%
  \begin{tikzpicture}[remember picture, overlay]
  \filldraw [fill=black, draw=black, line width=0.3mm]
    (current page.south west)+(#1,#2) rectangle +(#3,#4)
    node[pos=0.5, font=\normalfont\bfseries, text depth=0pt, text=white] {#5};
  \end{tikzpicture}%
}

\textbox{10.0mm}{5mm}{40.0mm}{10.0mm}{Levels}
\textbox{45.0mm}{5mm}{75.0mm}{10.0mm}{Gauging}

\end{document}

The output is exactly the same as above.

TeXnical way

Here, instead of using text depth=0pt for the nodes containing the boxed texts, we use a short macro that puts its argument in a box, sets the box depth to zero and outputs the box. This implies that again, TeX will ignore the depth of the argument when determining the vertical extent of the TikZ node contents.

\documentclass{article}
\usepackage[papersize={85mm, 15mm}]{geometry}
\usepackage{xcolor}
\usepackage{tikz}
\usetikzlibrary{calc}

\pagestyle{empty}

\newsavebox{\mybox}

\newcommand*{\killboxdepth}[1]{%
  \sbox{\mybox}{#1}%
  \dp\mybox=0pt \box\mybox
}

\begin{document}

\newcommand*{\textbox}[5]{%
  \begin{tikzpicture}[remember picture, overlay]
  \filldraw [fill=black, draw=black, line width=0.3mm]
    ($(current page.south west)+(#1,#2)$) rectangle
    ($(current page.south west)+(#3,#4)$)
    node[pos=0.5, font=\normalfont\bfseries] {\color{white}\killboxdepth{#5}};
  \end{tikzpicture}%
}

\textbox{10.0mm}{5mm}{40.0mm}{10.0mm}{Levels}
\textbox{45.0mm}{5mm}{75.0mm}{10.0mm}{Gauging}

\end{document}

The output is exactly the same as in the TikZy ways.

Note: another way to define \killboxdepth is the following:

\newcommand{\killboxdepth}{%
  \raisebox{0pt}[\height][0pt]%
}

This is a bit more LaTeXish than using the \dp and \box commands, which are TeX primitives. As @barbarabeeton pointed out, yet another way—assuming amsmath is loaded—would be to use \smash[b] instead of \killboxdepth (then this macro isn't necessary). There is definitely more than one way to do it!

frougon
  • 24,283
  • 1
  • 32
  • 55
  • This is it and it's perfect! Many thanks for the solution, frougon. I don't think a different strategy is needed. – Hansel Jan 25 '20 at 11:42
  • @Hansel Glad to hear that. :-) I added a method that is more TikZy and gives exactly the same output. – frougon Jan 25 '20 at 16:11
  • 1
    Maybe also add \newcommand*{\textbox}[5]{% \begin{tikzpicture}[remember picture, overlay] \filldraw [fill=black, draw=black, line width=0.3mm] (current page.south west)+(#1,#2) rectangle +(#3,#4) node[pos=0.5, font=\normalfont\bfseries,text depth=0pt,text=white] {#5}; \end{tikzpicture}% }. –  Jan 25 '20 at 17:39
  • @Schrödinger'scat Thanks for your suggestion! I've incorporated your solution too. I had tried with text=white when working on the chronologically first version—the TeXnical one—and it didn't work (I guess the \sbox doesn't see the text=white?). But it does work with your code and also with my TikZy method, which doesn't use \sbox. – frougon Jan 25 '20 at 18:13
  • Thanks a lot for the other options and especially for the addition of a detailed description. I am sure this will help others facing the same issue in the future. – Hansel Jan 25 '20 at 18:26
  • @Hansel I added an example showing why I think that the black-rectangles-are-filled-nodes approach is very convenient: it makes it trivial to add effects like “drop shadows.” – frougon Jan 25 '20 at 23:25
  • You don't mention what I consider the easiest approach. If the text is a single line, \smash[b]. – barbara beeton Jan 26 '20 at 16:03
  • @barbarabeeton Sorry, but I don't think this is satisfactory. \smash not only makes the depth equal to zero, it also does that for the height, which ruins the result here (screenshot). I could add a zero-width rule to give the height that \smash kills, but then it certainly wouldn't be simpler. I added a way using \raisebox though, which is about as simple as the one based on \sbox, \dp and \box. – frougon Jan 26 '20 at 16:31
  • I said \smash[b], which is "bottom smash". Of course a "full" smash is inappropriate. – barbara beeton Jan 27 '20 at 00:32
  • @barbarabeeton Sorry, I misread that because I didn't know amsmath redefined \smash. Indeed, \smash[b] can do the job here, but it requires one to load amsmath; therefore, I think the \raisebox way I added is (slightly) preferable, since it is as simple and only requires the LaTeX kernel. There is more than one way to do it... – frougon Jan 27 '20 at 00:38
  • @barbarabeeton \smash[b] is mentioned in the last version of the answer, thanks for the info. – frougon Jan 27 '20 at 00:47
  • Thanks. (I come from an environment where amsmath is always assumed to be loaded -- it's built into the AMS document classes -- so I instinctively don't assume that it's not there.) – barbara beeton Jan 27 '20 at 00:54