答案1
更新现在可以在 CTAN 上使用(见上文)。错误修复和改进通常可在github在上传到 CTAN 之前。如果从那里获取文件,请注意相关的 .dtx 文件是spath3.dtx
。这将生成所有必要的文件。
更新:我已将其添加到 TeX-SX 包中,该包已于您最喜欢的 TikZ/PGF 答案是什么?。dtx
可从以下网址下载http://bazaar.launchpad.net/+branch/tex-sx/files. 运行pdflatex
它来生成sty
文件和文档。
嗯,我猜测你正在寻找类似这样的东西:
这也许不容易,但我会尝试一下……
\documentclass{standalone}
\usepackage{tikz}
%%% TODO:
%
% 1. Be able to define a number of different pens and use appropriately
% 2. Add an annotation style (arrows with numbers)
% 3. Check how this interacts with colours and so forth
\makeatletter
\long\def\ge@addto@macro#1#2{%
\begingroup
\toks@\expandafter\expandafter\expandafter{\expandafter#1#2}%
\xdef#1{\the\toks@}%
\endgroup}
\long\def\ge@addbefore@macro#1#2{%
\begingroup
\toks@\expandafter\expandafter\expandafter{\expandafter#2#1}%
\xdef#1{\the\toks@}%
\endgroup}
\long\def\ge@addbefore@macro#1#2{%
\begingroup
\toks@\expandafter\expandafter\expandafter{\expandafter#2#1}%
\xdef#1{\the\toks@}%
\endgroup}
\long\def\g@addbefore@macro#1#2{%
\begingroup
\toks@\expandafter{\expandafter#2#1}%
\xdef#1{\the\toks@}%
\endgroup}
\pgfkeys{/tikz/store path in/.code={\pgfsyssoftpath@getcurrentpath{\my@path}\global\let#1=\my@path}}
\pgfkeys{/tikz/define pen/.style={preaction={store path in=\penpath}}}
\def\pen{\path[define pen]}
\def\calligraphy{\path[calligraphy]}
\pgfkeys{/tikz/calligraphy/.style={preaction={store path in=\calligraphypath},preaction={stroke with calligraphy pen=\penpath}}}
\pgfkeys{/tikz/stroke with calligraphy pen/.code={\thickenpath{\calligraphypath}{#1}}}
\def\thickenpartialpath#1#2{%
\def\thick@path{}%
\def\thick@action##1##2##3{%
\edef\this@action{\string##1}%
\ifx\this@action\cal@moveto
\ifx\thick@path\@empty
\else
\lengthofsoftpath{\thick@path}
\ifnum\value{softpathlength}=1
\thickenpartialpathwithstroke{#1}{\thick@path}
\else
\thickenpartialpathwithfill{#1}{\thick@path}
\fi
\def\thick@path{}%
\fi
\fi
\def\to@add{##1{##2}{##3}}
}
\let\thick@append=\ge@addto@macro
\expandafter\walksoftpath\expandafter\thick@path\expandafter\thick@action\expandafter\thick@append#2\relax
\lengthofsoftpath{\thick@path}
\ifnum\value{softpathlength}=1
\thickenpartialpathwithstroke{#1}{\thick@path}
\else
\thickenpartialpathwithfill{#1}{\thick@path}
\fi
}
\def\thickenpartialpathwithfill#1#2{%
\startsoftpath{#2}
\let\pen@sx=\tr@xlen
\let\pen@sy=\tr@ylen
\endsoftpath{#2}
\let\pen@ex=\tr@xlen
\let\pen@ey=\tr@ylen
\startsoftpath{#1}
\let\path@sx=\tr@xlen
\let\path@sy=\tr@ylen
\endsoftpath{#1}
\let\path@ex=\tr@xlen
\let\path@ey=\tr@ylen
\translatesoftpath{#1}{\pen@sx}{\pen@sy}%
\let\lower@path=\trpath
\translatesoftpath{#2}{\path@ex}{\path@ey}
\let\right@path=\trpath
\reversesoftpath{#1}
\translatesoftpath{\revpath}{\pen@ex}{\pen@ey}
\let\upper@path=\trpath
\reversesoftpath{#2}
\translatesoftpath{\revpath}{\path@sx}{\path@sy}
\let\left@path=\trpath
\catsoftpath{\lower@path}{\right@path}
\catsoftpath{\catpath}{\upper@path}
\catsoftpath{\catpath}{\left@path}
\def\to@add{\pgfsyssoftpath@closepath{0pt}{0pt}}
\ge@addto@macro\catpath\to@add
\pgfsyssoftpath@setcurrentpath{\catpath}
\pgfsyssoftpath@flushcurrentpath
\pgfusepath{fill}
}
\def\thickenpartialpathwithstroke#1#2{%
\startsoftpath{#2}
\let\pen@sx=\tr@xlen
\let\pen@sy=\tr@ylen
\translatesoftpath{#1}{\pen@sx}{\pen@sy}%
\pgfsyssoftpath@setcurrentpath{\trpath}
\pgfsyssoftpath@flushcurrentpath
\pgfusepath{stroke}
}
\def\catsoftpath#1#2{
\let\catpath=#1
\expandafter\trimfirst#2\relax
\ge@addto@macro\catpath\trimed@path
}
\def\trimfirst#1#2#3#4\relax{%
\edef\this@action{\string#1}%
\ifx\this@action\cal@moveto
\def\trimed@path{#4}%
\else
\def\trimed@path{#1{#2}{#3}#4}%
\fi
}
\def\walksoftpath#1#2#3#4{
\let\path@cmd=#4%
\ifx\path@cmd\relax
\let\next@action=\@gobblefour
\else
\let\next@action=\modifypath
\fi
\next@action{#1}{#2}{#3}{#4}%
}
\def\modifypath#1#2#3#4#5#6{%
#2{#4}{#5}{#6}
#3#1\to@add
\walksoftpath{#1}{#2}{#3}}
\newcounter{softpathlength}
\def\lengthofsoftpath#1{%
\def\len@path{}%
\setcounter{softpathlength}{0}%
\def\len@action##1##2##3{%
\stepcounter{softpathlength}%
\edef\to@add{}%
}
\let\len@append=\ge@addto@macro
\expandafter\walksoftpath\expandafter\len@path\expandafter\len@action\expandafter\len@append#1\relax
}
\def\translatesoftpath#1#2#3{%
\def\tr@path{}%
\def\tr@action##1##2##3{%
\pgfmathsetmacro{\tr@xlen}{##2+#2}
\pgfmathsetmacro{\tr@ylen}{##3+#3}
\edef\to@add{\noexpand##1{\tr@xlen pt}{\tr@ylen pt}}
}
\let\tr@append=\ge@addto@macro
\expandafter\walksoftpath\expandafter\tr@path\expandafter\tr@action\expandafter\tr@append#1\relax
\global\let\trpath=\tr@path
}
\def\trimlast#1\pgfsyssoftpath@movetotoken\relax{#1}
\foreach \cal@cpt in {
moveto,
lineto,
curvetosupporta,
curvetosupportb,
curveto,
rectcorner,
rectsize%
} {
\expandafter\xdef\csname cal@\cal@cpt\endcsname{\expandafter\string\csname pgfsyssoftpath@\cal@cpt token\endcsname}
}
\edef\cal@closepath{\string\pgfsyssoftpath@closepath}
\def\reversesoftpath#1{%
\def\re@path{}%
\def\re@action##1##2##3{%
\edef\this@re@action{\string##1}
\ifx\this@re@action\cal@curvetosupporta
\edef\to@add{\noexpand\pgfsyssoftpath@curvetosupportbtoken{##2}{##3}\noexpand\pgfsyssoftpath@curvetotoken}
\else
\ifx\this@re@action\cal@curvetosupportb
\edef\to@add{\noexpand\pgfsyssoftpath@curvetosupportatoken{##2}{##3}}
\else
\ifx\this@re@action\cal@curveto
\edef\to@add{{##2}{##3}}
\else
\edef\to@add{{##2}{##3}\noexpand##1}
\fi
\fi
\fi
}
\let\re@append=\ge@addbefore@macro
\expandafter\walksoftpath\expandafter\re@path\expandafter\re@action\expandafter\re@append#1\relax
\expandafter\expandafter\expandafter\def\expandafter\expandafter\expandafter\re@path\expandafter\expandafter\expandafter{\expandafter\trimlast\re@path\relax}
\g@addbefore@macro\re@path\pgfsyssoftpath@movetotoken
\global\let\revpath=\re@path
}
\def\startsoftpath#1{%
\def\st@path{}%
\def\st@action##1##2##3{%
\def\tr@xlen{##2}%
\def\tr@ylen{##3}%
\def\st@action####1####2####3{}
}
\let\st@append=\ge@addto@macro
\expandafter\walksoftpath\expandafter\st@path\expandafter\st@action\expandafter\st@append#1\relax
}
\def\endsoftpath#1{%
\def\end@path{}%
\def\end@action##1##2##3{%
\def\tr@xlen{##2}%
\def\tr@ylen{##3}%
}
\let\end@append=\ge@addto@macro
\expandafter\walksoftpath\expandafter\end@path\expandafter\end@action\expandafter\end@append#1\relax
}
\def\thickenpath#1#2{%
\def\th@path{}%
\def\th@action##1##2##3{%
\edef\this@action{\string##1}
\ifx\this@action\cal@moveto
\ifx\th@path\@empty
\else
\thickenpartialpath\th@path{#2}
\fi
\def\th@path{}
\fi
\def\to@add{##1{##2}{##3}}
}
\let\th@append=\ge@addto@macro
\expandafter\walksoftpath\expandafter\th@path\expandafter\th@action\expandafter\th@append#1\relax
\thickenpartialpath\th@path{#2}
}
\makeatother
\begin{document}
\begin{tikzpicture}
\pen (-.25,-.125) -- (0,0) ++(.125,.0625) -- (.25,.125);
\calligraphy (0,0) .. controls +(.5,.5) and +(-.5,-.5) .. ++(2,0) (1,0) -- ++(0,-2) -- ++(-.125,-.125) .. controls +(.1,.1) and +(-.1,-.1) .. ++(.35,0);
\calligraphy (3,-1) .. controls +(-1.5,1) and +(-1.5,-1) .. ++(0,-2) ++(-1.1,1) -- ++(1,0) -- ++(-.25,-.25);
\calligraphy (3.25,0) .. controls +(.5,.5) and +(-.5,-.5) .. ++(1.5,-2) ++(0,2.5) .. controls +(-.75,.75) and +(.75,-.75) .. ++(-1.5,-3);
\calligraphy (0,-4) .. controls +(1,1) and +(-1,-1) .. ++(5,0);
\end{tikzpicture}
\end{document}
首先,历史。我喜欢书法,喜欢用 TeX 制作文档,最近,随着 TikZ 的加入,我开始喜欢制作图形内容,因此,将书法与 TikZ 结合起来的想法已经萦绕在我的脑海中很长时间了。有几次,当我在这里读到问题时,我曾想过“我可以用书法笔做到这一点”。此外,当我发现 Inkscape 有一个书法工具时,我认为它与图形输入板结合会很棒,但我对结果一直不太满意——它们不够优雅,无法取代手绘,也不够精确,无法获得计算机渲染的好处(尽管能够制作截屏视频很有用:请参阅这一页对于这样的截屏视频,通过查看 SVG 的源代码,您还可以看到 Inkscape 的书法方法......并不优雅。)。
无论如何,当我闲暇时阅读 PGF 手册时,我偶然发现了软路径并意识到这是写书法的关键。基本上(有关详细信息,请参阅手册)当 TikZ/PGF 构建路径时,它会在多个层次上进行构建,并且在每个步骤中,都可以中断该过程以处理迄今为止的数据,然后再将其传递到下一个步骤。一个步骤是软路径。这是基本形式的路径,因此一旦计算出所有坐标并将所有有趣的路径渲染为它们更基本的组成部分(因此圆圈被贝塞尔曲线取代等等),但它还没有“硬烘焙”,所以仍然可以操作。
上面的脚本在此时介入,并说:“你刚刚构建的那条路径,而不是渲染它照原样,我要‘加粗’它。”具体来说,它将路径与另一条路径(称为“笔”)结合起来,形成两者的(笛卡尔)乘积。然后产生一个区域并填充它。效果就像笔沿着路径拖动一样。通过使用相当复杂的笔,可以实现相当复杂的效果。
大多数情况下,这种填充方法都能产生预期的效果。但当路径拐角与笔路径相切时,这种方法就会失效。如果真的沿着路径拖动笔,这个角就会被填充,但因为这种方法是通过平移来实现的,所以这种情况不会发生(图片中的一个例子是右侧“X”的顶部)。 然而,在写书法时,永远不要推笔,画一条与笔相切的笔画需要推笔(所以“X”的非对角线实际上应该是三描边:主路和两端。
请注意,一旦我将其打包成某个东西(并且促使约瑟夫提出这个问题的一个原因是为了证明将其包含在提议的“TikZ from TeX-SX”包中!),用法将是这样的:
\pen (-.25,-.125) -- (0,0) ++(.125,.0625) -- (.25,.125);
\calligraphy (0,0) .. controls +(.5,.5) and +(-.5,-.5) .. ++(2,0) (1,0) -- ++(0,-2) -- ++(-.125,-.125) .. controls +(.1,.1) and +(-.1,-.1) .. ++(.35,0);
\calligraphy (3,-1) .. controls +(-1.5,1) and +(-1.5,-1) .. ++(0,-2) ++(-1.1,1) -- ++(1,0) -- ++(-.25,-.25);
\calligraphy (3.25,0) .. controls +(.5,.5) and +(-.5,-.5) .. ++(1.5,-2) ++(0,2.5) .. controls +(-.75,.75) and +(.75,-.75) .. ++(-1.5,-3);
\calligraphy (0,-4) .. controls +(1,1) and +(-1,-1) .. ++(5,0);
这其实相当简单。
除了“嘿,看看我刚刚做了什么!”的乐趣之外,我还想把它放在公共的地方,因为我认为这种操纵软路径的能力对于其他效果非常有用。它有点像装饰品,但不完全是,我在这里确定的概念在有人想说“那条路径,我想用它,但用一种奇怪的方式。”的情况下很有用。特别是,用同一条路径做多次某事,但每次操作都稍微修改路径。