svg output and LuaTeX












11















Trying to find some ways to automate generation of svg output for TeX math formulas, I thought about two possibilities with luatex.




  1. Use lua inside a callback to parse the node tree and output svg (someone had the very idea some time ago).


  2. Use luatex itself to generate svg instead of pdf. As far as I can see, cairo library is included in luatex source code and cairo can output svg.



My questions are:




  1. What is the current status of svg output support with luatex (the program itself or "third parties" projects).


  2. Could I use mplib's metapost svg output for that matter? EDIT It seems that mplib does not support svg anymore.











share|improve this question

















This question had a bounty worth +200
reputation from cjorssen that ended 16 hours ago. Grace period ends in 7 hours


This question has not received enough attention.


In addition to ideas in my question or in comments, maybe ffi can be used with luatex?












  • 2





    it would be interesting generally to get xml out of the luatex back end (context is ahead of latex in this currently I suspect) but for this particular use is there a problem with using latex +dvisvgm or are you just wanting to experiment with other alternatives?

    – David Carlisle
    Apr 3 '17 at 15:36











  • @DavidCarlisle The answer given on the blender site is fine. I was just wondering if the conversion could be done at luatex level, not requiring other dependencies such as pdftocairo or dvisvgm. In addition luatex could label svg path according to its mathematical meaning.

    – cjorssen
    Apr 3 '17 at 15:41








  • 1





    Yes I suspect it could: I had a quick look for what context can do in this area, but searching for "context" is even more useless than searching for "latex" why can't we pick sensible program names:(

    – David Carlisle
    Apr 3 '17 at 15:44











  • @DavidCarlisle: I normally restrict context searches to the context garden and the mailing list: mail-archive.com/… (it doesn't look as if there a simple and obvious svg export).

    – Ulrike Fischer
    Apr 3 '17 at 16:21






  • 1





    I guess the main problem with this would be how to include fonts - I don't think you can get actual font data from Lua callbacks.

    – michal.h21
    Apr 3 '17 at 16:26
















11















Trying to find some ways to automate generation of svg output for TeX math formulas, I thought about two possibilities with luatex.




  1. Use lua inside a callback to parse the node tree and output svg (someone had the very idea some time ago).


  2. Use luatex itself to generate svg instead of pdf. As far as I can see, cairo library is included in luatex source code and cairo can output svg.



My questions are:




  1. What is the current status of svg output support with luatex (the program itself or "third parties" projects).


  2. Could I use mplib's metapost svg output for that matter? EDIT It seems that mplib does not support svg anymore.











share|improve this question

















This question had a bounty worth +200
reputation from cjorssen that ended 16 hours ago. Grace period ends in 7 hours


This question has not received enough attention.


In addition to ideas in my question or in comments, maybe ffi can be used with luatex?












  • 2





    it would be interesting generally to get xml out of the luatex back end (context is ahead of latex in this currently I suspect) but for this particular use is there a problem with using latex +dvisvgm or are you just wanting to experiment with other alternatives?

    – David Carlisle
    Apr 3 '17 at 15:36











  • @DavidCarlisle The answer given on the blender site is fine. I was just wondering if the conversion could be done at luatex level, not requiring other dependencies such as pdftocairo or dvisvgm. In addition luatex could label svg path according to its mathematical meaning.

    – cjorssen
    Apr 3 '17 at 15:41








  • 1





    Yes I suspect it could: I had a quick look for what context can do in this area, but searching for "context" is even more useless than searching for "latex" why can't we pick sensible program names:(

    – David Carlisle
    Apr 3 '17 at 15:44











  • @DavidCarlisle: I normally restrict context searches to the context garden and the mailing list: mail-archive.com/… (it doesn't look as if there a simple and obvious svg export).

    – Ulrike Fischer
    Apr 3 '17 at 16:21






  • 1





    I guess the main problem with this would be how to include fonts - I don't think you can get actual font data from Lua callbacks.

    – michal.h21
    Apr 3 '17 at 16:26














11












11








11








Trying to find some ways to automate generation of svg output for TeX math formulas, I thought about two possibilities with luatex.




  1. Use lua inside a callback to parse the node tree and output svg (someone had the very idea some time ago).


  2. Use luatex itself to generate svg instead of pdf. As far as I can see, cairo library is included in luatex source code and cairo can output svg.



My questions are:




  1. What is the current status of svg output support with luatex (the program itself or "third parties" projects).


  2. Could I use mplib's metapost svg output for that matter? EDIT It seems that mplib does not support svg anymore.











share|improve this question
















Trying to find some ways to automate generation of svg output for TeX math formulas, I thought about two possibilities with luatex.




  1. Use lua inside a callback to parse the node tree and output svg (someone had the very idea some time ago).


  2. Use luatex itself to generate svg instead of pdf. As far as I can see, cairo library is included in luatex source code and cairo can output svg.



My questions are:




  1. What is the current status of svg output support with luatex (the program itself or "third parties" projects).


  2. Could I use mplib's metapost svg output for that matter? EDIT It seems that mplib does not support svg anymore.








luatex svg






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Apr 13 '17 at 12:57









Community

1




1










asked Apr 3 '17 at 15:19









cjorssencjorssen

5,990225104




5,990225104






This question had a bounty worth +200
reputation from cjorssen that ended 16 hours ago. Grace period ends in 7 hours


This question has not received enough attention.


In addition to ideas in my question or in comments, maybe ffi can be used with luatex?








This question had a bounty worth +200
reputation from cjorssen that ended 16 hours ago. Grace period ends in 7 hours


This question has not received enough attention.


In addition to ideas in my question or in comments, maybe ffi can be used with luatex?










  • 2





    it would be interesting generally to get xml out of the luatex back end (context is ahead of latex in this currently I suspect) but for this particular use is there a problem with using latex +dvisvgm or are you just wanting to experiment with other alternatives?

    – David Carlisle
    Apr 3 '17 at 15:36











  • @DavidCarlisle The answer given on the blender site is fine. I was just wondering if the conversion could be done at luatex level, not requiring other dependencies such as pdftocairo or dvisvgm. In addition luatex could label svg path according to its mathematical meaning.

    – cjorssen
    Apr 3 '17 at 15:41








  • 1





    Yes I suspect it could: I had a quick look for what context can do in this area, but searching for "context" is even more useless than searching for "latex" why can't we pick sensible program names:(

    – David Carlisle
    Apr 3 '17 at 15:44











  • @DavidCarlisle: I normally restrict context searches to the context garden and the mailing list: mail-archive.com/… (it doesn't look as if there a simple and obvious svg export).

    – Ulrike Fischer
    Apr 3 '17 at 16:21






  • 1





    I guess the main problem with this would be how to include fonts - I don't think you can get actual font data from Lua callbacks.

    – michal.h21
    Apr 3 '17 at 16:26














  • 2





    it would be interesting generally to get xml out of the luatex back end (context is ahead of latex in this currently I suspect) but for this particular use is there a problem with using latex +dvisvgm or are you just wanting to experiment with other alternatives?

    – David Carlisle
    Apr 3 '17 at 15:36











  • @DavidCarlisle The answer given on the blender site is fine. I was just wondering if the conversion could be done at luatex level, not requiring other dependencies such as pdftocairo or dvisvgm. In addition luatex could label svg path according to its mathematical meaning.

    – cjorssen
    Apr 3 '17 at 15:41








  • 1





    Yes I suspect it could: I had a quick look for what context can do in this area, but searching for "context" is even more useless than searching for "latex" why can't we pick sensible program names:(

    – David Carlisle
    Apr 3 '17 at 15:44











  • @DavidCarlisle: I normally restrict context searches to the context garden and the mailing list: mail-archive.com/… (it doesn't look as if there a simple and obvious svg export).

    – Ulrike Fischer
    Apr 3 '17 at 16:21






  • 1





    I guess the main problem with this would be how to include fonts - I don't think you can get actual font data from Lua callbacks.

    – michal.h21
    Apr 3 '17 at 16:26








2




2





it would be interesting generally to get xml out of the luatex back end (context is ahead of latex in this currently I suspect) but for this particular use is there a problem with using latex +dvisvgm or are you just wanting to experiment with other alternatives?

– David Carlisle
Apr 3 '17 at 15:36





it would be interesting generally to get xml out of the luatex back end (context is ahead of latex in this currently I suspect) but for this particular use is there a problem with using latex +dvisvgm or are you just wanting to experiment with other alternatives?

– David Carlisle
Apr 3 '17 at 15:36













@DavidCarlisle The answer given on the blender site is fine. I was just wondering if the conversion could be done at luatex level, not requiring other dependencies such as pdftocairo or dvisvgm. In addition luatex could label svg path according to its mathematical meaning.

– cjorssen
Apr 3 '17 at 15:41







@DavidCarlisle The answer given on the blender site is fine. I was just wondering if the conversion could be done at luatex level, not requiring other dependencies such as pdftocairo or dvisvgm. In addition luatex could label svg path according to its mathematical meaning.

– cjorssen
Apr 3 '17 at 15:41






1




1





Yes I suspect it could: I had a quick look for what context can do in this area, but searching for "context" is even more useless than searching for "latex" why can't we pick sensible program names:(

– David Carlisle
Apr 3 '17 at 15:44





Yes I suspect it could: I had a quick look for what context can do in this area, but searching for "context" is even more useless than searching for "latex" why can't we pick sensible program names:(

– David Carlisle
Apr 3 '17 at 15:44













@DavidCarlisle: I normally restrict context searches to the context garden and the mailing list: mail-archive.com/… (it doesn't look as if there a simple and obvious svg export).

– Ulrike Fischer
Apr 3 '17 at 16:21





@DavidCarlisle: I normally restrict context searches to the context garden and the mailing list: mail-archive.com/… (it doesn't look as if there a simple and obvious svg export).

– Ulrike Fischer
Apr 3 '17 at 16:21




1




1





I guess the main problem with this would be how to include fonts - I don't think you can get actual font data from Lua callbacks.

– michal.h21
Apr 3 '17 at 16:26





I guess the main problem with this would be how to include fonts - I don't think you can get actual font data from Lua callbacks.

– michal.h21
Apr 3 '17 at 16:26










1 Answer
1






active

oldest

votes


















3














Use dvisvgm



If I understand the question correctly from the thread you linked about Blender, you don't care about fonts at all an are simply interested in getting SVG paths describing the math equations. In this case you can use dvisvgm.



Suppose you have the file test.tex with the contents



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


Then you can convert this to SVG using



latex test.tex
dvisvgm --no-fonts test


The --no-fonts option tells dvisvgm to convert text to SVG paths. You will not be able to copy and paste from the resulting SVG, but you only want to render the formula in a animation anyway.



This is the resulting 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>




Call dvisvgm from within LuaTeX



If you don't want to execute dvisvgm manually, you can also tell LuaTeX to execute it within the new wrapup_run callback (requires fairly new LuaTeX ≥ 1.08.0, I think). Just set outputmode=0 to force LuaTeX to output DVI instead of PDF and make sure --shell-escape is enabled.



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}


The wrapup_run callback is actually essential for this because it is triggered after the output file has already been closed, so we can be sure that it is safe to read from the output file and there are no more pending writes.



Use Poppler, Cairo, and GLib via LuaTeX FFI



You could also use the FFI to render the resulting PDF into an SVG using Poppler, Cairo, and GLib. I have annotated the source with the packages you need to install on your system for this to work. Keep in mind that Poppler and Cairo are no longer bundled with LuaTeX, because with version 1.08.0 the PDF library was switched to pplib.



Currently the code is limited to converting the first page of the resulting PDF to SVG but that can be trivially extended. I borrowed a lot from this file:




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




I have also made the Lua part of this code available on GitHub




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




FFI requires --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}


Use MetaPost



If you only want to convert a single equation and don't require a lot of TeX's algorithms, then you could just use MetaPost and set the output format to SVG.



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

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


Running mpost test.mp generates test1.svg with the following content:





<?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>







share|improve this answer


























  • Thanks Henri (+1). The idea I had in mind was to avoid any other tools than luatex and hoping that luatex could be included to blender as a library.

    – cjorssen
    yesterday











  • @cjorssen The only way to avoid other tools would be to have an svg output driver included LuaTeX (in addition to the DVI and PDF driver). However, judging from the complexity of dvisvgm, this is not an easy task.

    – Henri Menke
    yesterday











  • I see. So no way to use some ffi magics or your other hack of 'shipout', even for a subset of a real page (eg one formula)?

    – cjorssen
    22 hours ago











  • @cjorssen It might be possible but that would be an unreasonable amount of work. Especially when there is a solution which involves only two program calls.

    – Henri Menke
    21 hours ago











  • @cjorssen If your main problem is the two-stage command, you can also tell LuaTeX to execute dvisvgm at the end of the run, see updated answer.

    – Henri Menke
    8 hours ago













Your Answer








StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "85"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});

function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});


}
});














