Compare commits
2 commits
5211821a84
...
cb0db7fae1
Author | SHA1 | Date | |
---|---|---|---|
cb0db7fae1 | |||
4ac7de48b0 |
6 changed files with 140 additions and 40 deletions
|
@ -18,13 +18,12 @@
|
|||
|
||||
from PySide6.QtCore import QObject, Slot, Property, QCoreApplication
|
||||
from PySide6.QtGui import QImage, QColor
|
||||
from PySide6.QtWidgets import QApplication, QMessageBox
|
||||
from PySide6.QtWidgets import QMessageBox
|
||||
|
||||
from os import path, remove
|
||||
from string import Template
|
||||
from tempfile import TemporaryDirectory
|
||||
from subprocess import Popen, TimeoutExpired, PIPE
|
||||
from platform import system
|
||||
from shutil import which
|
||||
from sys import argv
|
||||
|
||||
|
@ -36,6 +35,7 @@ If not found, it will send an alert to the user.
|
|||
LATEX_PATH = which('latex')
|
||||
DVIPNG_PATH = which('dvipng')
|
||||
PACKAGES = ["calligra", "amsfonts", "inputenc"]
|
||||
SHOW_GUI_MESSAGES = "--test-build" not in argv
|
||||
|
||||
DEFAULT_LATEX_DOC = Template(r"""
|
||||
\documentclass[]{minimal}
|
||||
|
@ -54,6 +54,20 @@ $$$$ $markup $$$$
|
|||
""")
|
||||
|
||||
|
||||
def show_message(msg: str) -> None:
|
||||
"""
|
||||
Shows a GUI message if GUI messages are enabled
|
||||
"""
|
||||
if SHOW_GUI_MESSAGES:
|
||||
QMessageBox.warning(None, "LogarithmPlotter - Latex", msg)
|
||||
|
||||
|
||||
class MissingPackageException(Exception): pass
|
||||
|
||||
|
||||
class RenderError(Exception): pass
|
||||
|
||||
|
||||
class Latex(QObject):
|
||||
"""
|
||||
Base class to convert Latex equations into PNG images with custom font color and size.
|
||||
|
@ -77,22 +91,20 @@ class Latex(QObject):
|
|||
valid_install = True
|
||||
if LATEX_PATH is None:
|
||||
print("No Latex installation found.")
|
||||
if "--test-build" not in argv:
|
||||
msg = QCoreApplication.translate("latex",
|
||||
"No Latex installation found.\nIf you already have a latex distribution installed, make sure it's installed on your path.\nOtherwise, you can download a Latex distribution like TeX Live at https://tug.org/texlive/.")
|
||||
QMessageBox.warning(None, "LogarithmPlotter - Latex setup", msg)
|
||||
show_message(msg)
|
||||
valid_install = False
|
||||
elif DVIPNG_PATH is None:
|
||||
print("DVIPNG not found.")
|
||||
if "--test-build" not in argv:
|
||||
msg = QCoreApplication.translate("latex",
|
||||
"DVIPNG was not found. Make sure you include it from your Latex distribution.")
|
||||
QMessageBox.warning(None, "LogarithmPlotter - Latex setup", msg)
|
||||
show_message(msg)
|
||||
valid_install = False
|
||||
else:
|
||||
try:
|
||||
self.render("", 14, QColor(0, 0, 0, 255))
|
||||
except Exception as e:
|
||||
except MissingPackageException:
|
||||
valid_install = False # Should have sent an error message if failed to render
|
||||
return valid_install
|
||||
|
||||
|
@ -105,7 +117,6 @@ class Latex(QObject):
|
|||
if self.latexSupported and not path.exists(export_path + ".png"):
|
||||
print("Rendering", latex_markup, export_path)
|
||||
# Generating file
|
||||
try:
|
||||
latex_path = path.join(self.tempdir.name, str(markup_hash))
|
||||
# If the formula is just recolored or the font is just changed, no need to recreate the DVI.
|
||||
if not path.exists(latex_path + ".dvi"):
|
||||
|
@ -117,8 +128,6 @@ class Latex(QObject):
|
|||
# self.convert_dvi_to_png(latex_path, export_path+"@2", font_size*2, color)
|
||||
# self.convert_dvi_to_png(latex_path, export_path+"@3", font_size*3, color)
|
||||
# self.convert_dvi_to_png(latex_path, export_path+"@4", font_size*4, color)
|
||||
except Exception as e: # One of the processes failed. A message will be sent every time.
|
||||
raise e
|
||||
img = QImage(export_path)
|
||||
# Small hack, not very optimized since we load the image twice, but you can't pass a QImage to QML and expect it to be loaded
|
||||
return f'{export_path}.png,{img.width()},{img.height()}'
|
||||
|
@ -147,7 +156,6 @@ class Latex(QObject):
|
|||
"""
|
||||
Creates a temporary latex document with base file_hash as file name and a given expression markup latex_markup.
|
||||
"""
|
||||
ltx_path = export_path + ".tex"
|
||||
f = open(export_path + ".tex", 'w')
|
||||
f.write(DEFAULT_LATEX_DOC.substitute(markup=latex_markup))
|
||||
f.close()
|
||||
|
@ -193,10 +201,10 @@ class Latex(QObject):
|
|||
output = str(out, 'utf8') + "\n" + str(err, 'utf8')
|
||||
msg = QCoreApplication.translate("latex",
|
||||
"An exception occured within the creation of the latex formula.\nProcess '{}' ended with a non-zero return code {}:\n\n{}\nPlease make sure your latex installation is correct and report a bug if so.")
|
||||
msg = msg.format(cmd, proc.returncode, output)
|
||||
QMessageBox.warning(None, "LogarithmPlotter - Latex", msg)
|
||||
raise Exception(f"{cmd} process exited with return code {str(proc.returncode)}:\n{str(out, 'utf8')}\n{str(err, 'utf8')}")
|
||||
except TimeoutExpired as e:
|
||||
show_message(msg.format(cmd, proc.returncode, output))
|
||||
raise RenderError(
|
||||
f"{cmd} process exited with return code {str(proc.returncode)}:\n{str(out, 'utf8')}\n{str(err, 'utf8')}")
|
||||
except TimeoutExpired:
|
||||
# Process timed out
|
||||
proc.kill()
|
||||
out, err = proc.communicate()
|
||||
|
@ -207,12 +215,12 @@ class Latex(QObject):
|
|||
# Package missing.
|
||||
msg = QCoreApplication.translate("latex",
|
||||
"Your LaTeX installation does not include some required packages:\n\n- {} (https://ctan.org/pkg/{})\n\nMake sure said package is installed, or disable the LaTeX rendering in LogarithmPlotter.")
|
||||
QMessageBox.warning(None, "LogarithmPlotter - Latex", msg.format(pkg, pkg))
|
||||
raise Exception("Latex: Missing package " + pkg)
|
||||
show_message(msg.format(pkg, pkg))
|
||||
raise MissingPackageException("Latex: Missing package " + pkg)
|
||||
msg = QCoreApplication.translate("latex",
|
||||
"An exception occured within the creation of the latex formula.\nProcess '{}' took too long to finish:\n{}\nPlease make sure your latex installation is correct and report a bug if so.")
|
||||
QMessageBox.warning(None, "LogarithmPlotter - Latex", msg.format(cmd, output))
|
||||
raise Exception(f"{cmd} process timed out:\n{output}")
|
||||
show_message(msg.format(cmd, output))
|
||||
raise RenderError(f"{cmd} process timed out:\n{output}")
|
||||
|
||||
def cleanup(self, export_path):
|
||||
"""
|
||||
|
|
|
@ -14,7 +14,8 @@ steps:
|
|||
- name: Tests
|
||||
image: ad5001/ubuntu-pyside6-xvfb:noble-6.7.2
|
||||
commands:
|
||||
- pytest --cov --cov-report term-missing
|
||||
- apt install -y texlive-base dvipng texlive-latex-extra # Install latex dependencies.
|
||||
- bash scripts/run-tests.sh
|
||||
- xvfb-run python3 run.py --test-build --no-check-for-updates
|
||||
- xvfb-run python3 run.py --test-build --no-check-for-updates ./ci/test1.lpf
|
||||
- xvfb-run python3 run.py --test-build --no-check-for-updates ./ci/test2.lpf
|
||||
|
|
21
poetry.lock
generated
21
poetry.lock
generated
|
@ -315,6 +315,25 @@ pytest = ">=4.6"
|
|||
[package.extras]
|
||||
testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-qt"
|
||||
version = "4.4.0"
|
||||
description = "pytest support for PyQt and PySide applications"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pytest-qt-4.4.0.tar.gz", hash = "sha256:76896142a940a4285339008d6928a36d4be74afec7e634577e842c9cc5c56844"},
|
||||
{file = "pytest_qt-4.4.0-py3-none-any.whl", hash = "sha256:001ed2f8641764b394cf286dc8a4203e40eaf9fff75bf0bfe5103f7f8d0c591d"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pluggy = ">=1.1"
|
||||
pytest = "*"
|
||||
|
||||
[package.extras]
|
||||
dev = ["pre-commit", "tox"]
|
||||
doc = ["sphinx", "sphinx-rtd-theme"]
|
||||
|
||||
[[package]]
|
||||
name = "pywin32-ctypes"
|
||||
version = "0.2.3"
|
||||
|
@ -402,4 +421,4 @@ type = ["pytest-mypy"]
|
|||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.9,<3.13"
|
||||
content-hash = "8ce304f6a3fbab24428232c1a7d0b59ea412094e82d6b8ce47e4d93462cc235a"
|
||||
content-hash = "4693a671e927103ceeb946f688b84fdc56b8b39b2cd772d8d32475e1236d8a07"
|
||||
|
|
|
@ -15,8 +15,5 @@ PySide6-Essentials = "^6.7.2"
|
|||
pyinstaller = "^6.10.0"
|
||||
pytest = "^8.3.3"
|
||||
pytest-cov = "^5.0.0"
|
||||
pytest-qt = "^4.4.0"
|
||||
stdeb = "^0.10.0"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
|
7
scripts/run-tests.sh
Normal file
7
scripts/run-tests.sh
Normal file
|
@ -0,0 +1,7 @@
|
|||
#!/bin/bash
|
||||
cd "$(dirname "$(readlink -f "$0" || realpath "$0")")/.."
|
||||
|
||||
# Run python tests
|
||||
pytest --cov --cov-report term-missing
|
||||
|
||||
|
68
tests/python/test_latex.py
Normal file
68
tests/python/test_latex.py
Normal file
|
@ -0,0 +1,68 @@
|
|||
import pytest
|
||||
from tempfile import TemporaryDirectory
|
||||
from shutil import which
|
||||
from os.path import exists
|
||||
from re import match
|
||||
from PySide6.QtGui import QColor
|
||||
|
||||
from LogarithmPlotter.util import latex
|
||||
|
||||
latex.SHOW_GUI_MESSAGES = False
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def latex_obj():
|
||||
directory = TemporaryDirectory()
|
||||
obj = latex.Latex(directory)
|
||||
if not obj.checkLatexInstallation():
|
||||
raise Exception("Cannot run LaTeX tests without a proper LaTeX installation. Make sure to install a LaTeX distribution, DVIPNG, and the calligra package, and run the tests again.")
|
||||
yield obj
|
||||
directory.cleanup()
|
||||
|
||||
|
||||
class TestLatex:
|
||||
def test_check_install(self, latex_obj: latex.Latex) -> None:
|
||||
assert latex_obj.latexSupported == True
|
||||
assert latex_obj.checkLatexInstallation() == True
|
||||
bkp = [latex.DVIPNG_PATH, latex.LATEX_PATH]
|
||||
# Check what happens when one is missing.
|
||||
latex.DVIPNG_PATH = None
|
||||
assert latex_obj.latexSupported == False
|
||||
assert latex_obj.checkLatexInstallation() == False
|
||||
latex.DVIPNG_PATH = bkp[0]
|
||||
latex.LATEX_PATH = None
|
||||
assert latex_obj.latexSupported == False
|
||||
assert latex_obj.checkLatexInstallation() == False
|
||||
# Reset
|
||||
[latex.DVIPNG_PATH, latex.LATEX_PATH] = bkp
|
||||
|
||||
def test_render(self, latex_obj: latex.Latex) -> None:
|
||||
result = latex_obj.render(r"\frac{d\sqrt{\mathrm{f}(x \times 2.3)}}{dx}", 14, QColor(0, 0, 0, 255))
|
||||
# Ensure result format
|
||||
assert type(result) == str
|
||||
[path, width, height] = result.split(",")
|
||||
assert exists(path)
|
||||
assert match(r"\d+", width)
|
||||
assert match(r"\d+", height)
|
||||
# Ensure it returns errors on invalid latex.
|
||||
with pytest.raises(latex.RenderError):
|
||||
latex_obj.render(r"\nonexistant", 14, QColor(0, 0, 0, 255))
|
||||
# Replace latex bin with one that returns errors
|
||||
bkp = latex.LATEX_PATH
|
||||
latex.LATEX_PATH = which("false")
|
||||
with pytest.raises(latex.RenderError):
|
||||
latex_obj.render(r"\mathrm{f}(x)", 14, QColor(0, 0, 0, 255))
|
||||
latex.LATEX_PATH = bkp
|
||||
|
||||
def test_prerendered(self, latex_obj: latex.Latex) -> None:
|
||||
args = [r"\frac{d\sqrt{\mathrm{f}(x \times 2.3)}}{dx}", 14, QColor(0, 0, 0, 255)]
|
||||
latex_obj.render(*args)
|
||||
prerendered = latex_obj.findPrerendered(*args)
|
||||
assert type(prerendered) == str
|
||||
[path, width, height] = prerendered.split(",")
|
||||
assert exists(path)
|
||||
assert match(r"\d+", width)
|
||||
assert match(r"\d+", height)
|
||||
prerendered2 = latex_obj.findPrerendered(args[0], args[1]+2, args[2])
|
||||
assert prerendered2 == ""
|
||||
|
Loading…
Reference in a new issue