Removed dependency on Sympy for subprocesses directly.
New dependencies: latex, dvipng. Slight changes to default for fonts to avoid too many anti aliasing issues. Also adds proper checks for latex installation.
This commit is contained in:
parent
8251504fbe
commit
2ce66df4dd
7 changed files with 155 additions and 26 deletions
|
@ -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])
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -16,23 +16,156 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
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))
|
||||
|
|
|
@ -3,7 +3,7 @@ Source: logarithmplotter
|
|||
Version: 0.1.9
|
||||
Architecture: all
|
||||
Maintainer: Ad5001 <mail@ad5001.eu>
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
|
2
setup.py
2
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',
|
||||
|
|
Loading…
Reference in a new issue