Reorganizing paths
This commit is contained in:
parent
e9d204daab
commit
34cb856dd4
249 changed files with 118 additions and 294 deletions
334
common/src/module/latex.mjs
Normal file
334
common/src/module/latex.mjs
Normal file
|
@ -0,0 +1,334 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Module } from "./common.mjs"
|
||||
import * as Instruction from "../lib/expr-eval/instruction.mjs"
|
||||
import { escapeValue } from "../lib/expr-eval/expression.mjs"
|
||||
import { HelperInterface, LatexInterface } from "./interface.mjs"
|
||||
|
||||
const unicodechars = [
|
||||
"α", "β", "γ", "δ", "ε", "ζ", "η",
|
||||
"π", "θ", "κ", "λ", "μ", "ξ", "ρ",
|
||||
"ς", "σ", "τ", "φ", "χ", "ψ", "ω",
|
||||
"Γ", "Δ", "Θ", "Λ", "Ξ", "Π", "Σ",
|
||||
"Φ", "Ψ", "Ω", "ₐ", "ₑ", "ₒ", "ₓ",
|
||||
"ₕ", "ₖ", "ₗ", "ₘ", "ₙ", "ₚ", "ₛ",
|
||||
"ₜ", "¹", "²", "³", "⁴", "⁵", "⁶",
|
||||
"⁷", "⁸", "⁹", "⁰", "₁", "₂", "₃",
|
||||
"₄", "₅", "₆", "₇", "₈", "₉", "₀",
|
||||
"pi", "∞"]
|
||||
const 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}",
|
||||
"\\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() {
|
||||
super("Latex", {
|
||||
latex: LatexInterface,
|
||||
helper: HelperInterface
|
||||
})
|
||||
/**
|
||||
* true if latex has been enabled by the user, false otherwise.
|
||||
*/
|
||||
this.enabled = false
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {LatexInterface} latex
|
||||
* @param {HelperInterface} helper
|
||||
*/
|
||||
initialize({ latex, helper }) {
|
||||
super.initialize({ latex, helper })
|
||||
this.latex = latex
|
||||
this.helper = helper
|
||||
this.enabled = helper.getSettingBool("enable_latex")
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {string} color - Color of the text to render.
|
||||
* @returns {LatexRenderResult|null}
|
||||
*/
|
||||
findPrerendered(markup, fontSize, color) {
|
||||
if(!this.initialized) throw new Error("Attempting findPrerendered before initialize!")
|
||||
const data = this.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 {string} color - Color of the text to render.
|
||||
* @returns {Promise<LatexRenderResult>}
|
||||
*/
|
||||
async requestAsyncRender(markup, fontSize, color) {
|
||||
if(!this.initialized) throw new Error("Attempting requestAsyncRender before initialize!")
|
||||
let args = this.latex.render(markup, fontSize, color).split(",")
|
||||
return new LatexRenderResult(...args)
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts element within parenthesis.
|
||||
*
|
||||
* @param {string|number} elem - element to put within parenthesis.
|
||||
* @returns {string}
|
||||
*/
|
||||
par(elem) {
|
||||
return `(${elem})`
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the element contains at least one of the elements of
|
||||
* the string array contents, but not at the first position of the string,
|
||||
* and returns the parenthesis version if so.
|
||||
*
|
||||
* @param {string|number} elem - element to put within parenthesis.
|
||||
* @param {Array} contents - Array of elements to put within parenthesis.
|
||||
* @returns {string}
|
||||
*/
|
||||
parif(elem, contents) {
|
||||
elem = elem.toString()
|
||||
if(elem[0] !== "(" && elem[elem.length - 1] !== ")" && contents.some(x => elem.indexOf(x) > 0))
|
||||
return this.par(elem)
|
||||
if(elem[0] === "(" && elem[elem.length - 1] === ")")
|
||||
return elem.removeEnclosure()
|
||||
return elem
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a latex expression for a function.
|
||||
*
|
||||
* @param {string} f - Function to convert
|
||||
* @param {(number,string)[]} args - Arguments of the function
|
||||
* @returns {string}
|
||||
*/
|
||||
functionToLatex(f, args) {
|
||||
switch(f) {
|
||||
case "derivative":
|
||||
if(args.length === 3)
|
||||
return `\\frac{d${args[0].removeEnclosure().replaceAll(args[1].removeEnclosure(), "x")}}{dx}`
|
||||
else
|
||||
return `\\frac{d${args[0]}}{dx}(x)`
|
||||
case "integral":
|
||||
if(args.length === 4)
|
||||
return `\\int\\limits_{${args[0]}}^{${args[1]}}${args[2].removeEnclosure()} d${args[3].removeEnclosure()}`
|
||||
else
|
||||
return `\\int\\limits_{${args[0]}}^{${args[1]}}${args[2]}(t) dt`
|
||||
case "sqrt":
|
||||
return `\\sqrt\\left(${args.join(", ")}\\right)`
|
||||
case "abs":
|
||||
return `\\left|${args.join(", ")}\\right|`
|
||||
case "floor":
|
||||
return `\\left\\lfloor${args.join(", ")}\\right\\rfloor`
|
||||
case "ceil":
|
||||
return `\\left\\lceil${args.join(", ")}\\right\\rceil`
|
||||
default:
|
||||
return `\\mathrm{${f}}\\left(${args.join(", ")}\\right)`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a latex variable from a variable.
|
||||
*
|
||||
* @param {string} vari - variable text to convert
|
||||
* @param {boolean} wrapIn$ - checks whether the escaped chars should be escaped
|
||||
* @returns {string}
|
||||
*/
|
||||
variable(vari, wrapIn$ = false) {
|
||||
if(wrapIn$)
|
||||
for(let i = 0; i < unicodechars.length; i++) {
|
||||
if(vari.includes(unicodechars[i]))
|
||||
vari = vari.replaceAll(unicodechars[i], "$" + equivalchars[i] + "$")
|
||||
}
|
||||
else
|
||||
for(let i = 0; i < unicodechars.length; i++) {
|
||||
if(vari.includes(unicodechars[i]))
|
||||
vari = vari.replaceAll(unicodechars[i], equivalchars[i])
|
||||
}
|
||||
return vari
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts expr-eval instructions to a latex string.
|
||||
*
|
||||
* @param {Instruction[]} instructions - expr-eval tokens list
|
||||
* @returns {string}
|
||||
*/
|
||||
expression(instructions) {
|
||||
let nstack = []
|
||||
let n1, n2, n3
|
||||
let f, args, argCount
|
||||
for(let item of instructions) {
|
||||
let type = item.type
|
||||
|
||||
switch(type) {
|
||||
case Instruction.INUMBER:
|
||||
if(item.value === Infinity) {
|
||||
nstack.push("\\infty")
|
||||
} else if(typeof item.value === "number" && item.value < 0) {
|
||||
nstack.push(this.par(item.value))
|
||||
} else if(Array.isArray(item.value)) {
|
||||
nstack.push("[" + item.value.map(escapeValue).join(", ") + "]")
|
||||
} else {
|
||||
nstack.push(escapeValue(item.value))
|
||||
}
|
||||
break
|
||||
case Instruction.IOP2:
|
||||
n2 = nstack.pop()
|
||||
n1 = nstack.pop()
|
||||
f = item.value
|
||||
switch(f) {
|
||||
case "-":
|
||||
case "+":
|
||||
nstack.push(n1 + f + n2)
|
||||
break
|
||||
case "||":
|
||||
case "or":
|
||||
case "&&":
|
||||
case "and":
|
||||
case "==":
|
||||
case "!=":
|
||||
nstack.push(this.par(n1) + f + this.par(n2))
|
||||
break
|
||||
case "*":
|
||||
if(n2 === "\\pi" || n2 === "e" || n2 === "x" || n2 === "n")
|
||||
nstack.push(this.parif(n1, ["+", "-"]) + n2)
|
||||
else
|
||||
nstack.push(this.parif(n1, ["+", "-"]) + " \\times " + this.parif(n2, ["+", "-"]))
|
||||
break
|
||||
case "/":
|
||||
nstack.push("\\frac{" + n1 + "}{" + n2 + "}")
|
||||
break
|
||||
case "^":
|
||||
nstack.push(this.parif(n1, ["+", "-", "*", "/", "!"]) + "^{" + n2 + "}")
|
||||
break
|
||||
case "%":
|
||||
nstack.push(this.parif(n1, ["+", "-", "*", "/", "!", "^"]) + " \\mathrm{mod} " + this.parif(n2, ["+", "-", "*", "/", "!", "^"]))
|
||||
break
|
||||
case "[":
|
||||
nstack.push(n1 + "[" + n2 + "]")
|
||||
break
|
||||
default:
|
||||
throw new EvalError("Unknown operator " + item.value + ".")
|
||||
}
|
||||
break
|
||||
case Instruction.IOP3: // Thirdiary operator
|
||||
n3 = nstack.pop()
|
||||
n2 = nstack.pop()
|
||||
n1 = nstack.pop()
|
||||
f = item.value
|
||||
if(f === "?") {
|
||||
nstack.push("(" + n1 + " ? " + n2 + " : " + n3 + ")")
|
||||
} else {
|
||||
throw new EvalError("Unknown operator " + item.value + ".")
|
||||
}
|
||||
break
|
||||
case Instruction.IVAR:
|
||||
case Instruction.IVARNAME:
|
||||
nstack.push(this.variable(item.value.toString()))
|
||||
break
|
||||
case Instruction.IOP1: // Unary operator
|
||||
n1 = nstack.pop()
|
||||
f = item.value
|
||||
switch(f) {
|
||||
case "-":
|
||||
case "+":
|
||||
nstack.push(this.par(f + n1))
|
||||
break
|
||||
case "!":
|
||||
nstack.push(this.parif(n1, ["+", "-", "*", "/", "^"]) + "!")
|
||||
break
|
||||
default:
|
||||
nstack.push(f + this.parif(n1, ["+", "-", "*", "/", "^"]))
|
||||
break
|
||||
}
|
||||
break
|
||||
case Instruction.IFUNCALL:
|
||||
argCount = item.value
|
||||
args = []
|
||||
while(argCount-- > 0) {
|
||||
args.unshift(nstack.pop())
|
||||
}
|
||||
f = nstack.pop()
|
||||
// Handling various functions
|
||||
nstack.push(this.functionToLatex(f, args))
|
||||
break
|
||||
case Instruction.IMEMBER:
|
||||
n1 = nstack.pop()
|
||||
nstack.push(n1 + "." + item.value)
|
||||
break
|
||||
case Instruction.IARRAY:
|
||||
argCount = item.value
|
||||
args = []
|
||||
while(argCount-- > 0) {
|
||||
args.unshift(nstack.pop())
|
||||
}
|
||||
nstack.push("[" + args.join(", ") + "]")
|
||||
break
|
||||
case Instruction.IEXPR:
|
||||
nstack.push("(" + this.expression(item.value) + ")")
|
||||
break
|
||||
case Instruction.IENDSTATEMENT:
|
||||
break
|
||||
default:
|
||||
throw new EvalError("invalid Expression")
|
||||
}
|
||||
}
|
||||
if(nstack.length > 1) {
|
||||
nstack = [nstack.join(";")]
|
||||
}
|
||||
return String(nstack[0])
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {LatexAPI} */
|
||||
Modules.Latex = Modules.Latex || new LatexAPI()
|
||||
/** @type {LatexAPI} */
|
||||
export default Modules.Latex
|
Loading…
Add table
Add a link
Reference in a new issue