pgf-tikz / pgf

A Portable Graphic Format for TeX
https://pgf-tikz.github.io/
1.15k stars 108 forks source link

Meta-decoration states are missing `width` of `final` decoration state #1338

Open schtandard opened 5 months ago

schtandard commented 5 months ago

Brief outline of the bug

Many decorations fill up "missing" path length (i.e. a bit of the path at the end that cannot be filled by the other states) in the final state, e.g. by saying

  \state{final}
  {
    \pgfpathlineto{\pgfpointdecoratedpathlast}
  }%

When used in a meta-decoration, this state seems to be omitted, leading to the meta-state not filling its specified width. Over the whole meta-decoration these missing parts can accumulate to a substantial length.

Minimal working example (MWE)

In this example, the final bit of the first centimeter of the decorated path should be a straight line, coming from the final state of the zigzag decorator. This seems to be omitted, however, the second state starts (and finishes) too early. Thus, the decoration does not cover the expected width of 2 cm.

\documentclass{article}

\usepackage{tikz}
\usetikzlibrary{decorations.pathmorphing}

\pgfdeclaremetadecoration{too short}{first}
  {%
    \state{first}[width=1cm, next state=second]
      {
        \decoration{zigzag}
      }%
    \state{second}[width=1cm, next state=final]
      {
        \decoration{curveto}
        \afterdecoration{
            \pgfusepath{stroke}
          }
      }%
    \state{final}{}
  }

\begin{document}

\begin{tikzpicture}
  \draw [help lines] (0,-1) grid (2,1);
  \path [decorate, decoration=too short] (0,0) -- (5,0);
\end{tikzpicture}

\end{document}

image

schtandard commented 5 months ago

After some more tinkering, it looks like there is more to this. The decoration's final seems to be executed, but \pgfpointdecoratedpathlast points to the wrong coordinate, namely to (0pt,0pt) in the transformed coordinate system.

Additionally, the length of the input path seems to make a difference somehow, though this may be a separate bug: When it matches the decoration's width exactly, the resulting path is extended to the correct end point (though the decoration is still wrong). While this should not make a difference once the final transformation is fixed, it is a bit weird that the output path is apparently extended past the \pgfusepath in the meta-decoration's definition.

Here's an MWE illustrating this. foo steps to the side, draws a straight line parallel to the original one, and curves back to the line in the last 0.5 cm. In the meta-decoration it curves back to the end of the main state instead. When the input path has the length 2 cm, the result is extended. When the input path is longer, this (correctly) does not happen.

\documentclass{article}

\usepackage{tikz}
\usetikzlibrary{decorations.pathmorphing}

\pgfdeclaredecoration{foo}{initial}
  {%
    \state{initial}[
        width=0pt,
        next state=main,
        persistent precomputation={%
          \pgfmathsetlengthmacro\mainlength{\pgfdecoratedpathlength - .5cm}%
        },
      ]{
        \pgfpathlineto{\pgfpoint{0pt}{10pt}}
      }%
    \state{main}[width=\mainlength, next state=final]
      {
        \pgfpathlineto{\pgfpoint{\mainlength}{10pt}}
      }%
    \state{final}
      {
        \pgfpathcurveto
          {\pgfpoint{.1cm}{10pt}}
          {\pgfpoint{.4cm}{10pt}}
          {\pgfpointdecoratedpathlast}
      }%
  }

\pgfdeclaremetadecoration{too short}{first}
  {%
    \state{first}[width=1cm, next state=second]
      {
        \decoration{foo}
      }%
    \state{second}[width=1cm, next state=final]
      {
        \decoration{curveto}
        \afterdecoration{
            \pgfusepath{stroke}
          }
      }%
    \state{final}{}%
  }

\begin{document}

\begin{tikzpicture}
  \draw [help lines] (0,-2) grid (2,1);
  \draw [decorate, decoration=foo] (0,0) -- ++(1,0);
  \path [decorate, decoration=too short] (0,-1) -- ++(2.1,0);
  \path [decorate, decoration=too short] (0,-2) -- ++(2,0);
\end{tikzpicture}

\end{document}

image