draft saved

draft discarded


















StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2ftex.stackexchange.com%2fquestions%2f361930%2fsvg-output-and-luatex%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown

























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes









3














Use dvisvgm



If I understand the question correctly from the thread you linked about Blender, you don't care about fonts at all an are simply interested in getting SVG paths describing the math equations. In this case you can use dvisvgm.



Suppose you have the file test.tex with the contents



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


Then you can convert this to SVG using



latex test.tex
dvisvgm --no-fonts test


The --no-fonts option tells dvisvgm to convert text to SVG paths. You will not be able to copy and paste from the resulting SVG, but you only want to render the formula in a animation anyway.



This is the resulting 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>




Call dvisvgm from within LuaTeX



If you don't want to execute dvisvgm manually, you can also tell LuaTeX to execute it within the new wrapup_run callback (requires fairly new LuaTeX ≥ 1.08.0, I think). Just set outputmode=0 to force LuaTeX to output DVI instead of PDF and make sure --shell-escape is enabled.



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}


The wrapup_run callback is actually essential for this because it is triggered after the output file has already been closed, so we can be sure that it is safe to read from the output file and there are no more pending writes.



Use Poppler, Cairo, and GLib via LuaTeX FFI



You could also use the FFI to render the resulting PDF into an SVG using Poppler, Cairo, and GLib. I have annotated the source with the packages you need to install on your system for this to work. Keep in mind that Poppler and Cairo are no longer bundled with LuaTeX, because with version 1.08.0 the PDF library was switched to pplib.



