Starting async LaTeX rendering.
This commit is contained in:
parent
c9a597ea82
commit
8d6891c4f0
5 changed files with 110 additions and 33 deletions
|
@ -100,10 +100,10 @@ Item {
|
|||
anchors.left: parent.left
|
||||
visible: Modules.Latex.enabled
|
||||
property double depth: Screen.devicePixelRatio
|
||||
property var ltxInfo: visible ? Latex.render(obj.getLatexString(), depth*(parent.font.pixelSize+2), parent.color).split(",") : ["","0","0"]
|
||||
source: visible ? ltxInfo[0] : ""
|
||||
width: parseInt(ltxInfo[1])/depth
|
||||
height: parseInt(ltxInfo[2])/depth
|
||||
property var ltxInfo: visible ? Modules.Latex.renderSync(obj.getLatexString(), depth*(parent.font.pixelSize+2), parent.color) : { source: "", width: 0, height: 0 }
|
||||
source: visible ? ltxInfo.source : ""
|
||||
width: ltxInfo.width/depth
|
||||
height: ltxInfo.height/depth
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
|
|
|
@ -16,9 +16,10 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Module} from "./modules.mjs"
|
||||
import {textsup} from "./utils.mjs"
|
||||
import {Expression} from "./mathlib.mjs"
|
||||
import { Module } from "./modules.mjs"
|
||||
import { textsup } from "./utils.mjs"
|
||||
import { Expression } from "./mathlib.mjs"
|
||||
import Latex from "./math/latex.mjs"
|
||||
|
||||
|
||||
class CanvasAPI extends Module {
|
||||
|
@ -168,6 +169,8 @@ class CanvasAPI extends Module {
|
|||
obj.draw(this)
|
||||
} catch(e) {
|
||||
// Drawing throws an error. Generally, it's due to a new modification (or the opening of a file)
|
||||
console.error(e)
|
||||
console.log(e.stack)
|
||||
this._drawingErrorDialog.showDialog(objType, obj.name, e.message)
|
||||
Modules.History.undo()
|
||||
}
|
||||
|
@ -452,23 +455,24 @@ class CanvasAPI extends Module {
|
|||
* Renders latex markup ltxText to an image and loads it. Returns a dictionary with three values: source, width and height.
|
||||
* @param {string} ltxText
|
||||
* @param {string} color
|
||||
* @param {function({width: number, height: number, source: string})} callback
|
||||
* @param {function(LatexRenderResult|{width: number, height: number, source: string})} callback
|
||||
*/
|
||||
renderLatexImage(ltxText, color, callback) {
|
||||
let [ltxSrc, ltxWidth, ltxHeight] = Latex.render(ltxText, this.textsize, color).split(",")
|
||||
let imgData = {
|
||||
"source": ltxSrc,
|
||||
"width": parseFloat(ltxWidth),
|
||||
"height": parseFloat(ltxHeight)
|
||||
};
|
||||
if(!this._canvas.isImageLoaded(ltxSrc) && !this._canvas.isImageLoading(ltxSrc)){
|
||||
// Wait until the image is loaded to callback.
|
||||
this._canvas.loadImage(ltxSrc)
|
||||
this._canvas.imageLoaders[ltxSrc] = [callback, imgData]
|
||||
} else {
|
||||
// Callback directly
|
||||
callback(imgData)
|
||||
const onRendered = (imgData) => {
|
||||
if(!this._canvas.isImageLoaded(imgData.source) && !this._canvas.isImageLoading(imgData.source)){
|
||||
// Wait until the image is loaded to callback.
|
||||
this._canvas.loadImage(imgData.source)
|
||||
this._canvas.imageLoaders[imgData.source] = [callback, imgData]
|
||||
} else {
|
||||
// Callback directly
|
||||
callback(imgData)
|
||||
}
|
||||
}
|
||||
const prerendered = Latex.findPrerendered(ltxText, this.textsize, color)
|
||||
if(prerendered !== null)
|
||||
onRendered(prerendered)
|
||||
else
|
||||
Latex.requestAsyncRender(ltxText, this.textsize, color).then(onRendered)
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -519,4 +523,4 @@ class CanvasAPI extends Module {
|
|||
|
||||
/** @type {CanvasAPI} */
|
||||
Modules.Canvas = Modules.Canvas || new CanvasAPI()
|
||||
export const API = Modules.Canvas
|
||||
export const API = Modules.Canvas
|
||||
|
|
|
@ -115,12 +115,12 @@ export class Action {
|
|||
if(!Latex.enabled)
|
||||
throw new Error("Cannot render an item as LaTeX when LaTeX is disabled.")
|
||||
let imgDepth = Modules.History.imageDepth
|
||||
let [src, width, height] = Latex.render(
|
||||
let { source, width, height } = Latex.renderSync(
|
||||
latexString,
|
||||
imgDepth * (Modules.History.fontSize + 2),
|
||||
Modules.History.themeTextColor
|
||||
).split(",")
|
||||
return `<img src="${src}" width="${parseInt(width)/imgDepth}" height="${parseInt(height)/imgDepth}" style="vertical-align: middle"/>`
|
||||
)
|
||||
return `<img src="${source}" width="${width/imgDepth}" height="${height/imgDepth}" style="vertical-align: middle"/>`
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -39,6 +39,20 @@ const equivalchars = ["\\alpha","\\beta","\\gamma","\\delta","\\epsilon","\\zeta
|
|||
"{}_{4}","{}_{5}","{}_{6}","{}_{7}","{}_{8}","{}_{9}","{}_{0}",
|
||||
"\\pi", "\\infty"]
|
||||
|
||||
/**
|
||||
* Class containing the result of a LaTeX render.
|
||||
*
|
||||
* @property {string} source - Exported PNG file
|
||||
* @property {number} width
|
||||
* @property {number} height
|
||||
*/
|
||||
class LatexRenderResult {
|
||||
constructor(source, width, height) {
|
||||
this.source = source
|
||||
this.width = parseFloat(width)
|
||||
this.height = parseFloat(height)
|
||||
}
|
||||
}
|
||||
|
||||
class LatexAPI extends Module {
|
||||
constructor() {
|
||||
|
@ -50,11 +64,50 @@ class LatexAPI extends Module {
|
|||
* true if latex has been enabled by the user, false otherwise.
|
||||
*/
|
||||
this.enabled = Helper.getSettingBool("enable_latex")
|
||||
/**
|
||||
* Mirror method for Python object.
|
||||
* @type {function(string, number, string): string}.
|
||||
*/
|
||||
this.render = Latex.render
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares and renders a latex string into a png file.
|
||||
*
|
||||
* @param {string} markup - LaTeX markup to render.
|
||||
* @param {number} fontSize - Font size (in pt) to render.
|
||||
* @param {color} color - Color of the text to render.
|
||||
* @returns {LatexRenderResult}
|
||||
*/
|
||||
renderSync(markup, fontSize, color) {
|
||||
let args = Latex.render(markup, fontSize, color).split(",")
|
||||
return new LatexRenderResult(...args)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given markup (with given font size and color) has already been
|
||||
* rendered, and if so, returns its data. Otherwise, returns null.
|
||||
*
|
||||
* @param {string} markup - LaTeX markup to render.
|
||||
* @param {number} fontSize - Font size (in pt) to render.
|
||||
* @param {color} color - Color of the text to render.
|
||||
* @returns {LatexRenderResult|null}
|
||||
*/
|
||||
findPrerendered(markup, fontSize, color) {
|
||||
const data = Latex.findPrerendered(markup, fontSize, color)
|
||||
let ret = null
|
||||
if(data !== "")
|
||||
ret = new LatexRenderResult(...data.split(","))
|
||||
return ret
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares and renders a latex string into a png file asynchronously.
|
||||
*
|
||||
* @param {string} markup - LaTeX markup to render.
|
||||
* @param {number} fontSize - Font size (in pt) to render.
|
||||
* @param {color} color - Color of the text to render.
|
||||
* @returns {Promize<LatexRenderResult>}
|
||||
*/
|
||||
requestAsyncRender(markup, fontSize, color) {
|
||||
return new Promise(resolve => {
|
||||
resolve(this.renderSync(markup, fontSize, color))
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -92,15 +92,13 @@ class Latex(QObject):
|
|||
except Exception as e:
|
||||
valid_install = False # Should have sent an error message if failed to render
|
||||
return valid_install
|
||||
|
||||
|
||||
@Slot(str, float, QColor, result=str)
|
||||
def render(self, latex_markup: str, font_size: float, color: QColor) -> str:
|
||||
"""
|
||||
Prepares and renders a latex string into a png file.
|
||||
"""
|
||||
markup_hash = "render" + str(hash(latex_markup))
|
||||
export_path = path.join(self.tempdir.name, f'{markup_hash}_{int(font_size)}_{color.rgb()}')
|
||||
markup_hash, export_path = self.create_export_path(latex_markup, font_size, color)
|
||||
if self.latexSupported and not path.exists(export_path + ".png"):
|
||||
print("Rendering", latex_markup, export_path)
|
||||
# Generating file
|
||||
|
@ -121,6 +119,28 @@ class Latex(QObject):
|
|||
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
|
||||
return f'{export_path}.png,{img.width()},{img.height()}'
|
||||
|
||||
@Slot(str, float, QColor, result=str)
|
||||
def findPrerendered(self, latex_markup: str, font_size: float, color: QColor):
|
||||
"""
|
||||
Finds a prerendered image and returns its data if possible, and an empty string if not.
|
||||
"""
|
||||
markup_hash, export_path = self.create_export_path(latex_markup, font_size, color)
|
||||
data = ""
|
||||
if path.exists(export_path + ".png"):
|
||||
img = QImage(export_path)
|
||||
data = f'{export_path}.png,{img.width()},{img.height()}'
|
||||
return data
|
||||
|
||||
|
||||
def create_export_path(self, latex_markup: str, font_size: float, color: QColor):
|
||||
"""
|
||||
Standardizes export path for renders.
|
||||
"""
|
||||
markup_hash = "render" + str(hash(latex_markup))
|
||||
export_path = path.join(self.tempdir.name, f'{markup_hash}_{int(font_size)}_{color.rgb()}')
|
||||
return markup_hash, export_path
|
||||
|
||||
|
||||
def create_latex_doc(self, export_path: str, latex_markup: str):
|
||||
"""
|
||||
|
|
Loading…
Reference in a new issue