I usually $\LaTeX$ lecture notes in my classes, both those I take and those I teach. (If you’re a coder and you write $\LaTeX$ for absolutely everything you do in class for five years or so, you get really fast at it.) I’ve yet to find a good $\LaTeX\rightarrow\text{HTML}$ converter, and I’ve tried several. There are good reasons for this, the least not being that the markups of these languages are different enough to cause a lot of difficulty in the translation.

With MathJax (of which I am a huge fan) allowing for specific bits of $\LaTeX$ to be entered directly in to web sources via scripts, a lot of the problem is solved. But, I still want to be able to include parts and packages of $\LaTeX$ not supported by MathJax, such as PGF and TikZ.

I’m going to approach this from a modular perspective, bit by bit over time. The first thing that needs to be done is I need to write a converter that will form *.svg files from graphics created via pdflatex. I’m going to write this in Python, partially because it is well-suited to the task and partially because it will be fast to develop. That’s what this post will develop. After that is done, I’ll need to parse $\LaTeX$ code into three groups: (1) Text that can be formatted in HTML, (2) $\LaTeX$ commands that can transfer directly to MathJax, and (3) $\LaTeX$ commands that will need to be converted to *.svg files.

Dependencies

I’m writing this a bit selfishly for myself. I use Debian based Linux distributions and I’ll need the following to be callable in my path: pdflatex, pdfcrop, pdf2svg, standard BASH commands such as ‘rm’.

Main Idea

The idea is that each bit of $\LaTeX$ in my finished document that is not text and cannot be given to MathJax should be stripped from my document and placed on a separate page. Each page is then written as a pdf, then cropped (the page style is empty, so there are no page numbers and so on) to the bounding box of the generated image, then saved as a vector format svg file. Once complete, the images are hooked back into the HTML document among the MathJax code.

The Code

The Python code for the program latex2img is as follows.

#!/usr/bin/env python
 
###############################################################################
# Jason B. Hill (Jason.B.Hill@Colorado.edu)
###############################################################################
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>
###############################################################################
 
import sys              # used to parse command line options
import subprocess       # used to execute shell commands / obtain exit status
import re               # used to format filenames
 
# version numbers are printed in tenths
latex2img_version = 0.1
# 9-8-11 Initial coding
 
###############################################################################
# Define functions
###############################################################################
 
def print_intro(version):
    print "################################################"
    print "# latex2img version %.1f" % version
    print "# Jason B. Hill (www.jasonbhill.com)"
    print "################################################"
 
def read_latex(file):
    lines = []
    try:
        FILE = open(file, "r")
    except:
        print "Error: file read error"
        sys.exit(0)
    for blob in FILE: lines.append(blob)
    return lines
 
def write_latex(file,latex_data):
    try:
        FILE = open(file, "w")
    except:
        print "Error: file write error"
        sys.exit(0)
    FILE.write("\\documentclass[letterpaper,11pt]{article}\n")
    FILE.write("\\usepackage{fullpage,amsfonts,amsmath,amssymb,tikz}\n\n")
    FILE.write("\\pagestyle{empty}\n\n")
    FILE.write("\\begin{document}\n\n")
    #FILE.write("\\[\n")
    for blob in latex_data: FILE.write(blob)
    #FILE.write("\\]\n\n")
    FILE.write("\\end{document}\n")
    FILE.close()
 
###############################################################################
# Parse options and raise errors if needed
###############################################################################
 
if len(sys.argv) < 2:
    print "Error: Too few options passed."
    print "Usage: latex2img file[.tex]"
    sys.exit(0)
elif len(sys.argv) > 2:
    print "Error: Too many options passed."
    print "Usage: latex2img file[.tex]"
    sys.exit(0)
try:
    file_path = sys.argv[1]
    FILE = open(file_path,"r")
except:
    print "Error: File  does not exist or is not readable." % file_path
    sys.exit(0)
 
###############################################################################
# Execute
###############################################################################
 
### Set filenames
p = re.compile("^(.*)\.(tex|tikz)$", re.IGNORECASE)
m = p.match(file_path)
if m:
    tex_filename = "tmp.%s.tex" % m.group(1)
    pdf_filename = "tmp.%s.pdf" % m.group(1)
    svg_filename = "%s.svg" % m.group(1)
    temp_path = "tmp.%s*" % m.group(1)
else:
    tex_filename = "tmp.%s.tex" % file_path
    pdf_filename = "tmo.%s.pdf" % file_path
    svg_filename = "%s.svg" % file_path
    temp_path = "tmp.%s*" % file_path
 
