Compare commits
3 commits
650e43894c
...
ec90779912
Author | SHA1 | Date | |
---|---|---|---|
ec90779912 | |||
2ce66df4dd | |||
8251504fbe |
11 changed files with 200 additions and 72 deletions
|
@ -18,8 +18,6 @@
|
||||||
|
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
start_time = time()
|
|
||||||
|
|
||||||
from PySide2.QtWidgets import QApplication
|
from PySide2.QtWidgets import QApplication
|
||||||
from PySide2.QtQml import QQmlApplicationEngine
|
from PySide2.QtQml import QQmlApplicationEngine
|
||||||
from PySide2.QtCore import Qt, QTranslator, QLocale
|
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 os import getcwd, chdir, environ, path, remove, close
|
||||||
from platform import release as os_release
|
from platform import release as os_release
|
||||||
from sys import platform, argv, version as sys_version, exit
|
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
|
# Create the temporary directory for saving copied screenshots and latex files
|
||||||
tempdir = TemporaryDirectory()
|
tempdir = TemporaryDirectory()
|
||||||
|
@ -37,7 +38,6 @@ pwd = getcwd()
|
||||||
|
|
||||||
chdir(path.dirname(path.realpath(__file__)))
|
chdir(path.dirname(path.realpath(__file__)))
|
||||||
|
|
||||||
from sys import path as sys_path
|
|
||||||
if path.realpath(path.join(getcwd(), "..")) not in sys_path:
|
if path.realpath(path.join(getcwd(), "..")) not in sys_path:
|
||||||
sys_path.append(path.realpath(path.join(getcwd(), "..")))
|
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")))
|
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "settings", "custom")))
|
||||||
QIcon.setFallbackSearchPaths(icon_fallbacks);
|
QIcon.setFallbackSearchPaths(icon_fallbacks);
|
||||||
|
|
||||||
|
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
|
||||||
app = QApplication(argv)
|
app = QApplication(argv)
|
||||||
app.setApplicationName("LogarithmPlotter")
|
app.setApplicationName("LogarithmPlotter")
|
||||||
app.setOrganizationName("Ad5001")
|
app.setOrganizationName("Ad5001")
|
||||||
app.styleHints().setShowShortcutsInContextMenus(True)
|
app.styleHints().setShowShortcutsInContextMenus(True)
|
||||||
app.setAttribute(Qt.AA_EnableHighDpiScaling)
|
|
||||||
app.setWindowIcon(QIcon(path.realpath(path.join(getcwd(), "logarithmplotter.svg"))))
|
app.setWindowIcon(QIcon(path.realpath(path.join(getcwd(), "logarithmplotter.svg"))))
|
||||||
|
|
||||||
# Installing translators
|
# Installing translators
|
||||||
|
@ -109,7 +109,7 @@ def run():
|
||||||
engine = QQmlApplicationEngine()
|
engine = QQmlApplicationEngine()
|
||||||
global tmpfile
|
global tmpfile
|
||||||
helper = Helper(pwd, tmpfile)
|
helper = Helper(pwd, tmpfile)
|
||||||
latex = Latex(tempdir, app.palette())
|
latex = Latex(tempdir)
|
||||||
engine.rootContext().setContextProperty("Helper", helper)
|
engine.rootContext().setContextProperty("Helper", helper)
|
||||||
engine.rootContext().setContextProperty("Latex", latex)
|
engine.rootContext().setContextProperty("Latex", latex)
|
||||||
engine.rootContext().setContextProperty("TestBuild", "--test-build" in argv)
|
engine.rootContext().setContextProperty("TestBuild", "--test-build" in argv)
|
||||||
|
@ -135,6 +135,8 @@ def run():
|
||||||
if platform == "darwin":
|
if platform == "darwin":
|
||||||
macOSFileOpenHandler.init_graphics(engine.rootObjects()[0])
|
macOSFileOpenHandler.init_graphics(engine.rootObjects()[0])
|
||||||
|
|
||||||
|
latex.check_latex_install()
|
||||||
|
|
||||||
# Check for updates
|
# Check for updates
|
||||||
if config.getSetting("check_for_updates"):
|
if config.getSetting("check_for_updates"):
|
||||||
check_for_updates(__VERSION__, engine.rootObjects()[0])
|
check_for_updates(__VERSION__, engine.rootObjects()[0])
|
||||||
|
|
|
@ -200,7 +200,7 @@ Canvas {
|
||||||
// Reset
|
// Reset
|
||||||
ctx.fillStyle = "#FFFFFF"
|
ctx.fillStyle = "#FFFFFF"
|
||||||
ctx.strokeStyle = "#000000"
|
ctx.strokeStyle = "#000000"
|
||||||
ctx.font = `${canvas.textsize-2}px sans-serif`
|
ctx.font = `${canvas.textsize}px sans-serif`
|
||||||
ctx.fillRect(0,0,width,height)
|
ctx.fillRect(0,0,width,height)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,12 +257,12 @@ Canvas {
|
||||||
var axisxpx = y2px(0) // Y coordinate of X axis
|
var axisxpx = y2px(0) // Y coordinate of X axis
|
||||||
// Labels
|
// Labels
|
||||||
ctx.fillStyle = "#000000"
|
ctx.fillStyle = "#000000"
|
||||||
ctx.font = `${canvas.textsize+2}px sans-serif`
|
ctx.font = `${canvas.textsize}px sans-serif`
|
||||||
ctx.fillText(ylabel, axisypx+10, 24)
|
ctx.fillText(ylabel, axisypx+10, 24)
|
||||||
var textSize = ctx.measureText(xlabel).width
|
var textSize = ctx.measureText(xlabel).width
|
||||||
ctx.fillText(xlabel, canvasSize.width-14-textSize, axisxpx-5)
|
ctx.fillText(xlabel, canvasSize.width-14-textSize, axisxpx-5)
|
||||||
// Axis graduation labels
|
// Axis graduation labels
|
||||||
ctx.font = `${canvas.textsize-2}px sans-serif`
|
ctx.font = `${canvas.textsize-4}px sans-serif`
|
||||||
|
|
||||||
var txtMinus = ctx.measureText('-').width
|
var txtMinus = ctx.measureText('-').width
|
||||||
if(showxgrad) {
|
if(showxgrad) {
|
||||||
|
|
|
@ -99,7 +99,7 @@ ScrollView {
|
||||||
Font size of the text that will be drawn into the canvas, provided from settings.
|
Font size of the text that will be drawn into the canvas, provided from settings.
|
||||||
\sa Settings
|
\sa Settings
|
||||||
*/
|
*/
|
||||||
property double textsize: 14
|
property double textsize: 18
|
||||||
/*!
|
/*!
|
||||||
\qmlproperty bool Settings::logscalex
|
\qmlproperty bool Settings::logscalex
|
||||||
true if the canvas should be in logarithmic mode, false otherwise.
|
true if the canvas should be in logarithmic mode, false otherwise.
|
||||||
|
|
|
@ -21,6 +21,8 @@
|
||||||
.import "common.js" as C
|
.import "common.js" as C
|
||||||
.import "expression.js" as Expr
|
.import "expression.js" as Expr
|
||||||
.import "../utils.js" as Utils
|
.import "../utils.js" as Utils
|
||||||
|
.import "../math/latex.js" as Latex
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents mathematical object for sequences.
|
* Represents mathematical object for sequences.
|
||||||
|
@ -32,9 +34,13 @@ class Sequence extends Expr.Expression {
|
||||||
this.name = name
|
this.name = name
|
||||||
this.baseValues = baseValues
|
this.baseValues = baseValues
|
||||||
this.calcValues = Object.assign({}, baseValues)
|
this.calcValues = Object.assign({}, baseValues)
|
||||||
|
this.latexValues = Object.assign({}, baseValues)
|
||||||
for(var n in this.calcValues)
|
for(var n in this.calcValues)
|
||||||
if(['string', 'number'].includes(typeof this.calcValues[n]))
|
if(['string', 'number'].includes(typeof this.calcValues[n])) {
|
||||||
this.calcValues[n] = C.parser.parse(this.calcValues[n].toString()).simplify().evaluate(C.evalVariables)
|
let parsed = C.parser.parse(this.calcValues[n].toString()).simplify()
|
||||||
|
this.latexValues[n] = Latex.expressionToLatex(parsed.tokens)
|
||||||
|
this.calcValues[n] = parsed.evaluate(C.evalVariables)
|
||||||
|
}
|
||||||
this.valuePlus = parseInt(valuePlus)
|
this.valuePlus = parseInt(valuePlus)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,4 +81,15 @@ class Sequence extends Expr.Expression {
|
||||||
).join('; ')
|
).join('; ')
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toLatexString(forceSign=false) {
|
||||||
|
var str = this.latexMarkup
|
||||||
|
if(str[0] != '-' && forceSign) str = '+' + str
|
||||||
|
var subtxt = '_{n' + (this.valuePlus == 0 ? '' : '+' + this.valuePlus) + '}'
|
||||||
|
var ret = `\\begin{array}{l}${Latex.variable(this.name)}${subtxt} = ${str}${this.latexValues.length == 0 ? '' : "\n"}\\\\`
|
||||||
|
ret += Object.keys(this.latexValues).map(
|
||||||
|
n => `${this.name}_{${n}} = ${this.latexValues[n]}`
|
||||||
|
).join('; ') + "\\end{array}"
|
||||||
|
return ret
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,19 +23,13 @@
|
||||||
.import "../mathlib.js" as MathLib
|
.import "../mathlib.js" as MathLib
|
||||||
.import "../historylib.js" as HistoryLib
|
.import "../historylib.js" as HistoryLib
|
||||||
.import "../parameters.js" as P
|
.import "../parameters.js" as P
|
||||||
|
.import "../math/latex.js" as Latex
|
||||||
|
|
||||||
|
|
||||||
class PhaseBode extends Common.ExecutableObject {
|
class PhaseBode extends Common.ExecutableObject {
|
||||||
static type(){return 'Phase Bode'}
|
static type(){return 'Phase Bode'}
|
||||||
static displayType(){return qsTr('Bode Phase')}
|
static displayType(){return qsTr('Bode Phase')}
|
||||||
static displayTypeMultiple(){return qsTr('Bode Phases')}
|
static displayTypeMultiple(){return qsTr('Bode Phases')}
|
||||||
/*static properties() {return {
|
|
||||||
'om_0': new P.ObjectType('Point'),
|
|
||||||
'phase': 'Expression',
|
|
||||||
'unit': new P.Enum('°', 'deg', 'rad'),
|
|
||||||
'labelPosition': new P.Enum('above', 'below', 'left', 'right', 'above-left', 'above-right', 'below-left', 'below-right'),
|
|
||||||
'labelX': 'number'
|
|
||||||
}}*/
|
|
||||||
static properties() {return {
|
static properties() {return {
|
||||||
[QT_TRANSLATE_NOOP('prop','om_0')]: new P.ObjectType('Point'),
|
[QT_TRANSLATE_NOOP('prop','om_0')]: new P.ObjectType('Point'),
|
||||||
[QT_TRANSLATE_NOOP('prop','phase')]: 'Expression',
|
[QT_TRANSLATE_NOOP('prop','phase')]: 'Expression',
|
||||||
|
@ -82,6 +76,10 @@ class PhaseBode extends Common.ExecutableObject {
|
||||||
return `${this.name}: ${this.phase.toString(true)}${this.unit} at ${this.om_0.name} = ${this.om_0.x}`
|
return `${this.name}: ${this.phase.toString(true)}${this.unit} at ${this.om_0.name} = ${this.om_0.x}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getLatexString() {
|
||||||
|
return `${Latex.variable(this.name)}: ${this.phase.latexMarkup}\\textrm{${this.unit} at }${Latex.variable(this.om_0.name)} = ${this.om_0.x.latexMarkup}`
|
||||||
|
}
|
||||||
|
|
||||||
execute(x=1) {
|
execute(x=1) {
|
||||||
if(typeof x == 'string') x = MathLib.executeExpression(x)
|
if(typeof x == 'string') x = MathLib.executeExpression(x)
|
||||||
if(x < this.om_0.x) {
|
if(x < this.om_0.x) {
|
||||||
|
@ -120,37 +118,7 @@ class PhaseBode extends Common.ExecutableObject {
|
||||||
canvas.drawLine(ctx, Math.max(0, baseX), augmtY, canvas.canvasSize.width, augmtY)
|
canvas.drawLine(ctx, Math.max(0, baseX), augmtY, canvas.canvasSize.width, augmtY)
|
||||||
|
|
||||||
// Label
|
// Label
|
||||||
var text = this.getLabel()
|
this.drawLabel(canvas, ctx, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX)))
|
||||||
ctx.font = `${canvas.textsize}px sans-serif`
|
|
||||||
var textSize = canvas.measureText(ctx, text)
|
|
||||||
var posX = canvas.x2px(this.labelX)
|
|
||||||
var posY = canvas.y2px(this.execute(this.labelX))
|
|
||||||
switch(this.labelPosition) {
|
|
||||||
case 'above':
|
|
||||||
canvas.drawVisibleText(ctx, text, posX-textSize.width/2, posY-textSize.height)
|
|
||||||
break;
|
|
||||||
case 'below':
|
|
||||||
canvas.drawVisibleText(ctx, text, posX-textSize.width/2, posY+textSize.height)
|
|
||||||
break;
|
|
||||||
case 'left':
|
|
||||||
canvas.drawVisibleText(ctx, text, posX-textSize.width, posY-textSize.height/2)
|
|
||||||
break;
|
|
||||||
case 'right':
|
|
||||||
canvas.drawVisibleText(ctx, text, posX, posY-textSize.height/2)
|
|
||||||
break;
|
|
||||||
case 'above-left':
|
|
||||||
canvas.drawVisibleText(ctx, text, posX-textSize.width, posY-textSize.height)
|
|
||||||
break;
|
|
||||||
case 'above-right':
|
|
||||||
canvas.drawVisibleText(ctx, text, posX, posY-textSize.height)
|
|
||||||
break;
|
|
||||||
case 'below-left':
|
|
||||||
canvas.drawVisibleText(ctx, text, posX-textSize.width, posY+textSize.height)
|
|
||||||
break;
|
|
||||||
case 'below-right':
|
|
||||||
canvas.drawVisibleText(ctx, text, posX, posY+textSize.height)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
.import "function.js" as F
|
.import "function.js" as F
|
||||||
.import "../mathlib.js" as MathLib
|
.import "../mathlib.js" as MathLib
|
||||||
.import "../parameters.js" as P
|
.import "../parameters.js" as P
|
||||||
|
.import "../math/latex.js" as Latex
|
||||||
|
|
||||||
|
|
||||||
class Sequence extends Common.ExecutableObject {
|
class Sequence extends Common.ExecutableObject {
|
||||||
|
@ -76,11 +77,14 @@ class Sequence extends Common.ExecutableObject {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
getReadableString() {
|
getReadableString() {
|
||||||
return this.sequence.toString()
|
return this.sequence.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getLatexString() {
|
||||||
|
return this.sequence.toLatexString()
|
||||||
|
}
|
||||||
|
|
||||||
execute(x = 1) {
|
execute(x = 1) {
|
||||||
if(x % 1 == 0)
|
if(x % 1 == 0)
|
||||||
return this.sequence.execute(x)
|
return this.sequence.execute(x)
|
||||||
|
@ -103,7 +107,17 @@ class Sequence extends Common.ExecutableObject {
|
||||||
return this.getReadableString()
|
return this.getReadableString()
|
||||||
case 'null':
|
case 'null':
|
||||||
return ''
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getLatexLabel() {
|
||||||
|
switch(this.labelContent) {
|
||||||
|
case 'name':
|
||||||
|
return `(${Latex.variable(this.name)}_n)`
|
||||||
|
case 'name + value':
|
||||||
|
return this.getLatexString()
|
||||||
|
case 'null':
|
||||||
|
return ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,7 +125,8 @@ class Sequence extends Common.ExecutableObject {
|
||||||
F.Function.drawFunction(canvas, ctx, this.sequence, canvas.logscalex ? MathLib.Domain.NE : MathLib.Domain.N, MathLib.Domain.R, this.drawPoints, this.drawDashedLines)
|
F.Function.drawFunction(canvas, ctx, this.sequence, canvas.logscalex ? MathLib.Domain.NE : MathLib.Domain.N, MathLib.Domain.R, this.drawPoints, this.drawDashedLines)
|
||||||
|
|
||||||
// Label
|
// Label
|
||||||
var text = this.getLabel()
|
this.drawLabel(canvas, ctx, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX)))
|
||||||
|
/*var text = this.getLabel()
|
||||||
ctx.font = `${canvas.textsize}px sans-serif`
|
ctx.font = `${canvas.textsize}px sans-serif`
|
||||||
var textSize = canvas.measureText(ctx, text)
|
var textSize = canvas.measureText(ctx, text)
|
||||||
var posX = canvas.x2px(this.labelX)
|
var posX = canvas.x2px(this.labelX)
|
||||||
|
@ -141,7 +156,7 @@ class Sequence extends Common.ExecutableObject {
|
||||||
case 'below-right':
|
case 'below-right':
|
||||||
canvas.drawVisibleText(ctx, text, posX, posY+textSize.height)
|
canvas.drawVisibleText(ctx, text, posX, posY+textSize.height)
|
||||||
break;
|
break;
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,23 +16,156 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* 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.QtGui import QImage, QColor
|
||||||
from PySide2.QtWidgets import QApplication
|
from PySide2.QtWidgets import QApplication, QMessageBox
|
||||||
|
|
||||||
from os import path
|
from os import path, remove
|
||||||
from sympy import preview
|
from string import Template
|
||||||
from tempfile import TemporaryDirectory
|
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):
|
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)
|
QObject.__init__(self)
|
||||||
self.tempdir = tempdir
|
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)
|
@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')
|
exprpath = path.join(self.tempdir.name, f'{hash(latexstring)}_{font_size}_{color.rgb()}.png')
|
||||||
print("Rendering", latexstring, exprpath)
|
print("Rendering", latexstring, exprpath)
|
||||||
if not path.exists(exprpath):
|
if not path.exists(exprpath):
|
||||||
|
@ -49,9 +182,3 @@ class Latex(QObject):
|
||||||
img = QImage(exprpath);
|
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
|
# 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()}'
|
return f'{exprpath},{img.width()},{img.height()}'
|
||||||
|
|
||||||
@Slot(str)
|
|
||||||
def copyLatexImageToClipboard(self, latexstring):
|
|
||||||
global tempfile
|
|
||||||
clipboard = QApplication.clipboard()
|
|
||||||
clipboard.setImage(self.render(latexstring))
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ steps:
|
||||||
- name: Linux test
|
- name: Linux test
|
||||||
image: ad5001/ubuntu-pyside2-xvfb:hirsute-5.15.2
|
image: ad5001/ubuntu-pyside2-xvfb:hirsute-5.15.2
|
||||||
commands:
|
commands:
|
||||||
- pip3 install sympy
|
- apt install texlive-latex-base dvipng
|
||||||
- xvfb-run python3 run.py --test-build --no-check-for-updates
|
- 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/test1.lpf
|
||||||
- xvfb-run python3 run.py --test-build --no-check-for-updates ./ci/test2.lpf
|
- xvfb-run python3 run.py --test-build --no-check-for-updates ./ci/test2.lpf
|
||||||
|
@ -25,7 +25,6 @@ steps:
|
||||||
image: ad5001/ubuntu-pyside2-xvfb-wine:win7-5.15.2
|
image: ad5001/ubuntu-pyside2-xvfb-wine:win7-5.15.2
|
||||||
commands:
|
commands:
|
||||||
- # For some reason, launching GUI apps with wine, even with xvfb-run, fails.
|
- # For some reason, launching GUI apps with wine, even with xvfb-run, fails.
|
||||||
- pip install sympy
|
|
||||||
- xvfb-run python run.py --test-build --no-check-for-updates
|
- xvfb-run python run.py --test-build --no-check-for-updates
|
||||||
- xvfb-run python run.py --test-build --no-check-for-updates ./ci/test1.lpf
|
- xvfb-run python run.py --test-build --no-check-for-updates ./ci/test1.lpf
|
||||||
- xvfb-run python run.py --test-build --no-check-for-updates ./ci/test2.lpf
|
- xvfb-run python run.py --test-build --no-check-for-updates ./ci/test2.lpf
|
||||||
|
|
|
@ -3,7 +3,7 @@ Source: logarithmplotter
|
||||||
Version: 0.1.9
|
Version: 0.1.9
|
||||||
Architecture: all
|
Architecture: all
|
||||||
Maintainer: Ad5001 <mail@ad5001.eu>
|
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)
|
Build-Depends: debhelper (>=11~), dh-python, dpkg-dev (>= 1.16.1~), python-setuptools, python3-all-dev (>=3.6)
|
||||||
Section: science
|
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')
|
os.remove(os.environ["PREFIX"] + '/icons/hicolor/scalable/apps/logplotter.svg')
|
||||||
|
|
||||||
setuptools.setup(
|
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',
|
python_requires='>=3.8',
|
||||||
|
|
||||||
name='logarithmplotter',
|
name='logarithmplotter',
|
||||||
|
|
Loading…
Reference in a new issue