Add a preference to toggle LaTeX generation (#3218)
* Add a preference to toggle LaTeX generation * Fix test * Remove LaTeX security restrictions * Show existing LaTeX images regardless of preference * Lift config check out of loop (dae) * Shift option to review settings; display warning when disabled (dae)
This commit is contained in:
parent
d981a6e3c6
commit
06f7aa393d
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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:
|
||||
|
@ -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}")
|
||||
|
@ -6,7 +6,7 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>606</width>
|
||||
<width>636</width>
|
||||
<height>638</height>
|
||||
</rect>
|
||||
</property>
|
||||
@ -347,7 +347,7 @@
|
||||
<property name="title">
|
||||
<string>preferences_review</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_16">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showPlayButtons">
|
||||
<property name="sizePolicy">
|
||||
@ -413,6 +413,19 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="render_latex">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>preferences_generate_latex_images_automatically</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@ -1098,6 +1111,7 @@
|
||||
<tabstop>showProgress</tabstop>
|
||||
<tabstop>showEstimates</tabstop>
|
||||
<tabstop>spacebar_rates_card</tabstop>
|
||||
<tabstop>render_latex</tabstop>
|
||||
<tabstop>pastePNG</tabstop>
|
||||
<tabstop>paste_strips_formatting</tabstop>
|
||||
<tabstop>useCurrent</tabstop>
|
||||
|
@ -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()
|
||||
|
@ -36,6 +36,7 @@ impl From<BoolKeyProto> for BoolKey {
|
||||
BoolKeyProto::ResetCountsReviewer => BoolKey::ResetCountsReviewer,
|
||||
BoolKeyProto::RandomOrderReposition => BoolKey::RandomOrderReposition,
|
||||
BoolKeyProto::ShiftPositionOfExistingCards => BoolKey::ShiftPositionOfExistingCards,
|
||||
BoolKeyProto::RenderLatex => BoolKey::RenderLatex,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ pub enum BoolKey {
|
||||
NewCardsIgnoreReviewLimit,
|
||||
PasteImagesAsPng,
|
||||
PasteStripsFormatting,
|
||||
RenderLatex,
|
||||
PreviewBothSides,
|
||||
RestorePositionBrowser,
|
||||
RestorePositionReviewer,
|
||||
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user