为什么 TikZ 中绘制的线条会在图片中添加等于其宽度的空间以及如何防止这种情况?

为什么 TikZ 中绘制的线条会在图片中添加等于其宽度的空间以及如何防止这种情况?

有关的:TikZ 图片前的水平空间

考虑以下 MWE

\documentclass{article}
\usepackage{tikz}
\usepackage[showframe]{geometry}
\begin{document}
\noindent
\begin{tikzpicture}
  \draw [red] (0pt,0pt) -- ++(\linewidth,0pt);
  \node [anchor=south,font=\sffamily\footnotesize] at (current bounding box.north) {\textcolor{red}{\the\pgflinewidth} too wide};
\end{tikzpicture}

\noindent
\begin{tikzpicture}
  \draw [red,line width=1pt] (0pt,0pt) -- ++(\linewidth,0pt);
  \node [anchor=south,font=\sffamily\footnotesize] at (current bounding box.north) {\textcolor{red}{1pt} too wide};
\end{tikzpicture}

\noindent
\begin{tikzpicture}
  \draw [red,line width=10pt] (0pt,0pt) -- ++(\linewidth,0pt);
  \node [anchor=south,font=\sffamily\footnotesize] at (current bounding box.north) {\textcolor{red}{10pt} too wide};
\end{tikzpicture}

\noindent
\begin{tikzpicture}
  \draw [red,line width=100pt] (0pt,0pt) -- ++(\linewidth,0pt);
  \node [anchor=south,font=\sffamily\footnotesize] at (current bounding box.north) {\textcolor{red}{100pt} too wide};
\end{tikzpicture}
\end{document}

我天真地认为,这不会产生满满的盒子,但事实确实如此

Overfull \hbox (0.4pt too wide) in paragraph at lines 246--251
[] 

Overfull \hbox (1.0pt too wide) in paragraph at lines 252--257
[] 

Overfull \hbox (10.0pt too wide) in paragraph at lines 258--263
[] 

Overfull \hbox (100.0pt too wide) in paragraph at lines 264--269
[] 

输出结果非常清晰地显示了线的位移

位移线

是什么原因造成的?我该如何预防?

  • 显然,防止框过满的一个选项是简单地将线的长度减少\pgflinewidth。即使在较小的宽度下,这一点也很明显,但随着线条变粗,这显然不可行。
  • 另一种选择是在不考虑边界框的情况下绘制线,然后重复路径(不绘制它)来设置边界框。
  • 贡萨洛·梅迪纳建议使用overlay,但这意味着边界框是错误的。

[已编辑,删除误导性示例。]

有没有推荐的方法来解决此问题?

答案1

是什么原因造成的?我该如何预防?

当描边/绘制路径时,PGF 会将.5\pgflinewidth四边添加到边界框(如果此时建立的边界框较小)。

这样做不仅是为了覆盖线帽rectround也是为了覆盖线宽本身。

即使在您的示例中,一条宽度为 100pt 的简单水平线也应该产生一张高度为 100pt 的图片,否则它会覆盖文本:

\documentclass{article}
\usepackage{tikz}
\usepackage[showframe, pass]{geometry}
\setlength\parindent{0pt}
\begin{document}
I'm covered by a red bar.

\tikz[line width=100pt, opacity=.5]
  \useasboundingbox[postaction={draw=red, overlay}]
    (0pt,0pt) -- +(\linewidth,0pt);

I cover a red bar.

\vspace{50pt}

I'm covered by a red bar.
\tikz[line width=100pt, opacity=.5]
  \useasboundingbox[postaction={draw=red, overlay}]
    (0pt,0pt) -- +(0pt,2cm);
I cover a red bar.
\end{document}

并非全部miter线连接将被涵盖。

显然,防止框过满的一个选项是简单地将线的长度减少\pgflinewidth。即使在较小的宽度下,这一点也很明显,但随着线条变粗,这显然不可行。

line cap=rect可能相结合:

\tikz[line width=100pt]
  \draw[red, line cap=rect]
    (0pt, 0pt) -- +(\linewidth-\pgflinewidth,0pt);

另一种选择是在不考虑边界框的情况下绘制线,然后重复路径(不绘制它)来设置边界框。

不完全是,这仍然没有涵盖垂直维度上的实际线宽。(不过,对于 0.4pt 的线宽,这实际上并不明显。)我们至少可以postaction不必再次重复路径(也不必解析路径并构建软路径)。

Still covered.

\tikz[line width=100pt, opacity=.5]
  \path[postaction={draw=red, overlay}]
    (0pt, 0pt) -- +(\linewidth,0pt);

Still covering.

0pt如果你想要的只是从到画水平线,\linewidth 不要使用 TikZ,而是\rule使用trim left=0pt, trim right=\linewidth

\tikz[line width=100pt, trim left=+0pt, trim right=+\linewidth]
  \draw[red] (0pt, 0pt) -- +(\linewidth,0pt);

如果您知道路径的宽度而不知道线宽(或者可以通过两个坐标确定,因为它还允许)trim left=(left coordinate of the picture), trim right=(right coordinate of the picture),这些键很有用。

