mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 06:22:22 -04:00

Earlier today I pushed a change that split this code up into multiple repos, but that has proved to complicate things too much. So we're back to a single repo, except the individual submodules are better separated than they were before. The README files need updating again; I will push them out soon. Aside from splitting out the different modules, the sound code has moved from from anki to aqt.
183 lines
5.3 KiB
Python
183 lines
5.3 KiB
Python
# Copyright: Ankitects Pty Ltd and contributors
|
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
|
|
import html
|
|
import os
|
|
import re
|
|
import shutil
|
|
from typing import Any, Dict, List, Optional, Union
|
|
|
|
from anki.hooks import addHook
|
|
from anki.lang import _
|
|
from anki.types import NoteType
|
|
from anki.utils import call, checksum, isMac, namedtmp, stripHTML, tmpdir
|
|
|
|
pngCommands = [
|
|
["latex", "-interaction=nonstopmode", "tmp.tex"],
|
|
["dvipng", "-D", "200", "-T", "tight", "tmp.dvi", "-o", "tmp.png"],
|
|
]
|
|
|
|
svgCommands = [
|
|
["latex", "-interaction=nonstopmode", "tmp.tex"],
|
|
["dvisvgm", "--no-fonts", "--exact", "-Z", "2", "tmp.dvi", "-o", "tmp.svg"],
|
|
]
|
|
|
|
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),
|
|
"math": re.compile(r"\[\$\$\](.+?)\[/\$\$\]", re.DOTALL | re.IGNORECASE),
|
|
}
|
|
|
|
# add standard tex install location to osx
|
|
if isMac:
|
|
os.environ["PATH"] += ":/usr/texbin:/Library/TeX/texbin"
|
|
|
|
|
|
def stripLatex(text) -> Any:
|
|
for match in regexps["standard"].finditer(text):
|
|
text = text.replace(match.group(), "")
|
|
for match in regexps["expression"].finditer(text):
|
|
text = text.replace(match.group(), "")
|
|
for match in regexps["math"].finditer(text):
|
|
text = text.replace(match.group(), "")
|
|
return text
|
|
|
|
|
|
def mungeQA(
|
|
html: str,
|
|
type: Optional[str],
|
|
fields: Optional[Dict[str, str]],
|
|
model: NoteType,
|
|
data: Optional[List[Union[int, str]]],
|
|
col,
|
|
) -> Any:
|
|
"Convert TEXT with embedded latex tags to image links."
|
|
for match in regexps["standard"].finditer(html):
|
|
html = html.replace(match.group(), _imgLink(col, match.group(1), model))
|
|
for match in regexps["expression"].finditer(html):
|
|
html = html.replace(
|
|
match.group(), _imgLink(col, "$" + match.group(1) + "$", model)
|
|
)
|
|
for match in regexps["math"].finditer(html):
|
|
html = html.replace(
|
|
match.group(),
|
|
_imgLink(
|
|
col,
|
|
"\\begin{displaymath}" + match.group(1) + "\\end{displaymath}",
|
|
model,
|
|
),
|
|
)
|
|
return html
|
|
|
|
|
|
def _imgLink(col, latex: str, model: NoteType) -> str:
|
|
"Return an img link for LATEX, creating if necesssary."
|
|
txt = _latexFromHtml(col, latex)
|
|
|
|
if model.get("latexsvg", False):
|
|
ext = "svg"
|
|
else:
|
|
ext = "png"
|
|
|
|
# is there an existing file?
|
|
fname = "latex-%s.%s" % (checksum(txt.encode("utf8")), ext)
|
|
link = '<img class=latex src="%s">' % fname
|
|
if os.path.exists(fname):
|
|
return link
|
|
|
|
# building disabled?
|
|
if not build:
|
|
return "[latex]%s[/latex]" % latex
|
|
|
|
err = _buildImg(col, txt, fname, model)
|
|
if err:
|
|
return err
|
|
else:
|
|
return link
|
|
|
|
|
|
def _latexFromHtml(col, latex: str) -> str:
|
|
"Convert entities and fix newlines."
|
|
latex = re.sub("<br( /)?>|<div>", "\n", latex)
|
|
latex = stripHTML(latex)
|
|
return latex
|
|
|
|
|
|
def _buildImg(col, latex: str, fname: str, model: NoteType) -> Optional[str]:
|
|
# add header/footer
|
|
latex = model["latexPre"] + "\n" + latex + "\n" + model["latexPost"]
|
|
# it's only really secure if run in a jail, but these are the most common
|
|
tmplatex = latex.replace("\\includegraphics", "")
|
|
for bad in (
|
|
"\\write18",
|
|
"\\readline",
|
|
"\\input",
|
|
"\\include",
|
|
"\\catcode",
|
|
"\\openout",
|
|
"\\write",
|
|
"\\loop",
|
|
"\\def",
|
|
"\\shipout",
|
|
):
|
|
# don't mind if the sequence is only part of a command
|
|
bad_re = "\\" + bad + "[^a-zA-Z]"
|
|
if re.search(bad_re, tmplatex):
|
|
return (
|
|
_(
|
|
"""\
|
|
For security reasons, '%s' is not allowed on cards. You can still use \
|
|
it by placing the command in a different package, and importing that \
|
|
package in the LaTeX header instead."""
|
|
)
|
|
% bad
|
|
)
|
|
|
|
# commands to use?
|
|
if model.get("latexsvg", False):
|
|
latexCmds = svgCommands
|
|
ext = "svg"
|
|
else:
|
|
latexCmds = pngCommands
|
|
ext = "png"
|
|
|
|
# write into a temp file
|
|
log = open(namedtmp("latex_log.txt"), "w")
|
|
texpath = namedtmp("tmp.tex")
|
|
texfile = open(texpath, "w", encoding="utf8")
|
|
texfile.write(latex)
|
|
texfile.close()
|
|
mdir = col.media.dir()
|
|
oldcwd = os.getcwd()
|
|
png = namedtmp("tmp.%s" % ext)
|
|
try:
|
|
# generate png
|
|
os.chdir(tmpdir())
|
|
for latexCmd in latexCmds:
|
|
if call(latexCmd, stdout=log, stderr=log):
|
|
return _errMsg(latexCmd[0], texpath)
|
|
# add to media
|
|
shutil.copyfile(png, os.path.join(mdir, fname))
|
|
return None
|
|
finally:
|
|
os.chdir(oldcwd)
|
|
log.close()
|
|
|
|
|
|
def _errMsg(type: str, texpath: str) -> Any:
|
|
msg = (_("Error executing %s.") % type) + "<br>"
|
|
msg += (_("Generated file: %s") % texpath) + "<br>"
|
|
try:
|
|
with open(namedtmp("latex_log.txt", rm=False)) as f:
|
|
log = f.read()
|
|
if not log:
|
|
raise Exception()
|
|
msg += "<small><pre>" + html.escape(log) + "</pre></small>"
|
|
except:
|
|
msg += _("Have you installed latex and dvipng/dvisvgm?")
|
|
return msg
|
|
|
|
|
|
# setup q/a filter
|
|
addHook("mungeQA", mungeQA)
|