Currently the code is limited to converting the first page of the resulting PDF to SVG but that can be trivially extended. I borrowed a lot from this file:




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




I have also made the Lua part of this code available on GitHub




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




FFI requires --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}


Use MetaPost



If you only want to convert a single equation and don't require a lot of TeX's algorithms, then you could just use MetaPost and set the output format to SVG.



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

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


Running mpost test.mp generates test1.svg with the following content:





<?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>







share|improve this answer


























  • Thanks Henri (+1). The idea I had in mind was to avoid any other tools than luatex and hoping that luatex could be included to blender as a library.

    – cjorssen
    yesterday











  • @cjorssen The only way to avoid other tools would be to have an svg output driver included LuaTeX (in addition to the DVI and PDF driver). However, judging from the complexity of dvisvgm, this is not an easy task.

    – Henri Menke
    yesterday











  • I see. So no way to use some ffi magics or your other hack of 'shipout', even for a subset of a real page (eg one formula)?

    – cjorssen
    22 hours ago











  • @cjorssen It might be possible but that would be an unreasonable amount of work. Especially when there is a solution which involves only two program calls.

    – Henri Menke
    21 hours ago











  • @cjorssen If your main problem is the two-stage command, you can also tell LuaTeX to execute dvisvgm at the end of the run, see updated answer.

    – Henri Menke
    8 hours ago


















