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)
|
||||
latex = Latex(tempdir, app.palette())
|
||||
engine.rootContext().setContextProperty("Helper", helper)
|
||||
engine.rootContext().setContextProperty("Latex", latex)
|
||||
engine.rootContext().setContextProperty("TestBuild", "--test-build" in argv)
|
||||
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 var imageLoaders: {}
|
||||
property var ctx
|
||||
|
||||
onPaint: {
|
||||
Component.onCompleted: imageLoaders = {}
|
||||
|
||||
onPaint: function(rect) {
|
||||
//console.log('Redrawing')
|
||||
var ctx = getContext("2d");
|
||||
reset(ctx)
|
||||
drawGrille(ctx)
|
||||
drawAxises(ctx)
|
||||
ctx.lineWidth = linewidth
|
||||
for(var objType in Objects.currentObjects) {
|
||||
for(var obj of Objects.currentObjects[objType]){
|
||||
ctx.strokeStyle = obj.color
|
||||
ctx.fillStyle = obj.color
|
||||
if(obj.visible) obj.draw(canvas, ctx)
|
||||
if(rect.width == canvas.width) { // Redraw full canvas
|
||||
ctx = getContext("2d");
|
||||
reset(ctx)
|
||||
drawGrille(ctx)
|
||||
drawAxises(ctx)
|
||||
drawLabels(ctx)
|
||||
ctx.lineWidth = linewidth
|
||||
for(var objType in Objects.currentObjects) {
|
||||
for(var obj of Objects.currentObjects[objType]){
|
||||
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)
|
||||
|
@ -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)
|
||||
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();
|
||||
}
|
||||
|
||||
/*!
|
||||
\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
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @returns {string}
|
||||
|
@ -115,10 +182,7 @@ function expressionToLatex(tokens) {
|
|||
break;
|
||||
case ExprEval.IVAR:
|
||||
case ExprEval.IVARNAME:
|
||||
if(item.value == "pi")
|
||||
nstack.push("π")
|
||||
else
|
||||
nstack.push(item.value);
|
||||
nstack.push(variableToLatex(item.value));
|
||||
break;
|
||||
case ExprEval.IOP1: // Unary operator
|
||||
n1 = nstack.pop();
|
||||
|
@ -144,21 +208,7 @@ function expressionToLatex(tokens) {
|
|||
}
|
||||
f = nstack.pop();
|
||||
// Handling various functions
|
||||
if(f == "derivative")
|
||||
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;
|
||||
nstack.push(functionToLatex(f, args))
|
||||
case ExprEval.IFUNDEF:
|
||||
nstack.push(par(n1 + '(' + args.join(', ') + ') = ' + n2));
|
||||
break;
|
||||
|
@ -191,5 +241,6 @@ function expressionToLatex(tokens) {
|
|||
nstack = [ nstack.join(';') ];
|
||||
}
|
||||
}
|
||||
console.log(nstack[0]);
|
||||
return String(nstack[0]);
|
||||
}
|
||||
|
|
|
@ -84,12 +84,16 @@ class DrawableObject {
|
|||
return `${this.name} = Unknown`
|
||||
}
|
||||
|
||||
toLatexString() {
|
||||
return this.getReadableString()
|
||||
}
|
||||
|
||||
getLabel() {
|
||||
switch(this.labelContent) {
|
||||
case 'name':
|
||||
return this.name
|
||||
case 'name + value':
|
||||
return this.getReadableString()
|
||||
return this.toLatexString()
|
||||
case 'null':
|
||||
return ''
|
||||
|
||||
|
|
|
@ -58,6 +58,10 @@ class Point extends Common.DrawableObject {
|
|||
return `${this.name} = (${this.x}, ${this.y})`
|
||||
}
|
||||
|
||||
toLatexString() {
|
||||
return `${this.name} = \\left(${this.x.latexMarkup}, ${this.y.latexMarkup}\\right)`
|
||||
}
|
||||
|
||||
export() {
|
||||
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)
|
||||
break;
|
||||
}
|
||||
var text = this.getLabel()
|
||||
ctx.font = `${canvas.textsize}px sans-serif`
|
||||
var textSize = ctx.measureText(text).width
|
||||
switch(this.labelPosition) {
|
||||
case 'top':
|
||||
case 'above':
|
||||
canvas.drawVisibleText(ctx, text, canvasX-textSize/2, canvasY-16)
|
||||
break;
|
||||
case 'bottom':
|
||||
case 'below':
|
||||
canvas.drawVisibleText(ctx, text, canvasX-textSize/2, canvasY+16)
|
||||
break;
|
||||
case 'left':
|
||||
canvas.drawVisibleText(ctx, text, canvasX-textSize-10, canvasY+4)
|
||||
break;
|
||||
case 'right':
|
||||
canvas.drawVisibleText(ctx, text, canvasX+10, canvasY+4)
|
||||
break;
|
||||
case 'top-left':
|
||||
case 'above-left':
|
||||
canvas.drawVisibleText(ctx, text, canvasX-textSize-10, canvasY-16)
|
||||
break;
|
||||
case 'top-right':
|
||||
case 'above-right':
|
||||
canvas.drawVisibleText(ctx, text, canvasX+10, canvasY-16)
|
||||
break;
|
||||
case 'bottom-left':
|
||||
case 'below-left':
|
||||
canvas.drawVisibleText(ctx, text, canvasX-textSize-10, canvasY+16)
|
||||
break;
|
||||
case 'bottom-right':
|
||||
case 'below-right':
|
||||
canvas.drawVisibleText(ctx, text, canvasX+10, canvasY+16)
|
||||
break;
|
||||
|
||||
|
||||
let drawLabel = function(canvas, ctx, ltxImg) {
|
||||
//console.log(JSON.stringify(ltxImg), canvas.isImageLoaded(ltxImg.source), this, this.labelPosition)
|
||||
switch(this.labelPosition) {
|
||||
case 'top':
|
||||
case 'above':
|
||||
canvas.drawVisibleImage(ctx, ltxImg.source, canvasX-ltxImg.width/2, canvasY-(ltxImg.height+4), ltxImg.width, ltxImg.height)
|
||||
break;
|
||||
case 'bottom':
|
||||
case 'below':
|
||||
canvas.drawVisibleImage(ctx, ltxImg.source, canvasX-ltxImg.width/2, canvasY+4, ltxImg.width, ltxImg.height)
|
||||
break;
|
||||
case 'left':
|
||||
canvas.drawVisibleImage(ctx, ltxImg.source, canvasX-(ltxImg.width+4), canvasY+4, ltxImg.width, ltxImg.height)
|
||||
break;
|
||||
case 'right':
|
||||
canvas.drawVisibleImage(ctx, ltxImg.source, canvasX+4, canvasY+4, ltxImg.width, ltxImg.height)
|
||||
break;
|
||||
case 'top-left':
|
||||
case 'above-left':
|
||||
canvas.drawVisibleImage(ctx, ltxImg.source, canvasX-(ltxImg.width+4), canvasY-(ltxImg.height+4), ltxImg.width, ltxImg.height)
|
||||
break;
|
||||
case 'top-right':
|
||||
case 'above-right':
|
||||
canvas.drawVisibleImage(ctx, ltxImg.source, canvasX+4, canvasY-(ltxImg.height+4), ltxImg.width, ltxImg.height)
|
||||
break;
|
||||
case 'bottom-left':
|
||||
case 'below-left':
|
||||
canvas.drawVisibleImage(ctx, ltxImg.source, canvasX-(ltxImg.width+4), canvasY+4, ltxImg.width, ltxImg.height)
|
||||
break;
|
||||
case 'bottom-right':
|
||||
case 'below-right':
|
||||
canvas.drawVisibleImage(ctx, ltxImg.source, canvasX+4, canvasY+4, ltxImg.width, ltxImg.height)
|
||||
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
|
||||
fg = self.palette.windowText().color().convertTo(QColor.Rgb)
|
||||
|
||||
@Slot(str, float, bool, result=str)
|
||||
def render(self, latexstring, font_size, themeFg = True):
|
||||
exprpath = path.join(self.tempdir.name, str(hash(latexstring)) + '.png')
|
||||
print(latexstring, exprpath)
|
||||
@Slot(str, float, QColor, result=str)
|
||||
def render(self, latexstring, font_size, color = True):
|
||||
exprpath = path.join(self.tempdir.name, f'{hash(latexstring)}_{font_size}_{color.rgb()}.png')
|
||||
print("Rendering", latexstring, exprpath)
|
||||
if not path.exists(exprpath):
|
||||
if themeFg:
|
||||
fg = self.palette.windowText().color().convertTo(QColor.Rgb)
|
||||
fg = f'rgb {fg.redF()} {fg.greenF()} {fg.blueF()}'
|
||||
else:
|
||||
fg = 'rgb 1.0 1.0 1.0'
|
||||
fg = color.convertTo(QColor.Rgb)
|
||||
fg = f'rgb {fg.redF()} {fg.greenF()} {fg.blueF()}'
|
||||
preview('$$' + latexstring + '$$', viewer='file', filename=exprpath, dvioptions=[
|
||||
"-T", "tight",
|
||||
"-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
|
||||
"-bg", "Transparent",
|
||||
"-fg", fg],
|
||||
euler=False)
|
||||
return exprpath
|
||||
euler=True)
|
||||
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)
|
||||
def copyLatexImageToClipboard(self, latexstring):
|
||||
global tempfile
|
||||
clipboard = QApplication.clipboard()
|
||||
clipboard.setImage(QImage(self.render(latexstring)))
|
||||
clipboard.setImage(self.render(latexstring))
|
||||
|
|
Loading…
Reference in a new issue