使用 dvisvgm

使用 dvisvgm

尝试找到一些方法自动生成svg输出TeX我想到两种可能性luatex

  1. lua在回调中使用来解析节点树并输出 svg(不久前有人有这样的想法)。

  2. 使用luatex自身来生成svg,而不是pdf。据我所知,cairo图书馆包含在luatex源代码cairo可以输出svg

我的问题是:

  1. svg目前输出支持的状况如何luatex(项目本身或“第三方”项目)。

  2. 我可以使用mplibmetapost svg输出吗?编辑看起来mplib 不再svg支持

答案1

使用 dvisvgm

如果我正确理解了您链接的有关 Blender 的帖子中的问题,您根本不关心字体,而只是对获取描述数学方程的 SVG 路径感兴趣。在这种情况下,您可以使用dvisvgm

test.tex假设你有如下内容的文件

\documentclass{standalone}
\begin{document}
$x^2$
\end{document}

然后你可以使用以下方法将其转换为 SVG

latex test.tex
dvisvgm --no-fonts test

--no-fonts选项指示dvisvgm将文本转换为 SVG 路径。您将无法从生成的 SVG 中复制和粘贴,但无论如何您只想在动画中渲染公式。

这是最终的 SVG。

<?xml version='1.0' encoding='UTF-8'?>
<!-- This file was generated by dvisvgm 2.3.5 -->
<svg height='8.109622pt' version='1.1' viewBox='-72.000004 -72.000007 9.665173 8.109622' width='9.665173pt' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'>
<defs>
<path d='M3.521793 -1.26924H3.284682C3.263761 -1.115816 3.194022 -0.704359 3.103362 -0.63462C3.047572 -0.592777 2.510585 -0.592777 2.412951 -0.592777H1.129763C1.862017 -1.241345 2.106102 -1.436613 2.524533 -1.764384C3.040598 -2.175841 3.521793 -2.608219 3.521793 -3.270735C3.521793 -4.11457 2.782565 -4.630635 1.889913 -4.630635C1.025156 -4.630635 0.439352 -4.02391 0.439352 -3.382316C0.439352 -3.02665 0.739228 -2.991781 0.808966 -2.991781C0.976339 -2.991781 1.17858 -3.110336 1.17858 -3.361395C1.17858 -3.486924 1.129763 -3.731009 0.767123 -3.731009C0.983313 -4.226152 1.457534 -4.379577 1.785305 -4.379577C2.48269 -4.379577 2.84533 -3.835616 2.84533 -3.270735C2.84533 -2.66401 2.412951 -2.182814 2.189788 -1.931756L0.509091 -0.27198C0.439352 -0.209215 0.439352 -0.195268 0.439352 0H3.312578L3.521793 -1.26924Z' id='g1-50'/>
<path d='M3.327522 -3.008717C3.387298 -3.267746 3.616438 -4.184309 4.313823 -4.184309C4.363636 -4.184309 4.60274 -4.184309 4.811955 -4.054795C4.533001 -4.004981 4.333748 -3.755915 4.333748 -3.516812C4.333748 -3.35741 4.443337 -3.16812 4.712329 -3.16812C4.931507 -3.16812 5.250311 -3.347447 5.250311 -3.745953C5.250311 -4.26401 4.662516 -4.403487 4.323786 -4.403487C3.745953 -4.403487 3.39726 -3.875467 3.277709 -3.646326C3.028643 -4.303861 2.49066 -4.403487 2.201743 -4.403487C1.165629 -4.403487 0.597758 -3.118306 0.597758 -2.86924C0.597758 -2.769614 0.697385 -2.769614 0.71731 -2.769614C0.797011 -2.769614 0.826899 -2.789539 0.846824 -2.879203C1.185554 -3.935243 1.843088 -4.184309 2.181818 -4.184309C2.371108 -4.184309 2.719801 -4.094645 2.719801 -3.516812C2.719801 -3.20797 2.550436 -2.540473 2.181818 -1.145704C2.022416 -0.52802 1.673724 -0.109589 1.235367 -0.109589C1.175592 -0.109589 0.946451 -0.109589 0.737235 -0.239103C0.986301 -0.288917 1.205479 -0.498132 1.205479 -0.777086C1.205479 -1.046077 0.986301 -1.125778 0.836862 -1.125778C0.537983 -1.125778 0.288917 -0.86675 0.288917 -0.547945C0.288917 -0.089664 0.787049 0.109589 1.225405 0.109589C1.882939 0.109589 2.241594 -0.587796 2.271482 -0.647572C2.391034 -0.278954 2.749689 0.109589 3.347447 0.109589C4.373599 0.109589 4.941469 -1.175592 4.941469 -1.424658C4.941469 -1.524284 4.851806 -1.524284 4.821918 -1.524284C4.732254 -1.524284 4.712329 -1.484433 4.692403 -1.414695C4.363636 -0.348692 3.686177 -0.109589 3.367372 -0.109589C2.978829 -0.109589 2.819427 -0.428394 2.819427 -0.767123C2.819427 -0.986301 2.879203 -1.205479 2.988792 -1.643836L3.327522 -3.008717Z' id='g0-120'/>
</defs>
<g id='page1'>
<use x='-72.000004' xlink:href='#g0-120' y='-63.890385'/>
<use x='-66.306072' xlink:href='#g1-50' y='-67.505749'/>
</g>
</svg>

