diff --git a/anki/deck.py b/anki/deck.py
index 3b5c37b79..143dd15c9 100644
--- a/anki/deck.py
+++ b/anki/deck.py
@@ -422,7 +422,7 @@ select id from cards where fid in (select id from facts where mid = ?)""",
return [self._renderQA(mods[row[2]], groups[row[3]], row)
for row in self._qaData(where)]
- def _renderQA(self, model, gname, data, filters=True):
+ def _renderQA(self, model, gname, data):
"Returns hash of id, question, answer."
# data is [cid, fid, mid, gid, ord, tags, flds, data]
# unpack fields and create dict
@@ -446,12 +446,9 @@ select id from cards where fid in (select id from facts where mid = ?)""",
# render q & a
d = dict(id=data[0])
for (type, format) in (("q", template['qfmt']), ("a", template['afmt'])):
- # if filters:
- # fields = runFilter("renderQA.pre", fields, , self)
+ fields = runFilter("mungeFields", fields, model, gname, data, self)
html = anki.template.render(format, fields)
- # if filters:
- # d[type] = runFilter("renderQA.post", html, fields, meta, self)
- d[type] = html
+ d[type] = runFilter("mungeQA", html, fields, model, gname, data, self)
return d
def _qaData(self, where=""):
diff --git a/anki/latex.py b/anki/latex.py
index 97a847593..62fda9525 100644
--- a/anki/latex.py
+++ b/anki/latex.py
@@ -8,8 +8,9 @@ from anki.hooks import addHook
from htmlentitydefs import entitydefs
from anki.lang import _
+latexCmd = ["latex", "-interaction=nonstopmode"]
latexDviPngCmd = ["dvipng", "-D", "200", "-T", "tight"]
-
+build = True # if off, use existing media but don't create new
regexps = {
"standard": re.compile(r"\[latex\](.+?)\[/latex\]", re.DOTALL | re.IGNORECASE),
"expression": re.compile(r"\[\$\](.+?)\[/\$\]", re.DOTALL | re.IGNORECASE),
@@ -22,21 +23,6 @@ tmpdir = tempfile.mkdtemp(prefix="anki")
if sys.platform == "darwin":
os.environ['PATH'] += ":/usr/texbin"
-def renderLatex(deck, text, build=True):
- "Convert TEXT with embedded latex tags to image links."
- for match in regexps['standard'].finditer(text):
- text = text.replace(match.group(), imgLink(deck, match.group(1),
- build))
- for match in regexps['expression'].finditer(text):
- text = text.replace(match.group(), imgLink(
- deck, "$" + match.group(1) + "$", build))
- for match in regexps['math'].finditer(text):
- text = text.replace(match.group(), imgLink(
- deck,
- "\\begin{displaymath}" + match.group(1) + "\\end{displaymath}",
- build))
- return text
-
def stripLatex(text):
for match in regexps['standard'].finditer(text):
text = text.replace(match.group(), "")
@@ -46,23 +32,50 @@ def stripLatex(text):
text = text.replace(match.group(), "")
return text
-def latexImgFile(deck, latexCode):
- key = checksum(latexCode)
- return "latex-%s.png" % key
+def mungeQA(html, fields, model, gname, data, deck):
+ "Convert TEXT with embedded latex tags to image links."
+ for match in regexps['standard'].finditer(html):
+ html = html.replace(match.group(), _imgLink(deck, match.group(1)))
+ for match in regexps['expression'].finditer(html):
+ html = html.replace(match.group(), _imgLink(
+ deck, "$" + match.group(1) + "$"))
+ for match in regexps['math'].finditer(html):
+ html = html.replace(match.group(), _imgLink(
+ deck,
+ "\\begin{displaymath}" + match.group(1) + "\\end{displaymath}"))
+ return html
-def mungeLatex(deck, latex):
- "Convert entities, fix newlines, convert to utf8, and wrap pre/postamble."
+def _imgLink(deck, latex):
+ "Return an img link for LATEX, creating if necesssary."
+ txt = _latexFromHtml(deck, latex)
+ fname = "latex-%s.png" % checksum(txt)
+ link = '
' % fname
+ if os.path.exists(fname):
+ return link
+ elif not build:
+ return "[latex]"+latex+"[/latex]"
+ else:
+ err = _buildImg(deck, txt, fname)
+ if err:
+ return err
+ else:
+ return link
+
+def _latexFromHtml(deck, latex):
+ "Convert entities, fix newlines, and convert to utf8."
for match in re.compile("&([a-z]+);", re.IGNORECASE).finditer(latex):
if match.group(1) in entitydefs:
latex = latex.replace(match.group(), entitydefs[match.group(1)])
latex = re.sub("
", "\n", latex)
- latex = (deck.getVar("latexPre") + "\n" +
- latex + "\n" +
- deck.getVar("latexPost"))
latex = latex.encode("utf-8")
return latex
-def buildImg(deck, latex):
+def _buildImg(deck, latex, fname):
+ # add header/footer
+ latex = (deck.conf["latexPre"] + "\n" +
+ latex + "\n" +
+ deck.conf["latexPost"])
+ # write into a temp file
log = open(os.path.join(tmpdir, "latex_log.txt"), "w+")
texpath = os.path.join(tmpdir, "tmp.tex")
texfile = file(texpath, "w")
@@ -71,60 +84,33 @@ def buildImg(deck, latex):
# make sure we have a valid mediaDir
mdir = deck.media.dir(create=True)
oldcwd = os.getcwd()
- if sys.platform == "win32":
- si = subprocess.STARTUPINFO()
- try:
- si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
- except:
- si.dwFlags |= subprocess._subprocess.STARTF_USESHOWWINDOW
- else:
- si = None
try:
+ # generate dvi
os.chdir(tmpdir)
- def errmsg(type):
- msg = _("Error executing %s.\n") % type
- try:
- log = open(os.path.join(tmpdir, "latex_log.txt")).read()
- msg += "" + cgi.escape(log) + "
"
- except:
- msg += _("Have you installed latex and dvipng?")
- pass
- return msg
- if call(["latex", "-interaction=nonstopmode",
- "tmp.tex"], stdout=log, stderr=log, startupinfo=si):
- return (False, errmsg("latex"))
+ if call(latexCmd + ["tmp.tex"], stdout=log, stderr=log):
+ return _errMsg("latex")
+ # and png
if call(latexDviPngCmd + ["tmp.dvi", "-o", "tmp.png"],
- stdout=log, stderr=log, startupinfo=si):
- return (False, errmsg("dvipng"))
+ stdout=log, stderr=log):
+ return _errMsg("dvipng")
# add to media
- target = latexImgFile(deck, latex)
shutil.copy2(os.path.join(tmpdir, "tmp.png"),
- os.path.join(mdir, target))
- return (True, target)
+ os.path.join(mdir, fname))
+ return
finally:
os.chdir(oldcwd)
-def imageForLatex(deck, latex, build=True):
- "Return an image that represents 'latex', building if necessary."
- imageFile = latexImgFile(deck, latex)
- ok = True
- if build and (not imageFile or not os.path.exists(imageFile)):
- (ok, imageFile) = buildImg(deck, latex)
- if not ok:
- return (False, imageFile)
- return (True, imageFile)
-
-def imgLink(deck, latex, build=True):
- "Parse LATEX and return a HTML image representing the output."
- munged = mungeLatex(deck, latex)
- (ok, img) = imageForLatex(deck, munged, build)
- if ok:
- return '
' % (img, latex)
- else:
- return img
-
-def formatQA(html, type, cid, mid, fact, tags, cm, deck):
- return renderLatex(deck, html)
+def _errMsg(type):
+ msg = (_("Error executing %s.") % type) + "
"
+ try:
+ log = open(os.path.join(tmpdir, "latex_log.txt")).read()
+ if not log:
+ raise Exception()
+ msg += "" + cgi.escape(log) + "
"
+ except:
+ msg += _("Have you installed latex and dvipng?")
+ pass
+ return msg
# setup q/a filter
-addHook("formatQA", formatQA)
+addHook("mungeQA", mungeQA)
diff --git a/anki/media.py b/anki/media.py
index 2a5813405..a8c313b72 100644
--- a/anki/media.py
+++ b/anki/media.py
@@ -130,6 +130,8 @@ If the same name exists, compare checksums."""
# loop through directory and find unused & missing media
unused = []
for file in os.listdir(mdir):
+ if file.startswith("latex-"):
+ continue
path = os.path.join(mdir, file)
if not os.path.isfile(path):
# ignore directories
diff --git a/anki/utils.py b/anki/utils.py
index 49516160e..895e8ba59 100644
--- a/anki/utils.py
+++ b/anki/utils.py
@@ -276,11 +276,23 @@ def fieldChecksum(data):
return int(checksum(data.encode("utf-8"))[:8], 16)
def call(argv, wait=True, **kwargs):
+ "Execute a command. If WAIT, return exit code."
+ # ensure we don't open a separate window for forking process on windows
+ if sys.platform == "win32":
+ si = subprocess.STARTUPINFO()
+ try:
+ si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
+ except:
+ si.dwFlags |= subprocess._subprocess.STARTF_USESHOWWINDOW
+ else:
+ si = None
+ # run
try:
- o = subprocess.Popen(argv, **kwargs)
+ o = subprocess.Popen(argv, startupinfo=si, **kwargs)
except OSError:
# command not found
return -1
+ # wait for command to finish
if wait:
while 1:
try:
diff --git a/tests/test_latex.py b/tests/test_latex.py
new file mode 100644
index 000000000..8e5af7467
--- /dev/null
+++ b/tests/test_latex.py
@@ -0,0 +1,58 @@
+# coding: utf-8
+
+import os
+from tests.shared import assertException, getEmptyDeck
+from anki.stdmodels import BasicModel
+from anki.utils import stripHTML, intTime
+from anki.hooks import addHook
+
+def test_latex():
+ d = getEmptyDeck()
+ # no media directory to start
+ assert not d.media.dir()
+ # change latex cmd to simulate broken build
+ import anki.latex
+ anki.latex.latexCmd[0] = "nolatex"
+ # add a fact with latex
+ f = d.newFact()
+ f['Front'] = u"[latex]hello[/latex]"
+ d.addFact(f)
+ # adding will have created the media
+ assert d.media.dir()
+ # but since latex couldn't run, it will be empty
+ assert len(os.listdir(d.media.dir())) == 0
+ # check the error message
+ msg = f.cards()[0].q()
+ assert "executing latex" in msg
+ assert "installed" in msg
+ # check if we have latex installed, and abort test if we don't
+ if not os.path.exists("/usr/bin/latex"):
+ print "aborting test; latex is not installed"
+ return
+ # fix path
+ anki.latex.latexCmd[0] = "latex"
+ # check media db should cause latex to be generated
+ d.media.check()
+ assert len(os.listdir(d.media.dir())) == 1
+ assert ".png" in f.cards()[0].q()
+ # adding new facts should cause immediate generation
+ f = d.newFact()
+ f['Front'] = u"[latex]world[/latex]"
+ d.addFact(f)
+ assert len(os.listdir(d.media.dir())) == 2
+ # another fact with the same media should reuse
+ f = d.newFact()
+ f['Front'] = u" [latex]world[/latex]"
+ d.addFact(f)
+ assert len(os.listdir(d.media.dir())) == 2
+ oldcard = f.cards()[0]
+ assert ".png" in oldcard.q()
+ # if we turn off building, then previous cards should work, but cards with
+ # missing media will show the latex
+ anki.latex.build = False
+ f = d.newFact()
+ f['Front'] = u"[latex]foo[/latex]"
+ d.addFact(f)
+ assert len(os.listdir(d.media.dir())) == 2
+ assert stripHTML(f.cards()[0].q()) == "[latex]foo[/latex]"
+ assert ".png" in oldcard.q()