update latex support

This commit is contained in:
Damien Elmes 2011-03-21 10:28:03 +09:00
parent da0fb0c555
commit 8705085200
5 changed files with 134 additions and 79 deletions

View file

@ -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=""):

View file

@ -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 = '<img src="%s">' % 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("<br( /)?>", "\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
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):
return _errMsg("dvipng")
# add to media
shutil.copy2(os.path.join(tmpdir, "tmp.png"),
os.path.join(mdir, fname))
return
finally:
os.chdir(oldcwd)
def _errMsg(type):
msg = (_("Error executing %s.") % type) + "<br>"
try:
log = open(os.path.join(tmpdir, "latex_log.txt")).read()
if not log:
raise Exception()
msg += "<small><pre>" + cgi.escape(log) + "</pre></small>"
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(latexDviPngCmd + ["tmp.dvi", "-o", "tmp.png"],
stdout=log, stderr=log, startupinfo=si):
return (False, 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)
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 src="%s" alt="%s">' % (img, latex)
else:
return img
def formatQA(html, type, cid, mid, fact, tags, cm, deck):
return renderLatex(deck, html)
# setup q/a filter
addHook("formatQA", formatQA)
addHook("mungeQA", mungeQA)

View file

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

View file

@ -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:
o = subprocess.Popen(argv, **kwargs)
si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
except:
si.dwFlags |= subprocess._subprocess.STARTF_USESHOWWINDOW
else:
si = None
# run
try:
o = subprocess.Popen(argv, startupinfo=si, **kwargs)
except OSError:
# command not found
return -1
# wait for command to finish
if wait:
while 1:
try:

58
tests/test_latex.py Normal file
View file

@ -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()