Starting latex rendering (canvas side).
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
parent
23cd86a2e3
commit
1142ca1c00
6 changed files with 191 additions and 82 deletions
|
@ -110,6 +110,7 @@ def run():
|
||||||
helper = Helper(pwd, tmpfile)
|
helper = Helper(pwd, tmpfile)
|
||||||
latex = Latex(tempdir, app.palette())
|
latex = Latex(tempdir, app.palette())
|
||||||
engine.rootContext().setContextProperty("Helper", helper)
|
engine.rootContext().setContextProperty("Helper", helper)
|
||||||
|
engine.rootContext().setContextProperty("Latex", latex)
|
||||||
engine.rootContext().setContextProperty("TestBuild", "--test-build" in argv)
|
engine.rootContext().setContextProperty("TestBuild", "--test-build" in argv)
|
||||||
engine.rootContext().setContextProperty("StartTime", dep_time)
|
engine.rootContext().setContextProperty("StartTime", dep_time)
|
||||||
|
|
||||||
|
|
|
@ -157,26 +157,40 @@ Canvas {
|
||||||
*/
|
*/
|
||||||
property int drawMaxX: Math.ceil(Math.max(Math.abs(xmin), Math.abs(px2x(canvasSize.width)))/xaxisstep1)
|
property int drawMaxX: Math.ceil(Math.max(Math.abs(xmin), Math.abs(px2x(canvasSize.width)))/xaxisstep1)
|
||||||
|
|
||||||
|
property var imageLoaders: {}
|
||||||
|
property var ctx
|
||||||
|
|
||||||
onPaint: {
|
Component.onCompleted: imageLoaders = {}
|
||||||
|
|
||||||
|
onPaint: function(rect) {
|
||||||
//console.log('Redrawing')
|
//console.log('Redrawing')
|
||||||
var ctx = getContext("2d");
|
if(rect.width == canvas.width) { // Redraw full canvas
|
||||||
reset(ctx)
|
ctx = getContext("2d");
|
||||||
drawGrille(ctx)
|
reset(ctx)
|
||||||
drawAxises(ctx)
|
drawGrille(ctx)
|
||||||
ctx.lineWidth = linewidth
|
drawAxises(ctx)
|
||||||
for(var objType in Objects.currentObjects) {
|
drawLabels(ctx)
|
||||||
for(var obj of Objects.currentObjects[objType]){
|
ctx.lineWidth = linewidth
|
||||||
ctx.strokeStyle = obj.color
|
for(var objType in Objects.currentObjects) {
|
||||||
ctx.fillStyle = obj.color
|
for(var obj of Objects.currentObjects[objType]){
|
||||||
if(obj.visible) obj.draw(canvas, ctx)
|
ctx.strokeStyle = obj.color
|
||||||
|
ctx.fillStyle = obj.color
|
||||||
|
if(obj.visible) obj.draw(canvas, ctx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
ctx.lineWidth = 1
|
||||||
}
|
}
|
||||||
ctx.lineWidth = 1
|
|
||||||
drawLabels(ctx)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onImageLoaded: {
|
||||||
|
Object.keys(imageLoaders).forEach((key) => {
|
||||||
|
if(isImageLoaded(key)) {
|
||||||
|
// Calling callback
|
||||||
|
imageLoaders[key][0](canvas, ctx, imageLoaders[key][1])
|
||||||
|
delete imageLoaders[key]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\qmlmethod void LogGraphCanvas::reset(var ctx)
|
\qmlmethod void LogGraphCanvas::reset(var ctx)
|
||||||
|
@ -314,6 +328,19 @@ Canvas {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\qmlmethod void LogGraphCanvas::drawVisibleImage(var ctx, var image, double x, double y)
|
||||||
|
Draws an \c image onto the canvas using 2D \c ctx.
|
||||||
|
\note The \c x, \c y \c width and \c height properties here are relative to the canvas, not the plot.
|
||||||
|
*/
|
||||||
|
function drawVisibleImage(ctx, image, x, y, width, height) {
|
||||||
|
console.log("Drawing image", isImageLoaded(image), isImageError(image))
|
||||||
|
markDirty(Qt.rect(x, y, width, height));
|
||||||
|
ctx.drawImage(image, x, y, width, height)
|
||||||
|
/*if(true || (x > 0 && x < canvasSize.width && y > 0 && y < canvasSize.height)) {
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\qmlmethod var LogGraphCanvas::measureText(var ctx, string text)
|
\qmlmethod var LogGraphCanvas::measureText(var ctx, string text)
|
||||||
Measures the wicth and height of a multiline \c text that would be drawn onto the canvas using 2D \c ctx.
|
Measures the wicth and height of a multiline \c text that would be drawn onto the canvas using 2D \c ctx.
|
||||||
|
@ -415,4 +442,25 @@ Canvas {
|
||||||
}
|
}
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\qmlmethod var LogGraphCanvas::renderLatexImage(string ltxText, color)
|
||||||
|
Renders latex markup \c ltxText to an image and loads it. Returns a dictionary with three values: source, width and height.
|
||||||
|
*/
|
||||||
|
function renderLatexImage(ltxText, color, callback) {
|
||||||
|
let [ltxSrc, ltxWidth, ltxHeight] = Latex.render(ltxText, textsize, color).split(",")
|
||||||
|
let imgData = {
|
||||||
|
"source": ltxSrc,
|
||||||
|
"width": parseFloat(ltxWidth),
|
||||||
|
"height": parseFloat(ltxHeight)
|
||||||
|
};
|
||||||
|
if(!isImageLoaded(ltxSrc) && !isImageLoading(ltxSrc)){
|
||||||
|
// Wait until the image is loaded to callback.
|
||||||
|
loadImage(ltxSrc)
|
||||||
|
imageLoaders[ltxSrc] = [callback, imgData]
|
||||||
|
} else {
|
||||||
|
// Callback directly
|
||||||
|
callback(canvas, ctx, imgData)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,8 +42,75 @@ function parif(elem, contents) {
|
||||||
return contents.some(x => elem.toString().includes(x)) ? par(elem) : elem
|
return contents.some(x => elem.toString().includes(x)) ? par(elem) : elem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function converts expr-eval tokens to a latex string.
|
* Creates a latex expression for a function.
|
||||||
|
*
|
||||||
|
* @param {string} f - Function to convert
|
||||||
|
* @param {Array} args - Arguments of the function
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function functionToLatex(f, args) {
|
||||||
|
switch(f) {
|
||||||
|
case "derivative":
|
||||||
|
return '\\frac{d' + args[0].substr(1, args[0].length-2).replace(new RegExp(by, 'g'), 'x') + '}{dx}';
|
||||||
|
break;
|
||||||
|
case "integral":
|
||||||
|
return '\\int\\limits^{' + args[0] + '}_{' + args[1] + '}' + args[2].substr(1, args[2].length-2) + ' d' + args[3];
|
||||||
|
break;
|
||||||
|
case "sqrt":
|
||||||
|
return '\\sqrt\\left(' + args.join(', ') + '\\right)';
|
||||||
|
break;
|
||||||
|
case "abs":
|
||||||
|
return '\\left|' + args.join(', ') + '\\right|';
|
||||||
|
break;
|
||||||
|
case "floor":
|
||||||
|
return '\\left\\lfloor' + args.join(', ') + '\\right\\rfloor';
|
||||||
|
break;
|
||||||
|
case "ceil":
|
||||||
|
return '\\left\\lceil' + args.join(', ') + '\\right\\rceil';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return '\\mathrm{' + f + '}\\left(' + args.join(', ') + '\\right)';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a latex variable from a variable.
|
||||||
|
*
|
||||||
|
* @param {string} vari - variable to convert
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function variableToLatex(vari) {
|
||||||
|
unicodechars = ["α","β","γ","δ","ε","ζ","η",
|
||||||
|
"π","θ","κ","λ","μ","ξ","ρ",
|
||||||
|
"ς","σ","τ","φ","χ","ψ","ω",
|
||||||
|
"Γ","Δ","Θ","Λ","Ξ","Π","Σ",
|
||||||
|
"Φ","Ψ","Ω","ₐ","ₑ","ₒ","ₓ",
|
||||||
|
"ₕ","ₖ","ₗ","ₘ","ₙ","ₚ","ₛ",
|
||||||
|
"ₜ","¹","²","³","⁴","⁵","⁶",
|
||||||
|
"⁷","⁸","⁹","⁰","₁","₂","₃",
|
||||||
|
"₄","₅","₆","₇","₈","₉","₀"]
|
||||||
|
equivalchars = ["alpha","beta","gamma","delta","epsilon","zeta","eta",
|
||||||
|
"pi","theta","kappa","lambda","mu","xi","rho",
|
||||||
|
"sigma","sigma","tau","phi","chi","psi","omega",
|
||||||
|
"Gamma","Delta","Theta","Lambda","Xi","Pi","Sigma",
|
||||||
|
"Phy","Psi","Omega","{}_{a}","{}_{e}","{}_{o}","{}_{x}",
|
||||||
|
"{}_{h}","{}_{k}","{}_{l}","{}_{m}","{}_{n}","{}_{p}","{}_{s}",
|
||||||
|
"{}_{t}","{}^{1}","{}^{2}","{}^{3}","{}^{4}","{}^{5}","{}^{6}",
|
||||||
|
"{}^{7}","{}^{8}","{}^{9}","{}^{0}","{}_{1}","{}_{2}","{}_{3}",
|
||||||
|
"{}_{4}","{}_{5}","{}_{6}","{}_{7}","{}_{8}","{}_{9}","{}_{0}"]
|
||||||
|
for(int i = 0; i < unicodechars.length; i++) {
|
||||||
|
if(vari.includes(unicodechars[i]))
|
||||||
|
vari = vari.replaceAll(unicodechars[i], equivalchars[i])
|
||||||
|
}
|
||||||
|
return vari;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts expr-eval tokens to a latex string.
|
||||||
*
|
*
|
||||||
* @param {Array} tokens - expr-eval tokens list
|
* @param {Array} tokens - expr-eval tokens list
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
|
@ -115,10 +182,7 @@ function expressionToLatex(tokens) {
|
||||||
break;
|
break;
|
||||||
case ExprEval.IVAR:
|
case ExprEval.IVAR:
|
||||||
case ExprEval.IVARNAME:
|
case ExprEval.IVARNAME:
|
||||||
if(item.value == "pi")
|
nstack.push(variableToLatex(item.value));
|
||||||
nstack.push("π")
|
|
||||||
else
|
|
||||||
nstack.push(item.value);
|
|
||||||
break;
|
break;
|
||||||
case ExprEval.IOP1: // Unary operator
|
case ExprEval.IOP1: // Unary operator
|
||||||
n1 = nstack.pop();
|
n1 = nstack.pop();
|
||||||
|
@ -144,21 +208,7 @@ function expressionToLatex(tokens) {
|
||||||
}
|
}
|
||||||
f = nstack.pop();
|
f = nstack.pop();
|
||||||
// Handling various functions
|
// Handling various functions
|
||||||
if(f == "derivative")
|
nstack.push(functionToLatex(f, args))
|
||||||
nstack.push('\\frac{d' + args[0].substr(1, args[0].length-2).replace(new RegExp(by, 'g'), 'x') + '}{dx}');
|
|
||||||
else if(f == "integral")
|
|
||||||
nstack.push('\\int\\limits^{' + args[0] + '}_{' + args[1] + '}' + args[2].substr(1, args[2].length-2) + ' d' + args[3]);
|
|
||||||
else if(f == "sqrt")
|
|
||||||
nstack.push('\\sqrt\\left(' + args.join(', ') + '\\right)');
|
|
||||||
else if(f == "abs")
|
|
||||||
nstack.push('\\left|' + args.join(', ') + '\\right|');
|
|
||||||
else if(f == "floor")
|
|
||||||
nstack.push('\\left\\lfloor' + args.join(', ') + '\\right\\rfloor');
|
|
||||||
else if(f == "ceil")
|
|
||||||
nstack.push('\\left\\lceil' + args.join(', ') + '\\right\\rceil');
|
|
||||||
else
|
|
||||||
nstack.push('\\mathrm{' + f + '}\\left(' + args.join(', ') + '\\right)');
|
|
||||||
break;
|
|
||||||
case ExprEval.IFUNDEF:
|
case ExprEval.IFUNDEF:
|
||||||
nstack.push(par(n1 + '(' + args.join(', ') + ') = ' + n2));
|
nstack.push(par(n1 + '(' + args.join(', ') + ') = ' + n2));
|
||||||
break;
|
break;
|
||||||
|
@ -191,5 +241,6 @@ function expressionToLatex(tokens) {
|
||||||
nstack = [ nstack.join(';') ];
|
nstack = [ nstack.join(';') ];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.log(nstack[0]);
|
||||||
return String(nstack[0]);
|
return String(nstack[0]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,12 +84,16 @@ class DrawableObject {
|
||||||
return `${this.name} = Unknown`
|
return `${this.name} = Unknown`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toLatexString() {
|
||||||
|
return this.getReadableString()
|
||||||
|
}
|
||||||
|
|
||||||
getLabel() {
|
getLabel() {
|
||||||
switch(this.labelContent) {
|
switch(this.labelContent) {
|
||||||
case 'name':
|
case 'name':
|
||||||
return this.name
|
return this.name
|
||||||
case 'name + value':
|
case 'name + value':
|
||||||
return this.getReadableString()
|
return this.toLatexString()
|
||||||
case 'null':
|
case 'null':
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
|
|
@ -58,6 +58,10 @@ class Point extends Common.DrawableObject {
|
||||||
return `${this.name} = (${this.x}, ${this.y})`
|
return `${this.name} = (${this.x}, ${this.y})`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toLatexString() {
|
||||||
|
return `${this.name} = \\left(${this.x.latexMarkup}, ${this.y.latexMarkup}\\right)`
|
||||||
|
}
|
||||||
|
|
||||||
export() {
|
export() {
|
||||||
return [this.name, this.visible, this.color.toString(), this.labelContent, this.x.toEditableString(), this.y.toEditableString(), this.labelPosition, this.pointStyle]
|
return [this.name, this.visible, this.color.toString(), this.labelContent, this.x.toEditableString(), this.y.toEditableString(), this.labelPosition, this.pointStyle]
|
||||||
}
|
}
|
||||||
|
@ -80,41 +84,43 @@ class Point extends Common.DrawableObject {
|
||||||
ctx.fillRect(canvasX-1, canvasY-pointSize/2, 2, pointSize)
|
ctx.fillRect(canvasX-1, canvasY-pointSize/2, 2, pointSize)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
var text = this.getLabel()
|
|
||||||
ctx.font = `${canvas.textsize}px sans-serif`
|
let drawLabel = function(canvas, ctx, ltxImg) {
|
||||||
var textSize = ctx.measureText(text).width
|
//console.log(JSON.stringify(ltxImg), canvas.isImageLoaded(ltxImg.source), this, this.labelPosition)
|
||||||
switch(this.labelPosition) {
|
switch(this.labelPosition) {
|
||||||
case 'top':
|
case 'top':
|
||||||
case 'above':
|
case 'above':
|
||||||
canvas.drawVisibleText(ctx, text, canvasX-textSize/2, canvasY-16)
|
canvas.drawVisibleImage(ctx, ltxImg.source, canvasX-ltxImg.width/2, canvasY-(ltxImg.height+4), ltxImg.width, ltxImg.height)
|
||||||
break;
|
break;
|
||||||
case 'bottom':
|
case 'bottom':
|
||||||
case 'below':
|
case 'below':
|
||||||
canvas.drawVisibleText(ctx, text, canvasX-textSize/2, canvasY+16)
|
canvas.drawVisibleImage(ctx, ltxImg.source, canvasX-ltxImg.width/2, canvasY+4, ltxImg.width, ltxImg.height)
|
||||||
break;
|
break;
|
||||||
case 'left':
|
case 'left':
|
||||||
canvas.drawVisibleText(ctx, text, canvasX-textSize-10, canvasY+4)
|
canvas.drawVisibleImage(ctx, ltxImg.source, canvasX-(ltxImg.width+4), canvasY+4, ltxImg.width, ltxImg.height)
|
||||||
break;
|
break;
|
||||||
case 'right':
|
case 'right':
|
||||||
canvas.drawVisibleText(ctx, text, canvasX+10, canvasY+4)
|
canvas.drawVisibleImage(ctx, ltxImg.source, canvasX+4, canvasY+4, ltxImg.width, ltxImg.height)
|
||||||
break;
|
break;
|
||||||
case 'top-left':
|
case 'top-left':
|
||||||
case 'above-left':
|
case 'above-left':
|
||||||
canvas.drawVisibleText(ctx, text, canvasX-textSize-10, canvasY-16)
|
canvas.drawVisibleImage(ctx, ltxImg.source, canvasX-(ltxImg.width+4), canvasY-(ltxImg.height+4), ltxImg.width, ltxImg.height)
|
||||||
break;
|
break;
|
||||||
case 'top-right':
|
case 'top-right':
|
||||||
case 'above-right':
|
case 'above-right':
|
||||||
canvas.drawVisibleText(ctx, text, canvasX+10, canvasY-16)
|
canvas.drawVisibleImage(ctx, ltxImg.source, canvasX+4, canvasY-(ltxImg.height+4), ltxImg.width, ltxImg.height)
|
||||||
break;
|
break;
|
||||||
case 'bottom-left':
|
case 'bottom-left':
|
||||||
case 'below-left':
|
case 'below-left':
|
||||||
canvas.drawVisibleText(ctx, text, canvasX-textSize-10, canvasY+16)
|
canvas.drawVisibleImage(ctx, ltxImg.source, canvasX-(ltxImg.width+4), canvasY+4, ltxImg.width, ltxImg.height)
|
||||||
break;
|
break;
|
||||||
case 'bottom-right':
|
case 'bottom-right':
|
||||||
case 'below-right':
|
case 'below-right':
|
||||||
canvas.drawVisibleText(ctx, text, canvasX+10, canvasY+16)
|
canvas.drawVisibleImage(ctx, ltxImg.source, canvasX+4, canvasY+4, ltxImg.width, ltxImg.height)
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
canvas.renderLatexImage(this.getLabel(), this.color, drawLabel.bind(this))
|
||||||
|
//canvas.drawVisibleImage(ctx, ltxImg.source, canvasX, canvasY)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,16 +31,13 @@ class Latex(QObject):
|
||||||
self.palette = palette
|
self.palette = palette
|
||||||
fg = self.palette.windowText().color().convertTo(QColor.Rgb)
|
fg = self.palette.windowText().color().convertTo(QColor.Rgb)
|
||||||
|
|
||||||
@Slot(str, float, bool, result=str)
|
@Slot(str, float, QColor, result=str)
|
||||||
def render(self, latexstring, font_size, themeFg = True):
|
def render(self, latexstring, font_size, color = True):
|
||||||
exprpath = path.join(self.tempdir.name, str(hash(latexstring)) + '.png')
|
exprpath = path.join(self.tempdir.name, f'{hash(latexstring)}_{font_size}_{color.rgb()}.png')
|
||||||
print(latexstring, exprpath)
|
print("Rendering", latexstring, exprpath)
|
||||||
if not path.exists(exprpath):
|
if not path.exists(exprpath):
|
||||||
if themeFg:
|
fg = color.convertTo(QColor.Rgb)
|
||||||
fg = self.palette.windowText().color().convertTo(QColor.Rgb)
|
fg = f'rgb {fg.redF()} {fg.greenF()} {fg.blueF()}'
|
||||||
fg = f'rgb {fg.redF()} {fg.greenF()} {fg.blueF()}'
|
|
||||||
else:
|
|
||||||
fg = 'rgb 1.0 1.0 1.0'
|
|
||||||
preview('$$' + latexstring + '$$', viewer='file', filename=exprpath, dvioptions=[
|
preview('$$' + latexstring + '$$', viewer='file', filename=exprpath, dvioptions=[
|
||||||
"-T", "tight",
|
"-T", "tight",
|
||||||
"-z", "0",
|
"-z", "0",
|
||||||
|
@ -48,11 +45,13 @@ class Latex(QObject):
|
||||||
f"-D {font_size * 72.27 / 10}", # See https://linux.die.net/man/1/dvipng#-D for convertion
|
f"-D {font_size * 72.27 / 10}", # See https://linux.die.net/man/1/dvipng#-D for convertion
|
||||||
"-bg", "Transparent",
|
"-bg", "Transparent",
|
||||||
"-fg", fg],
|
"-fg", fg],
|
||||||
euler=False)
|
euler=True)
|
||||||
return 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
|
||||||
|
return f'{exprpath},{img.width()},{img.height()}'
|
||||||
|
|
||||||
@Slot(str)
|
@Slot(str)
|
||||||
def copyLatexImageToClipboard(self, latexstring):
|
def copyLatexImageToClipboard(self, latexstring):
|
||||||
global tempfile
|
global tempfile
|
||||||
clipboard = QApplication.clipboard()
|
clipboard = QApplication.clipboard()
|
||||||
clipboard.setImage(QImage(self.render(latexstring)))
|
clipboard.setImage(self.render(latexstring))
|
||||||
|
|
Loading…
Reference in a new issue