diff --git a/LogarithmPlotter/logarithmplotter.py b/LogarithmPlotter/logarithmplotter.py
index 56985e2..70073ef 100644
--- a/LogarithmPlotter/logarithmplotter.py
+++ b/LogarithmPlotter/logarithmplotter.py
@@ -18,8 +18,6 @@
from time import time
-start_time = time()
-
from PySide2.QtWidgets import QApplication
from PySide2.QtQml import QQmlApplicationEngine
from PySide2.QtCore import Qt, QTranslator, QLocale
@@ -29,6 +27,9 @@ from tempfile import TemporaryDirectory
from os import getcwd, chdir, environ, path, remove, close
from platform import release as os_release
from sys import platform, argv, version as sys_version, exit
+from sys import path as sys_path
+
+start_time = time()
# Create the temporary directory for saving copied screenshots and latex files
tempdir = TemporaryDirectory()
@@ -37,7 +38,6 @@ pwd = getcwd()
chdir(path.dirname(path.realpath(__file__)))
-from sys import path as sys_path
if path.realpath(path.join(getcwd(), "..")) not in sys_path:
sys_path.append(path.realpath(path.join(getcwd(), "..")))
@@ -85,11 +85,11 @@ def run():
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "settings", "custom")))
QIcon.setFallbackSearchPaths(icon_fallbacks);
+ QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
app = QApplication(argv)
app.setApplicationName("LogarithmPlotter")
app.setOrganizationName("Ad5001")
app.styleHints().setShowShortcutsInContextMenus(True)
- app.setAttribute(Qt.AA_EnableHighDpiScaling)
app.setWindowIcon(QIcon(path.realpath(path.join(getcwd(), "logarithmplotter.svg"))))
# Installing translators
@@ -109,7 +109,7 @@ def run():
engine = QQmlApplicationEngine()
global tmpfile
helper = Helper(pwd, tmpfile)
- latex = Latex(tempdir, app.palette())
+ latex = Latex(tempdir)
engine.rootContext().setContextProperty("Helper", helper)
engine.rootContext().setContextProperty("Latex", latex)
engine.rootContext().setContextProperty("TestBuild", "--test-build" in argv)
@@ -135,6 +135,8 @@ def run():
if platform == "darwin":
macOSFileOpenHandler.init_graphics(engine.rootObjects()[0])
+ latex.check_latex_install()
+
# Check for updates
if config.getSetting("check_for_updates"):
check_for_updates(__VERSION__, engine.rootObjects()[0])
diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogGraphCanvas.qml b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogGraphCanvas.qml
index a819da1..19b82e9 100644
--- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogGraphCanvas.qml
+++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogGraphCanvas.qml
@@ -200,7 +200,7 @@ Canvas {
// Reset
ctx.fillStyle = "#FFFFFF"
ctx.strokeStyle = "#000000"
- ctx.font = `${canvas.textsize-2}px sans-serif`
+ ctx.font = `${canvas.textsize}px sans-serif`
ctx.fillRect(0,0,width,height)
}
@@ -257,12 +257,12 @@ Canvas {
var axisxpx = y2px(0) // Y coordinate of X axis
// Labels
ctx.fillStyle = "#000000"
- ctx.font = `${canvas.textsize+2}px sans-serif`
+ ctx.font = `${canvas.textsize}px sans-serif`
ctx.fillText(ylabel, axisypx+10, 24)
var textSize = ctx.measureText(xlabel).width
ctx.fillText(xlabel, canvasSize.width-14-textSize, axisxpx-5)
// Axis graduation labels
- ctx.font = `${canvas.textsize-2}px sans-serif`
+ ctx.font = `${canvas.textsize-4}px sans-serif`
var txtMinus = ctx.measureText('-').width
if(showxgrad) {
diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Settings.qml b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Settings.qml
index 2115b6b..6bde410 100644
--- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Settings.qml
+++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Settings.qml
@@ -99,7 +99,7 @@ ScrollView {
Font size of the text that will be drawn into the canvas, provided from settings.
\sa Settings
*/
- property double textsize: 14
+ property double textsize: 18
/*!
\qmlproperty bool Settings::logscalex
true if the canvas should be in logarithmic mode, false otherwise.
diff --git a/LogarithmPlotter/util/latex.py b/LogarithmPlotter/util/latex.py
index 07b4b9d..e76fd3e 100644
--- a/LogarithmPlotter/util/latex.py
+++ b/LogarithmPlotter/util/latex.py
@@ -16,23 +16,156 @@
* along with this program. If not, see .
"""
-from PySide2.QtCore import QObject, Slot
+from PySide2.QtCore import QObject, Slot, Property, QCoreApplication
from PySide2.QtGui import QImage, QColor
-from PySide2.QtWidgets import QApplication
+from PySide2.QtWidgets import QApplication, QMessageBox
-from os import path
-from sympy import preview
+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
+"""
+Searches for a valid Latex and DVIPNG (http://savannah.nongnu.org/projects/dvipng/)
+installation and collects the binary path in the DVIPNG_PATH variable.
+If not found, it will send an alert to the user.
+"""
+LATEX_PATH = which('latex')
+DVIPNG_PATH = which('dvipng')
+#subprocess.run(["ls", "-l", "/dev/null"], capture_output=True)
+
+DEFAULT_LATEX_DOC = Template(r"""
+\documentclass[]{minimal}
+\usepackage[utf8]{inputenc}
+\usepackage{calligra}
+\usepackage{amsfonts}
+
+\title{}
+\author{}
+
+\begin{document}
+
+$$$$ $markup $$$$
+
+\end{document}
+""")
+
class Latex(QObject):
- def __init__(self, tempdir: str, palette):
+ """
+ Base class to convert Latex equations into PNG images with custom font color and size.
+ It doesn't have any python dependency, but requires a working latex installation and
+ dvipng to be installed on the system.
+ """
+ def __init__(self, tempdir: TemporaryDirectory):
QObject.__init__(self)
self.tempdir = tempdir
- self.palette = palette
- fg = self.palette.windowText().color().convertTo(QColor.Rgb)
+
+ def check_latex_install(self):
+ """
+ Checks if the current latex installation is valid.
+ """
+ if LATEX_PATH is None:
+ QMessageBox.warning(None, "LogarithmPlotter - Latex setup", 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/."))
+ elif DVIPNG_PATH is None:
+ QMessageBox.warning(None, "LogarithmPlotter - Latex setup", QCoreApplication.translate("latex", "DVIPNG was not found. Make sure you include it from your Latex distribution."))
+
+ @Property(bool)
+ def latexSupported(self):
+ return LATEX_PATH is not None and DVIPNG_PATH is not None
@Slot(str, float, QColor, result=str)
- def render(self, latexstring, font_size, color = True):
+ def render(self, latex_markup: str, font_size: float, color: QColor = True) -> str:
+ """
+ Renders a latex string into a png file.
+ """
+ export_path = path.join(self.tempdir.name, f'{hash(latex_markup)}_{font_size}_{color.rgb()}')
+ print(export_path)
+ if self.latexSupported and not path.exists(export_path + ".png"):
+ # Generating file
+ try:
+ self.create_latex_doc(export_path, latex_markup)
+ self.convert_latex_to_dvi(export_path)
+ self.convert_dvi_to_png(export_path, font_size, color)
+ self.cleanup(export_path)
+ except Exception as e: # One of the processes failed. A message will be sent every time.
+ raise e
+ img = QImage(export_path + ".png");
+ # 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()}'
+
+ def create_latex_doc(self, export_path: str, latex_markup: str):
+ """
+ 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()
+
+ def convert_latex_to_dvi(self, export_path: str):
+ """
+ Converts a DVI file to a PNG file.
+ """
+ self.run([
+ LATEX_PATH,
+ export_path + ".tex"
+ ])
+
+
+ def convert_dvi_to_png(self, export_path: str, font_size: float, color: QColor):
+ """
+ Converts a DVI file to a PNG file.
+ Documentation: https://linux.die.net/man/1/dvipng
+ """
+ fg = color.convertTo(QColor.Rgb)
+ fg = f'rgb {fg.redF()} {fg.greenF()} {fg.blueF()}'
+ depth = int(font_size * 72.27 / 100) * 10
+ self.run([
+ DVIPNG_PATH,
+ '-T', 'tight', # Make sure image borders are as tight around the equation as possible to avoid blank space.
+ '--truecolor', # Make sure it's rendered in 24 bit colors.
+ '-D',f'{depth}', # Depth of the image
+ '-bg', 'Transparent', # Transparent background
+ '-fg',f'{fg}', # Foreground of the wanted color.
+ f'{export_path}.dvi', # Input file
+ '-o',f'{export_path}.png', # Output file
+ ])
+
+ def run(self, process: list):
+ """
+ Runs a subprocess and handles exceptions and messages them to the user.
+ """
+ proc = Popen(process, stdout=PIPE, stderr=PIPE, cwd=self.tempdir.name)
+ try:
+ out, err = proc.communicate(timeout=5) # 5 seconds is already FAR too long.
+ if proc.returncode != 0:
+ # Process errored
+ QMessageBox.warning(None, "LogarithmPlotter - Latex",
+ QCoreApplication.translate("latex", "An exception occured within the creation of the latex formula.\nProcess '{}' ended with a non-zero return code {}:\n{}\nPlease make sure your latex installation is correct and report a bug if so.")
+ .format(" ".join(process), proc.returncode, str(out, 'utf8')+"\n"+str(err,'utf8')))
+ raise Exception(" ".join(process) + " process exited with return code " + str(proc.returncode) + ":\n" + str(out, 'utf8')+"\n"+str(err,'utf8'))
+ print(out)
+ except TimeoutExpired as e:
+ # Process timed out
+ proc.kill()
+ out, err = proc.communicate()
+ QMessageBox.warning(None, "LogarithmPlotter - Latex",
+ 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.")
+ .format(" ".join(process), str(out, 'utf8')+"\n"+str(err,'utf8')))
+ raise Exception(" ".join(process) + " process timed out:\n" + str(out, 'utf8')+"\n"+str(err,'utf8'))
+
+ def cleanup(self, export_path):
+ """
+ Removes Tex, auxiliary, logs and DVI temporary files.
+ """
+ for i in [".tex", ".dvi", ".aux", ".log"]:
+ remove(export_path + i)
+
+
+ @Slot(str, float, QColor, result=str)
+ def render_legacy(self, latexstring, font_size, color = True):
exprpath = path.join(self.tempdir.name, f'{hash(latexstring)}_{font_size}_{color.rgb()}.png')
print("Rendering", latexstring, exprpath)
if not path.exists(exprpath):
@@ -49,9 +182,3 @@ class Latex(QObject):
img = QImage(exprpath);
# 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'{exprpath},{img.width()},{img.height()}'
-
- @Slot(str)
- def copyLatexImageToClipboard(self, latexstring):
- global tempfile
- clipboard = QApplication.clipboard()
- clipboard.setImage(self.render(latexstring))
diff --git a/linux/debian/control b/linux/debian/control
index a4fe523..6379384 100644
--- a/linux/debian/control
+++ b/linux/debian/control
@@ -3,7 +3,7 @@ Source: logarithmplotter
Version: 0.1.9
Architecture: all
Maintainer: Ad5001
-Depends: python3, python3-pip, qml-module-qtquick-controls2 (>= 5.12.0), qml-module-qtmultimedia (>= 5.12.0), qml-module-qtgraphicaleffects (>= 5.12.0), qml-module-qtquick2 (>= 5.12.0), qml-module-qtqml-models2 (>= 5.12.0), qml-module-qtquick-controls (>= 5.12.0), python3-pyside2.qtcore (>= 5.12.0), python3-pyside2.qtqml (>= 5.12.0), python3-pyside2.qtgui (>= 5.12.0), python3-pyside2.qtquick (>= 5.12.0), python3-pyside2.qtwidgets (>= 5.12.0), python3-pyside2.qtmultimedia (>= 5.12.0), python3-pyside2.qtnetwork (>= 5.12.0), python3-sympy
+Depends: python3, python3-pip, qml-module-qtquick-controls2 (>= 5.12.0), qml-module-qtmultimedia (>= 5.12.0), qml-module-qtgraphicaleffects (>= 5.12.0), qml-module-qtquick2 (>= 5.12.0), qml-module-qtqml-models2 (>= 5.12.0), qml-module-qtquick-controls (>= 5.12.0), python3-pyside2.qtcore (>= 5.12.0), python3-pyside2.qtqml (>= 5.12.0), python3-pyside2.qtgui (>= 5.12.0), python3-pyside2.qtquick (>= 5.12.0), python3-pyside2.qtwidgets (>= 5.12.0), python3-pyside2.qtmultimedia (>= 5.12.0), python3-pyside2.qtnetwork (>= 5.12.0), texlive-latex-base, dvipng
Build-Depends: debhelper (>=11~), dh-python, dpkg-dev (>= 1.16.1~), python-setuptools, python3-all-dev (>=3.6)
Section: science
diff --git a/linux/debian/depends b/linux/debian/depends
index 725059d..53dcda3 100644
--- a/linux/debian/depends
+++ b/linux/debian/depends
@@ -1 +1 @@
-python3-pip, qml-module-qtquick-controls2 (>= 5.12.0), qml-module-qtmultimedia (>= 5.12.0), qml-module-qtgraphicaleffects (>= 5.12.0), qml-module-qtquick2 (>= 5.12.0), qml-module-qtqml-models2 (>= 5.12.0), qml-module-qtquick-controls (>= 5.12.0), python3-pyside2.qtcore (>= 5.12.0), python3-pyside2.qtqml (>= 5.12.0), python3-pyside2.qtgui (>= 5.12.0), python3-pyside2.qtquick (>= 5.12.0), python3-pyside2.qtwidgets (>= 5.12.0), python3-pyside2.qtmultimedia (>= 5.12.0), python3-pyside2.qtnetwork (>= 5.12.0), python3-sympy
+python3-pip, qml-module-qtquick-controls2 (>= 5.12.0), qml-module-qtmultimedia (>= 5.12.0), qml-module-qtgraphicaleffects (>= 5.12.0), qml-module-qtquick2 (>= 5.12.0), qml-module-qtqml-models2 (>= 5.12.0), qml-module-qtquick-controls (>= 5.12.0), python3-pyside2.qtcore (>= 5.12.0), python3-pyside2.qtqml (>= 5.12.0), python3-pyside2.qtgui (>= 5.12.0), python3-pyside2.qtquick (>= 5.12.0), python3-pyside2.qtwidgets (>= 5.12.0), python3-pyside2.qtmultimedia (>= 5.12.0), python3-pyside2.qtnetwork (>= 5.12.0), texlive-latex-base, dvipng
diff --git a/setup.py b/setup.py
index b7817bf..20568b5 100644
--- a/setup.py
+++ b/setup.py
@@ -127,7 +127,7 @@ if sys.platform == 'linux':
os.remove(os.environ["PREFIX"] + '/icons/hicolor/scalable/apps/logplotter.svg')
setuptools.setup(
- install_requires=([] if "FLATPAK_INSTALL" in os.environ else ["PySide2", "sympy"]),
+ install_requires=([] if "FLATPAK_INSTALL" in os.environ else ["PySide2"]),
python_requires='>=3.8',
name='logarithmplotter',