Compare commits
No commits in common. "7e0262e4fe3418f744a4d1e608ffe11e714db190" and "c9a597ea826770503d2ec2a9dbb13635328f5e4b" have entirely different histories.
7e0262e4fe
...
c9a597ea82
8 changed files with 55 additions and 196 deletions
|
@ -116,23 +116,10 @@ Button {
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: !hidden
|
visible: !hidden
|
||||||
font.pixelSize: 14
|
font.pixelSize: 14
|
||||||
text: ""
|
text: historyAction.getHTMLString().replace(/\$\{tag_color\}/g, clr)
|
||||||
textFormat: Text.RichText
|
textFormat: Text.RichText
|
||||||
clip: true
|
clip: true
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
|
|
||||||
Component.onCompleted: function() {
|
|
||||||
// Render HTML, might be string, but could also be a promise
|
|
||||||
const html = historyAction.getHTMLString()
|
|
||||||
if(typeof html === "string") {
|
|
||||||
label.text = html.replace(/\$\{tag_color\}/g, clr)
|
|
||||||
} else {
|
|
||||||
// Promise! We need to way to wait for it to be completed.
|
|
||||||
html.then(rendered => {
|
|
||||||
label.text = rendered.replace(/\$\{tag_color\}/g, clr)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
|
|
@ -100,26 +100,10 @@ Item {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
visible: Modules.Latex.enabled
|
visible: Modules.Latex.enabled
|
||||||
property double depth: Screen.devicePixelRatio
|
property double depth: Screen.devicePixelRatio
|
||||||
source: ""
|
property var ltxInfo: visible ? Latex.render(obj.getLatexString(), depth*(parent.font.pixelSize+2), parent.color).split(",") : ["","0","0"]
|
||||||
width: 0/depth
|
source: visible ? ltxInfo[0] : ""
|
||||||
height: 0/depth
|
width: parseInt(ltxInfo[1])/depth
|
||||||
|
height: parseInt(ltxInfo[2])/depth
|
||||||
Component.onCompleted: function() {
|
|
||||||
if(Modules.Latex.enabled) {
|
|
||||||
const args = [obj.getLatexString(), depth*(parent.font.pixelSize+2), parent.color]
|
|
||||||
const prerendered = Modules.Latex.findPrerendered(...args)
|
|
||||||
if(prerendered !== null) {
|
|
||||||
source = prerendered.source
|
|
||||||
width = prerendered.width/depth
|
|
||||||
height = prerendered.height/depth
|
|
||||||
} else
|
|
||||||
Modules.Latex.requestAsyncRender(...args).then(info => {
|
|
||||||
source = info.source
|
|
||||||
width = info.width/depth
|
|
||||||
height = info.height/depth
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
|
|
|
@ -16,10 +16,9 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Module } from "./modules.mjs"
|
import {Module} from "./modules.mjs"
|
||||||
import { textsup } from "./utils.mjs"
|
import {textsup} from "./utils.mjs"
|
||||||
import { Expression } from "./mathlib.mjs"
|
import {Expression} from "./mathlib.mjs"
|
||||||
import Latex from "./math/latex.mjs"
|
|
||||||
|
|
||||||
|
|
||||||
class CanvasAPI extends Module {
|
class CanvasAPI extends Module {
|
||||||
|
@ -169,8 +168,6 @@ class CanvasAPI extends Module {
|
||||||
obj.draw(this)
|
obj.draw(this)
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
// Drawing throws an error. Generally, it's due to a new modification (or the opening of a file)
|
// 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)
|
this._drawingErrorDialog.showDialog(objType, obj.name, e.message)
|
||||||
Modules.History.undo()
|
Modules.History.undo()
|
||||||
}
|
}
|
||||||
|
@ -455,24 +452,23 @@ class CanvasAPI extends Module {
|
||||||
* Renders latex markup ltxText to an image and loads it. Returns a dictionary with three values: source, width and height.
|
* 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} ltxText
|
||||||
* @param {string} color
|
* @param {string} color
|
||||||
* @param {function(LatexRenderResult|{width: number, height: number, source: string})} callback
|
* @param {function({width: number, height: number, source: string})} callback
|
||||||
*/
|
*/
|
||||||
renderLatexImage(ltxText, color, callback) {
|
renderLatexImage(ltxText, color, callback) {
|
||||||
const onRendered = (imgData) => {
|
let [ltxSrc, ltxWidth, ltxHeight] = Latex.render(ltxText, this.textsize, color).split(",")
|
||||||
if(!this._canvas.isImageLoaded(imgData.source) && !this._canvas.isImageLoading(imgData.source)){
|
let imgData = {
|
||||||
// Wait until the image is loaded to callback.
|
"source": ltxSrc,
|
||||||
this._canvas.loadImage(imgData.source)
|
"width": parseFloat(ltxWidth),
|
||||||
this._canvas.imageLoaders[imgData.source] = [callback, imgData]
|
"height": parseFloat(ltxHeight)
|
||||||
} else {
|
};
|
||||||
// Callback directly
|
if(!this._canvas.isImageLoaded(ltxSrc) && !this._canvas.isImageLoading(ltxSrc)){
|
||||||
callback(imgData)
|
// Wait until the image is loaded to callback.
|
||||||
}
|
this._canvas.loadImage(ltxSrc)
|
||||||
|
this._canvas.imageLoaders[ltxSrc] = [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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -523,4 +519,4 @@ class CanvasAPI extends Module {
|
||||||
|
|
||||||
/** @type {CanvasAPI} */
|
/** @type {CanvasAPI} */
|
||||||
Modules.Canvas = Modules.Canvas || new CanvasAPI()
|
Modules.Canvas = Modules.Canvas || new CanvasAPI()
|
||||||
export const API = Modules.Canvas
|
export const API = Modules.Canvas
|
|
@ -109,28 +109,24 @@ export class Action {
|
||||||
* Renders a LaTeX-formatted string to an image and wraps it in an HTML tag in a string.
|
* Renders a LaTeX-formatted string to an image and wraps it in an HTML tag in a string.
|
||||||
*
|
*
|
||||||
* @param {string} latexString - Source string of the latex.
|
* @param {string} latexString - Source string of the latex.
|
||||||
* @returns {Promise<string>}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
renderLatexAsHtml(latexString) {
|
renderLatexAsHtml(latexString) {
|
||||||
if(!Latex.enabled)
|
if(!Latex.enabled)
|
||||||
throw new Error("Cannot render an item as LaTeX when LaTeX is disabled.")
|
throw new Error("Cannot render an item as LaTeX when LaTeX is disabled.")
|
||||||
return new Promise(resolve => {
|
let imgDepth = Modules.History.imageDepth
|
||||||
let imgDepth = Modules.History.imageDepth
|
let [src, width, height] = Latex.render(
|
||||||
Latex.requestAsyncRender(
|
latexString,
|
||||||
latexString,
|
imgDepth * (Modules.History.fontSize + 2),
|
||||||
imgDepth * (Modules.History.fontSize + 2),
|
Modules.History.themeTextColor
|
||||||
Modules.History.themeTextColor
|
).split(",")
|
||||||
).then((imgData) => {
|
return `<img src="${src}" width="${parseInt(width)/imgDepth}" height="${parseInt(height)/imgDepth}" style="vertical-align: middle"/>`
|
||||||
const { source, width, height } = imgData
|
|
||||||
resolve(`<img src="${source}" width="${width/imgDepth}" height="${height/imgDepth}" style="vertical-align: middle"/>`)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a string with the HTML-formatted description of the action.
|
* Returns a string with the HTML-formatted description of the action.
|
||||||
*
|
*
|
||||||
* @returns {string|Promise<string>}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
getHTMLString() {
|
getHTMLString() {
|
||||||
return this.getReadableString()
|
return this.getReadableString()
|
||||||
|
|
|
@ -85,9 +85,8 @@ export default class EditedProperty extends Action {
|
||||||
}
|
}
|
||||||
|
|
||||||
setReadableValues() {
|
setReadableValues() {
|
||||||
this.prevString = ""
|
this.prevString = "";
|
||||||
this.nextString = ""
|
this.nextString = "";
|
||||||
this._renderPromises = []
|
|
||||||
if(this.propertyType instanceof Object) {
|
if(this.propertyType instanceof Object) {
|
||||||
switch(this.propertyType.type) {
|
switch(this.propertyType.type) {
|
||||||
case "Enum":
|
case "Enum":
|
||||||
|
@ -119,11 +118,8 @@ export default class EditedProperty extends Action {
|
||||||
this.prevHTML = '<tt style="background: rgba(128,128,128,0.1);"> '+this.prevString+' </tt>'
|
this.prevHTML = '<tt style="background: rgba(128,128,128,0.1);"> '+this.prevString+' </tt>'
|
||||||
this.nextHTML = '<tt style="background: rgba(128,128,128,0.1);"> '+this.nextString+' </tt>'
|
this.nextHTML = '<tt style="background: rgba(128,128,128,0.1);"> '+this.nextString+' </tt>'
|
||||||
if(Latex.enabled && typeof this.propertyType == 'object' && this.propertyType.type === "Expression") {
|
if(Latex.enabled && typeof this.propertyType == 'object' && this.propertyType.type === "Expression") {
|
||||||
// Store promises so that querying can wait for them to finish.
|
this.prevHTML= this.renderLatexAsHtml(this.previousValue.latexMarkup)
|
||||||
this._renderPromises = [
|
this.nextHTML= this.renderLatexAsHtml(this.newValue.latexMarkup)
|
||||||
this.renderLatexAsHtml(this.previousValue.latexMarkup).then(prev => this.prevHTML = prev),
|
|
||||||
this.renderLatexAsHtml(this.newValue.latexMarkup).then(next => this.nextHTML = prev)
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,21 +131,10 @@ export default class EditedProperty extends Action {
|
||||||
}
|
}
|
||||||
|
|
||||||
getHTMLString() {
|
getHTMLString() {
|
||||||
return new Promise(resolve => {
|
return qsTr('%1 of %2 changed from %3 to %4.')
|
||||||
const translation = qsTr('%1 of %2 changed from %3 to %4.')
|
.arg(this.targetPropertyReadable)
|
||||||
.arg(this.targetPropertyReadable)
|
.arg('<b style="font-size: 15px;"> ' + this.targetName + ' </b>')
|
||||||
.arg('<b style="font-size: 15px;"> ' + this.targetName + ' </b>')
|
.arg(this.prevHTML)
|
||||||
// Check if we need to wait for LaTeX HTML to be rendered.
|
.arg(this.nextHTML)
|
||||||
if(this.prevHTML !== undefined && this.nextHTML !== undefined)
|
|
||||||
resolve(translation.arg(this.prevHTML).arg(this.nextHTML))
|
|
||||||
else
|
|
||||||
Promise.all(this._renderPromises).then((rendered) => {
|
|
||||||
// Rendered are (potentially) two HTML strings which are defined during rendering
|
|
||||||
this.prevHTML = this.prevHTML ?? rendered[0]
|
|
||||||
this.nextHTML = this.prevHTML ?? rendered[1]
|
|
||||||
resolve(translation.arg(this.prevHTML).arg(this.nextHTML))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,15 +61,10 @@ export default class EditedPosition extends Action {
|
||||||
setReadableValues() {
|
setReadableValues() {
|
||||||
this.prevString = `(${this.previousXValue.toString()},${this.previousYValue.toString()})`
|
this.prevString = `(${this.previousXValue.toString()},${this.previousYValue.toString()})`
|
||||||
this.nextString = `(${this.newXValue.toString()},${this.newYValue.toString()})`
|
this.nextString = `(${this.newXValue.toString()},${this.newYValue.toString()})`
|
||||||
this._renderPromises = []
|
|
||||||
// Render as LaTeX
|
// Render as LaTeX
|
||||||
if(Latex.enabled) {
|
if(Latex.enabled) {
|
||||||
const prevMarkup = `\\left(${this.previousXValue.latexMarkup},${this.previousYValue.latexMarkup}\\right)`
|
this.prevHTML = this.renderLatexAsHtml(`\\left(${this.previousXValue.latexMarkup},${this.previousYValue.latexMarkup}\\right)`)
|
||||||
const nextMarkup = `\\left(${this.newXValue.latexMarkup},${this.newYValue.latexMarkup}\\right)`
|
this.nextHTML = this.renderLatexAsHtml(`\\left(${this.newXValue.latexMarkup},${this.newYValue.latexMarkup}\\right)`)
|
||||||
this._renderPromises = [ // Will be taken in promise.all
|
|
||||||
this.renderLatexAsHtml(prevMarkup),
|
|
||||||
this.renderLatexAsHtml(nextMarkup)
|
|
||||||
]
|
|
||||||
} else {
|
} else {
|
||||||
this.prevHTML = '<tt style="background: rgba(128,128,128,0.1);"> '+escapeHTML(this.prevString)+' </tt>'
|
this.prevHTML = '<tt style="background: rgba(128,128,128,0.1);"> '+escapeHTML(this.prevString)+' </tt>'
|
||||||
this.nextHTML = '<tt style="background: rgba(128,128,128,0.1);"> '+escapeHTML(this.nextString)+' </tt>'
|
this.nextHTML = '<tt style="background: rgba(128,128,128,0.1);"> '+escapeHTML(this.nextString)+' </tt>'
|
||||||
|
@ -90,20 +85,9 @@ export default class EditedPosition extends Action {
|
||||||
}
|
}
|
||||||
|
|
||||||
getHTMLString() {
|
getHTMLString() {
|
||||||
return new Promise(resolve => {
|
return qsTr('Position of %1 set from %2 to %3.')
|
||||||
const translation = qsTr('Position of %1 set from %2 to %3.')
|
.arg('<b style="font-size: 15px;"> ' + this.targetName + ' </b>')
|
||||||
.arg('<b style="font-size: 15px;"> ' + this.targetName + ' </b>')
|
.arg(this.prevHTML)
|
||||||
// Check if we need to wait for LaTeX HTML to be rendered.
|
.arg(this.nextHTML)
|
||||||
if(this.prevHTML !== undefined && this.nextHTML !== undefined)
|
|
||||||
resolve(translation.arg(this.prevHTML).arg(this.nextHTML))
|
|
||||||
else
|
|
||||||
Promise.all(this._renderPromises).then((rendered) => {
|
|
||||||
// Rendered are (potentially) two HTML strings which are defined during rendering
|
|
||||||
this.prevHTML = this.prevHTML ?? rendered[0]
|
|
||||||
this.nextHTML = this.prevHTML ?? rendered[1]
|
|
||||||
resolve(translation.arg(this.prevHTML).arg(this.nextHTML))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,20 +39,6 @@ const equivalchars = ["\\alpha","\\beta","\\gamma","\\delta","\\epsilon","\\zeta
|
||||||
"{}_{4}","{}_{5}","{}_{6}","{}_{7}","{}_{8}","{}_{9}","{}_{0}",
|
"{}_{4}","{}_{5}","{}_{6}","{}_{7}","{}_{8}","{}_{9}","{}_{0}",
|
||||||
"\\pi", "\\infty"]
|
"\\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 {
|
class LatexAPI extends Module {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -64,50 +50,11 @@ class LatexAPI extends Module {
|
||||||
* true if latex has been enabled by the user, false otherwise.
|
* true if latex has been enabled by the user, false otherwise.
|
||||||
*/
|
*/
|
||||||
this.enabled = Helper.getSettingBool("enable_latex")
|
this.enabled = Helper.getSettingBool("enable_latex")
|
||||||
}
|
/**
|
||||||
|
* Mirror method for Python object.
|
||||||
/**
|
* @type {function(string, number, string): string}.
|
||||||
* Prepares and renders a latex string into a png file.
|
*/
|
||||||
*
|
this.render = Latex.render
|
||||||
* @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,13 +92,15 @@ class Latex(QObject):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
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=str)
|
@Slot(str, float, QColor, result=str)
|
||||||
def render(self, latex_markup: str, font_size: float, color: QColor) -> str:
|
def render(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.
|
||||||
"""
|
"""
|
||||||
markup_hash, export_path = self.create_export_path(latex_markup, font_size, color)
|
markup_hash = "render" + str(hash(latex_markup))
|
||||||
|
export_path = path.join(self.tempdir.name, f'{markup_hash}_{int(font_size)}_{color.rgb()}')
|
||||||
if self.latexSupported and not path.exists(export_path + ".png"):
|
if self.latexSupported and not path.exists(export_path + ".png"):
|
||||||
print("Rendering", latex_markup, export_path)
|
print("Rendering", latex_markup, export_path)
|
||||||
# Generating file
|
# Generating file
|
||||||
|
@ -119,28 +121,6 @@ class Latex(QObject):
|
||||||
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()}'
|
||||||
|
|
||||||
@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):
|
def create_latex_doc(self, export_path: str, latex_markup: str):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in a new issue