### Print information at shell
print_intro(latex2img_version)
 
### Read tikz info and place in a LaTeX file
latex_data = read_latex(file_path)
write_latex(tex_filename,latex_data)
 
### Execute pdflatex
command = "pdflatex -shell-escape %s" % tex_filename
print "Executing command: " + command
process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
process.wait()
if process.returncode is 0:
    print "(success) " + command
 
### Execute pdfcrop
command = "pdfcrop %s %s" % (pdf_filename,pdf_filename)
print "Executing command: " + command
process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
process.wait()
if process.returncode is 0:
    print "(success) " + command
 
### Execute pdf2svg
command = "pdf2svg %s %s" % (pdf_filename,svg_filename)
print "Executing command: " + command
process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
process.wait()
if process.returncode is 0:
    print "(success) " + command
 
### Remove temporary files
command = "rm %s" % temp_path
process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
process.wait()
if process.returncode is 0:
    print "(success) " + command + " (temp files removed)"

 Example

I’ll create a file named dynkin.tex somewhere in a directory with appropriate permissions. The file looks like this. (Notice that there are no $\LaTeX$ headers/footers or other definitions in the file.)

\[
\begin{array}{|c|c|c|}
\hline
\textbf{Type} & \textbf{Cartan Matrix} & \textbf{Dynkin Diagram}\\
\hline
\hline
 & & \\
E_6 & \left(
\begin{array}{rrrrrr}
2 & 0 & -1 & 0 & 0 & 0\\
0 & 2 & 0 & -1 & 0 & 0\\
-1 & 0 & 2 & -1 & 0 & 0\\
0 & -1 & -1 & 2 & -1 & 0\\
0 & 0 & 0 & -1 & 2 & -1\\
0 & 0 & 0 & 0 & -1 & 2
\end{array}
\right) &
\begin{tikzpicture}[scale=0.7]
    \draw (1,0) -- (5,0);
    \draw (3,0) -- (3,1);
 
    \filldraw[color=black] (1,0) circle (5pt);
    \filldraw[color=white] (1,0) circle (4pt);
    \filldraw[color=black] (2,0) circle (5pt);
    \filldraw[color=white] (2,0) circle (4pt);
    \filldraw[color=black] (3,0) circle (5pt);
    \filldraw[color=white] (3,0) circle (4pt);
    \filldraw[color=black] (4,0) circle (5pt);
    \filldraw[color=white] (4,0) circle (4pt);
    \filldraw[color=black] (5,0) circle (5pt);
    \filldraw[color=white] (5,0) circle (4pt);
    \filldraw[color=black] (3,1) circle (5pt);
    \filldraw[color=white] (3,1) circle (4pt);
\end{tikzpicture}\\
 
& & \\
\hline
& & \\
 
E_7 & \left(
\begin{array}{rrrrrrr}
2 & 0 & -1 & 0 & 0 & 0 & 0\\
0 & 2 & 0 & -1 & 0 & 0 & 0\\
-1 & 0 & 2 & -1 & 0 & 0 & 0\\
0 & -1 & -1 & 2 & -1 & 0 & 0\\
0 & 0 & 0 & -1 & 2 & -1 & 0\\
0 & 0 & 0 & 0 & -1 & 2 & -1\\
0 & 0 & 0 & 0 & 0 & -1 & 2
\end{array}
\right) &
\begin{tikzpicture}[scale=0.7]
    \draw (1,0) -- (6,0);
    \draw (3,0) -- (3,1);
 
    \filldraw[color=black] (1,0) circle (5pt);
    \filldraw[color=white] (1,0) circle (4pt);
    \filldraw[color=black] (2,0) circle (5pt);
    \filldraw[color=white] (2,0) circle (4pt);
    \filldraw[color=black] (3,0) circle (5pt);
    \filldraw[color=white] (3,0) circle (4pt);
    \filldraw[color=black] (4,0) circle (5pt);
    \filldraw[color=white] (4,0) circle (4pt);
    \filldraw[color=black] (5,0) circle (5pt);
    \filldraw[color=white] (5,0) circle (4pt);
    \filldraw[color=black] (6,0) circle (5pt);
    \filldraw[color=white] (6,0) circle (4pt);
    \filldraw[color=black] (3,1) circle (5pt);
    \filldraw[color=white] (3,1) circle (4pt);
\end{tikzpicture}\\
 
& & \\
\hline
& & \\
 
E_8 & \left(
\begin{array}{rrrrrrrr}
2 & 0 & -1 & 0 & 0 & 0 & 0 & 0\\
0 & 2 & 0 & -1 & 0 & 0 & 0 & 0\\
-1 & 0 & 2 & -1 & 0 & 0 & 0 & 0\\
0 & -1 & -1 & 2 & -1 & 0 & 0 & 0\\
0 & 0 & 0 & -1 & 2 & -1 & 0 & 0\\
0 & 0 & 0 & 0 & -1 & 2 & -1 & 0\\
0 & 0 & 0 & 0 & 0 & -1 & 2 & -1\\
0 & 0 & 0 & 0 & 0 & 0 & -1 & 2
\end{array}
\right) &
\begin{tikzpicture}[scale=0.7]
    \draw (1,0) -- (7,0);
    \draw (3,0) -- (3,1);
 
    \filldraw[color=black] (1,0) circle (5pt);
    \filldraw[color=white] (1,0) circle (4pt);
    \filldraw[color=black] (2,0) circle (5pt);
    \filldraw[color=white] (2,0) circle (4pt);
    \filldraw[color=black] (3,0) circle (5pt);
    \filldraw[color=white] (3,0) circle (4pt);
    \filldraw[color=black] (4,0) circle (5pt);
    \filldraw[color=white] (4,0) circle (4pt);
    \filldraw[color=black] (5,0) circle (5pt);
    \filldraw[color=white] (5,0) circle (4pt);
    \filldraw[color=black] (6,0) circle (5pt);
    \filldraw[color=white] (6,0) circle (4pt);
    \filldraw[color=black] (7,0) circle (5pt);
    \filldraw[color=white] (7,0) circle (4pt);
    \filldraw[color=black] (3,1) circle (5pt);
    \filldraw[color=white] (3,1) circle (4pt);
\end{tikzpicture}\\
 
& & \\
\hline
& & \\
 
F_4 & \left(
\begin{array}{rrrr}
2 & -1 & 0 & 0\\
-1 & 2 & -2 & 0\\
0 & -1 & 2 & -1\\
0 & 0 & -1 & 2
\end{array}
\right) &
\begin{tikzpicture}[scale=0.7]
    \draw (1,0) -- (2,0);
    \draw (3,0) -- (4,0);
    \draw (2,0.1) -- (3,0.1);
    \draw (2,-0.1) -- (3,-0.1);
    \draw (2.4,0.2) -- (2.6,0) -- (2.4,-0.2);
 
    \filldraw[color=black] (1,0) circle (5pt);
    \filldraw[color=white] (1,0) circle (4pt);
    \filldraw[color=black] (2,0) circle (5pt);
    \filldraw[color=white] (2,0) circle (4pt);
    \filldraw[color=black] (3,0) circle (5pt);
    \filldraw[color=white] (3,0) circle (4pt);
    \filldraw[color=black] (4,0) circle (5pt);
    \filldraw[color=white] (4,0) circle (4pt);
\end{tikzpicture}\\
 
& & \\
\hline
& & \\
 
G_2 & \left(
\begin{array}{rr}
2 & -1\\
-3 & 2
\end{array}
\right) &
\begin{tikzpicture}[scale=0.7]
    \draw (1,0.1) -- (2,0.1);
    \draw (1,-0.1) -- (2,-0.1);
    \draw (1,0) -- (2,0);
    \draw (1.6,0.2) -- (1.4,0) -- (1.6,-0.2);
 
    \filldraw[color=black] (1,0) circle (5pt);
    \filldraw[color=white] (1,0) circle (4pt);
    \filldraw[color=black] (2,0) circle (5pt);
    \filldraw[color=white] (2,0) circle (4pt);
\end{tikzpicture}\\
 
& & \\
\hline
\end{array} 
\]

Then, I place the latex2img program somewhere accessible by my path. Then, we issue the following command.

jason@descartes:~$ latex2img dynkin.tex
################################################
# latex2img version 0.1
# Jason B. Hill (www.jasonbhill.com)
################################################
Executing command: pdflatex -shell-escape tmp.dynkin.tex
(success) pdflatex -shell-escape tmp.dynkin.tex
Executing command: pdfcrop tmp.dynkin.pdf tmp.dynkin.pdf
(success) pdfcrop tmp.dynkin.pdf tmp.dynkin.pdf
Executing command: pdf2svg tmp.dynkin.pdf dynkin.svg
(success) pdf2svg tmp.dynkin.pdf dynkin.svg
(success) rm tmp.dynkin* (temp files removed)

The resulting picture is shown below. (O.K., so I just realized that my website isn’t currently set up to handle svg files. I’ll change that soon. For now, here’s the png.)