Adding experimental async LaTeX renderer (speeds up rendering ridiculously, but causes instability)

This commit is contained in:
Ad5001 2024-10-15 03:01:27 +02:00
parent f734e40ad9
commit cf73b35a9a
Signed by: Ad5001
GPG key ID: EF45F9C6AFE20160
6 changed files with 50 additions and 12 deletions

View file

@ -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

View file

@ -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)
} }

View file

@ -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
] ]

View file

@ -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,

View file

@ -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()}'

View file

@ -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)