从 LuaTeX 内部调用 dvisvgm

如果您不想dvisvgm手动执行,也可以告诉 LuaTeX 在新的wrapup_run回调中执行它(我认为需要相当新的 LuaTeX ≥ 1.08.0)。只需设置\outputmode=0为强制 LuaTeX 输出 DVI 而不是 PDF,并确保--shell-escape已启用。

\outputmode=0 % write DVI instead of PDF
\documentclass{standalone}
\directlua{
if status.shell_escape \string~= 1 then
    error("dvisvgm requires shell-escape")
end

local function dvisvgm()
    os.execute("dvisvgm --no-fonts \jobname.dvi")
end

% luatexbase does not know the wrapup_run callback yet
luatexbase.callbacktypes.wrapup_run = 1 % simple
luatexbase.add_to_callback("wrapup_run", dvisvgm, "dvisvgm")
}
\begin{document}
$x^2$
\end{document}

回调wrapup_run实际上对于这一点至关重要,因为它被触发输出文件已经关闭,因此我们可以确保从输出文件中读取是安全的,并且没有更多待处理的写入。

通过 LuaTeX FFI 使用 Poppler、Cairo 和 GLib

您还可以使用 FFI 将生成的 PDF 渲染为 SVG,使用 Poppler、Cairo 和 GLib。我已经在源代码中标注了您需要在系统上安装的软件包,以便实现此功能。请记住,Poppler 和 Cairo 不再与 LuaTeX 捆绑在一起,因为从 1.08.0 版开始,PDF 库已切换为 pplib。

目前代码仅限于将生成的 PDF 的第一页转换为 SVG,但可以轻松扩展。我从这个文件中借鉴了很多:

https://github.com/dawbarton/pdf2svg/blob/master/pdf2svg.c

我还将这段代码的 Lua 部分发布在了 GitHub 上

https://gist.github.com/hmenke/9facc3fe5ede9ed46c1838a919f7376f#file-pdf-to-svg-lua

FFI 要求--shell-escape

\documentclass{standalone}
\usepackage{luacode}
\begin{luacode}
local ffi = require"ffi"

ffi.cdef[[
// Cairo types
typedef struct _cairo_surface cairo_surface_t;
typedef int cairo_status_t;
typedef struct _cairo cairo_t;

// Poppler types
typedef struct _PopplerPage PopplerPage;
typedef struct _PopplerDocument PopplerDocument;

// Glib types
typedef struct {
    int domain;
    int code;
    char *message;
} GError;

// Cairo functions
cairo_surface_t * cairo_svg_surface_create(const char *filename,
                                           double width_in_points,
                                           double height_in_points);
cairo_status_t cairo_surface_status(cairo_surface_t *surface);
cairo_t * cairo_create(cairo_surface_t *);
cairo_status_t cairo_status(cairo_t *cr);
void cairo_show_page(cairo_t *cr);
void cairo_destroy(cairo_t *);
void cairo_surface_destroy(cairo_surface_t *);

// Poppler functions
PopplerDocument * poppler_document_new_from_file(const char *uri,
                                                 const char *password,
                                                 GError **error);
int poppler_document_get_n_pages(PopplerDocument *document);
PopplerPage * poppler_document_get_page(PopplerDocument *document,
                                        int index);
void poppler_page_get_size(PopplerPage *page,
                           double *width,
                           double *height);
void poppler_page_render_for_printing(PopplerPage *page,
                                      cairo_t *cairo);

// Glib functions
char * g_get_current_dir(void);
char * g_build_filename(const char *first_element, ...);
char * g_filename_to_uri(const char *filename,
                         const char *hostname,
                         GError **error);
void g_free(void *);
void g_object_unref(void *);
]]