3














Use dvisvgm



If I understand the question correctly from the thread you linked about Blender, you don't care about fonts at all an are simply interested in getting SVG paths describing the math equations. In this case you can use dvisvgm.



Suppose you have the file test.tex with the contents



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


Then you can convert this to SVG using



latex test.tex
dvisvgm --no-fonts test


The --no-fonts option tells dvisvgm to convert text to SVG paths. You will not be able to copy and paste from the resulting SVG, but you only want to render the formula in a animation anyway.



This is the resulting 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>




Call dvisvgm from within LuaTeX



If you don't want to execute dvisvgm manually, you can also tell LuaTeX to execute it within the new wrapup_run callback (requires fairly new LuaTeX ≥ 1.08.0, I think). Just set outputmode=0 to force LuaTeX to output DVI instead of PDF and make sure --shell-escape is enabled.



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}


The wrapup_run callback is actually essential for this because it is triggered after the output file has already been closed, so we can be sure that it is safe to read from the output file and there are no more pending writes.



Use Poppler, Cairo, and GLib via LuaTeX FFI



You could also use the FFI to render the resulting PDF into an SVG using Poppler, Cairo, and GLib. I have annotated the source with the packages you need to install on your system for this to work. Keep in mind that Poppler and Cairo are no longer bundled with LuaTeX, because with version 1.08.0 the PDF library was switched to pplib.



Currently the code is limited to converting the first page of the resulting PDF to SVG but that can be trivially extended. I borrowed a lot from this file:




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




I have also made the Lua part of this code available on GitHub




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




FFI requires --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}


Use MetaPost



