loopspace / spath3

TikZ/PGF package for manipulating soft paths, includes the knots and calligraphy TikZ libraries.
16 stars 2 forks source link

Spath3 used in `preaction` polutes the main action #30

Open muzimuzhi opened 1 month ago

muzimuzhi commented 1 month ago

I was trying with spath3 to achieve a shadow-like effect with bounding box auto updated. Then I found that if a preaction contains both line width change and spath/transform, the new line width was also applied to the main action, causing a wrong final bounding box.

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{shadows, spath3}

\tikzset{every picture/.append style={
  execute at end picture={
    \draw[dashed] (current bounding box.north east) rectangle
                  (current bounding box.south west);
  }
}}

\parindent=0pt

\begin{document}
Expected \\
\begin{tikzpicture}[line width=1pt]
  \path[draw=blue!50, line width=10pt]
    (0.3,0.3) rectangle +(1,.8);
  \path[draw=red]
    (0,0) rectangle +(1,.8);
\end{tikzpicture}

Using \verb|preaction={transform canvas={...}}| \\[.4cm]
\begin{tikzpicture}[line width=1pt]
  \path[draw=red]
       [preaction={draw=blue!50, line width=10pt,
                   transform canvas={shift={(.3,.3)}}}]
    (0,0) rectangle (1,.8);
\end{tikzpicture}

Using \verb|preaction={spath/transform={current}{...}}| \\
\begin{tikzpicture}[line width=1pt]
  \path[draw=red]
       [preaction={draw=blue!50, line width=10pt,
                   spath/transform={current}{shift={(.3,.3)}}}]
    (0,0) rectangle (1,.8);
\end{tikzpicture}
\end{document}

image

loopspace commented 1 month ago

Thanks for reporting, I'll try your code to see what's going on. My immediate thought is that I wonder whether this is just about how pre-actions interact with the main path. Does the effect persist if you take out the spath stuff?

Oh, hang on - that's what your second example demonstrates.

loopspace commented 1 month ago

I think I've isolated the issue, but I'm not sure what the fix is.

The problem (I think) is that the original coordinates of the path are getting included when the preaction part updates the picture bounding box. This is nothing to do with the preaction and is because the path is transformed before it is used.

When a path is created then the coordinates are added to the path bounding box, but because you are transforming it before using it then actually these coordinates shouldn't be used but rather their transformed ones (well, the interaction is more complicated because you are using both the transformed and original paths, but in essence this is what is going on).

So when a path is reused I need to ensure that all the various bounding boxes are updated correctly, but also if the current path is transformed then these bounding boxes need to be reset before updating so that the original values are discarded.

muzimuzhi commented 1 month ago

An attempt which adds \pgf@resetpathsizes if the path to modify and use is current seems to work. But I really don't know any details about spath3.

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{spath3}

\tikzset{every picture/.append style={
  execute at end picture={
    \draw[dashed]
      (current bounding box.north east) rectangle
      (current bounding box.south west);
  }
}}

\makeatletter
\ExplSyntaxOn
% TODO: do similar for \@@_maybe_current_two_paths_reuse_first:nnnn and
% \@@_maybe_current_two_paths_reuse_second:nnnn
\cs_new_protected_nopar:Npn \__tikzspath_maybe_current_path_reuse_NEW:nnn #1#2#3
{
  \bool_set_true:N \l_spath_movetorelevant_bool
  \tl_if_eq:nnT {#2} {current}
  {
    \pgf@resetpathsizes % <<< added
    \spath_get_current_path:c {\__tikzspath_path_name:n {#2}}
  }
  #1 {#2} #3
  \tl_if_eq:nnT {#2} {current}
  {
    \tl_if_empty:cF {\__tikzspath_path_name:n {#2}}
    {
      \spath_set_current_path:c {\__tikzspath_path_name:n {#2}}
      \spath_set_tikz_data:v {\__tikzspath_path_name:n {#2}}
    }
  }
}
\ExplSyntaxOff
\makeatother

\parindent=0pt
\begin{document}

\def\examples{%
Expected bbox \\
\begin{tikzpicture}[line width=1pt]
  \path[spath/save=p1] (0,0) rectangle (1,.8);
  \path[draw=blue!50, line width=10pt,
        spath/transform={p1}{shift={(.3,.3)}}, spath/use=p1];
\end{tikzpicture}

With \texttt{preaction} \\
\begin{tikzpicture}[line width=1pt]
  \path[draw=none]
       [preaction={draw=blue!50, line width=10pt,
                   spath/transform={current}{shift={(.3,.3)}}
                   }]
    (0,0) rectangle (1,.8);
\end{tikzpicture}

Without \texttt{preaction} \\
\begin{tikzpicture}[line width=1pt]
  % \path[draw=red] (0,0) rectangle (1,.8);
  \path[draw=blue!50, line width=10pt]
    (0,0) rectangle (1,.8)
    [spath/transform={current}{shift={(.3,.3)}}];
\end{tikzpicture}
}

\parbox[t]{.3\textwidth}{Before\par \examples}%
%
\ExplSyntaxOn
\cs_set_eq:NN \__tikzspath_maybe_current_path_reuse:nnn
              \__tikzspath_maybe_current_path_reuse_NEW:nnn
\ExplSyntaxOff
\parbox[t]{.3\textwidth}{After\par \examples}%

\end{document}

image

I tried to test with ./testsuite.tex but only found some tests already failed with current dev.

loopspace commented 1 month ago

This seems to me to be the right fix. If the current path is manipulated, then the original path sizes are now defunct and should be re-read from the start. The right place to put the fix is when the current path is removed and put back, as you have identified, though I think it works best in \spath_set_current_path:N since the path bounding box should be reset whenever the current path is overwritten, and that's what that command does.

There is a more general issue with bounding boxes and modifying the current path. There are two bounding boxes in play: the picture bounding box and the path bounding box. When coordinates are read in, and the path is built, then - under normal circumstances - both of these are updated. Then if the path is drawn, the path bounding box is used to add half the linewidth to the picture bounding box. This is what was happening with your example, since the path bounding box included the original path. So it wasn't that the preaction was polluting the main action, but rather the original path was still there as a "ghost" in the preaction path.

If modifying the current path then actually one could say that the picture bounding box shouldn't be updated with the original path coordinates. This isn't so easy to fix at the code level since the coordinates are used to adjust the bounding box as they are read in, and we might not know that we're going to modify the path until it is finished. However, the overlay key exists to prevent this updating so judicious use of this key can solve this issue. That just requires better documentation so I will add that in.

So fix is hopefully in: 86d48b50a59d47f977ca263febbedb596c45f343

(Not closing this yet as it feels like it needs extensive testing to check that nothing else has broken. Incidentally, the testsuite.tex is for testing the routines in the underlying spath3.sty code, it doesn't test anything in the TikZ library.)