1# -*- coding: utf-8 -*- 2# Copyright: Ankitects Pty Ltd and contributors 3# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html 4 5import re, os, shutil, html 6from anki.utils import checksum, call, namedtmp, tmpdir, isMac, stripHTML 7from anki.hooks import addHook 8from anki.lang import _ 9 10pngCommands = [ 11 ["latex", "-interaction=nonstopmode", "tmp.tex"], 12 ["dvipng", "-D", "200", "-T", "tight", "tmp.dvi", "-o", "tmp.png"] 13] 14 15svgCommands = [ 16 ["latex", "-interaction=nonstopmode", "tmp.tex"], 17 ["dvisvgm", "--no-fonts", "--exact", "-Z", "2", "tmp.dvi", "-o", "tmp.svg"] 18] 19 20build = True # if off, use existing media but don't create new 21regexps = { 22 "standard": re.compile(r"\[latex\](.+?)\[/latex\]", re.DOTALL | re.IGNORECASE), 23 "expression": re.compile(r"\[\$\](.+?)\[/\$\]", re.DOTALL | re.IGNORECASE), 24 "math": re.compile(r"\[\$\$\](.+?)\[/\$\$\]", re.DOTALL | re.IGNORECASE), 25 } 26 27# add standard tex install location to osx 28if isMac: 29 os.environ['PATH'] += ":/usr/texbin:/Library/TeX/texbin" 30 31def stripLatex(text): 32 for match in regexps['standard'].finditer(text): 33 text = text.replace(match.group(), "") 34 for match in regexps['expression'].finditer(text): 35 text = text.replace(match.group(), "") 36 for match in regexps['math'].finditer(text): 37 text = text.replace(match.group(), "") 38 return text 39 40def mungeQA(html, type, fields, model, data, col): 41 "Convert TEXT with embedded latex tags to image links." 42 for match in regexps['standard'].finditer(html): 43 html = html.replace(match.group(), _imgLink(col, match.group(1), model)) 44 for match in regexps['expression'].finditer(html): 45 html = html.replace(match.group(), _imgLink( 46 col, "$" + match.group(1) + "$", model)) 47 for match in regexps['math'].finditer(html): 48 html = html.replace(match.group(), _imgLink( 49 col, 50 "\\begin{displaymath}" + match.group(1) + "\\end{displaymath}", model)) 51 return html 52 53def _imgLink(col, latex, model): 54 "Return an img link for LATEX, creating if necesssary." 55 txt = _latexFromHtml(col, latex) 56 57 if model.get("latexsvg", False): 58 ext = "svg" 59 else: 60 ext = "png" 61 62 # is there an existing file? 63 fname = "latex-%s.%s" % (checksum(txt.encode("utf8")), ext) 64 link = '<img class=latex src="%s">' % fname 65 if os.path.exists(fname): 66 return link 67 68 # building disabled? 69 if not build: 70 return "[latex]%s[/latex]" % latex 71 72 err = _buildImg(col, txt, fname, model) 73 if err: 74 return err 75 else: 76 return link 77 78def _latexFromHtml(col, latex): 79 "Convert entities and fix newlines." 80 latex = re.sub("<br( /)?>|<div>", "\n", latex) 81 latex = stripHTML(latex) 82 return latex 83 84def _buildImg(col, latex, fname, model): 85 # add header/footer 86 latex = (model["latexPre"] + "\n" + 87 latex + "\n" + 88 model["latexPost"]) 89 # it's only really secure if run in a jail, but these are the most common 90 tmplatex = latex.replace("\\includegraphics", "") 91 for bad in ("\\write18", "\\readline", "\\input", "\\include", 92 "\\catcode", "\\openout", "\\write", "\\loop", 93 "\\def", "\\shipout"): 94 # don't mind if the sequence is only part of a command 95 bad_re = "\\" + bad + "[^a-zA-Z]" 96 if re.search(bad_re, tmplatex): 97 return _("""\ 98For security reasons, '%s' is not allowed on cards. You can still use \ 99it by placing the command in a different package, and importing that \ 100package in the LaTeX header instead.""") % bad 101 102 # commands to use? 103 if model.get("latexsvg", False): 104 latexCmds = svgCommands 105 ext = "svg" 106 else: 107 latexCmds = pngCommands 108 ext = "png" 109 110 # write into a temp file 111 log = open(namedtmp("latex_log.txt"), "w") 112 texpath = namedtmp("tmp.tex") 113 texfile = open(texpath, "w", encoding="utf8") 114 texfile.write(latex) 115 texfile.close() 116 mdir = col.media.dir() 117 oldcwd = os.getcwd() 118 png = namedtmp("tmp.%s" % ext) 119 try: 120 # generate png 121 os.chdir(tmpdir()) 122 for latexCmd in latexCmds: 123 if call(latexCmd, stdout=log, stderr=log): 124 return _errMsg(latexCmd[0], texpath) 125 # add to media 126 shutil.copyfile(png, os.path.join(mdir, fname)) 127 return 128 finally: 129 os.chdir(oldcwd) 130 log.close() 131 132def _errMsg(type, texpath): 133 msg = (_("Error executing %s.") % type) + "<br>" 134 msg += (_("Generated file: %s") % texpath) + "<br>" 135 try: 136 with open(namedtmp("latex_log.txt", rm=False)) as f: 137 log = f.read() 138 if not log: 139 raise Exception() 140 msg += "<small><pre>" + html.escape(log) + "</pre></small>" 141 except: 142 msg += _("Have you installed latex and dvipng/dvisvgm?") 143 return msg 144 145# setup q/a filter 146addHook("mungeQA", mungeQA) 147