local POPPLER = ffi.load("poppler-glib") -- libpoppler-glib-dev
local CAIRO = ffi.load("cairo")          -- libcairo2-dev
local GLIB = ffi.load("gobject-2.0")     -- libglib2.0-dev

local CAIRO_STATUS_SUCCESS = 0

local errmessage = tex.error

local function page_to_svg(pdfname, svgname, idx)
    -- Allocate an error object
    local err = ffi.new("GError*[1]", ffi.NULL)

    -- Convert relative path to absolute path
    local currentdir = GLIB.g_get_current_dir()
    local absolutefilename = GLIB.g_build_filename(currentdir, pdfname, ffi.NULL)
    GLIB.g_free(currentdir)

    -- Convert path to URI
    local filename_uri = GLIB.g_filename_to_uri(absolutefilename, ffi.NULL, err)
    GLIB.g_free(absolutefilename)
    if filename_uri == ffi.NULL then
        errmessage(ffi.string(err[0].message))
    end

    -- Open PDF file
    local pdffile = POPPLER.poppler_document_new_from_file(filename_uri, ffi.NULL, err)
    GLIB.g_free(filename_uri)
    if pdffile == ffi.NULL then
        errmessage(ffi.string(err[0].message))
    end

    -- Test page count and get page
    local pagecount = POPPLER.poppler_document_get_n_pages(pdffile)
    if not (idx < pagecount) then
        errmessage("Page out of range (index " .. idx .. " >= " .. pagecount .. " pages)")
    end
    local page = POPPLER.poppler_document_get_page(pdffile, idx)

    -- Get page dimensions
    local width = ffi.new("double[1]")
    local height = ffi.new("double[1]")
    POPPLER.poppler_page_get_size(page, width, height)

    -- Open Cairo surface
    local surface = CAIRO.cairo_svg_surface_create(svgname, width[0], height[0]);
    local status = CAIRO.cairo_surface_status(surface)
    if status ~= CAIRO_STATUS_SUCCESS then
        errmessage("Cairo surface error (code " .. status .. ")")
    end

    -- Open Cairo context
    local cr = CAIRO.cairo_create(surface)
    local status = CAIRO.cairo_status(cr)
    if status ~= CAIRO_STATUS_SUCCESS then
        errmessage("Cairo error (code " .. status .. ")")
    end

    -- Render PDF in Cairo context
    POPPLER.poppler_page_render_for_printing(page, cr)
    CAIRO.cairo_show_page(cr)

    -- Clean up
    if (cr ~= ffi.NULL) then CAIRO.cairo_destroy(cr) end
    if (surface ~= ffi.NULL) then CAIRO.cairo_surface_destroy(surface) end
    if (page ~= ffi.NULL) then GLIB.g_object_unref(page) end
    if (pdffile ~= ffi.NULL) then GLIB.g_object_unref(pdffile) end
    if (err[0] ~= ffi.NULL) then GLIB.g_object_unref(err[0]) end
end

local function wrapup()
    page_to_svg("\jobname.pdf", "\jobname.svg", 0)
end

-- luatexbase does not know the wrapup_run callback yet
luatexbase.callbacktypes.wrapup_run = 1 -- simple
luatexbase.add_to_callback("wrapup_run", wrapup, "wrapup")
\end{luacode}
\begin{document}
$x^2$
\end{document}

使用 MetaPost

如果您只想转换单个方程式并且不需要大量的 TeX 算法,那么您可以使用 MetaPost 并将输出格式设置为 SVG。

prologues := 3;
outputformat := "svg";
outputtemplate := "%j%c.svg";

beginfig(1)
  label(btex $x^2$ etex, origin);
endfig;
end

运行mpost test.mp生成test1.svg以下内容:

