6

I have a LaTeX document with the following code which generates a picture using TiKZ. (Minimum working example here)

\documentclass{article} 
\usepackage{amsmath,stanli}

\begin{document}
Analyse the plane frame shown in the figure by using FEM. Consider $E = 400\    GPa$,  $I = 1 \times 10^{-4}\ m^4$, and $A = 2 \times 10^{-2}\ m^2$. 

\begin{center}
\begin{tikzpicture}
\point{a}{0}{0}
\point{b}{5}{0}
\point{c}{9}{-3}
\point{a1}{2.5}{1.5}
\beam{2}{a}{b}[0][1]
\beam{2}{b}{c}[0][1]
\support{3}{a}[270]
\support{3}{c}
\lineload{1}{a}{b}[1][1]
\dimensioning{1}{a}{b}{-3.5}[$5m$]
\dimensioning{1}{b}{c}{-3.5}[$4m$]
\dimensioning{2}{b}{c}{5}[$3m$]
\notation{1}{a1}{$5 kN/m$}
\end{tikzpicture} 
\end{center} 
\end{document}

If I use the following command

pandoc -s latex.tex -o sample.docx 

the TikZ picture is not embedded into the DOCX file. How to alleviate this problem?

8

Pandoc doesn't know how to handle TikZ environments. However, you can use a Lua filter to teach pandoc. Specifically, one can use the method outlined in the building images with TikZ example. The example assumes the input to be Markdown mixed with LaTeX, so you'll need to perform a few tweaks for it to work with pure LaTeX input.

First, we want pandoc to keep any LaTeX it can't read as-is instead of doing its best to convert it anyway. Do this by adding --from latex+raw_tex to your command.

Next, we want to run the image generator only on LaTeX snippets which look like a tikzpicture environment, and only if we haven't done this conversion yet.

local function file_exists(name)
  local f = io.open(name, 'r')
  if f ~= nil then io.close(f); return true
  else return false end
end

function RawBlock(el)
  -- Don't alter element if it's not a tikzpicture environment
  if not el.text:match'^\\begin{tikzpicture}' then
    return nil
    -- Alternatively, parse the contained LaTeX now:
    -- return pandoc.read(el.text, 'latex').blocks
  end  
  local fname = pandoc.sha1(el.text) .. ".png"
  if not file_exists(fname) then
    tikz2image(el.text, fname)
  end
  return pandoc.Para({pandoc.Image({}, fname)})
end

Finally, we include the actual image-conversion code

--- Create a standalone LaTeX document which contains only the TikZ picture.
--- Convert to png via Imagemagick.
local function tikz2image(src, outfile)
  local tmp = os.tmpname()
  local tmpdir = string.match(tmp, "^(.*[\\/])") or "."
  local f = io.open(tmp .. ".tex", 'w')
  f:write("\\documentclass{standalone}\n")
  -- include all packages needed to compile your images
  f:write("\\usepackage{tikz}\n\\usepackage{stanli}\n")
  f:write("\\begin{document}\n")
  f:write(src)
  f:write("\n\\end{document}\n")
  f:close()
  os.execute("pdflatex -output-directory " .. tmpdir  .. " " .. tmp)
  os.execute("convert " .. tmp .. ".pdf " .. outfile)
  os.remove(tmp .. ".tex")
  os.remove(tmp .. ".pdf")
  os.remove(tmp .. ".log")
  os.remove(tmp .. ".aux")
end

Put all of the above code into a file named tikz-to-png.lua and run it by calling pandoc with the --lua-filter=tikz-to-png.lua option. Note that you'll need ImageMagick's convert program in your path.

2

An alternative approach to Lua filters, is to use pandocfilters and the newer panflute Python packages. At least for someone with no knowledge of Lua, this offer an easier approach to writing filters. Slightly adapting this filter did the trick:

"""
Pandoc filter to process raw latex tikz environments into images.
Assumes that pdflatex is in the path, and that the standalone
package is available.  Also assumes that ImageMagick's convert
is in the path. Images are put in the tikz-images directory.
"""

import hashlib
import re
import os
import sys
import shutil
import panflute as pf
from subprocess import Popen, PIPE, call
from tempfile import mkdtemp

imagedir = "tikz-images"

def sha1(x):
    return hashlib.sha1(x.encode(sys.getfilesystemencoding())).hexdigest()


def tikz2image(tikz, filetype, outfile):
    tmpdir = mkdtemp()
    olddir = os.getcwd()
    os.chdir(tmpdir)
    f = open('tikz.tex', 'w')
    f.write("""\\documentclass{standalone}
             \\usepackage{tikz}
             \\begin{document}
             """)
    f.write(tikz)
    f.write("\n\\end{document}\n")
    f.close()
    p = call(["pdflatex", 'tikz.tex'], stdout=sys.stderr)
    os.chdir(olddir)
    if filetype == 'pdf':
        shutil.copyfile(tmpdir + '/tikz.pdf', outfile + '.pdf')
    else:
        call(["convert", tmpdir + '/tikz.pdf', outfile + '.' + filetype])
    shutil.rmtree(tmpdir)


def action(elem, doc):
    """
    return None -> element unchanged
    return [] -> delete element

    """
    if type(elem) == pf.RawBlock and elem.format == "latex":

        code = elem.text

        if code.strip().startswith(r"\begin{tikzpicture}"):
            outfile = imagedir + '/' + sha1(code)
            filetype = {'html': 'png', 'latex': 'pdf'}.get(doc.format, 'png')
            src = outfile + '.' + filetype
            if not os.path.isfile(src):
                try:
                    os.mkdir(imagedir)
                    sys.stderr.write('Created directory ' + imagedir + '\n')
                except OSError:
                    pass
                tikz2image(code, filetype, outfile)
                sys.stderr.write('Created image ' + src + '\n')

            return pf.Para(pf.Image(url=src))
        else:
            return pf.convert_text(code, input_format="latex")

def main(doc=None):
    return pf.run_filter(action, doc=doc)

if __name__ == "__main__":
   main()

In particular, the line return pf.convert_text(code, input_format="latex") gives the added bonus of ensuring that all other raw_tex is processed in the standard pandoc way (when using --from latex+raw_tex), as I discuss in another question here.

1
  • Nice answer, panflute it great. Excellent point about processing the remaining raw LaTeX. I've updated my answer to do this as well. – tarleb Aug 11 '18 at 7:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.