svg
库中的命令可以在 TikZ 中svg.path
显示 svg <path>
。但在一种特殊情况下,坐标计算失败。
当路径的一部分由z
(或Z
) 关闭并且以下路径指令是相对的 (小字符,如l
, c
, ...) 时,会发生这种情况。然后 tikz 应该计算相对于移动点的下一个坐标z
(因此是路径上一部分的开头),但它的行为就像z
不存在一样。
举个例子。前两条路径应该与第二条路径等价。蓝色路径(没有z
)是这样的,但红色路径(z
有 )不是这样的。
\documentclass[tikz,border=7mm]{standalone}
\usetikzlibrary{svg.path}
\begin{document}
\begin{tikzpicture}[yscale=-1]
\begin{scope} % absolute paths
\draw[blue] svg{M0,0 L 10,10 L 10,20};
\draw[red] svg{M0,0 L 10,10 Z L 0,10};
\end{scope}
\begin{scope}[xshift=20pt] % "equivalent" relative paths
\draw[blue] svg{M0,0 l 10,10 l 0,10};
\draw[red] svg{M0,0 l 10,10 z l 0,10}; % tikz put the last point at (10,20)
\end{scope}
\end{tikzpicture}
\end{document}
我们可以看出等价SVG
是没有问题的。
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="-10 -10 50 40" width="500" height="400">
<g fill="none" stroke-width=".4">
<path stroke="blue" d="M0,0 L 10,10 L 10,20"/>
<path stroke="red" d="M0,0 L 10,10 Z L 0,10"/>
</g>
<g fill="none" stroke-width=".4" transform="translate(20)">
<path stroke="blue" d="M0,0 l 10,10 l 0,10"/>
<path stroke="red" d="M0,0 l 10,10 z l 0,10"/>
</g>
</svg>
问题:我们怎样才能修复svg.path
图书馆?
笔记:我还很惊讶像(20,0) -- cycle -- ++(0,10)
和这样的路径在 TikZ 中并不等价(例如,(20,0) -- ++(0,10)
它们在内部不会产生相同的结果)......\draw[xshift=10] ...;
为什么?
答案1
SVG 中的字母z
/ 、 Ti 中的Z
--cycle
钾Z、\closepath
PGF 中的 以及 PDF 中的字母h
关闭当前子路径。对于 Ti钾Z 和 PGF,它们不需要记住当前子路径的起点,因为 PDF 渲染器会完成这项工作。§¶对于SVG,由 Web 浏览器完成这项工作。
于是尴尬的事情出现了:对于 PFG 上的 SVG 引擎,相对命令(小写)的参考点由\pgf@lib@svg@last@x
和保存\pgf@lib@svg@last@y
。它们由所有命令更新,但z
/Z
因为 PGF 不知道它们应该是什么——它们应该代表当前子路径的初始点,但 PGF 就是不记得了。
因此我们必须分配两个寄存器来帮助 PGF 记住它。
\newdimen\pgf@lib@svg@lastmoveto@x
\newdimen\pgf@lib@svg@lastmoveto@y
然后每当我们遇到m
/时更新它们M
(代码都是从 复制的,pgflibrarysvg.path.code.tex
除了带有% NEW LINE
. 的代码)
\def\pgf@lib@svg@moveto{%
\pgf@lib@svg@clear@bezier@quad%
\pgf@lib@svg@last@x=\pgf@lib@svg@get@num{0}pt%
\pgf@lib@svg@last@y=\pgf@lib@svg@get@num{1}pt%
\pgf@lib@svg@lastmoveto@x=\pgf@lib@svg@last@x% NEW LINE
\pgf@lib@svg@lastmoveto@y=\pgf@lib@svg@last@y% NEW LINE
\pgfpathmoveto{\pgfqpoint{\pgf@lib@svg@last@x}{\pgf@lib@svg@last@y}}%
\pgf@lib@svg@read@nums{2}{\pgf@lib@svg@lineto}
}
\def\pgf@lib@svg@moveto@rel{%
\pgf@lib@svg@clear@bezier@quad%
\advance\pgf@lib@svg@last@x by\pgf@lib@svg@get@num{0}pt%
\advance\pgf@lib@svg@last@y by\pgf@lib@svg@get@num{1}pt%
\pgf@lib@svg@lastmoveto@x=\pgf@lib@svg@last@x% NEW LINE
\pgf@lib@svg@lastmoveto@y=\pgf@lib@svg@last@y% NEW LINE
\pgfpathmoveto{\pgfqpoint{\pgf@lib@svg@last@x}{\pgf@lib@svg@last@y}}%
\pgf@lib@svg@read@nums{2}{\pgf@lib@svg@lineto@rel}
}
现在我们知道如何更新\pgf@lib@svg@last@x
和\pgf@lib@svg@last@y
\def\pgf@lib@svg@closepath{
\pgf@lib@svg@finish@prev
\pgf@lib@svg@last@x=\pgf@lib@svg@lastmoveto@x% NEW LINE
\pgf@lib@svg@last@y=\pgf@lib@svg@lastmoveto@y% NEW LINE
\pgfpathclose
\let\pgf@lib@svg@finish@prev=\relax
\pgf@lib@svg@clear@bezier@quad%
\pgfparserswitch{initial}%
}
完整代码
\documentclass[tikz,border=7mm]{standalone}
\usetikzlibrary{svg.path}
\makeatletter
\newdimen\pgf@lib@svg@lastmoveto@x
\newdimen\pgf@lib@svg@lastmoveto@y
\def\pgf@lib@svg@moveto{%
\pgf@lib@svg@clear@bezier@quad%
\pgf@lib@svg@last@x=\pgf@lib@svg@get@num{0}pt%
\pgf@lib@svg@last@y=\pgf@lib@svg@get@num{1}pt%
\pgf@lib@svg@lastmoveto@x=\pgf@lib@svg@last@x% NEW LINE
\pgf@lib@svg@lastmoveto@y=\pgf@lib@svg@last@y% NEW LINE
\pgfpathmoveto{\pgfqpoint{\pgf@lib@svg@last@x}{\pgf@lib@svg@last@y}}%
\pgf@lib@svg@read@nums{2}{\pgf@lib@svg@lineto}
}
\def\pgf@lib@svg@moveto@rel{%
\pgf@lib@svg@clear@bezier@quad%
\advance\pgf@lib@svg@last@x by\pgf@lib@svg@get@num{0}pt%
\advance\pgf@lib@svg@last@y by\pgf@lib@svg@get@num{1}pt%
\pgf@lib@svg@lastmoveto@x=\pgf@lib@svg@last@x% NEW LINE
\pgf@lib@svg@lastmoveto@y=\pgf@lib@svg@last@y% NEW LINE
\pgfpathmoveto{\pgfqpoint{\pgf@lib@svg@last@x}{\pgf@lib@svg@last@y}}%
\pgf@lib@svg@read@nums{2}{\pgf@lib@svg@lineto@rel}
}
\def\pgf@lib@svg@closepath{
\pgf@lib@svg@finish@prev
\pgf@lib@svg@last@x=\pgf@lib@svg@lastmoveto@x% NEW LINE
\pgf@lib@svg@last@y=\pgf@lib@svg@lastmoveto@y% NEW LINE
\pgfpathclose
\let\pgf@lib@svg@finish@prev=\relax
\pgf@lib@svg@clear@bezier@quad%
\pgfparserswitch{initial}%
}
\begin{document}
\begin{tikzpicture}[yscale=-1]
\begin{scope} % absolute paths
\draw svg{M0,0 L 10,10 Z L 0,10};
\end{scope}
\begin{scope}[xshift=20pt] % "equivalent" relative paths
\draw svg{M0,0 l 10,10 z l 0,10}; % tikz put the last point at (10,20)
\end{scope}
\end{tikzpicture}
\end{document}
§ PGF 实际上记得一些事情:pgfsyssoftpath.code.tex
我们看到
\def\pgfsyssoftpath@closepath{% \expandafter\pgfsyssoftpath@addtocurrentpath\expandafter{\expandafter\pgfsyssoftpath@closepathtoken\pgfsyssoftpath@lastmoveto}% }
\pgfsyssoftpath@lastmoveto
类似的东西在哪里{10pt}{10pt}
代表初始点。这个宏对于装饰和形状来说是必不可少的。但这是相当低级的。
¶ 钛钾Z 确实记得一些事情:tikz.code.tex
我们看到
\def\tikz@@close cycle{% \tikz@flush@moveto% \edef\tikz@timer@start{\noexpand\pgfqpoint{\the\tikz@lastx}{\the\tikz@lasty}} \tikz@make@last@position{\expandafter\pgfpoint\pgfsyssoftpath@lastmoveto}% \tikz@path@close{\expandafter\pgfpoint\pgfsyssoftpath@lastmoveto}% \def\pgfstrokehook{}% \edef\tikz@timer@end{\noexpand\pgfqpoint{\the\tikz@lastx}{\the\tikz@lasty}}% \let\tikz@timer=\tikz@timer@line% \let\tikz@tangent\tikz@timer@start% \tikz@scan@next@command% }
其中\tikz@make@last@position
检索中的坐标\pgfsyssoftpath@lastmoveto
并将其存储在\tikz@lastxsaved
和中\tikz@lastysaved
。因此 Ti钾Z 变体
\tikz\draw(0,0)--(1,1)--cycle--++(1,0);
表现良好。