<?xml version="1.0"?>
<!-- Created by MetaPost 2.00 on 2019.02.14:1518 -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="10.163300" height="8.109604" viewBox="0 0 10.163300 8.109604">
<!-- Original BoundingBox: -5.081650 -4.054810 5.081650 4.054794 -->
  <defs>
    <g transform="scale(0.009963,0.009963)" id="GLYPHcmmi10_120">
      <path style="fill-rule: evenodd;" d="M334.000000 -302.000000C340.000000 -328.000000,363.000000 -420.000000,433.000000 -420.000000C438.000000 -420.000000,462.000000 -420.000000,483.000000 -407.000000C455.000000 -402.000000,435.000000 -377.000000,435.000000 -353.000000C435.000000 -337.000000,446.000000 -318.000000,473.000000 -318.000000C495.000000 -318.000000,527.000000 -336.000000,527.000000 -376.000000C527.000000 -428.000000,468.000000 -442.000000,434.000000 -442.000000C376.000000 -442.000000,341.000000 -389.000000,329.000000 -366.000000C304.000000 -432.000000,250.000000 -442.000000,221.000000 -442.000000C117.000000 -442.000000,60.000000 -313.000000,60.000000 -288.000000C60.000000 -278.000000,70.000000 -278.000000,72.000000 -278.000000C80.000000 -278.000000,83.000000 -280.000000,85.000000 -289.000000C119.000000 -395.000000,185.000000 -420.000000,219.000000 -420.000000C238.000000 -420.000000,273.000000 -411.000000,273.000000 -353.000000C273.000000 -322.000000,256.000000 -255.000000,219.000000 -115.000000C203.000000 -53.000000,168.000000 -11.000000,124.000000 -11.000000C118.000000 -11.000000,95.000000 -11.000000,74.000000 -24.000000C99.000000 -29.000000,121.000000 -50.000000,121.000000 -78.000000C121.000000 -105.000000,99.000000 -113.000000,84.000000 -113.000000C54.000000 -113.000000,29.000000 -87.000000,29.000000 -55.000000C29.000000 -9.000000,79.000000 11.000000,123.000000 11.000000C189.000000 11.000000,225.000000 -59.000000,228.000000 -65.000000C240.000000 -28.000000,276.000000 11.000000,336.000000 11.000000C439.000000 11.000000,496.000000 -118.000000,496.000000 -143.000000C496.000000 -153.000000,487.000000 -153.000000,484.000000 -153.000000C475.000000 -153.000000,473.000000 -149.000000,471.000000 -142.000000C438.000000 -35.000000,370.000000 -11.000000,338.000000 -11.000000C299.000000 -11.000000,283.000000 -43.000000,283.000000 -77.000000C283.000000 -99.000000,289.000000 -121.000000,300.000000 -165.000000"></path>
    </g>
    <g transform="scale(0.006974,0.006974)" id="GLYPHcmr7_50">
      <path style="fill-rule: evenodd;" d="M505.000000 -182.000000L471.000000 -182.000000C468.000000 -160.000000,458.000000 -101.000000,445.000000 -91.000000C437.000000 -85.000000,360.000000 -85.000000,346.000000 -85.000000L162.000000 -85.000000C267.000000 -178.000000,302.000000 -206.000000,362.000000 -253.000000C436.000000 -312.000000,505.000000 -374.000000,505.000000 -469.000000C505.000000 -590.000000,399.000000 -664.000000,271.000000 -664.000000C147.000000 -664.000000,63.000000 -577.000000,63.000000 -485.000000C63.000000 -434.000000,106.000000 -429.000000,116.000000 -429.000000C140.000000 -429.000000,169.000000 -446.000000,169.000000 -482.000000C169.000000 -500.000000,162.000000 -535.000000,110.000000 -535.000000C141.000000 -606.000000,209.000000 -628.000000,256.000000 -628.000000C356.000000 -628.000000,408.000000 -550.000000,408.000000 -469.000000C408.000000 -382.000000,346.000000 -313.000000,314.000000 -277.000000L73.000000 -39.000000C63.000000 -30.000000,63.000000 -28.000000,63.000000 -0.000000L475.000000 -0.000000"></path>
    </g>
  </defs>
  <g transform="translate(-0.081650 8.054810)" style="fill: rgb(0.000000%,0.000000%,0.000000%);">
    <use xlink:href="#GLYPHcmmi10_120"></use>
  </g>
  <g transform="translate(5.612244 4.439407)" style="fill: rgb(0.000000%,0.000000%,0.000000%);">
    <use xlink:href="#GLYPHcmr7_50"></use>
  </g>
</svg>

相关内容