If you only want to convert a single equation and don't require a lot of TeX's algorithms, then you could just use MetaPost and set the output format to SVG.



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

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


Running mpost test.mp generates test1.svg with the following content:





<?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>







share|improve this answer


























  • Thanks Henri (+1). The idea I had in mind was to avoid any other tools than luatex and hoping that luatex could be included to blender as a library.

    – cjorssen
    yesterday











  • @cjorssen The only way to avoid other tools would be to have an svg output driver included LuaTeX (in addition to the DVI and PDF driver). However, judging from the complexity of dvisvgm, this is not an easy task.

    – Henri Menke
    yesterday











  • I see. So no way to use some ffi magics or your other hack of 'shipout', even for a subset of a real page (eg one formula)?

    – cjorssen
    22 hours ago











  • @cjorssen It might be possible but that would be an unreasonable amount of work. Especially when there is a solution which involves only two program calls.

    – Henri Menke
    21 hours ago











  • @cjorssen If your main problem is the two-stage command, you can also tell LuaTeX to execute dvisvgm at the end of the run, see updated answer.

    – Henri Menke
    8 hours ago
















3












3








3







Use dvisvgm



If I understand the question correctly from the thread you linked about Blender, you don't care about fonts at all an are simply interested in getting SVG paths describing the math equations. In this case you can use dvisvgm.



Suppose you have the file test.tex with the contents



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


Then you can convert this to SVG using



latex test.tex
dvisvgm --no-fonts test


The --no-fonts option tells dvisvgm to convert text to SVG paths. You will not be able to copy and paste from the resulting SVG, but you only want to render the formula in a animation anyway.



This is the resulting 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>




Call dvisvgm from within LuaTeX



If you don't want to execute dvisvgm manually, you can also tell LuaTeX to execute it within the new wrapup_run callback (requires fairly new LuaTeX ≥ 1.08.0, I think). Just set outputmode=0 to force LuaTeX to output DVI instead of PDF and make sure --shell-escape is enabled.



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}


The wrapup_run callback is actually essential for this because it is triggered after the output file has already been closed, so we can be sure that it is safe to read from the output file and there are no more pending writes.



Use Poppler, Cairo, and GLib via LuaTeX FFI



You could also use the FFI to render the resulting PDF into an SVG using Poppler, Cairo, and GLib. I have annotated the source with the packages you need to install on your system for this to work. Keep in mind that Poppler and Cairo are no longer bundled with LuaTeX, because with version 1.08.0 the PDF library was switched to pplib.



Currently the code is limited to converting the first page of the resulting PDF to SVG but that can be trivially extended. I borrowed a lot from this file:




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




I have also made the Lua part of this code available on GitHub




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




FFI requires --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}


Use MetaPost



If you only want to convert a single equation and don't require a lot of TeX's algorithms, then you could just use MetaPost and set the output format to SVG.



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

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


Running mpost test.mp generates test1.svg with the following content:





<?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>







share|improve this answer















Use dvisvgm



If I understand the question correctly from the thread you linked about Blender, you don't care about fonts at all an are simply interested in getting SVG paths describing the math equations. In this case you can use dvisvgm.



Suppose you have the file test.tex with the contents



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


Then you can convert this to SVG using



latex test.tex
dvisvgm --no-fonts test


The --no-fonts option tells dvisvgm to convert text to SVG paths. You will not be able to copy and paste from the resulting SVG, but you only want to render the formula in a animation anyway.



This is the resulting 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>




Call dvisvgm from within LuaTeX



If you don't want to execute dvisvgm manually, you can also tell LuaTeX to execute it within the new wrapup_run callback (requires fairly new LuaTeX ≥ 1.08.0, I think). Just set outputmode=0 to force LuaTeX to output DVI instead of PDF and make sure --shell-escape is enabled.



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}


The wrapup_run callback is actually essential for this because it is triggered after the output file has already been closed, so we can be sure that it is safe to read from the output file and there are no more pending writes.



Use Poppler, Cairo, and GLib via LuaTeX FFI



You could also use the FFI to render the resulting PDF into an SVG using Poppler, Cairo, and GLib. I have annotated the source with the packages you need to install on your system for this to work. Keep in mind that Poppler and Cairo are no longer bundled with LuaTeX, because with version 1.08.0 the PDF library was switched to pplib.



Currently the code is limited to converting the first page of the resulting PDF to SVG but that can be trivially extended. I borrowed a lot from this file:




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




