问题

问题

我有一个汉字字符的多个路径,其中一个简单的路径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。它仅支持、 、 、 和MSVGm路径L运算l符。但是,这些似乎足以完成当前任务。Cc

生成的 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

我认为这个接近你想要实现的目标。动态绘制笔画当然是可能的,但需要做更多的工作。

仅适用于 Adob​​e 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.pathxml2lua
  • 代码将 svg 文件解析为 xml 文件
  • 然后提取各种信息(边界框、样式)和路径
  • 然后生成乳胶动画

请注意,为了获得良好的比例,必须将xy放到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}

在此处输入图片描述

相关内容