diff --git a/ftl/core/preferences.ftl b/ftl/core/preferences.ftl
index 04243a494..3e0a2414b 100644
--- a/ftl/core/preferences.ftl
+++ b/ftl/core/preferences.ftl
@@ -13,6 +13,8 @@ preferences-media-is-not-backed-up = Media is not backed up. Please create a per
preferences-on-next-sync-force-changes-in = On next sync, force changes in one direction
preferences-paste-clipboard-images-as-png = Paste clipboard images as PNG
preferences-paste-without-shift-key-strips-formatting = Paste without shift key strips formatting
+preferences-generate-latex-images-automatically = Generate LaTeX images (security risk)
+preferences-latex-generation-disabled = LaTeX image generation is disabled in the preferences.
preferences-periodically-sync-media = Periodically sync media
preferences-please-restart-anki-to-complete-language = Please restart Anki to complete language change.
preferences-preferences = Preferences
diff --git a/proto/anki/config.proto b/proto/anki/config.proto
index 1eda78e03..2cee6172b 100644
--- a/proto/anki/config.proto
+++ b/proto/anki/config.proto
@@ -53,6 +53,7 @@ message ConfigKey {
RESET_COUNTS_REVIEWER = 22;
RANDOM_ORDER_REPOSITION = 23;
SHIFT_POSITION_OF_EXISTING_CARDS = 24;
+ RENDER_LATEX = 25;
}
enum String {
SET_DUE_BROWSER = 0;
@@ -121,6 +122,7 @@ message Preferences {
bool paste_strips_formatting = 3;
string default_search_text = 4;
bool ignore_accents_in_search = 5;
+ bool render_latex = 6;
}
message BackupLimits {
uint32 daily = 1;
diff --git a/pylib/anki/latex.py b/pylib/anki/latex.py
index e492422c8..75c7d9e9f 100644
--- a/pylib/anki/latex.py
+++ b/pylib/anki/latex.py
@@ -5,12 +5,12 @@ from __future__ import annotations
import html
import os
-import re
from dataclasses import dataclass
import anki
import anki.collection
from anki import card_rendering_pb2, hooks
+from anki.config import Config
from anki.models import NotetypeDict
from anki.template import TemplateRenderContext, TemplateRenderOutput
from anki.utils import call, is_mac, namedtmp, tmpdir
@@ -36,9 +36,6 @@ svgCommands = [
["dvisvgm", "--no-fonts", "--exact", "-Z", "2", "tmp.dvi", "-o", "tmp.svg"],
]
-# if off, use existing media but don't create new
-build = True # pylint: disable=invalid-name
-
# add standard tex install location to osx
if is_mac:
os.environ["PATH"] += ":/usr/texbin:/Library/TeX/texbin"
@@ -104,11 +101,15 @@ def render_latex_returning_errors(
out = ExtractedLatexOutput.from_proto(proto)
errors = []
html = out.html
+ render_latex = col.get_config_bool(Config.Bool.RENDER_LATEX)
for latex in out.latex:
# don't need to render?
- if not build or col.media.have(latex.filename):
+ if col.media.have(latex.filename):
continue
+ if not render_latex:
+ errors.append(col.tr.preferences_latex_generation_disabled())
+ return html, errors
err = _save_latex_image(col, latex, header, footer, svg)
if err is not None:
@@ -126,24 +127,6 @@ def _save_latex_image(
) -> str | None:
# add header/footer
latex = f"{header}\n{extracted.latex_body}\n{footer}"
- # 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 = f"\\{bad}[^a-zA-Z]"
- if re.search(bad_re, tmplatex):
- return col.tr.media_for_security_reasons_is_not(val=bad)
# commands to use
if svg:
diff --git a/pylib/tests/test_latex.py b/pylib/tests/test_latex.py
index 89c8229a3..41dc14bf6 100644
--- a/pylib/tests/test_latex.py
+++ b/pylib/tests/test_latex.py
@@ -6,12 +6,14 @@
import os
import shutil
+from anki.config import Config
from anki.lang import without_unicode_isolation
from tests.shared import getEmptyCol
def test_latex():
col = getEmptyCol()
+ col.set_config_bool(Config.Bool.RENDER_LATEX, True)
# change latex cmd to simulate broken build
import anki.latex
@@ -51,49 +53,9 @@ def test_latex():
assert ".png" in oldcard.question()
# if we turn off building, then previous cards should work, but cards with
# missing media will show a broken image
- anki.latex.build = False
+ col.set_config_bool(Config.Bool.RENDER_LATEX, False)
note = col.newNote()
note["Front"] = "[latex]foo[/latex]"
col.addNote(note)
assert len(os.listdir(col.media.dir())) == 2
assert ".png" in oldcard.question()
- # turn it on again so other test don't suffer
- anki.latex.build = True
-
- # bad commands
- (result, msg) = _test_includes_bad_command("\\write18")
- assert result, msg
- (result, msg) = _test_includes_bad_command("\\readline")
- assert result, msg
- (result, msg) = _test_includes_bad_command("\\input")
- assert result, msg
- (result, msg) = _test_includes_bad_command("\\include")
- assert result, msg
- (result, msg) = _test_includes_bad_command("\\catcode")
- assert result, msg
- (result, msg) = _test_includes_bad_command("\\openout")
- assert result, msg
- (result, msg) = _test_includes_bad_command("\\write")
- assert result, msg
- (result, msg) = _test_includes_bad_command("\\loop")
- assert result, msg
- (result, msg) = _test_includes_bad_command("\\def")
- assert result, msg
- (result, msg) = _test_includes_bad_command("\\shipout")
- assert result, msg
-
- # inserting commands beginning with a bad name should not raise an error
- (result, msg) = _test_includes_bad_command("\\defeq")
- assert not result, msg
- # normal commands should not either
- (result, msg) = _test_includes_bad_command("\\emph")
- assert not result, msg
-
-
-def _test_includes_bad_command(bad):
- col = getEmptyCol()
- note = col.newNote()
- note["Front"] = f"[latex]{bad}[/latex]"
- col.addNote(note)
- q = without_unicode_isolation(note.cards()[0].question())
- return (f"'{bad}' is not allowed on cards" in q, f"Card content: {q}")
diff --git a/qt/aqt/forms/preferences.ui b/qt/aqt/forms/preferences.ui
index 8e5572783..871c07f9b 100644
--- a/qt/aqt/forms/preferences.ui
+++ b/qt/aqt/forms/preferences.ui
@@ -6,7 +6,7 @@
0
0
- 606
+ 636
638
@@ -347,7 +347,7 @@
preferences_review
-
+
-
@@ -413,6 +413,19 @@
+ -
+
+
+
+ 0
+ 0
+
+
+
+ preferences_generate_latex_images_automatically
+
+
+
@@ -1098,6 +1111,7 @@
showProgress
showEstimates
spacebar_rates_card
+ render_latex
pastePNG
paste_strips_formatting
useCurrent
diff --git a/qt/aqt/preferences.py b/qt/aqt/preferences.py
index 89e4ac8e0..b0af7cce2 100644
--- a/qt/aqt/preferences.py
+++ b/qt/aqt/preferences.py
@@ -126,6 +126,7 @@ class Preferences(QDialog):
form.paste_strips_formatting.setChecked(editing.paste_strips_formatting)
form.ignore_accents_in_search.setChecked(editing.ignore_accents_in_search)
form.pastePNG.setChecked(editing.paste_images_as_png)
+ form.render_latex.setChecked(editing.render_latex)
form.default_search_text.setText(editing.default_search_text)
form.backup_explanation.setText(
@@ -154,6 +155,7 @@ class Preferences(QDialog):
editing.adding_defaults_to_current_deck = not form.useCurrent.currentIndex()
editing.paste_images_as_png = self.form.pastePNG.isChecked()
editing.paste_strips_formatting = self.form.paste_strips_formatting.isChecked()
+ editing.render_latex = self.form.render_latex.isChecked()
editing.default_search_text = self.form.default_search_text.text()
editing.ignore_accents_in_search = (
self.form.ignore_accents_in_search.isChecked()
diff --git a/rslib/src/backend/config.rs b/rslib/src/backend/config.rs
index 7637d30d1..721d84fc9 100644
--- a/rslib/src/backend/config.rs
+++ b/rslib/src/backend/config.rs
@@ -36,6 +36,7 @@ impl From for BoolKey {
BoolKeyProto::ResetCountsReviewer => BoolKey::ResetCountsReviewer,
BoolKeyProto::RandomOrderReposition => BoolKey::RandomOrderReposition,
BoolKeyProto::ShiftPositionOfExistingCards => BoolKey::ShiftPositionOfExistingCards,
+ BoolKeyProto::RenderLatex => BoolKey::RenderLatex,
}
}
}
diff --git a/rslib/src/config/bool.rs b/rslib/src/config/bool.rs
index 1177baa71..4f320cd76 100644
--- a/rslib/src/config/bool.rs
+++ b/rslib/src/config/bool.rs
@@ -27,6 +27,7 @@ pub enum BoolKey {
NewCardsIgnoreReviewLimit,
PasteImagesAsPng,
PasteStripsFormatting,
+ RenderLatex,
PreviewBothSides,
RestorePositionBrowser,
RestorePositionReviewer,
diff --git a/rslib/src/preferences.rs b/rslib/src/preferences.rs
index 6ee564d3f..cfeae3fa3 100644
--- a/rslib/src/preferences.rs
+++ b/rslib/src/preferences.rs
@@ -128,6 +128,7 @@ impl Collection {
paste_strips_formatting: self.get_config_bool(BoolKey::PasteStripsFormatting),
default_search_text: self.get_config_string(StringKey::DefaultSearchText),
ignore_accents_in_search: self.get_config_bool(BoolKey::IgnoreAccentsInSearch),
+ render_latex: self.get_config_bool(BoolKey::RenderLatex),
})
}
@@ -141,6 +142,7 @@ impl Collection {
self.set_config_bool_inner(BoolKey::PasteStripsFormatting, s.paste_strips_formatting)?;
self.set_config_string_inner(StringKey::DefaultSearchText, &s.default_search_text)?;
self.set_config_bool_inner(BoolKey::IgnoreAccentsInSearch, s.ignore_accents_in_search)?;
+ self.set_config_bool_inner(BoolKey::RenderLatex, s.render_latex)?;
Ok(())
}
}