I have also made the Lua part of this code available on GitHub




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




FFI requires --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}


Use MetaPost



If you only want to convert a single equation and don't require a lot of TeX's algorithms, then you could just use MetaPost and set the output format to SVG.



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

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


Running mpost test.mp generates test1.svg with the following content:





<?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>








share|improve this answer














share|improve this answer



share|improve this answer








edited 1 hour ago

























answered 2 days ago









Henri MenkeHenri Menke

73.9k8162274




73.9k8162274













  • Thanks Henri (+1). The idea I had in mind was to avoid any other tools than luatex and hoping that luatex could be included to blender as a library.

    – cjorssen
    yesterday











  • @cjorssen The only way to avoid other tools would be to have an svg output driver included LuaTeX (in addition to the DVI and PDF driver). However, judging from the complexity of dvisvgm, this is not an easy task.

    – Henri Menke
    yesterday











  • I see. So no way to use some ffi magics or your other hack of 'shipout', even for a subset of a real page (eg one formula)?

    – cjorssen
    22 hours ago











  • @cjorssen It might be possible but that would be an unreasonable amount of work. Especially when there is a solution which involves only two program calls.

    – Henri Menke
    21 hours ago











  • @cjorssen If your main problem is the two-stage command, you can also tell LuaTeX to execute dvisvgm at the end of the run, see updated answer.

    – Henri Menke
    8 hours ago





















  • Thanks Henri (+1). The idea I had in mind was to avoid any other tools than luatex and hoping that luatex could be included to blender as a library.

    – cjorssen
    yesterday











  • @cjorssen The only way to avoid other tools would be to have an svg output driver included LuaTeX (in addition to the DVI and PDF driver). However, judging from the complexity of dvisvgm, this is not an easy task.

    – Henri Menke
    yesterday











  • I see. So no way to use some ffi magics or your other hack of 'shipout', even for a subset of a real page (eg one formula)?

    – cjorssen
    22 hours ago











  • @cjorssen It might be possible but that would be an unreasonable amount of work. Especially when there is a solution which involves only two program calls.

    – Henri Menke
    21 hours ago











  • @cjorssen If your main problem is the two-stage command, you can also tell LuaTeX to execute dvisvgm at the end of the run, see updated answer.

    – Henri Menke
    8 hours ago



















Thanks Henri (+1). The idea I had in mind was to avoid any other tools than luatex and hoping that luatex could be included to blender as a library.

– cjorssen
yesterday





Thanks Henri (+1). The idea I had in mind was to avoid any other tools than luatex and hoping that luatex could be included to blender as a library.

– cjorssen
yesterday













@cjorssen The only way to avoid other tools would be to have an svg output driver included LuaTeX (in addition to the DVI and PDF driver). However, judging from the complexity of dvisvgm, this is not an easy task.

– Henri Menke
yesterday





@cjorssen The only way to avoid other tools would be to have an svg output driver included LuaTeX (in addition to the DVI and PDF driver). However, judging from the complexity of dvisvgm, this is not an easy task.

– Henri Menke
yesterday













I see. So no way to use some ffi magics or your other hack of 'shipout', even for a subset of a real page (eg one formula)?

– cjorssen
22 hours ago





I see. So no way to use some ffi magics or your other hack of 'shipout', even for a subset of a real page (eg one formula)?

– cjorssen
22 hours ago













@cjorssen It might be possible but that would be an unreasonable amount of work. Especially when there is a solution which involves only two program calls.

– Henri Menke
21 hours ago





@cjorssen It might be possible but that would be an unreasonable amount of work. Especially when there is a solution which involves only two program calls.

– Henri Menke
21 hours ago













@cjorssen If your main problem is the two-stage command, you can also tell LuaTeX to execute dvisvgm at the end of the run, see updated answer.

– Henri Menke
8 hours ago







@cjorssen If your main problem is the two-stage command, you can also tell LuaTeX to execute dvisvgm at the end of the run, see updated answer.

– Henri Menke
8 hours ago




















draft saved

draft discarded




















































Thanks for contributing an answer to TeX - LaTeX Stack Exchange!


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2ftex.stackexchange.com%2fquestions%2f361930%2fsvg-output-and-luatex%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

mysqli_query(): Empty query in /home/lucindabrummitt/public_html/blog/wp-includes/wp-db.php on line 1924

How to change which sound is reproduced for terminal bell?

Can I use Tabulator js library in my java Spring + Thymeleaf project?