我以前用过这些选项,尤其是当涉及到跨越整个的图片时\linewidth


现在,让我们忽略圆线帽(无论如何,边界框总是完美的)和对接线帽(我相信只有水平和垂直线才适合它)。

对于圆线连接边界框也将是完美的,

什么决定了“真正的”边界框?

对于每个路径段,我们只需要计算与该段平行的路径.5\pgflinewidth。对于两个路径段之间的每个连接,我们需要计算

这需要进行大量的数学解析,我不想这样做,但对于对接线帽和斜接线连接,nfold库提供了算法。我们使用它来将原始路径向两侧偏移线宽的一半,并将其用作路径的边界框。

代码

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{nfold}
\makeatletter
\tikzset{
  real bb/.style={% this is not perfect because it messes with the original
                  % postaction and softpath system
    overlay,      % ignore the original's path bb
    postaction={overlay=false, path only, qoffset=+1},
    postaction={overlay=false, path only, qoffset=-1}},
  qoffset/.code=\tikz@addoption{\pgfgetpath\tikz@temp\pgfsetpath\pgfutil@empty
                   \pgfoffsetpathqfraction\tikz@temp{.5\pgflinewidth}{#1}}}
\makeatother
\usepackage[showframe, pass]{geometry}
\setlength\parindent{0pt}
\tikzset{every picture/.append style={execute at end picture={
  \draw[help lines] ([xshift=+.5\pgflinewidth,yshift=+.5\pgflinewidth]
    current bounding box.south west) rectangle
    ([xshift=+-.5\pgflinewidth,yshift=+-.5\pgflinewidth]
    current bounding box.north east);}}}
\begin{document}

\section{Using only the path as bounding box}
Using only the path for the bounding box does not work
because a horizontal line still has a height and a
vertical line still has a width.

\vspace{35pt}

I'm covered by a red bar.

\tikz[line width=100pt, opacity=.5]
  \useasboundingbox[postaction={draw=red, overlay}]
    (0pt,0pt) -- +(\linewidth,0pt);

I cover a red bar.

\vspace{50pt}

I'm covered by a red bar.
\tikz[line width=100pt, opacity=.5]
  \useasboundingbox[postaction={draw=red, overlay}]
    (0pt,0pt) -- +(0pt,2cm);
I cover a red bar.
\bigskip

\section{Trimming left and right and \texttt{real bb}}
For a simple horizontal line, I'd use \verb|trim left| and \verb|trim right|:

\tikz[line width=100pt, trim left=+0pt, trim right=+\linewidth]
  \draw (0pt,0pt) --+(\linewidth,0pt);

Otherwise, we'll try \verb|real bb|:

\tikz[line width=100pt]
  \draw[real bb] (0pt,0pt) --+(\linewidth,0pt);\bigskip

\pagebreak

Another example where the default approach fails:\medskip

\tikz[line width=25pt, baseline=(current bounding box)]
  \draw [           rotate=45] (0,-1) rectangle (2,1);\qquad\qquad
\tikz[line width=25pt, baseline=(current bounding box)]
  \draw [yscale=.5, rotate=45] (0,-1) rectangle (2,1);\bigskip

And with \verb|real bb|:

\tikz[line width=25pt, baseline=(current bounding box)]
  \draw [           rotate=45, real bb] (0,-1) rectangle (2,1);
\tikz[line width=25pt, baseline=(current bounding box)]
  \draw [yscale=.5, rotate=45, real bb] (0,-1) rectangle (2,1);
  
\begingroup
\tikzset{every picture/.append style={line width=20pt, text=white, sloped}}

\section{Line Caps}
Only a round line cap will always fit perfectly.

\foreach \linecap in {butt, rect, round}
  \tikz[line cap=\linecap]
    \draw          (0,0) -- node{\linecap} +(30:2);\medskip

The \verb|real bb| key can be used to “fix” the default butt line cap.
The round cap does not need fixing.
Good luck with the rect line cap.\medskip

\foreach \linecap in {butt, rect, round}
  \tikz[line cap=\linecap]
    \draw[real bb] (0,0) -- node{\linecap} +(30:2);

\pagebreak
\section{Line Joins}
Once again, only a round line join will fit perfectly.

\foreach \linejoin in {miter, bevel, round}
  \tikz[line join=\linejoin]
    \draw (0,0) -- node{\linejoin} ++(30:1.5) -- +(-30:1.5);

With \verb|real bb| again:
Miter line join looks good.
The round line join didn't need it.
And the bevel does its own thing.
(And there's also the \verb|miter limit| changing things.)

\foreach \linejoin in {miter, bevel, round}
  \tikz[line join=\linejoin]
    \draw[real bb] (0,0) -- node{\linejoin} ++(30:1.5) -- +(-30:1.5);
\endgroup

\section{Hmm…}
\tikz[line width=1cm, trim left=+-.5\linewidth, trim right=+.5\linewidth]
  \draw (0,0) node {What about this?} circle[radius=.5\linewidth];
\end{document}

相关内容