我有一个汉字字符的多个路径,其中一个简单的路径test.svg
如下。
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100">
<style>
path {
fill: none;
stroke: black;
stroke-width: 3;
}
</style>
<path d="M52.25,14c0.25,2.28-0.52,3.59-1.8,5.62c-5.76,9.14-17.9,27-39.2,39.88" />
<path d="M54.5,19.25c6.73,7.3,24.09,24.81,32.95,31.91c2.73,2.18,5.61,3.8,9.05,4.59" />
<path d="M37.36,50.16c1.64,0.34,4.04,0.36,4.98,0.25c6.79-0.79,14.29-1.91,19.66-2.4c1.56-0.14,3.25-0.39,4.66,0" />
<path d="M23,65.98c2.12,0.52,4.25,0.64,7.01,0.3c13.77-1.71,30.99-3.66,46.35-3.74c3.04-0.02,4.87,0.14,6.4,0.29" />
<path d="M47.16,66.38c0.62,1.65-0.03,2.93-0.92,4.28c-5.17,7.8-8.02,11.38-14.99,18.84c-2.11,2.25-1.5,4.18,2,3.75c7.35-0.91,28.19-5.83,40.16-7.95" />
<path d="M66.62,77.39c4.52,3.23,11,12.73,13.06,18.82" />
</svg>
问题
是否可以将这些路径导入为动画 PDF,animate
这样每帧都显示书写字符的渐进步骤。例如,第一帧仅显示第一条路径,第二帧显示前两条路径,等等。
编辑
用于测试的 HTML 动画。
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<body>
<object type="image/svg+xml" data="test.svg"></object>
<p><button class="animate">Animate</button></p>
<script>
(function () {
var button = document.querySelector('.animate');
button.onclick = function (event) {
var object = document.querySelector("object");
var doc = object.contentDocument;
var paths = doc.querySelectorAll('path');
for (var i = 0; i < paths.length; i++) {
var path = paths[i];
var length = path.getTotalLength();
// Clear any previous transition
path.style.transition = path.style.WebkitTransition = 'none';
// Set up the starting positions
path.style.strokeDasharray = length + ' ' + length;
path.style.strokeDashoffset = length;
// Trigger a layout so styles are calculated & the browser
// picks up the starting position before animating
path.getBoundingClientRect();
// Define our transition
path.style.transition = path.style.WebkitTransition = 'stroke-dashoffset 2s ease-in-out ' + (2 * i) + 's';
// Go!
path.style.strokeDashoffset = '0';
}
};
}());
</script>
</body>
</html>
答案1
此解决方案使笔画变得像用笔绘制的一样,正如 OP 所要求的那样。
更新:SVG 解析器得到改进,动画性能(独立 PDF 和 SVG 动画)和输出文件大小得到改善;LaTeX 输入得到简化。
点击图片以交互方式运行动画:
已知的 TeX 引擎均不直接支持 SVG。因此我们必须将其转换为合适的格式。为此,我们用 Perl 编写了一个 SVG 解析器,将 SVG 路径转换为 Postscript。它仅支持、 、 、 和M
SVGm
路径L
运算l
符。但是,这些似乎足以完成当前任务。C
c
生成的 Postscript 代码利用了flattenpath
当通过 Ghostscript 运行时,运算符将曲线转换为直线序列。
Ghostscript 运行两次,从单页 Postscript 文件转换为笔画不断增加的多页 PDF 文件。
最后一步是使用将多页 PDF 组装为独立的动画 PDF 或 SVG \animategraphics
。
这些是需要在终端中运行的必要步骤,从测试.svg. (更多示例请参见此处:https://github.com/KanjiVG/kanjivg/releases。
Unix:
svgpath2ps.pl test.svg > single.ps
ps2pdf single.ps > multi.ps 2>&1 # also writes `single.pdf' which we do not use further
ps2pdf multi.ps # creates `multi.pdf' we animate in the next step
pdflatex animatepath.tex
pdflatex animatepath.tex
Windows/DOS(当然,perl.exe
必须在系统上可用):
perl svgpath2ps.pl test.svg > single.ps
ps2pdf single.ps > multi.ps
ps2pdf multi.ps
pdflatex animatepath.tex
pdflatex animatepath.tex
这SVG 解析器 svgpath2ps.pl
:
#!/usr/bin/perl
$header=0;
$strokewd=1;
$strokelc=0;
$strokelj=0;
while(<>){
chomp;
if (/viewBox\s*=\s*"([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)"/){($ulx,$uly,$lrx,$lry)=($1,$2,$3,$4);}
if (/width\s*=\s*"([0-9]+)"/){$wd=$1;}
if (/height\s*=\s*"([0-9]+)"/){$ht=$1;}
if (/stroke-width:\s*([^;\s]+)/){$strokewd=$1;}
if (/stroke-linejoin:\s*round/){$strokelj=1;}
if (/stroke-linejoin:\s*bevel/){$strokelj=2;}
if (/stroke-linecap:\s*square/){$strokelc=2;}
if (/stroke-linecap:\s*round/){$strokelc=1;}
if (/<path.* d\s*=\s*/) {
if (!$header){
$header=1;
print "%!PS-Adobe-3.0\n",
"%%BoundingBox: $ulx $uly $lrx $lry\n",
"%%EndComments\n",
"/curpage 0 def\n",
"/curpath [{}] def\n",
"/setbbox {pop pop pop pop} def\n",
"/printany {0 cvs print flush} def /printany load 0 256 string put\n",
"/typepath {flattenpath{\n",
" gsave newpath curpath {exec} forall moveto false upath [ exch ] /curpath exch def grestore\n",
"}{\n",
" gsave newpath curpath {exec} forall lineto false upath [ exch ] /curpath exch def grestore\n",
" /curpage curpage 1 add def\n",
" (\\n%%Page: ) print curpage printany ( ) print curpage printany (\\n) print\n",
" (0 $lry translate 1 -1 scale $strokewd setlinewidth $strokelj setlinejoin $strokelc setlinecap\\n) print\n",
" curpath {{printany ( ) print} forall} forall (stroke showpage) print\n",
"}{}{} pathforall} def\n",
"<</PageSize [$wd $ht]>> setpagedevice\n",
"0 $lry translate 1 -1 scale $strokewd setlinewidth\n",
"(%!PS-Adobe-3.0\\n) print\n",
"(%%BoundingBox: $ulx $uly $lrx $lry\\n) print\n",
"(%%EndComments\\n) print\n",
"(<</PageSize [$wd $ht]>> setpagedevice) print\n";
}
s/^.*d\s*=\s*"([^"]*)".*$/$1/;
s/-/ -/g;
s/,/ /g;
s/M\s?([0-9.-]+) ([0-9.-]+)/ $1 $2 moveto /g;
s/m\s?([0-9.-]+) ([0-9.-]+)/ $1 $2 rmoveto /g;
s/L\s?([0-9.-]+) ([0-9.-]+)/ $1 $2 lineto /g;
s/l\s?([0-9.-]+) ([0-9.-]+)/ $1 $2 rlineto /g;
s/C\s?([0-9.-]+) ([0-9.-]+) ([0-9.-]+) ([0-9.-]+) ([0-9.-]+) ([0-9.-]+)/ $1 $2 $3 $4 $5 $6 curveto /g;
s/c\s?([0-9.-]+) ([0-9.-]+) ([0-9.-]+) ([0-9.-]+) ([0-9.-]+) ([0-9.-]+)/ $1 $2 $3 $4 $5 $6 rcurveto /g;
s/^\s*//g;
print;
print "typepath stroke\n";
}else{next;}
}
print "(\\n%%EOF\\n) print\n";
print "%%EOF\n";
这LaTeX 输入 animatepath.tex
:
%\documentclass[dvisvgm]{standalone}
\documentclass{standalone}
\usepackage{animate,graphicx}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Adjust this if necessary!
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% basename (!) of PDF file with stroke segments
\def\fileWithStrokes{multi}
% frame rate
\def\FPS{20}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\begin{document}
\animategraphics[
controls={play,stop},
buttonsize=2em,
poster=last,
]{\FPS}{\fileWithStrokes}{}{}
\end{document}
动画 SVG(位于本答案顶部)使用以下方式制作:
latex animatepath.tex
latex animatepath.tex
dvisvgm --bbox=papersize --zoom=2 animatepath.dvi
答案2
我认为这个接近你想要实现的目标。动态绘制笔画当然是可能的,但需要做更多的工作。
仅适用于 Adobe Reader。
\documentclass{article}
\usepackage{animate,tikz}
\usetikzlibrary{svg.path}
\begin{document}
\begin{animateinline}[
palindrome,
autoplay,
controls,
begin={
\begin{tikzpicture}[yscale=-1,very thick]
\useasboundingbox (0,0) rectangle (3.8,4);
},
end={\end{tikzpicture}}
]{2}
\multiframe{6}{i=1+1}{%
\ifnum\i>0
\draw svg {M52.25,14c0.25,2.28-0.52,3.59-1.8,5.62c-5.76,9.14-17.9,27-39.2,39.88};
\fi
\ifnum\i>1
\draw svg {M54.5,19.25c6.73,7.3,24.09,24.81,32.95,31.91c2.73,2.18,5.61,3.8,9.05,4.59};
\fi
\ifnum\i>2
\draw svg {M37.36,50.16c1.64,0.34,4.04,0.36,4.98,0.25c6.79-0.79,14.29-1.91,19.66-2.4c1.56-0.14,3.25-0.39,4.66,0};
\fi
\ifnum\i>3
\draw svg {M23,65.98c2.12,0.52,4.25,0.64,7.01,0.3c13.77-1.71,30.99-3.66,46.35-3.74c3.04-0.02,4.87,0.14,6.4,0.29};
\fi
\ifnum\i>4
\draw svg {M47.16,66.38c0.62,1.65-0.03,2.93-0.92,4.28c-5.17,7.8-8.02,11.38-14.99,18.84c-2.11,2.25-1.5,4.18,2,3.75c7.35-0.91,28.19-5.83,40.16-7.95};
\fi
\ifnum\i>5
\draw svg {M66.62,77.39c4.52,3.23,11,12.73,13.06,18.82};
\fi
}
\end{animateinline}
\end{document}
您可以使用 LPEG 解析 SVG 文件中的路径。需要 LuaTeX。
\documentclass{article}
\usepackage{animate,tikz}
\usetikzlibrary{svg.path}
\directlua{
local lpeg = require"lpeg"
local C, Ct, P, S = lpeg.C, lpeg.Ct, lpeg.P, lpeg.S
local ws = S" \string\t\string\r\string\n"^0
local quot = P"\string\""
local opts = P"d" * ws * P"=" * ws * quot * C((1 - quot)^0) * quot
local path = P"<path" * ws * opts * ws * P"/>"
local other = (1 - path)^0
local svg = Ct(other * path * (other * path)^0)
local f = io.open("test.svg"):read("*all")
data = lpeg.match(svg,f)
}
\begin{document}
\begin{animateinline}[
palindrome,
autoplay,
controls,
begin={
\begin{tikzpicture}[yscale=-1,very thick]
\useasboundingbox (0,0) rectangle (3.8,4);
},
end={\end{tikzpicture}}
]{2}
\multiframe{6}{i=1+1}{%
\directlua{
for i = 1,\i\space do
tex.sprint([[\string\draw\space svg {]] .. data[i] .. [[};]])
end
}
}
\end{animateinline}
\end{document}
TeX Live 中还有一个 XML 解析器,名为luaxml
. 还需要 LuaTeX。
\documentclass{article}
\usepackage{animate,tikz}
\usetikzlibrary{svg.path}
\directlua{
local dom = require"luaxml-domobject"
local f = io.open("test.svg"):read("*all")
local obj = dom.parse(f)
data = obj:query_selector("path")
}
\begin{document}
\begin{animateinline}[
palindrome,
autoplay,
controls,
begin={
\begin{tikzpicture}[yscale=-1,very thick]
\useasboundingbox (0,0) rectangle (3.8,4);
},
end={\end{tikzpicture}}
]{2}
\multiframe{6}{i=1+1}{%
\directlua{
for i = 1,\i\space do
tex.sprint([[\string\draw\space svg {]] .. data[i]:get_attribute("d") .. [[};]])
end
}
}
\end{animateinline}
\end{document}
答案3
好吧,我作弊了,我确实包含了提供的 SVG(和另一个),我可以拆分 SVG 来生成内联动画
然而,这是一个概念证明,即动画 GIF 或简单的拇指书翻页适合更广泛的受众。
如果我们拍摄一个 GIF例如https://www.learnchineseez.com/characters/learn-to-write-chinese/images/common-chinese-character-20.gif或者使用提供的 SVG 的帧序列,我们可以按照适合用户的速度手动浏览它们
连续 gif 页面适用于许多图像或网络浏览器无需 javascript 或其他插件。如果它们作为图像存储在 zip 中并重命名为 .cbz,它们将在电子书阅读器中使用,例如 SumatraPDF,它们也可以将它们视为静态连续的 GIF 或 PNG 甚至 PDF……
如果我们要在 PDF 包装器中为它们添加动画,那么只有少数阅读器允许使用 java-script。我无法在 Acrobat 阅读器中让动画工作,但在 Foxit 阅读器中却可以。
\documentclass[a4paper]{report}
\usepackage{graphicx}
\usepackage{svg}
\svgpath{{foo/}}
\usepackage{animate}
\begin{document}
\begin{figure}[htbp]
\centering \includesvg[width=\linewidth]{logo} \par \addvspace{\baselineskip}
\includesvg[width=0.3\linewidth]{Join} \caption{ To Join or meet}
\end{figure}
\animategraphics[autoplay,loop]{1}{./foo/bar-}{0}{63}
\end{document}
会
答案4
我从 Henri Menke 的回答开始,使用以下命令自动实现绘图lualatex
:
- 首先安装
xml2lua
用于 xml 解析的 lua 包,使用命令luarocks install xml2lua
- 必须加载此包,但它不在 texmf 树中,因此您必须加载包并将安装目录
luapackageloader
添加到变量中package.path
xml2lua
- 代码将 svg 文件解析为 xml 文件
- 然后提取各种信息(边界框、样式)和路径
- 然后生成乳胶动画
请注意,为了获得良好的比例,必须将x
和y
放到1pt
。
以下是需要编译的程序lualatex
:
\documentclass{standalone}
\usepackage{tikz}
\usetikzlibrary{svg.path}
\usepackage{animate}
\usepackage{luacode}
\usepackage{luapackageloader}
% execute before: sudo luarocks install xml2lua
\begin{luacode*}
package.path="/usr/share/lua/5.3/?.lua;"..package.path -- change if necessary
xml2lua = require("xml2lua")
local handler = require("xmlhandler.tree")
local parser = xml2lua.parser(handler)
function animate_kanji_character(filename)
local paths={}
local line_width=1
local color="black"
-- read svg file
local file = io.open(filename, "rb")
if not file then return nil end
local content = file:read "*a"
file:close()
parser:parse(content)
local svg=handler.root.svg
-- extract the boundingbox
local bbox=""
for x1,y1,x2,y2 in string.gmatch(svg._attr.viewBox,"(%d+)%s+(%d+)%s+(%d+)%s+(%d+)") do
bbox="("..x1..","..y1..") rectangle ("..x2..","..y2..")"
end
-- extract styles (color and line width)
for clr in string.gmatch(svg.style,"stroke: (%a*)") do
color=clr
end
for lw in string.gmatch(svg.style,"stroke.width: (%d*)") do
line_width=tonumber(lw,10)
end
-- store paths
for i,path in pairs(svg.path) do
paths[i]=path._attr.d -- we store the i-th path
end
-- latex printing
tex.print("\\begin{animateinline}[poster="..(#paths-1)..",autoplay,loop,begin={\\begin{tikzpicture}[y=1pt,x=1pt,yscale=-1] \\useasboundingbox "..bbox..";},end={\\end{tikzpicture}}]{1}")
-- print paths
for i=1,#paths do
if i>1 then tex.print("\\newframe") end
for j=1,i do
tex.print("\\draw[color="..color..",line width="..line_width.."pt] svg {"..paths[j].."};")
end
end
tex.print("\\end{animateinline}")
end
\end{luacode*}
\begin{document}
\directlua{animate_kanji_character("test.svg")}
\end{document}