Adding experimental async LaTeX renderer (speeds up rendering ridiculously, but causes instability)
This commit is contained in:
parent
f734e40ad9
commit
cf73b35a9a
6 changed files with 50 additions and 12 deletions
|
@ -84,13 +84,21 @@ export class DialogInterface extends Interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LatexInterface extends Interface {
|
export class LatexInterface extends Interface {
|
||||||
|
supportsAsyncRender = BOOLEAN
|
||||||
/**
|
/**
|
||||||
* @param {string} markup - LaTeX markup to render
|
* @param {string} markup - LaTeX markup to render
|
||||||
* @param {number} fontSize - Font size (in pt) to render
|
* @param {number} fontSize - Font size (in pt) to render
|
||||||
* @param {string} color - Color of the text to render
|
* @param {string} color - Color of the text to render
|
||||||
* @returns {string} - Comma separated data of the image (source, width, height)
|
* @returns {string} - Comma separated data of the image (source, width, height)
|
||||||
*/
|
*/
|
||||||
render = FUNCTION
|
renderSync = FUNCTION
|
||||||
|
/**
|
||||||
|
* @param {string} markup - LaTeX markup to render
|
||||||
|
* @param {number} fontSize - Font size (in pt) to render
|
||||||
|
* @param {string} color - Color of the text to render
|
||||||
|
* @returns {Promise<string>} - Comma separated data of the image (source, width, height)
|
||||||
|
*/
|
||||||
|
renderAsync = FUNCTION
|
||||||
/**
|
/**
|
||||||
* @param {string} markup - LaTeX markup to render
|
* @param {string} markup - LaTeX markup to render
|
||||||
* @param {number} fontSize - Font size (in pt) to render
|
* @param {number} fontSize - Font size (in pt) to render
|
||||||
|
|
|
@ -112,7 +112,12 @@ class LatexAPI extends Module {
|
||||||
*/
|
*/
|
||||||
async requestAsyncRender(markup, fontSize, color) {
|
async requestAsyncRender(markup, fontSize, color) {
|
||||||
if(!this.initialized) throw new Error("Attempting requestAsyncRender before initialize!")
|
if(!this.initialized) throw new Error("Attempting requestAsyncRender before initialize!")
|
||||||
let args = this.#latex.render(markup, fontSize, color).split(",")
|
let render
|
||||||
|
if(this.#latex.supportsAsyncRender)
|
||||||
|
render = await this.#latex.renderAsync(markup, fontSize, color)
|
||||||
|
else
|
||||||
|
render = this.#latex.renderSync(markup, fontSize, color)
|
||||||
|
const args = render.split(",")
|
||||||
return new LatexRenderResult(...args)
|
return new LatexRenderResult(...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,8 +46,15 @@ class EnableLatex extends BoolSetting {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ENABLE_LATEX_ASYNC = new BoolSetting(
|
||||||
|
qsTranslate("general", "Enable asynchronous LaTeX renderer (experimental)"),
|
||||||
|
"enable_latex_async",
|
||||||
|
"new"
|
||||||
|
)
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
CHECK_FOR_UPDATES,
|
CHECK_FOR_UPDATES,
|
||||||
RESET_REDO_STACK,
|
RESET_REDO_STACK,
|
||||||
new EnableLatex()
|
new EnableLatex(),
|
||||||
|
ENABLE_LATEX_ASYNC
|
||||||
]
|
]
|
||||||
|
|
|
@ -26,6 +26,7 @@ DEFAULT_SETTINGS = {
|
||||||
"reset_redo_stack": True,
|
"reset_redo_stack": True,
|
||||||
"last_install_greet": "0",
|
"last_install_greet": "0",
|
||||||
"enable_latex": False,
|
"enable_latex": False,
|
||||||
|
"enable_latex_async": False,
|
||||||
"expression_editor": {
|
"expression_editor": {
|
||||||
"autoclose": True,
|
"autoclose": True,
|
||||||
"colorize": True,
|
"colorize": True,
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* 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 time import sleep
|
||||||
|
|
||||||
from PySide6.QtCore import QObject, Slot, Property, QCoreApplication, Signal
|
from PySide6.QtCore import QObject, Slot, Property, QCoreApplication, Signal
|
||||||
from PySide6.QtGui import QImage, QColor
|
from PySide6.QtGui import QImage, QColor
|
||||||
|
@ -27,6 +28,9 @@ from hashlib import sha512
|
||||||
from shutil import which
|
from shutil import which
|
||||||
from sys import argv
|
from sys import argv
|
||||||
|
|
||||||
|
from LogarithmPlotter.util import config
|
||||||
|
from LogarithmPlotter.util.promise import PyPromise
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Searches for a valid Latex and DVIPNG (http://savannah.nongnu.org/projects/dvipng/)
|
Searches for a valid Latex and DVIPNG (http://savannah.nongnu.org/projects/dvipng/)
|
||||||
installation and collects the binary path in the DVIPNG_PATH variable.
|
installation and collects the binary path in the DVIPNG_PATH variable.
|
||||||
|
@ -75,7 +79,6 @@ class Latex(QObject):
|
||||||
dvipng to be installed on the system.
|
dvipng to be installed on the system.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, cache_path):
|
def __init__(self, cache_path):
|
||||||
QObject.__init__(self)
|
QObject.__init__(self)
|
||||||
self.tempdir = path.join(cache_path, "latex")
|
self.tempdir = path.join(cache_path, "latex")
|
||||||
|
@ -85,6 +88,10 @@ class Latex(QObject):
|
||||||
def latexSupported(self) -> bool:
|
def latexSupported(self) -> bool:
|
||||||
return LATEX_PATH is not None and DVIPNG_PATH is not None
|
return LATEX_PATH is not None and DVIPNG_PATH is not None
|
||||||
|
|
||||||
|
@Property(bool)
|
||||||
|
def supportsAsyncRender(self) -> bool:
|
||||||
|
return config.getSetting("enable_latex_async")
|
||||||
|
|
||||||
@Slot(result=bool)
|
@Slot(result=bool)
|
||||||
def checkLatexInstallation(self) -> bool:
|
def checkLatexInstallation(self) -> bool:
|
||||||
"""
|
"""
|
||||||
|
@ -105,13 +112,20 @@ class Latex(QObject):
|
||||||
valid_install = False
|
valid_install = False
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
self.render("", 14, QColor(0, 0, 0, 255))
|
self.renderSync("", 14, QColor(0, 0, 0, 255))
|
||||||
except MissingPackageException:
|
except MissingPackageException:
|
||||||
valid_install = False # Should have sent an error message if failed to render
|
valid_install = False # Should have sent an error message if failed to render
|
||||||
return valid_install
|
return valid_install
|
||||||
|
|
||||||
|
@Slot(str, float, QColor, result=PyPromise)
|
||||||
|
def renderAsync(self, latex_markup: str, font_size: float, color: QColor) -> PyPromise:
|
||||||
|
"""
|
||||||
|
Prepares and renders a latex string into a png file asynchronously.
|
||||||
|
"""
|
||||||
|
return PyPromise(self.renderSync, [latex_markup, font_size, color])
|
||||||
|
|
||||||
@Slot(str, float, QColor, result=str)
|
@Slot(str, float, QColor, result=str)
|
||||||
def render(self, latex_markup: str, font_size: float, color: QColor) -> str:
|
def renderSync(self, latex_markup: str, font_size: float, color: QColor) -> str:
|
||||||
"""
|
"""
|
||||||
Prepares and renders a latex string into a png file.
|
Prepares and renders a latex string into a png file.
|
||||||
"""
|
"""
|
||||||
|
@ -124,12 +138,14 @@ class Latex(QObject):
|
||||||
if not path.exists(latex_path + ".dvi"):
|
if not path.exists(latex_path + ".dvi"):
|
||||||
self.create_latex_doc(latex_path, latex_markup)
|
self.create_latex_doc(latex_path, latex_markup)
|
||||||
self.convert_latex_to_dvi(latex_path)
|
self.convert_latex_to_dvi(latex_path)
|
||||||
self.cleanup(latex_path)
|
# self.cleanup(latex_path)
|
||||||
# Creating four pictures of different sizes to better handle dpi.
|
# Creating four pictures of different sizes to better handle dpi.
|
||||||
self.convert_dvi_to_png(latex_path, export_path, font_size, color)
|
self.convert_dvi_to_png(latex_path, export_path, font_size, color)
|
||||||
# self.convert_dvi_to_png(latex_path, export_path+"@2", font_size*2, color)
|
# 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+"@3", font_size*3, color)
|
||||||
# self.convert_dvi_to_png(latex_path, export_path+"@4", font_size*4, color)
|
# self.convert_dvi_to_png(latex_path, export_path+"@4", font_size*4, color)
|
||||||
|
else:
|
||||||
|
sleep(0)
|
||||||
img = QImage(export_path)
|
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
|
# 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()}'
|
return f'{export_path}.png,{img.width()},{img.height()}'
|
||||||
|
|
|
@ -30,15 +30,15 @@ class PyPromiseRunner(QRunnable):
|
||||||
"""
|
"""
|
||||||
QRunnable for running Promises in different threads.
|
QRunnable for running Promises in different threads.
|
||||||
"""
|
"""
|
||||||
def __init__(self, runner, promise):
|
def __init__(self, runner, promise, args):
|
||||||
QRunnable.__init__(self)
|
QRunnable.__init__(self)
|
||||||
self.runner = runner
|
self.runner = runner
|
||||||
self.promise = promise
|
self.promise = promise
|
||||||
print("Initialized", self.runner)
|
self.args = args
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
try:
|
try:
|
||||||
data = self.runner()
|
data = self.runner(*self.args)
|
||||||
if isinstance(data, QObject):
|
if isinstance(data, QObject):
|
||||||
data = data
|
data = data
|
||||||
elif type(data) in [int, str, float, bool, bytes]:
|
elif type(data) in [int, str, float, bool, bytes]:
|
||||||
|
@ -64,13 +64,13 @@ class PyPromise(QObject):
|
||||||
finished = Signal((QJSValue,), (QObject,))
|
finished = Signal((QJSValue,), (QObject,))
|
||||||
errored = Signal(Exception)
|
errored = Signal(Exception)
|
||||||
|
|
||||||
def __init__(self, to_run: Callable):
|
def __init__(self, to_run: Callable, args):
|
||||||
QObject.__init__(self)
|
QObject.__init__(self)
|
||||||
self._fulfills = []
|
self._fulfills = []
|
||||||
self._rejects = []
|
self._rejects = []
|
||||||
self.finished.connect(self._fulfill)
|
self.finished.connect(self._fulfill)
|
||||||
self.errored.connect(self._reject)
|
self.errored.connect(self._reject)
|
||||||
self._runner = PyPromiseRunner(to_run, self)
|
self._runner = PyPromiseRunner(to_run, self, args)
|
||||||
QThreadPool.globalInstance().start(self._runner)
|
QThreadPool.globalInstance().start(self._runner)
|
||||||
|
|
||||||
|
|
||||||
|
@ -88,6 +88,7 @@ class PyPromise(QObject):
|
||||||
self._rejects.append(PyJSValue(on_reject))
|
self._rejects.append(PyJSValue(on_reject))
|
||||||
elif isinstance(on_reject, Callable):
|
elif isinstance(on_reject, Callable):
|
||||||
self._rejects.append(on_reject)
|
self._rejects.append(on_reject)
|
||||||
|
return self
|
||||||
|
|
||||||
@Slot(QJSValue)
|
@Slot(QJSValue)
|
||||||
@Slot(QObject)
|
@Slot(QObject)
|
||||||
|
|
Loading…
Reference in a new issue