" + styles);
+ const svgBlob = new Blob([svgString], {
+ type: "image/svg+xml"
+ });
+ const svgUrl = URL.createObjectURL(svgBlob);
+ const canvas = new OffscreenCanvas(width, height);
+ const ctx = canvas.getContext("2d");
+ try {
+ // load this svg on an image
+ const svgImage = new Image(width, height);
+ svgImage.src = svgUrl;
+ // wait for load
+ await new Promise((resolve, reject) => {
+ svgImage.onload = () => resolve();
+ svgImage.onerror = err => reject(err);
+ });
+ // draw onto canvas
+ ctx.drawImage(svgImage, 0, 0);
+ const pngBlob = await canvas.convertToBlob({
+ type: "image/png"
+ });
+ console.log(URL.createObjectURL(pngBlob));
+ return pngBlob;
+ }
+ finally {
+ URL.revokeObjectURL(svgUrl);
+ }
+ // TODO: error handling
+}
/**
* On document load, assign click handlers to each button. Added document.ready.
*/
@@ -29,15 +92,15 @@
}, 300);
}
function getCurrentSettings() {
- var sizeRaw = $('#size :selected').val();
- var delimiter = $('#delimit :selected').val();
- return { sizeRaw: sizeRaw, delimiter: delimiter };
+ const sizeRaw = $('#size :selected').val();
+ const delimiter = $('#delimit :selected').val();
+ return { sizeRaw, delimiter };
}
//$('donate_button').on("click",function(e){e.preventDefault;}); // for paypal to disable sidebar disappearing
// Close the dropdown menu if the user clicks outside of it
window.onclick = function (event) {
if (!event.target.matches('.dropbtn')) {
- document.querySelectorAll(".dropdown-content.show").forEach(function (openDropdown) { return openDropdown.classList.remove('show'); });
+ document.querySelectorAll(".dropdown-content.show").forEach(openDropdown => openDropdown.classList.remove('show'));
}
};
$("#advanced").click(function (event) {
@@ -63,46 +126,47 @@
$('#edit-text').prop("disabled", false);
$('#undo-all').prop("disabled", false);
}
+function makeStatusText(successCount) {
+ if (successCount == 0)
+ return "Status: No equations rendered";
+ else if (successCount == 1)
+ return "Status: 1 equation rendered";
+ else
+ return `Status: ${successCount} equations rendered`;
+}
function insertText() {
this.disabled = true;
$('#error').remove();
$("#loading").html("Status: Loading");
- var runDots = runDotAnimation();
- var _a = getCurrentSettings(), sizeRaw = _a.sizeRaw, delimiter = _a.delimiter;
+ const runDots = runDotAnimation();
+ const { sizeRaw, delimiter } = getCurrentSettings();
google.script.run
- .withSuccessHandler(function (returnSuccess, element) {
- $("#loading").html('');
- clearInterval(runDots);
- element.disabled = false;
- console.log(returnSuccess);
- var flag = 0;
- var renderCount = 1;
- if (returnSuccess < -1) {
- flag = -2;
- renderCount = -2 - returnSuccess;
- }
- else if (returnSuccess == -1) {
- flag = -1;
- renderCount = 0;
+ .withSuccessHandler(function ({ lastStatus, successCount, clientEquations }, element) {
+ if (lastStatus === 1 /* google.script.DocsEquationRenderStatus.ClientRender */) {
+ // we're not done yet - these equations need to be rendered on the client
+ Promise.all(clientEquations.map(async (c) => ({ options: c, result: await renderMathJaxEquation(c) })))
+ .then(rendered => {
+ const formData = new FormData();
+ for (const equation of rendered) {
+ formData.append("");
+ }
+ });
}
else {
- flag = 0;
- renderCount = returnSuccess;
+ $("#loading").html('');
+ clearInterval(runDots);
+ element.disabled = false;
+ const statusText = makeStatusText(successCount);
+ if (lastStatus === 3 /* google.script.DocsEquationRenderStatus.NoDocument */)
+ showError("Sorry, the script has conflicting authorizations. Try signing out of other active Gsuite accounts.", statusText);
+ else if (lastStatus === 0 /* google.script.DocsEquationRenderStatus.AllRenderersFailed */ && successCount > 0)
+ showError("Sorry, an equation is incorrect, or (temporarily) unavailable commands (i.e. align, &) were used.", statusText);
+ else if (lastStatus === 0 /* google.script.DocsEquationRenderStatus.AllRenderersFailed */ && successCount === 0)
+ showError("Sorry, likely (temporarily) unavailable commands (i.e. align, &) were used or the equation was too long.", statusText);
+ else {
+ $("#loading").html(statusText);
+ }
}
- // var flag = returnSuccess.flag
- // var renderCount = returnSuccess.renderCount
- if (flag == -1)
- showError("Sorry, the script has conflicting authorizations. Try signing out of other active Gsuite accounts.", "Status: " + renderCount + " equations replaced");
- else if (flag == -2 && renderCount > 0)
- showError("Sorry, an equation is incorrect, or (temporarily) unavailable commands (i.e. align, &) were used.", "Status: " + renderCount + " equations replaced");
- else if (flag == -2 && renderCount == 0)
- showError("Sorry, likely (temporarily) unavailable commands (i.e. align, &) were used or the equation was too long.", "Status: " + "no" + " equations replaced");
- else if (flag == 0 && renderCount == 0)
- $("#loading").html("Status: " + "No" + " equations rendered");
- else if (flag == 0 && renderCount == 1)
- $("#loading").html("Status: " + renderCount + " equation rendered");
- else
- $("#loading").html("Status: " + renderCount + " equations rendered");
})
.withFailureHandler(function (msg, element) {
$("#loading").html('');
@@ -118,8 +182,8 @@
this.disabled = true;
$('#error').remove();
$("#loading").html("Status: Loading");
- var runDots = runDotAnimation();
- var _a = getCurrentSettings(), sizeRaw = _a.sizeRaw, delimiter = _a.delimiter;
+ const runDots = runDotAnimation();
+ const { sizeRaw, delimiter } = getCurrentSettings();
google.script.run
.withSuccessHandler(function (returnSuccess, element) {
$("#loading").html('');
@@ -165,8 +229,8 @@
$("#loading").html("Status: Loading");
//var div = $('' + 'Ctrl + q detected' + '
');
//$('#button-bar').after(div);
- var runDots = runDotAnimation();
- var delimiter = getCurrentSettings().delimiter;
+ const runDots = runDotAnimation();
+ const { delimiter } = getCurrentSettings();
google.script.run
.withSuccessHandler(function (returnSuccess, element) {
$("#loading").html('');
@@ -215,12 +279,12 @@
$("#loading").html("Status: Loading");
//var div = $('' + 'Ctrl + q detected' + '
');
//$('#button-bar').after(div);
- var runDots_1 = runDotAnimation();
- var delimiter = getCurrentSettings().delimiter;
+ const runDots = runDotAnimation();
+ const { delimiter } = getCurrentSettings();
google.script.run
.withSuccessHandler(function (returnSuccess) {
$("#loading").html('');
- clearInterval(runDots_1);
+ clearInterval(runDots);
$("#loading").html("Status: " + 0 + " equations de-rendered.");
if (returnSuccess < 0) {
$("#loading").html("Status: " + "No" + " equations de-rendered.");
@@ -235,7 +299,7 @@
})
.withFailureHandler(function () {
$("#loading").html('');
- clearInterval(runDots_1);
+ clearInterval(runDots);
showError("Please ensure cursor is inside document.", "Status: Error, please move cursor into document.");
})
.removeAll(delimiter);
@@ -253,4 +317,5 @@
$('#loading').after(div);
$('#loading').html(msg2);
}
-
\ No newline at end of file
+
+
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 514b56b..5cd5aeb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18,6 +18,7 @@
"@grimsteel/clasp-types": "^1.7.3",
"@types/google-apps-script": "^1.0.73",
"@types/jquery": "^3.5.16",
+ "@types/mathjax": "^0.0.40",
"@typescript-eslint/eslint-plugin": "^5.55.0",
"@typescript-eslint/parser": "^5.55.0",
"eslint": "^7.32.0",
@@ -243,6 +244,13 @@
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
+ "node_modules/@types/mathjax": {
+ "version": "0.0.40",
+ "resolved": "https://registry.npmjs.org/@types/mathjax/-/mathjax-0.0.40.tgz",
+ "integrity": "sha512-rHusx08LCg92WJxrsM3SPjvLTSvK5C+gealtSuhKbEOcUZfWlwigaFoPLf6Dfxhg4oryN5qP9Sj7zOQ4HYXINw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/semver": {
"version": "7.3.13",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz",
@@ -2260,6 +2268,12 @@
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
+ "@types/mathjax": {
+ "version": "0.0.40",
+ "resolved": "https://registry.npmjs.org/@types/mathjax/-/mathjax-0.0.40.tgz",
+ "integrity": "sha512-rHusx08LCg92WJxrsM3SPjvLTSvK5C+gealtSuhKbEOcUZfWlwigaFoPLf6Dfxhg4oryN5qP9Sj7zOQ4HYXINw==",
+ "dev": true
+ },
"@types/semver": {
"version": "7.3.13",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz",
diff --git a/package.json b/package.json
index 2497e48..4df1782 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,7 @@
"@grimsteel/clasp-types": "^1.7.3",
"@types/google-apps-script": "^1.0.73",
"@types/jquery": "^3.5.16",
+ "@types/mathjax": "^0.0.40",
"@typescript-eslint/eslint-plugin": "^5.55.0",
"@typescript-eslint/parser": "^5.55.0",
"eslint": "^7.32.0",
diff --git a/types/common-types/index.d.ts b/types/common-types/index.d.ts
index 6235848..9735e9c 100644
--- a/types/common-types/index.d.ts
+++ b/types/common-types/index.d.ts
@@ -61,6 +61,29 @@ declare namespace AutoLatexCommon {
}
+ /**
+ * Options/state for rendering on the client - these are settings for a specific equation
+ */
+ export interface ClientRenderOptions {
+
+ b: number;
+
+ delim: Delimiter;
+
+ equation: string;
+
+ g: number;
+
+ inline: boolean;
+
+ r: number;
+
+ rangeId: string;
+
+ size: number;
+
+ }
+
export interface Delimiter {
0: string;
@@ -92,10 +115,14 @@ declare namespace AutoLatexCommon {
}
/**
- * Options for rendering. Currently not actually used in Common.renderEquation
+ * Options for rendering on the server - these are general settings for all equations
*/
export interface RenderOptions {
+ b: number;
+
+ clientRender: boolean;
+
defaultSize: number;
delim: Delimiter;
diff --git a/types/docs-types/index.d.ts b/types/docs-types/index.d.ts
index 4b88f68..4e58af5 100644
--- a/types/docs-types/index.d.ts
+++ b/types/docs-types/index.d.ts
@@ -18,7 +18,25 @@ declare namespace google {
removeAll(defaultDelimRaw: string): void //intrinsic;
- replaceEquations(sizeRaw: string, delimiter: string): void //intrinsic;
+ replaceEquations(sizeRaw: string, delimiter: string, clientRender: boolean): void //union;
+
+ }
+
+ export const enum DocsEquationRenderStatus {
+
+ AllRenderersFailed,
+
+ ClientRender,
+
+ EmptyEquation,
+
+ NoDocument,
+
+ NoEndDelimiter,
+
+ NoStartDelimiter,
+
+ Success,
}
From d2a4aca127eb3a40fced55114a5ac1c640799e0d Mon Sep 17 00:00:00 2001
From: Siddhant Kameswar <115331356+grimsteel@users.noreply.github.com>
Date: Sun, 1 Jun 2025 18:48:55 -0500
Subject: [PATCH 03/11] cleanup main render code
- move most options into a single interface
- remove redundant params (start/end for range where the RangeElement is
already passed in, rendererType + renderer[5])
- remove quality param
- improve readability of Docs replaceEquations return value (use object
instead of numerical flag)
- add client rendering option to render code
---
Common/Code.ts | 14 ++--
Docs/Code.ts | 145 +++++++++++++++++++++++-----------
Docs/Sidebar.html | 5 ++
types/common-types/index.d.ts | 6 +-
4 files changed, 117 insertions(+), 53 deletions(-)
diff --git a/Common/Code.ts b/Common/Code.ts
index bb29a9e..aa0eda6 100644
--- a/Common/Code.ts
+++ b/Common/Code.ts
@@ -228,13 +228,13 @@ function deEncode(equation: string) {
* @param size The size of the text, whose neg/pos indicated whether the equation is inline or not.
*/
-function getStyle(equationStringEncoded: string, quality: number, renderer: Renderer, isInline: boolean, type: number, red: number, green: number, blue: number) {
+function getStyle(equationStringEncoded: string, renderer: Renderer, type: number, { inline, r: red, g: green, b: blue }: RenderOptions) {
//ERROR?
const equation: string[] = [];
- equationStringEncoded = equationStringEncoded;
+
reportDeltaTime(307);
if (renderer[5] !== "Texrendr") {
- if (isInline) {
+ if (inline) {
equationStringEncoded = renderer[3] + "%7B%5Ccolor%5BRGB%5D%7B" + red + "%2C" + green + "%2C" + blue + "%7D" + equationStringEncoded + renderer[4] + "%7D";
} else {
equationStringEncoded = "%7B%5Ccolor%5BRGB%5D%7B" + red + "%2C" + green + "%2C" + blue + "%7D" + equationStringEncoded + "%7D";
@@ -285,8 +285,8 @@ function getKey() {
/**
* @public
*/
-function renderEquation(equationOriginal: string, quality: number, delim: Delimiter, isInline: boolean, red: number, green: number, blue: number) {
- var equation = "";
+function renderEquation(equationOriginal: string, renderOptions: RenderOptions) {
+ let equation = "";
let renderer: Renderer | null = null;
let resp: GoogleAppsScript.URL_Fetch.HTTPResponse | null = null;
let failure = 1;
@@ -306,7 +306,7 @@ function renderEquation(equationOriginal: string, quality: number, delim: Delimi
try {
renderer = getRenderer(worked);
rendererType = renderer[5];
- equation = getStyle(equationOriginal, quality, renderer, isInline, worked, red, green, blue);
+ equation = getStyle(equationOriginal, renderer, worked, renderOptions);
// console.log(rendererType, "Texrendr", rendererType == "Texrendr")
if (rendererType == "Texrendr") {
// console.log("Used texrendr", equation, equation.replace("%5C%5C", "%0D"))
@@ -326,7 +326,7 @@ function renderEquation(equationOriginal: string, quality: number, delim: Delimi
renderer[1] = renderer[1].split("EQUATION").join(equation);
renderer[2] = renderer[2].split("FILENAME").join(getFilenameEncode(equation, 0)); // since mutating original object, important each is a new one
debugLog("Link with equation", renderer[1]);
- debugLog("Title Alt Text " + renderer[2] + equationOriginal + "#" + delim[6]);
+ debugLog("Title Alt Text " + renderer[2] + equationOriginal + "#" + renderOptions.delim[6]);
debugLog("Cached equation: " + renderer[2] + renderer[6] + equation);
reportDeltaTime(453);
console.log("Fetching ", renderer[1], " and ", renderer[2] + renderer[6] + equation);
diff --git a/Docs/Code.ts b/Docs/Code.ts
index 8dbba7a..476ce25 100644
--- a/Docs/Code.ts
+++ b/Docs/Code.ts
@@ -27,7 +27,8 @@ const enum DocsEquationRenderStatus {
interface DocsEquationRenderResult {
status: DocsEquationRenderStatus,
equationSize?: number,
- nextStartElement?: GoogleAppsScript.Document.RangeElement
+ nextStartElement?: GoogleAppsScript.Document.RangeElement,
+ clientRenderOptions?: AutoLatexCommon.ClientRenderOptions
}
const DocsApp = {
@@ -103,8 +104,7 @@ function getKey() {
* Constantly keep replacing latex till all are finished
* @public
*/
-function replaceEquations(sizeRaw: string, delimiter: string) {
- const quality = 900;
+function replaceEquations(sizeRaw: string, delimiter: string, clientRender: boolean) {
let size = Common.getSize(sizeRaw);
let isInline = false;
if (size < 0) {
@@ -122,16 +122,28 @@ function replaceEquations(sizeRaw: string, delimiter: string) {
body = DocumentApp.getActiveDocument();
} catch (error) {
console.error(error);
- return Common.encodeFlag(-1, 0);
+
+ return {
+ lastStatus: DocsEquationRenderStatus.NoDocument,
+ successCount: 0
+ };
}
const baseRenderOptions: AutoLatexCommon.RenderOptions = {
- quality,
size,
defaultSize: 11,
inline: isInline,
- delim
+ delim,
+
+ clientRender,
+
+ // TODO: color support for Docs
+ r: 0,
+ g: 0,
+ b: 0
};
+
+ const clientEquations: AutoLatexCommon.ClientRenderOptions[] = [];
const childCount = body.getBody().getParent().getNumChildren();
Common.reportDeltaTime(156);
@@ -142,7 +154,8 @@ function replaceEquations(sizeRaw: string, delimiter: string) {
const {
status,
equationSize,
- nextStartElement
+ nextStartElement,
+ clientRenderOptions
} = findPos(index, baseRenderOptions, failedStartElemIfIsEmpty); //or: "\\\$\\\$", "\\\$\\\$"
if (nextStartElement) failedStartElemIfIsEmpty = nextStartElement;
@@ -158,14 +171,21 @@ function replaceEquations(sizeRaw: string, delimiter: string) {
if (allEmpty > 10) break; //Assume we quit on 10 consecutive empty equations.
- // quit if all renderers failed
- if (status == DocsEquationRenderStatus.AllRenderersFailed) {
- return Common.encodeFlag(-2, c)
+ // quit if all renderers failed or if document failed to load (conflicting authorizations)
+ if (status == DocsEquationRenderStatus.AllRenderersFailed || status == DocsEquationRenderStatus.NoDocument) {
+ return {
+ lastStatus: status,
+ successCount: c
+ };
}
- // error status - move on to the next section
- // note: EmptyEquation is not an error status
- if (status == DocsEquationRenderStatus.NoDocument || status == DocsEquationRenderStatus.NoStartDelimiter || status == DocsEquationRenderStatus.NoEndDelimiter) {
+ if (status === DocsEquationRenderStatus.ClientRender) {
+ clientEquations.push(clientRenderOptions);
+ }
+
+ // could not find next equation
+ // move to next section
+ if (status == DocsEquationRenderStatus.NoStartDelimiter || status == DocsEquationRenderStatus.NoEndDelimiter) {
break;
}
@@ -175,7 +195,19 @@ function replaceEquations(sizeRaw: string, delimiter: string) {
console.log("Rendered equations: " + c);
}
}
- return Common.encodeFlag(0, c);
+
+ if (clientRender) {
+ return {
+ lastStatus: DocsEquationRenderStatus.ClientRender,
+ clientEquations,
+ successCount: 0
+ };
+ }
+
+ return {
+ lastStatus: DocsEquationRenderStatus.Success,
+ successCount: c
+ };
}
/**
@@ -233,40 +265,44 @@ function findPos(index: number, renderOptions: AutoLatexCommon.RenderOptions, pr
};
}
- return placeImage(startElement, placeHolderStart, placeHolderEnd, renderOptions);
+ return placeImage(startElement, renderOptions);
}
-function getEquation(textElement: GoogleAppsScript.Document.Text, start: number, end: number, delimiters: AutoLatexCommon.Delimiter) {
- const equationOriginal = [];
+function getEquation(rangeElement: GoogleAppsScript.Document.RangeElement, delimiters: AutoLatexCommon.Delimiter) {
+ const textElement = rangeElement.getElement().asText();
Common.reportDeltaTime(284);
Common.debugLog("See text", textElement.getText(), textElement.getText().length);
const equation = textElement
.getText()
- .substring(start + delimiters[4], end - delimiters[4] + 1);
- Common.debugLog("See equation", equation);
- const equationStringEncoded = Common.reEncode(equation); //escape deprecated
- equationOriginal.push(equationStringEncoded);
+ .substring(
+ rangeElement.getStartOffset() + delimiters[4], rangeElement.getEndOffsetInclusive() - delimiters[4] + 1
+ );
+ Common.debugLog("See equation", equation);
+ const equationStringEncoded = Common.reEncode(equation); //escape deprecated
+
Common.reportDeltaTime(290);
//console.log("Encoded: " + equationStringEncoded);
return equationStringEncoded;
}
//retrieve size from text
-function setSize(size: number, defaultSize: number, textElement: GoogleAppsScript.Document.Text, start: number) {
+function getSize(size: number, defaultSize: number, rangeElement: GoogleAppsScript.Document.RangeElement) {
//GET SIZE
let newSize = size;
if (size == 0) {
try {
- newSize = textElement
+ newSize = rangeElement
+ .getElement()
.asText()
.editAsText()
- .getFontSize(start + 3); //Fix later: Change from 3 to 1
+ .getFontSize(rangeElement.getStartOffset() + 3); //Fix later: Change from 3 to 1
} catch (err) {
- newSize = textElement
+ newSize = rangeElement
+ .getElement()
.asText()
.editAsText()
- .getFontSize(start + 1); //Fix later: Change from 3 to 1
+ .getFontSize(rangeElement.getStartOffset() + 1); //Fix later: Change from 3 to 1
}
// size = paragraph.getChild(childIndex).editAsText().getFontSize(start+1);//Fix later: Change from 3 to 1
// console.log("New size is " + size); //Causes: Index (3) must be less than the content length (2).
@@ -291,7 +327,7 @@ function setSize(size: number, defaultSize: number, textElement: GoogleAppsScrip
* @param {string} delim[6] The text delimiters and regex delimiters for start and end in that order, and offset from front and back.
*/
-function placeImage(startElement: GoogleAppsScript.Document.RangeElement, start: number, end: number, renderOptions: AutoLatexCommon.RenderOptions): DocsEquationRenderResult {
+function placeImage(startElement: GoogleAppsScript.Document.RangeElement, renderOptions: AutoLatexCommon.RenderOptions): DocsEquationRenderResult {
Common.reportDeltaTime(411);
Common.reportDeltaTime(413);
// GET VARIABLES
@@ -299,11 +335,11 @@ function placeImage(startElement: GoogleAppsScript.Document.RangeElement, start:
const text = textElement.getText();
const paragraph = textElement.getParent().asParagraph();
const childIndex = paragraph.getChildIndex(textElement); //gets index of found text in paragaph
- const size = setSize(renderOptions.size, renderOptions.defaultSize, textElement, start);
- const equationOriginal = getEquation(textElement, start, end, renderOptions.delim);
+ const size = getSize(renderOptions.size, renderOptions.defaultSize, startElement);
+ const equationOriginal = getEquation(startElement, renderOptions.delim);
if (equationOriginal == "") {
- console.log("No equation but undetected start and end as ", start, " ", end);
+ console.log("No equation but undetected start and end as ", startElement.getStartOffset(), " ", startElement.getEndOffsetInclusive());
return {
status: DocsEquationRenderStatus.EmptyEquation,
@@ -311,25 +347,46 @@ function placeImage(startElement: GoogleAppsScript.Document.RangeElement, start:
nextStartElement: startElement
};
}
+
+ // send info to the client for rendering
+ if (renderOptions.clientRender) {
+ const doc = DocumentApp.getActiveDocument();
+ const range = doc.newRange()
+ .addElement(textElement, startElement.getStartOffset(), startElement.getEndOffsetInclusive())
+ .build();
+ // save this range for later
+ const namedRange = doc.addNamedRange("ale-equation-range", range);
+ const clientRenderOptions: AutoLatexCommon.ClientRenderOptions = {
+ ...renderOptions,
+ size,
+ rangeId: namedRange.getId(),
+ equation: equationOriginal
+ };
+ // make sure we can retrieve this element later
+ return {
+ status: DocsEquationRenderStatus.ClientRender,
+ equationSize: size,
+ clientRenderOptions
+ };
+ }
- let { resp, renderer, rendererType, worked, equation } = Common.renderEquation(equationOriginal, renderOptions.quality, renderOptions.delim, renderOptions.inline, 0, 0, 0);
+ let { resp, renderer, worked } = Common.renderEquation(equationOriginal, renderOptions);
if (worked > Common.capableRenderers) return {
status: DocsEquationRenderStatus.AllRenderersFailed
};
// SAVING FORMATTING
Common.reportDeltaTime(511);
if (escape(resp.getBlob().getDataAsString()).substring(0, 50) == Common.invalidEquationHashCodecogsFirst50) {
- worked = 1; //assumes codecogs is 1
- renderer = Common.getRenderer(worked);
- rendererType = renderer[5];
+ //assumes codecogs is 1
+ renderer = Common.getRenderer(1);
}
Common.reportDeltaTime(517);
const textCopy = textElement.asText().copy();
- let endLimit = end;
+ let endLimit = startElement.getEndOffsetInclusive();
if (text.length - 1 < endLimit) endLimit = text.length - 1;
textCopy.asText().editAsText().deleteText(0, endLimit); // the copy only has the stuff after the equation
Common.reportDeltaTime(522);
- textElement.editAsText().deleteText(start, text.length - 1); // from the original, yeet the equation and all the remaining text so its possible to insert the equation (try moving after the equation insertion?)
+ textElement.editAsText().deleteText(startElement.getStartOffset(), text.length - 1); // from the original, yeet the equation and all the remaining text so its possible to insert the equation (try moving after the equation insertion?)
const logoBlob = resp.getBlob();
Common.reportDeltaTime(526);
@@ -337,7 +394,7 @@ function placeImage(startElement: GoogleAppsScript.Document.RangeElement, start:
for (let tryNum = 1; tryNum <= 2; tryNum++) {
try {
paragraph.insertInlineImage(childIndex + 1, logoBlob); // TODO ISSUE: sometimes fails because it times out and yeets
- return repairImage(paragraph, childIndex, size, renderOptions.defaultSize, renderer, renderOptions.delim, textCopy, resp, rendererType, equation, equationOriginal);
+ return repairImage(paragraph, childIndex, size, renderer, renderOptions.delim, textCopy, resp, equationOriginal);
} catch (err) {
console.log(`Could not insert image try ${tryNum}`);
console.error(err);
@@ -349,7 +406,7 @@ function placeImage(startElement: GoogleAppsScript.Document.RangeElement, start:
throw new Error("Could not insert image at childindex!");
}
-function repairImage(paragraph: GoogleAppsScript.Document.Paragraph, childIndex: number, size: number, defaultSize: number, renderer: AutoLatexCommon.Renderer, delim: AutoLatexCommon.Delimiter, textCopy: GoogleAppsScript.Document.Text, resp: GoogleAppsScript.URL_Fetch.HTTPResponse, rendererType: string, equation: string, equationOriginal: string): DocsEquationRenderResult {
+function repairImage(paragraph: GoogleAppsScript.Document.Paragraph, childIndex: number, size: number, renderer: AutoLatexCommon.Renderer, delim: AutoLatexCommon.Delimiter, textCopy: GoogleAppsScript.Document.Text, resp: GoogleAppsScript.URL_Fetch.HTTPResponse, equationOriginal: string): DocsEquationRenderResult {
let attemptsToSetImageUrl = 3;
Common.reportDeltaTime(552); // 3 seconds!! inserting an inline image takes time
while (attemptsToSetImageUrl > 0) {
@@ -363,7 +420,7 @@ function repairImage(paragraph: GoogleAppsScript.Document.Paragraph, childIndex:
}
}
if (attemptsToSetImageUrl < 3) {
- console.log("At ", attemptsToSetImageUrl, " attemptsToSetImageUrls of failing to get child and link , ", equation);
+ console.log("At ", attemptsToSetImageUrl, " attemptsToSetImageUrls of failing to get child and link , ", equationOriginal);
if (attemptsToSetImageUrl == 0) {
throw new Error("Couldn't get equation child!"); // of image immediately after inserting
}
@@ -387,19 +444,19 @@ function repairImage(paragraph: GoogleAppsScript.Document.Paragraph, childIndex:
// if(rendererType.valueOf() === "Texrendr".valueOf()) //Old TexRendr
// size = Math.round(size * height / 174);
let multiple = size / 100.0;
- if (rendererType.valueOf() === "Texrendr".valueOf())
+ if (renderer[5] === "Texrendr")
//TexRendr
multiple = size / 42.0;
- else if (rendererType.valueOf() === "Roger's renderer".valueOf())
+ else if (renderer[5] === "Roger's renderer")
//Rogers renderer
multiple = size / 200.0;
- else if (rendererType.valueOf() === "Codecogs".valueOf())
+ else if (renderer[5] === "Codecogs")
//CodeCogs, other
multiple = size / 100.0;
- else if (rendererType.valueOf() === "Sciweavers".valueOf())
+ else if (renderer[5] === "Sciweavers")
//Scieweavers
multiple = size / 98.0;
- else if (rendererType.valueOf() === "Sciweavers_old".valueOf())
+ else if (renderer[5] === "Sciweavers_old")
//C [75.4, 79.6] on width and height ratio
multiple = size / 76.0;
//CodeCogs, other
diff --git a/Docs/Sidebar.html b/Docs/Sidebar.html
index 329e656..8dc30a3 100644
--- a/Docs/Sidebar.html
+++ b/Docs/Sidebar.html
@@ -49,6 +49,11 @@
+
+
+
+
+
Show Advanced Settings
diff --git a/types/common-types/index.d.ts b/types/common-types/index.d.ts
index 9735e9c..666c9f8 100644
--- a/types/common-types/index.d.ts
+++ b/types/common-types/index.d.ts
@@ -42,7 +42,7 @@ declare namespace AutoLatexCommon {
*/
reEncode(equation: string): string;
- renderEquation(equationOriginal: string, quality: number, delim: Delimiter, isInline: boolean, red: number, green: number, blue: number): {equation: string, renderer: Renderer, rendererType: string, resp: GoogleAppsScript.URL_Fetch.HTTPResponse, worked: number};
+ renderEquation(equationOriginal: string, renderOptions: RenderOptions): {equation: string, renderer: Renderer, rendererType: string, resp: GoogleAppsScript.URL_Fetch.HTTPResponse, worked: number};
reportDeltaTime(line?: number, forcePrint?: string): number;
@@ -127,9 +127,11 @@ declare namespace AutoLatexCommon {
delim: Delimiter;
+ g: number;
+
inline: boolean;
- quality: number;
+ r: number;
size: number;
From 9b1c31364f238e462af721d5d324e51368356db1 Mon Sep 17 00:00:00 2001
From: Siddhant Kameswar <115331356+grimsteel@users.noreply.github.com>
Date: Wed, 4 Jun 2025 01:19:13 -0500
Subject: [PATCH 04/11] finish mathjax renderer
---
Docs/Code.ts | 113 ++++++++++++++++++++++++++----------
Docs/Sidebar.html | 4 +-
Docs/Sidebar.ts | 113 ++++++++++++++++++++++--------------
Docs/SidebarJS.html | 105 +++++++++++++++++++--------------
types/docs-types/index.d.ts | 5 ++
5 files changed, 220 insertions(+), 120 deletions(-)
diff --git a/Docs/Code.ts b/Docs/Code.ts
index 476ce25..ced8570 100644
--- a/Docs/Code.ts
+++ b/Docs/Code.ts
@@ -8,20 +8,17 @@
var DEBUG = false; //doing ctrl + m to get key to see errors is still needed; DEBUG is for all nondiagnostic information
/**
+ * enums should be alphabetical in order to work with clasp-types
* @public
*/
const enum DocsEquationRenderStatus {
- // error types
- NoDocument,
- NoStartDelimiter,
- NoEndDelimiter,
- EmptyEquation,
AllRenderersFailed,
-
- // equation should be rendered on the client side (MathJax)
ClientRender,
-
- Success
+ EmptyEquation,
+ NoDocument,
+ NoEndDelimiter,
+ NoStartDelimiter,
+ Success,
}
interface DocsEquationRenderResult {
@@ -264,8 +261,14 @@ function findPos(index: number, renderOptions: AutoLatexCommon.RenderOptions, pr
status: DocsEquationRenderStatus.EmptyEquation
};
}
+
+ // build the RangeElement for this equation
+ // we make the assumption that the entire equation is contained within one TextElement
+ const range = DocsApp.getActive().newRange()
+ .addElement(startElement.getElement().asText(), startElement.getStartOffset(), endElement.getEndOffsetInclusive())
+ .build();
- return placeImage(startElement, renderOptions);
+ return findEquationAndPlaceImage(range.getRangeElements()[0], renderOptions);
}
@@ -288,20 +291,16 @@ function getEquation(rangeElement: GoogleAppsScript.Document.RangeElement, delim
//retrieve size from text
function getSize(size: number, defaultSize: number, rangeElement: GoogleAppsScript.Document.RangeElement) {
+ const textElement = rangeElement.getElement().asText();
+
//GET SIZE
let newSize = size;
if (size == 0) {
try {
- newSize = rangeElement
- .getElement()
- .asText()
- .editAsText()
+ newSize = textElement
.getFontSize(rangeElement.getStartOffset() + 3); //Fix later: Change from 3 to 1
} catch (err) {
- newSize = rangeElement
- .getElement()
- .asText()
- .editAsText()
+ newSize = textElement
.getFontSize(rangeElement.getStartOffset() + 1); //Fix later: Change from 3 to 1
}
// size = paragraph.getChild(childIndex).editAsText().getFontSize(start+1);//Fix later: Change from 3 to 1
@@ -315,6 +314,47 @@ function getSize(size: number, defaultSize: number, rangeElement: GoogleAppsScri
return newSize;
}
+/**
+* Given a list of rendered equations, place these onto the page
+*
+* @param equations The rendered equations
+* @public
+*/
+function clientRenderComplete(equations: { options: AutoLatexCommon.ClientRenderOptions, renderedEquationB64: string }[]) {
+ const mathjaxRenderer: AutoLatexCommon.Renderer = [
+ 0,
+ "about:blank?type=mathjax&equation=",
+ "about:blank?type=mathjax&equation=",
+ "",
+ "",
+ "MathJax",
+ "",
+ ];
+ let c = 0;
+
+ for (const equation of equations) {
+ const namedRange = DocsApp.getActive().getNamedRangeById(equation.options.rangeId);
+ // get the RangeElement for this equation
+ const range = namedRange.getRange().getRangeElements()[0];
+ const equationBlob = Utilities.newBlob(Utilities.base64Decode(equation.renderedEquationB64), "image/png");
+
+ // this will never actually return any error results
+ const result = placeImage(range, equationBlob, mathjaxRenderer, equation.options.equation, equation.options.size, equation.options.delim);
+
+ if (result.status === DocsEquationRenderStatus.Success) c++;
+
+ namedRange.remove();
+ }
+
+ return {
+ lastStatus: DocsEquationRenderStatus.Success,
+ successCount: c
+ };
+
+ // clean up - remove all of our ranges
+ //DocsApp.getActive().getNamedRanges("ale-equation-range").forEach(range => range.remove());
+}
+
/**
* Given the locations of the delimiters, run code to get font size, get equation, remove equation, encode/style equation, insert/style image.
*
@@ -327,14 +367,11 @@ function getSize(size: number, defaultSize: number, rangeElement: GoogleAppsScri
* @param {string} delim[6] The text delimiters and regex delimiters for start and end in that order, and offset from front and back.
*/
-function placeImage(startElement: GoogleAppsScript.Document.RangeElement, renderOptions: AutoLatexCommon.RenderOptions): DocsEquationRenderResult {
+function findEquationAndPlaceImage(startElement: GoogleAppsScript.Document.RangeElement, renderOptions: AutoLatexCommon.RenderOptions): DocsEquationRenderResult {
Common.reportDeltaTime(411);
Common.reportDeltaTime(413);
// GET VARIABLES
const textElement = startElement.getElement().asText();
- const text = textElement.getText();
- const paragraph = textElement.getParent().asParagraph();
- const childIndex = paragraph.getChildIndex(textElement); //gets index of found text in paragaph
const size = getSize(renderOptions.size, renderOptions.defaultSize, startElement);
const equationOriginal = getEquation(startElement, renderOptions.delim);
@@ -350,6 +387,8 @@ function placeImage(startElement: GoogleAppsScript.Document.RangeElement, rende
// send info to the client for rendering
if (renderOptions.clientRender) {
+ // we don't need URL encoding or double escaping for client renderers
+ const clientEquation = decodeURIComponent(equationOriginal).replace(/\\\\/g, "\\");
const doc = DocumentApp.getActiveDocument();
const range = doc.newRange()
.addElement(textElement, startElement.getStartOffset(), startElement.getEndOffsetInclusive())
@@ -360,13 +399,14 @@ function placeImage(startElement: GoogleAppsScript.Document.RangeElement, rende
...renderOptions,
size,
rangeId: namedRange.getId(),
- equation: equationOriginal
+ equation: clientEquation
};
// make sure we can retrieve this element later
return {
status: DocsEquationRenderStatus.ClientRender,
equationSize: size,
- clientRenderOptions
+ clientRenderOptions,
+ nextStartElement: startElement
};
}
@@ -381,20 +421,29 @@ function placeImage(startElement: GoogleAppsScript.Document.RangeElement, rende
renderer = Common.getRenderer(1);
}
Common.reportDeltaTime(517);
+
+ return placeImage(startElement, resp.getBlob(), renderer, equationOriginal, size, renderOptions.delim);
+}
+
+function placeImage(startElement: GoogleAppsScript.Document.RangeElement, renderedEquation: GoogleAppsScript.Base.Blob, renderer: AutoLatexCommon.Renderer, equation: string, size: number, delim: AutoLatexCommon.Delimiter) {
+ // GET VARIABLES
+ const textElement = startElement.getElement().asText();
+ const text = textElement.getText();
+ const paragraph = textElement.getParent().asParagraph();
+ const childIndex = paragraph.getChildIndex(textElement); //gets index of found text in paragaph
const textCopy = textElement.asText().copy();
let endLimit = startElement.getEndOffsetInclusive();
if (text.length - 1 < endLimit) endLimit = text.length - 1;
textCopy.asText().editAsText().deleteText(0, endLimit); // the copy only has the stuff after the equation
Common.reportDeltaTime(522);
textElement.editAsText().deleteText(startElement.getStartOffset(), text.length - 1); // from the original, yeet the equation and all the remaining text so its possible to insert the equation (try moving after the equation insertion?)
- const logoBlob = resp.getBlob();
Common.reportDeltaTime(526);
// try inserting twice
for (let tryNum = 1; tryNum <= 2; tryNum++) {
try {
- paragraph.insertInlineImage(childIndex + 1, logoBlob); // TODO ISSUE: sometimes fails because it times out and yeets
- return repairImage(paragraph, childIndex, size, renderer, renderOptions.delim, textCopy, resp, equationOriginal);
+ paragraph.insertInlineImage(childIndex + 1, renderedEquation); // TODO ISSUE: sometimes fails because it times out and yeets
+ return repairImage(paragraph, childIndex, size, renderer, delim, textCopy, renderedEquation, equation);
} catch (err) {
console.log(`Could not insert image try ${tryNum}`);
console.error(err);
@@ -406,7 +455,7 @@ function placeImage(startElement: GoogleAppsScript.Document.RangeElement, rende
throw new Error("Could not insert image at childindex!");
}
-function repairImage(paragraph: GoogleAppsScript.Document.Paragraph, childIndex: number, size: number, renderer: AutoLatexCommon.Renderer, delim: AutoLatexCommon.Delimiter, textCopy: GoogleAppsScript.Document.Text, resp: GoogleAppsScript.URL_Fetch.HTTPResponse, equationOriginal: string): DocsEquationRenderResult {
+function repairImage(paragraph: GoogleAppsScript.Document.Paragraph, childIndex: number, size: number, renderer: AutoLatexCommon.Renderer, delim: AutoLatexCommon.Delimiter, textCopy: GoogleAppsScript.Document.Text, resp: GoogleAppsScript.Base.Blob, equationOriginal: string): DocsEquationRenderResult {
let attemptsToSetImageUrl = 3;
Common.reportDeltaTime(552); // 3 seconds!! inserting an inline image takes time
while (attemptsToSetImageUrl > 0) {
@@ -435,7 +484,7 @@ function repairImage(paragraph: GoogleAppsScript.Document.Paragraph, childIndex:
//SET PROPERTIES OF IMAGE (Height, Width)
const oldSize = size; // why use oldsize instead of new size
- if (escape(resp.getBlob().getDataAsString()).substring(0, 50) == Common.invalidEquationHashCodecogsFirst50 || (size > 10 && width == 126 && height == 24)) {
+ if (escape(resp.getDataAsString()).substring(0, 50) == Common.invalidEquationHashCodecogsFirst50 || (size > 10 && width == 126 && height == 24)) {
size *= 5; // make codecogs errors readable, size constraint just in case some small equation is 126x24 as well
}
// console.log(rendererType, rendererType.valueOf(), "Texrendr".valueOf(), rendererType.valueOf() === "Codecogs".valueOf(), rendererType.valueOf() == "Codecogs".valueOf(), rendererType === "Codecogs", rendererType.valueOf() === "Texrendr".valueOf(), rendererType.valueOf() == "Texrendr".valueOf(), rendererType === "Texrendr")
@@ -459,12 +508,14 @@ function repairImage(paragraph: GoogleAppsScript.Document.Paragraph, childIndex:
else if (renderer[5] === "Sciweavers_old")
//C [75.4, 79.6] on width and height ratio
multiple = size / 76.0;
+ else if (renderer[5] === "MathJax")
+ // The MathJax renderer returns scaled equations. We scale up by 5, and 1.16 is just for consistency with other renderers.
+ multiple = 1.294 / 5;
//CodeCogs, other
else multiple = size / 100.0;
- size = Math.round(height * multiple);
Common.reportDeltaTime(595);
- Common.sizeImage(DocsApp, paragraph, childIndex + 1, size, Math.round(width * multiple));
+ Common.sizeImage(DocsApp, paragraph, childIndex + 1, Math.round(height * multiple), Math.round(width * multiple));
return {
status: DocsEquationRenderStatus.Success,
diff --git a/Docs/Sidebar.html b/Docs/Sidebar.html
index 8dc30a3..d7bae8e 100644
--- a/Docs/Sidebar.html
+++ b/Docs/Sidebar.html
@@ -51,8 +51,8 @@
-
-
+
+
Show Advanced Settings
diff --git a/Docs/Sidebar.ts b/Docs/Sidebar.ts
index af163d8..de219fa 100644
--- a/Docs/Sidebar.ts
+++ b/Docs/Sidebar.ts
@@ -17,6 +17,25 @@ window.MathJax = {
}
};
+// animation timeout ID
+let runDots = -1;
+
+/**
+* Convert a Blob to a base64 string for transmission to the server
+*
+* @param blob the blob to convert
+* @returns
+*/
+async function blobToB64(blob: Blob) {
+ const dataUrl = await new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.onloadend = () => resolve(reader.result as string);
+ reader.onerror = err => reject(err);
+ reader.readAsDataURL(blob);
+ });
+ return dataUrl.substring(dataUrl.indexOf(",") + 1); // strip dataurl header
+}
+
async function renderMathJaxEquation(renderOptions: AutoLatexCommon.ClientRenderOptions) {
// newline becomes \\
const equation = renderOptions.equation.replace(/\n|\r|\r\n/g, "\\\\");
@@ -32,8 +51,9 @@ async function renderMathJaxEquation(renderOptions: AutoLatexCommon.ClientRender
svg.style.fontSize = `${renderOptions.size}px`;
document.body.appendChild(svg);
- const width = svg.clientWidth;
- const height = svg.clientHeight;
+ // scale up by 5
+ const width = svg.clientWidth * 5;
+ const height = svg.clientHeight * 5;
svg.remove();
@@ -72,7 +92,6 @@ async function renderMathJaxEquation(renderOptions: AutoLatexCommon.ClientRender
const pngBlob = await canvas.convertToBlob({
type: "image/png"
});
- console.log(URL.createObjectURL(pngBlob));
return pngBlob;
} finally {
URL.revokeObjectURL(svgUrl);
@@ -151,54 +170,58 @@ function makeStatusText(successCount: number) {
else if (successCount == 1) return "Status: 1 equation rendered";
else return `Status: ${successCount} equations rendered`;
}
+
+function successHandler({ lastStatus, successCount, clientEquations }: { lastStatus: google.script.DocsEquationRenderStatus, successCount: number, clientEquations?: AutoLatexCommon.ClientRenderOptions[] }, element: HTMLButtonElement) {
+ if (lastStatus === google.script.DocsEquationRenderStatus.ClientRender) {
+ console.log(clientEquations);
+ // we're not done yet - these equations need to be rendered on the client
+ Promise.all(clientEquations.map(async c => ({ options: c, renderedEquationB64: await renderMathJaxEquation(c).then(b => blobToB64(b)) })))
+ .then(rendered => {
+ google.script.run
+ .withSuccessHandler(successHandler)
+ .withFailureHandler(errorHandler)
+ .withUserObject(element)
+ .clientRenderComplete(rendered);
+ });
+ } else {
+ $("#loading").html('');
+ clearInterval(runDots);
+ element.disabled = false;
+
+ const statusText = makeStatusText(successCount);
+
+ if (lastStatus === google.script.DocsEquationRenderStatus.NoDocument)
+ showError("Sorry, the script has conflicting authorizations. Try signing out of other active Gsuite accounts.", statusText);
+ else if (lastStatus === google.script.DocsEquationRenderStatus.AllRenderersFailed && successCount > 0)
+ showError("Sorry, an equation is incorrect, or (temporarily) unavailable commands (i.e. align, &) were used.", statusText);
+ else if (lastStatus === google.script.DocsEquationRenderStatus.AllRenderersFailed && successCount === 0)
+ showError("Sorry, likely (temporarily) unavailable commands (i.e. align, &) were used or the equation was too long.", statusText);
+ else {
+ $("#loading").html(statusText);
+ }
+ }
+}
+
+function errorHandler(msg, element) {
+ $("#loading").html('');
+ clearInterval(runDots);
+ console.error("Error console errored!", msg, element)
+ showError("Please ensure your equations are surrounded by $$ on both sides (or \\[ and an \\]), without any enters in between, or reload the page. If authorization required, try signing out of other google accounts.", "Status: Error, please reload.");
+ element.disabled = false;
+}
function insertText(){
this.disabled = true;
$('#error').remove();
$("#loading").html("Status: Loading");
- const runDots = runDotAnimation();
+ runDots = runDotAnimation();
const {sizeRaw, delimiter} = getCurrentSettings();
google.script.run
- .withSuccessHandler(
- function ({ lastStatus, successCount, clientEquations }: { lastStatus: google.script.DocsEquationRenderStatus, successCount: number, clientEquations?: AutoLatexCommon.ClientRenderOptions[] }, element) {
- if (lastStatus === google.script.DocsEquationRenderStatus.ClientRender) {
- // we're not done yet - these equations need to be rendered on the client
- Promise.all(clientEquations.map(async c => ({ options: c, result: await renderMathJaxEquation(c) })))
- .then(rendered => {
- const formData = new FormData();
- for (const equation of rendered) {
- formData.append("")
- }
- });
- } else {
- $("#loading").html('');
- clearInterval(runDots);
- element.disabled = false;
-
- const statusText = makeStatusText(successCount);
-
- if (lastStatus === google.script.DocsEquationRenderStatus.NoDocument)
- showError("Sorry, the script has conflicting authorizations. Try signing out of other active Gsuite accounts.", statusText);
- else if (lastStatus === google.script.DocsEquationRenderStatus.AllRenderersFailed && successCount > 0)
- showError("Sorry, an equation is incorrect, or (temporarily) unavailable commands (i.e. align, &) were used.", statusText);
- else if (lastStatus === google.script.DocsEquationRenderStatus.AllRenderersFailed && successCount === 0)
- showError("Sorry, likely (temporarily) unavailable commands (i.e. align, &) were used or the equation was too long.", statusText);
- else {
- $("#loading").html(statusText);
- }
- }
- })
- .withFailureHandler(
- function(msg, element) {
- $("#loading").html('');
- clearInterval(runDots);
- console.error("Error console errored!", msg, element)
- showError("Please ensure your equations are surrounded by $$ on both sides (or \\[ and an \\]), without any enters in between, or reload the page. If authorization required, try signing out of other google accounts.", "Status: Error, please reload.");
- element.disabled = false;
- })
+ .withSuccessHandler(successHandler)
+ .withFailureHandler(errorHandler)
.withUserObject(this)
- .replaceEquations(sizeRaw, delimiter);
+ .replaceEquations(sizeRaw, delimiter, document.querySelector("#input-use-mathjax").checked);
}
@@ -207,7 +230,7 @@ function editText(){
$('#error').remove();
$("#loading").html("Status: Loading");
- const runDots = runDotAnimation();
+ runDots = runDotAnimation();
const {sizeRaw, delimiter} = getCurrentSettings();
google.script.run
.withSuccessHandler(
@@ -260,7 +283,7 @@ function undoAll(){
//var div = $('' + 'Ctrl + q detected' + '
');
//$('#button-bar').after(div);
- const runDots = runDotAnimation();
+ runDots = runDotAnimation();
const {delimiter} = getCurrentSettings();
google.script.run
.withSuccessHandler(
@@ -319,7 +342,7 @@ $(document).keydown(function(e){
//var div = $('' + 'Ctrl + q detected' + '
');
//$('#button-bar').after(div);
- const runDots = runDotAnimation();
+ runDots = runDotAnimation();
const {delimiter} = getCurrentSettings();
google.script.run
.withSuccessHandler(
diff --git a/Docs/SidebarJS.html b/Docs/SidebarJS.html
index aacf25b..0abd413 100644
--- a/Docs/SidebarJS.html
+++ b/Docs/SidebarJS.html
@@ -16,6 +16,23 @@
enableAssistiveMml: false
}
};
+// animation timeout ID
+let runDots = -1;
+/**
+* Convert a Blob to a base64 string for transmission to the server
+*
+* @param blob the blob to convert
+* @returns
+*/
+async function blobToB64(blob) {
+ const dataUrl = await new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.onloadend = () => resolve(reader.result);
+ reader.onerror = err => reject(err);
+ reader.readAsDataURL(blob);
+ });
+ return dataUrl.substring(dataUrl.indexOf(",") + 1); // strip dataurl header
+}
async function renderMathJaxEquation(renderOptions) {
// newline becomes \\
const equation = renderOptions.equation.replace(/\n|\r|\r\n/g, "\\\\");
@@ -28,8 +45,9 @@
svg.classList.add("mathjax-equation-hidden-render");
svg.style.fontSize = `${renderOptions.size}px`;
document.body.appendChild(svg);
- const width = svg.clientWidth;
- const height = svg.clientHeight;
+ // scale up by 5
+ const width = svg.clientWidth * 5;
+ const height = svg.clientHeight * 5;
svg.remove();
// set width/height explicitly on the svg
svg.setAttribute("width", `${width}px`);
@@ -59,7 +77,6 @@
const pngBlob = await canvas.convertToBlob({
type: "image/png"
});
- console.log(URL.createObjectURL(pngBlob));
return pngBlob;
}
finally {
@@ -134,55 +151,59 @@
else
return `Status: ${successCount} equations rendered`;
}
+function successHandler({ lastStatus, successCount, clientEquations }, element) {
+ if (lastStatus === 1 /* google.script.DocsEquationRenderStatus.ClientRender */) {
+ console.log(clientEquations);
+ // we're not done yet - these equations need to be rendered on the client
+ Promise.all(clientEquations.map(async (c) => ({ options: c, renderedEquationB64: await renderMathJaxEquation(c).then(b => blobToB64(b)) })))
+ .then(rendered => {
+ google.script.run
+ .withSuccessHandler(successHandler)
+ .withFailureHandler(errorHandler)
+ .withUserObject(element)
+ .clientRenderComplete(rendered);
+ });
+ }
+ else {
+ $("#loading").html('');
+ clearInterval(runDots);
+ element.disabled = false;
+ const statusText = makeStatusText(successCount);
+ if (lastStatus === 3 /* google.script.DocsEquationRenderStatus.NoDocument */)
+ showError("Sorry, the script has conflicting authorizations. Try signing out of other active Gsuite accounts.", statusText);
+ else if (lastStatus === 0 /* google.script.DocsEquationRenderStatus.AllRenderersFailed */ && successCount > 0)
+ showError("Sorry, an equation is incorrect, or (temporarily) unavailable commands (i.e. align, &) were used.", statusText);
+ else if (lastStatus === 0 /* google.script.DocsEquationRenderStatus.AllRenderersFailed */ && successCount === 0)
+ showError("Sorry, likely (temporarily) unavailable commands (i.e. align, &) were used or the equation was too long.", statusText);
+ else {
+ $("#loading").html(statusText);
+ }
+ }
+}
+function errorHandler(msg, element) {
+ $("#loading").html('');
+ clearInterval(runDots);
+ console.error("Error console errored!", msg, element);
+ showError("Please ensure your equations are surrounded by $$ on both sides (or \\[ and an \\]), without any enters in between, or reload the page. If authorization required, try signing out of other google accounts.", "Status: Error, please reload.");
+ element.disabled = false;
+}
function insertText() {
this.disabled = true;
$('#error').remove();
$("#loading").html("Status: Loading");
- const runDots = runDotAnimation();
+ runDots = runDotAnimation();
const { sizeRaw, delimiter } = getCurrentSettings();
google.script.run
- .withSuccessHandler(function ({ lastStatus, successCount, clientEquations }, element) {
- if (lastStatus === 1 /* google.script.DocsEquationRenderStatus.ClientRender */) {
- // we're not done yet - these equations need to be rendered on the client
- Promise.all(clientEquations.map(async (c) => ({ options: c, result: await renderMathJaxEquation(c) })))
- .then(rendered => {
- const formData = new FormData();
- for (const equation of rendered) {
- formData.append("");
- }
- });
- }
- else {
- $("#loading").html('');
- clearInterval(runDots);
- element.disabled = false;
- const statusText = makeStatusText(successCount);
- if (lastStatus === 3 /* google.script.DocsEquationRenderStatus.NoDocument */)
- showError("Sorry, the script has conflicting authorizations. Try signing out of other active Gsuite accounts.", statusText);
- else if (lastStatus === 0 /* google.script.DocsEquationRenderStatus.AllRenderersFailed */ && successCount > 0)
- showError("Sorry, an equation is incorrect, or (temporarily) unavailable commands (i.e. align, &) were used.", statusText);
- else if (lastStatus === 0 /* google.script.DocsEquationRenderStatus.AllRenderersFailed */ && successCount === 0)
- showError("Sorry, likely (temporarily) unavailable commands (i.e. align, &) were used or the equation was too long.", statusText);
- else {
- $("#loading").html(statusText);
- }
- }
- })
- .withFailureHandler(function (msg, element) {
- $("#loading").html('');
- clearInterval(runDots);
- console.error("Error console errored!", msg, element);
- showError("Please ensure your equations are surrounded by $$ on both sides (or \\[ and an \\]), without any enters in between, or reload the page. If authorization required, try signing out of other google accounts.", "Status: Error, please reload.");
- element.disabled = false;
- })
+ .withSuccessHandler(successHandler)
+ .withFailureHandler(errorHandler)
.withUserObject(this)
- .replaceEquations(sizeRaw, delimiter);
+ .replaceEquations(sizeRaw, delimiter, document.querySelector("#input-use-mathjax").checked);
}
function editText() {
this.disabled = true;
$('#error').remove();
$("#loading").html("Status: Loading");
- const runDots = runDotAnimation();
+ runDots = runDotAnimation();
const { sizeRaw, delimiter } = getCurrentSettings();
google.script.run
.withSuccessHandler(function (returnSuccess, element) {
@@ -229,7 +250,7 @@
$("#loading").html("Status: Loading");
//var div = $('' + 'Ctrl + q detected' + '
');
//$('#button-bar').after(div);
- const runDots = runDotAnimation();
+ runDots = runDotAnimation();
const { delimiter } = getCurrentSettings();
google.script.run
.withSuccessHandler(function (returnSuccess, element) {
@@ -279,7 +300,7 @@
$("#loading").html("Status: Loading");
//var div = $('' + 'Ctrl + q detected' + '
');
//$('#button-bar').after(div);
- const runDots = runDotAnimation();
+ runDots = runDotAnimation();
const { delimiter } = getCurrentSettings();
google.script.run
.withSuccessHandler(function (returnSuccess) {
diff --git a/types/docs-types/index.d.ts b/types/docs-types/index.d.ts
index 4e58af5..a396b9d 100644
--- a/types/docs-types/index.d.ts
+++ b/types/docs-types/index.d.ts
@@ -10,6 +10,8 @@ declare namespace google {
withUserObject(object: any): Runner;
+ clientRenderComplete(equations: {options: AutoLatexCommon.ClientRenderOptions, renderedEquationB64: string}[]): void //intrinsic;
+
editEquations(sizeRaw: string, delimiter: string): void //reference;
getKey(): void //intrinsic;
@@ -22,6 +24,9 @@ declare namespace google {
}
+ /**
+ * enums should be alphabetical in order to work with clasp-types
+ */
export const enum DocsEquationRenderStatus {
AllRenderersFailed,
From 54e37d3457cb75442f541f477baca0492dbd708d Mon Sep 17 00:00:00 2001
From: Siddhant Kameswar <115331356+grimsteel@users.noreply.github.com>
Date: Tue, 17 Jun 2025 21:03:15 -0500
Subject: [PATCH 05/11] MathJax: scale font size to match CodeCogs
---
Docs/Code.ts | 5 +++--
Docs/Sidebar.ts | 1 -
Docs/SidebarJS.html | 1 -
3 files changed, 3 insertions(+), 4 deletions(-)
diff --git a/Docs/Code.ts b/Docs/Code.ts
index ced8570..d76ce23 100644
--- a/Docs/Code.ts
+++ b/Docs/Code.ts
@@ -509,8 +509,9 @@ function repairImage(paragraph: GoogleAppsScript.Document.Paragraph, childIndex:
//C [75.4, 79.6] on width and height ratio
multiple = size / 76.0;
else if (renderer[5] === "MathJax")
- // The MathJax renderer returns scaled equations. We scale up by 5, and 1.16 is just for consistency with other renderers.
- multiple = 1.294 / 5;
+ // The MathJax renderer returns scaled equations. We scale down by 5 (resolution), and 1.26 is just for consistency with other renderers.
+ // TODO: When MathJax supports changing font, switch to a font that's more similar to CodeCogs
+ multiple = 1.26 / 5;
//CodeCogs, other
else multiple = size / 100.0;
diff --git a/Docs/Sidebar.ts b/Docs/Sidebar.ts
index de219fa..63d9e50 100644
--- a/Docs/Sidebar.ts
+++ b/Docs/Sidebar.ts
@@ -173,7 +173,6 @@ function makeStatusText(successCount: number) {
function successHandler({ lastStatus, successCount, clientEquations }: { lastStatus: google.script.DocsEquationRenderStatus, successCount: number, clientEquations?: AutoLatexCommon.ClientRenderOptions[] }, element: HTMLButtonElement) {
if (lastStatus === google.script.DocsEquationRenderStatus.ClientRender) {
- console.log(clientEquations);
// we're not done yet - these equations need to be rendered on the client
Promise.all(clientEquations.map(async c => ({ options: c, renderedEquationB64: await renderMathJaxEquation(c).then(b => blobToB64(b)) })))
.then(rendered => {
diff --git a/Docs/SidebarJS.html b/Docs/SidebarJS.html
index 0abd413..6c08ec5 100644
--- a/Docs/SidebarJS.html
+++ b/Docs/SidebarJS.html
@@ -153,7 +153,6 @@
}
function successHandler({ lastStatus, successCount, clientEquations }, element) {
if (lastStatus === 1 /* google.script.DocsEquationRenderStatus.ClientRender */) {
- console.log(clientEquations);
// we're not done yet - these equations need to be rendered on the client
Promise.all(clientEquations.map(async (c) => ({ options: c, renderedEquationB64: await renderMathJaxEquation(c).then(b => blobToB64(b)) })))
.then(rendered => {
From efa9553be12e5bdc253c2034c999385e7b2bdea5 Mon Sep 17 00:00:00 2001
From: Siddhant Kameswar <115331356+grimsteel@users.noreply.github.com>
Date: Wed, 18 Jun 2025 20:41:53 -0500
Subject: [PATCH 06/11] MathJax: implement derendering
---
Common/Code.ts | 29 ++++++++++++++++++++++++++---
Docs/Code.ts | 13 ++-----------
types/common-types/index.d.ts | 14 ++++++++++++--
3 files changed, 40 insertions(+), 16 deletions(-)
diff --git a/Common/Code.ts b/Common/Code.ts
index aa0eda6..94c21d0 100644
--- a/Common/Code.ts
+++ b/Common/Code.ts
@@ -93,7 +93,17 @@ const capableRenderers = 8;
/**
* @public
*/
-const capableDerenderers = 12;
+const capableDerenderers = 13;
+
+/**
+ * Renderer ID constants for retreiving info about specific renderers
+ * @public
+*/
+const rendererIds = {
+ CODECOGS: 1,
+ MATHJAX: 13
+};
+
//render bug variables
/**
* @public
@@ -563,9 +573,22 @@ function getRenderer(worked: number): Renderer {
"Number empire",
"",
];
- } // to de render possibly very old equations
+ }
+ // to de render MathJax equations
+ else if (worked == 13) {
+ return [
+ 13,
+ "about:blank?type=mathjax&equation=",
+ "about:blank?type=mathjax&equation=",
+ "",
+ "",
+ "MathJax",
+ "",
+ ];
+ }
+ // to de render possibly very old equations
else
- return [13, "https://latex.codecogs.com/png.latex?%5Cdpi%7B900%7DEQUATION", "https://www.codecogs.com/eqnedit.php?latex=", "%5Cinline%20", "", "Codecogs", "%5Cdpi%7B900%7D"];
+ return [14, "https://latex.codecogs.com/png.latex?%5Cdpi%7B900%7DEQUATION", "https://www.codecogs.com/eqnedit.php?latex=", "%5Cinline%20", "", "Codecogs", "%5Cdpi%7B900%7D"];
}
/**
diff --git a/Docs/Code.ts b/Docs/Code.ts
index d76ce23..4728ee2 100644
--- a/Docs/Code.ts
+++ b/Docs/Code.ts
@@ -321,15 +321,7 @@ function getSize(size: number, defaultSize: number, rangeElement: GoogleAppsScri
* @public
*/
function clientRenderComplete(equations: { options: AutoLatexCommon.ClientRenderOptions, renderedEquationB64: string }[]) {
- const mathjaxRenderer: AutoLatexCommon.Renderer = [
- 0,
- "about:blank?type=mathjax&equation=",
- "about:blank?type=mathjax&equation=",
- "",
- "",
- "MathJax",
- "",
- ];
+ const mathjaxRenderer = Common.getRenderer(Common.rendererIds.MATHJAX);
let c = 0;
for (const equation of equations) {
@@ -417,8 +409,7 @@ function findEquationAndPlaceImage(startElement: GoogleAppsScript.Document.Range
// SAVING FORMATTING
Common.reportDeltaTime(511);
if (escape(resp.getBlob().getDataAsString()).substring(0, 50) == Common.invalidEquationHashCodecogsFirst50) {
- //assumes codecogs is 1
- renderer = Common.getRenderer(1);
+ renderer = Common.getRenderer(Common.rendererIds.CODECOGS);
}
Common.reportDeltaTime(517);
diff --git a/types/common-types/index.d.ts b/types/common-types/index.d.ts
index 666c9f8..bf07f8c 100644
--- a/types/common-types/index.d.ts
+++ b/types/common-types/index.d.ts
@@ -53,12 +53,17 @@ declare namespace AutoLatexCommon {
*/
sizeImage(app: IntegratedApp, paragraph: GoogleAppsScript.Document.Paragraph, childIndex: number, height: number, width: number): void;
- capableDerenderers: 12;
+ capableDerenderers: 13;
capableRenderers: 8;
invalidEquationHashCodecogsFirst50: "GIF89a%7F%00%18%00%uFFFD%00%00%uFFFD%u0315%uFFFD3%";
+ /**
+ * Renderer ID constants for retreiving info about specific renderers
+ */
+ rendererIds: {CODECOGS: number, MATHJAX: number};
+
}
/**
@@ -176,12 +181,17 @@ declare namespace AutoLatexCommon {
}
- export const capableDerenderers: 12;
+ export const capableDerenderers: 13;
export const capableRenderers: 8;
export const invalidEquationHashCodecogsFirst50: "GIF89a%7F%00%18%00%uFFFD%00%00%uFFFD%u0315%uFFFD3%";
+ /**
+ * Renderer ID constants for retreiving info about specific renderers
+ */
+ export const rendererIds: {CODECOGS: number, MATHJAX: number};
+
}
declare const Common: AutoLatexCommon.Common;
\ No newline at end of file
From c8da03a86841edf877696d9d2182249e67c8c261 Mon Sep 17 00:00:00 2001
From: Siddhant Kameswar <115331356+grimsteel@users.noreply.github.com>
Date: Tue, 15 Jul 2025 17:57:46 -0500
Subject: [PATCH 07/11] mathjax: enable color rendering, fix rendering for
multiple equations in the same paragraph
---
Docs/Code.ts | 26 ++++++++++++++++----------
Docs/Sidebar.ts | 20 +++++++++++---------
Docs/SidebarJS.html | 7 ++++---
3 files changed, 31 insertions(+), 22 deletions(-)
diff --git a/Docs/Code.ts b/Docs/Code.ts
index fb14b90..02ec7b1 100644
--- a/Docs/Code.ts
+++ b/Docs/Code.ts
@@ -324,6 +324,9 @@ function clientRenderComplete(equations: { options: AutoLatexCommon.ClientRender
const mathjaxRenderer = Common.getRenderer(Common.rendererIds.MATHJAX);
let c = 0;
+ // Go backwards so that the named ranges for multiple equations in the same paragraph don't get removed
+ equations.reverse();
+
for (const equation of equations) {
const namedRange = DocsApp.getActive().getNamedRangeById(equation.options.rangeId);
// get the RangeElement for this equation
@@ -377,6 +380,17 @@ function findEquationAndPlaceImage(startElement: GoogleAppsScript.Document.Range
};
}
+ // get font color
+ const colorHex = textElement.getForegroundColor(startElement.getStartOffset());
+ // convert to rgb (default to black when colorHex is null - this happens in some weird cases where Docs can't figure out the color)
+ const [r, g, b] = colorHex ? [1, 3, 5].map(i => parseInt(colorHex.slice(i, i + 2), 16)) : [0, 0, 0];
+
+ // add color info to render options
+ const coloredRenderOptions = {
+ ...renderOptions,
+ r, g, b,
+ };
+
// send info to the client for rendering
if (renderOptions.clientRender) {
// we don't need URL encoding or double escaping for client renderers
@@ -388,7 +402,7 @@ function findEquationAndPlaceImage(startElement: GoogleAppsScript.Document.Range
// save this range for later
const namedRange = doc.addNamedRange("ale-equation-range", range);
const clientRenderOptions: AutoLatexCommon.ClientRenderOptions = {
- ...renderOptions,
+ ...coloredRenderOptions,
size,
rangeId: namedRange.getId(),
equation: clientEquation
@@ -401,16 +415,8 @@ function findEquationAndPlaceImage(startElement: GoogleAppsScript.Document.Range
nextStartElement: startElement
};
}
-
- // get font color
- const colorHex = textElement.getForegroundColor(startElement.getStartOffset());
- // convert to rgb (default to black when colorHex is null - this happens in some weird cases where Docs can't figure out the color)
- const [r, g, b] = colorHex ? [1, 3, 5].map(i => parseInt(colorHex.slice(i, i + 2), 16)) : [0, 0, 0];
- let { resp, renderer, worked } = Common.renderEquation(equationOriginal, {
- ...renderOptions,
- r, g, b,
- });
+ let { resp, renderer, worked } = Common.renderEquation(equationOriginal, coloredRenderOptions);
if (worked > Common.capableRenderers) return {
status: DocsEquationRenderStatus.AllRenderersFailed
};
diff --git a/Docs/Sidebar.ts b/Docs/Sidebar.ts
index 63d9e50..8718776 100644
--- a/Docs/Sidebar.ts
+++ b/Docs/Sidebar.ts
@@ -5,13 +5,14 @@
///
window.MathJax = {
- loader: {load: ['tex-svg']},
- svg: {
- fontCache: 'none'
- },
- startup: {
- typeset: false // Prevent auto-typesetting
- },
+ loader: { load: ['tex-svg', '[tex]/color'] },
+ tex: { packages: { '[+]': ['color'] } },
+ svg: {
+ fontCache: 'none'
+ },
+ startup: {
+ typeset: false // Prevent auto-typesetting
+ },
options: {
enableAssistiveMml: false
}
@@ -37,8 +38,9 @@ async function blobToB64(blob: Blob) {
}
async function renderMathJaxEquation(renderOptions: AutoLatexCommon.ClientRenderOptions) {
- // newline becomes \\
- const equation = renderOptions.equation.replace(/\n|\r|\r\n/g, "\\\\");
+ // apply RGB coloring + newline becomes \\
+ const equation = `\\color[RGB]{${renderOptions.r},${renderOptions.g},${renderOptions.b}}` + renderOptions.equation.replace(/\n|\r|\r\n/g, "\\\\");
+
const result = await window.MathJax.tex2svgPromise(equation, {
display: !renderOptions.inline,
diff --git a/Docs/SidebarJS.html b/Docs/SidebarJS.html
index 6c08ec5..fdd1fe2 100644
--- a/Docs/SidebarJS.html
+++ b/Docs/SidebarJS.html
@@ -5,7 +5,8 @@
///
///
window.MathJax = {
- loader: { load: ['tex-svg'] },
+ loader: { load: ['tex-svg', '[tex]/color'] },
+ tex: { packages: { '[+]': ['color'] } },
svg: {
fontCache: 'none'
},
@@ -34,8 +35,8 @@
return dataUrl.substring(dataUrl.indexOf(",") + 1); // strip dataurl header
}
async function renderMathJaxEquation(renderOptions) {
- // newline becomes \\
- const equation = renderOptions.equation.replace(/\n|\r|\r\n/g, "\\\\");
+ // apply RGB coloring + newline becomes \\
+ const equation = `\\color[RGB]{${renderOptions.r},${renderOptions.g},${renderOptions.b}}` + renderOptions.equation.replace(/\n|\r|\r\n/g, "\\\\");
const result = await window.MathJax.tex2svgPromise(equation, {
display: !renderOptions.inline,
em: renderOptions.size
From f3bd3a263c61866243f1c33c1045a66f7b059d11 Mon Sep 17 00:00:00 2001
From: Siddhant Kameswar <115331356+grimsteel@users.noreply.github.com>
Date: Thu, 17 Jul 2025 00:14:25 -0500
Subject: [PATCH 08/11] mathjax: fix error styles for some equations
---
Docs/Sidebar.ts | 2 +-
Docs/SidebarJS.html | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Docs/Sidebar.ts b/Docs/Sidebar.ts
index 8718776..e8f3190 100644
--- a/Docs/Sidebar.ts
+++ b/Docs/Sidebar.ts
@@ -68,7 +68,7 @@ async function renderMathJaxEquation(renderOptions: AutoLatexCommon.ClientRender
// create a URL for this svg
const svgString = new XMLSerializer().serializeToString(svg)
// inject css
- .replace("", "" + styles);
+ .replace("", styles + "");
const svgBlob = new Blob([svgString], {
type: "image/svg+xml"
});
diff --git a/Docs/SidebarJS.html b/Docs/SidebarJS.html
index fdd1fe2..1590ac9 100644
--- a/Docs/SidebarJS.html
+++ b/Docs/SidebarJS.html
@@ -57,7 +57,7 @@
// create a URL for this svg
const svgString = new XMLSerializer().serializeToString(svg)
// inject css
- .replace("", "" + styles);
+ .replace("", styles + "");
const svgBlob = new Blob([svgString], {
type: "image/svg+xml"
});
From 8b316377e40341a892c29ea8a58a7b65f03d9b42 Mon Sep 17 00:00:00 2001
From: Siddhant Kameswar <115331356+grimsteel@users.noreply.github.com>
Date: Fri, 18 Jul 2025 16:44:46 -0500
Subject: [PATCH 09/11] slides: update for common library changes
---
Common/Code.ts | 2 +
Slides/Code.ts | 115 ++++++++++++++++++++++++++-----------------------
2 files changed, 63 insertions(+), 54 deletions(-)
diff --git a/Common/Code.ts b/Common/Code.ts
index 8f002db..398266f 100644
--- a/Common/Code.ts
+++ b/Common/Code.ts
@@ -17,6 +17,7 @@ interface Renderer {
}
interface CommonRenderOptions {
+ // The size of the text, whose neg/pos indicated whether the equation is inline or not.
size: number;
inline: boolean;
// color
@@ -31,6 +32,7 @@ interface CommonRenderOptions {
* @public
*/
interface RenderOptions extends CommonRenderOptions {
+ // The default/previous size of the text, in case size is null.
defaultSize: number;
clientRender: boolean;
}
diff --git a/Slides/Code.ts b/Slides/Code.ts
index 3e43f25..b49a558 100644
--- a/Slides/Code.ts
+++ b/Slides/Code.ts
@@ -117,6 +117,19 @@ function replaceEquations(sizeRaw: string, delimiter: string) {
let c = 0; //counter
const defaultSize = 11;
Common.reportDeltaTime(146);
+
+ // base render options common to all equations rendered
+ const renderOptions: AutoLatexCommon.RenderOptions = {
+ r: 0, g: 0, b: 0,
+ delim,
+ defaultSize,
+ size,
+ inline: isInline,
+ // not yet supported for Slides
+ clientRender: false
+ };
+
+ // this can error if there are ungranted permissions
try {
IntegratedApp.getActive();
} catch (error) {
@@ -131,7 +144,7 @@ function replaceEquations(sizeRaw: string, delimiter: string) {
let element = getElementFromIndices(slideNum, elementNum);
if (element === null) continue;
- c += renderElement(slideNum, element, delim, quality, size, defaultSize, isInline);
+ c += renderElement(slideNum, element, renderOptions);
}
}
return Common.encodeFlag(0, c);
@@ -184,7 +197,7 @@ function castElement(element: GoogleAppsScript.Slides.PageElement) {
* @param element Element to search for equations
* @returns Count of equations successfully rendered
*/
-function renderElement(slideNum: number, element: GoogleAppsScript.Slides.Group | GoogleAppsScript.Slides.Table | GoogleAppsScript.Slides.Shape, delim: AutoLatexCommon.Delimiter, quality: number, size: number, defaultSize: number, isInline: boolean) {
+function renderElement(slideNum: number, element: GoogleAppsScript.Slides.Group | GoogleAppsScript.Slides.Table | GoogleAppsScript.Slides.Shape, renderOptions: AutoLatexCommon.RenderOptions) {
if ("ungroup" in element) {
// recursively process all elements in this group
let c = 0;
@@ -192,7 +205,7 @@ function renderElement(slideNum: number, element: GoogleAppsScript.Slides.Group
// returns null if we don't recognize the type
const castedPageElement = castElement(childElement);
if (castedPageElement) {
- c += renderElement(slideNum, castedPageElement, delim, quality, size, defaultSize, isInline);
+ c += renderElement(slideNum, castedPageElement, renderOptions);
}
}
return c;
@@ -205,14 +218,14 @@ function renderElement(slideNum: number, element: GoogleAppsScript.Slides.Group
// ignore merged cells (the head cells of merged cells will still be counted)
if (cell.getMergeState() === SlidesApp.CellMergeState.MERGED) continue;
- let parsedEquations = findPos(slideNum, cell, delim, quality, size, defaultSize, isInline); //or: "\\\$\\\$", "\\\$\\\$"
+ let parsedEquations = findPos(slideNum, cell, renderOptions); //or: "\\\$\\\$", "\\\$\\\$"
c += parsedEquations.filter(([, imagesPlaced]) => imagesPlaced).length;
}
}
return c;
} else {
// single shape
- let parsedEquations = findPos(slideNum, element, delim, quality, size, defaultSize, isInline); //or: "\\\$\\\$", "\\\$\\\$"
+ let parsedEquations = findPos(slideNum, element, renderOptions); //or: "\\\$\\\$", "\\\$\\\$"
return parsedEquations.filter(([, imagesPlaced]) => imagesPlaced).length;
}
}
@@ -269,7 +282,7 @@ function unwrapEQ(element: PageElement) {
1 if eqn is "" and 0 if not. Assume we close on 4 consecutive empty ones.
*/
-function findPos(slideNum: number, element: PageElement, delim: AutoLatexCommon.Delimiter, quality: number, size: number, defaultSize: number, isInline: boolean) {
+function findPos(slideNum: number, element: PageElement, renderOptions: AutoLatexCommon.RenderOptions) {
// get the shape (elementNum) on the given slide (slideNum)
// var element = getElementFromIndices(slideNum, elementNum);
// debugLog("shape is: " + shape.getPageElementType())
@@ -286,7 +299,7 @@ function findPos(slideNum: number, element: PageElement, delim: AutoLatexCommon.
continue;
}
// debugLog("Looking for delimiter :" + delim[2] + " in text");
- const checkForDelimiter = elementText.find(delim[2]); // TextRange[]
+ const checkForDelimiter = elementText.find(renderOptions.delim[2]); // TextRange[]
if (checkForDelimiter == null) {
imagesPlaced.push([0, 0]); // didn't find first delimiter
@@ -294,7 +307,7 @@ function findPos(slideNum: number, element: PageElement, delim: AutoLatexCommon.
}
// start position of image
- const placeHolderStart = findTextOffsetInSlide(elementText.asRenderedString(), delim[0], 0);
+ const placeHolderStart = findTextOffsetInSlide(elementText.asRenderedString(), renderOptions.delim[0], 0);
if (placeHolderStart === -1) {
imagesPlaced.push([0, 0]); // didn't find first delimiter
@@ -304,7 +317,7 @@ function findPos(slideNum: number, element: PageElement, delim: AutoLatexCommon.
const offset = 2 + placeHolderStart;
// end position till of image
- const placeHolderEnd = findTextOffsetInSlide(elementText.asRenderedString(), delim[1], offset);
+ const placeHolderEnd = findTextOffsetInSlide(elementText.asRenderedString(), renderOptions.delim[1], offset);
Common.debugLog("Start and End of equation: " + placeHolderStart + " " + placeHolderEnd);
// debugLog("Isolating Equation Textrange: " + element.getText().getRange(placeHolderStart, placeHolderEnd).asRenderedString());
@@ -312,31 +325,40 @@ function findPos(slideNum: number, element: PageElement, delim: AutoLatexCommon.
const textColor = getRgbColor(element.getText().getRange(placeHolderStart + 1, placeHolderEnd), slideNum);
Common.debugLog(`RGB: ${textColor.join()}`);
+
+ // include the ending delimiter as well
+ const endOffset = Math.min(elementText.getLength(), placeHolderEnd + renderOptions.delim[4]);
+ const equationRange = elementText.getRange(placeHolderStart, endOffset);
- if (placeHolderEnd - placeHolderStart == 2.0) {
+ if (placeHolderEnd - placeHolderStart === renderOptions.delim[4]) {
// empty equation
Common.debugLog("Empty equation!");
- elementText.clear(placeHolderStart, Math.min(elementText.getLength(), placeHolderEnd + 2));
- imagesPlaced.push([defaultSize, 0]); // default behavior of placeImage
+ equationRange.clear();
+ imagesPlaced.push([renderOptions.defaultSize, 0]); // default behavior of placeImage
continue;
}
- imagesPlaced.push(placeImage(slideNum, element, elementText, placeHolderStart, placeHolderEnd, quality, size, defaultSize, delim, isInline, textColor));
+ imagesPlaced.push(placeImage(slideNum, element, equationRange, {
+ // add color to renderOptions
+ ...renderOptions,
+ r: textColor[0],
+ g: textColor[1],
+ b: textColor[2]
+ }));
}
}
return imagesPlaced;
}
-function getEquation(paragraph: GoogleAppsScript.Slides.TextRange, start: number, end: number, delimiters: AutoLatexCommon.Delimiter) {
- var equationOriginal = [];
- var equation = paragraph.asRenderedString().substring(start + delimiters[4], end - delimiters[4] + 2);
- var checkForEquation = paragraph.asRenderedString();
+function getEquation(textRange: GoogleAppsScript.Slides.TextRange, delimiters: AutoLatexCommon.Delimiter) {
+ // remove delimiters from range
+ const equation = textRange.asRenderedString().substring(delimiters[4], textRange.getLength() - delimiters[4]);
+ const checkForEquation = textRange.asRenderedString();
Common.debugLog("getEquation- " + equation.length);
Common.debugLog("checkForEquation- " + checkForEquation.length);
-
- var equationStringEncoded = Common.reEncode(equation); //escape deprecated
- equationOriginal.push(equationStringEncoded);
- return equationStringEncoded;
+
+ // encode/escape equation
+ return Common.reEncode(equation);
}
/**
@@ -378,7 +400,7 @@ function getBounds(textElement: PageElement) {
}
}
-function resize(eqnImage: GoogleAppsScript.Slides.Image, textElement: PageElement, size: number, scale: number, horizontalAlignment: GoogleAppsScript.Slides.ParagraphAlignment, verticalAlignment: GoogleAppsScript.Slides.ContentAlignment, bounds: ReturnType) {
+function resize(eqnImage: GoogleAppsScript.Slides.Image, size: number, scale: number, horizontalAlignment: GoogleAppsScript.Slides.ParagraphAlignment, verticalAlignment: GoogleAppsScript.Slides.ContentAlignment, bounds: ReturnType) {
eqnImage.setWidth(((size * eqnImage.getWidth()) / eqnImage.getHeight()) * scale);
eqnImage.setHeight(size * scale);
@@ -401,27 +423,18 @@ function resize(eqnImage: GoogleAppsScript.Slides.Image, textElement: PageElemen
/**
* Given the locations of the delimiters, run code to get font size, get equation, remove equation, encode/style equation, insert/style image.
- *
- * @param {integer} start The offset in the childIndex where the equation start-delimiter starts.
- * @param {integer} end The offset in the childIndex where the equation end-delimiter starts.
- * @param {integer} quality The dpi quality to be rendered in (default 900).
- * @param {integer} size The size of the text, whose neg/pos indicated whether the equation is inline or not.
- * @param {integer} defaultSize The default/previous size of the text, in case size is null.
- * @param {string} delim[6] The text delimiters and regex delimiters for start and end in that order, and offset from front and back.
*/
-
-function placeImage(slideNum: number, textElement: PageElement, text: GoogleAppsScript.Slides.TextRange, start: number, end: number, quality: number, size: number, defaultSize: number, delim: AutoLatexCommon.Delimiter, isInline: boolean, [red, green, blue]: number[]) {
+function placeImage(slideNum: number, textElement: PageElement, text: GoogleAppsScript.Slides.TextRange, renderOptions: AutoLatexCommon.RenderOptions) {
Common.debugLog("placeImage- EquationOriginal: " + textElement + ", type: " + typeof textElement);
- const equationRange = text.getRange(start + 1, end);
+ const equationRange = text.getRange(1, text.getLength());
let textSize = equationRange
.getTextStyle()
.getFontSize();
// Gets the horizontal alignment of the equation. If it somehow spans multiple paragraphs, this will return the alignment of the first one
- const textHorizontalAlignment = text
- .getRange(start + 1, end)
+ const textHorizontalAlignment = equationRange
.getParagraphs()[0]
.getRange()
.getParagraphStyle()
@@ -431,18 +444,18 @@ function placeImage(slideNum: number, textElement: PageElement, text: GoogleApps
// var textSize = text.getTextStyle().getFontSize();
Common.debugLog("My Text Size is: " + textSize.toString());
if (textSize == null) {
- textSize = defaultSize;
+ textSize = renderOptions.defaultSize;
}
- const equationOriginal = getEquation(text, start, end, delim);
+ const equationOriginal = getEquation(text, renderOptions.delim);
Common.debugLog("placeImage- EquationOriginal: " + equationOriginal);
if (equationOriginal == "") {
- console.log("No equation but undetected start and end as ", start, " ", end);
- return [defaultSize, 1];
+ console.log("No equation but undetected start and end as ", text.getStartIndex(), " ", text.getEndIndex());
+ return [renderOptions.defaultSize, 1];
}
- const { renderer, rendererType, worked } = Common.renderEquation(equationOriginal, quality, delim, isInline, red, green, blue);
+ const { renderer, rendererType, worked } = Common.renderEquation(equationOriginal, renderOptions);
if (worked > Common.capableRenderers) return -100000;
var doc = IntegratedApp.getBody();
var body = doc[slideNum];
@@ -452,24 +465,18 @@ function placeImage(slideNum: number, textElement: PageElement, text: GoogleApps
// This is a relatively expensive call for tables, so we store it in a variable
const bounds = getBounds(textElement);
- const origURL = renderer[2] + equationOriginal + "#" + delim[6];
+ const origURL = renderer[2] + equationOriginal + "#" + renderOptions.delim[6];
const derenderData: DerenderData = {
- red,
- green,
- blue,
+ red: renderOptions.r,
+ green: renderOptions.g,
+ blue: renderOptions.b,
origURL,
size: textSize,
width: bounds.width,
height: bounds.height
};
-
- if (isTableCell(textElement)) {
- // if table
- text.clear(start, Math.min(text.getLength(), end + 2));
- } else {
- // else if text box
- textElement.getText().clear(start, end + 2);
- }
+
+ text.clear();
// textElement.setLeft(textElement.getLeft() + image.getWidth() * 1.1);
@@ -492,18 +499,18 @@ function placeImage(slideNum: number, textElement: PageElement, text: GoogleApps
var image = body.insertImage(renderer[1]);
- resize(image, textElement, textSize, scale, textHorizontalAlignment, textVerticalAlignment, bounds);
+ resize(image, textSize, scale, textHorizontalAlignment, textVerticalAlignment, bounds);
// remove empty textboxes
if (
!isTableCell(textElement) &&
textElement.getShapeType() === SlidesApp.ShapeType.TEXT_BOX &&
- textElement.getText().asRenderedString().length == 1
+ textElement.getText().asRenderedString().length <= 1
) {
textElement.remove();
}
image.setTitle(JSON.stringify(derenderData));
- return [size, 1];
+ return [renderOptions.size, 1];
}
/**
From cfe95c912ecbf02acbba08b498b2a34d316b8800 Mon Sep 17 00:00:00 2001
From: Siddhant Kameswar <115331356+grimsteel@users.noreply.github.com>
Date: Fri, 18 Jul 2025 17:05:23 -0500
Subject: [PATCH 10/11] sidebar: add mathjax config to all sidebars
---
BuildSidebarJS.js | 15 +++++++++++++
Docs/Sidebar.ts | 14 ------------
Docs/SidebarJS.html | 28 +++++++++++++-----------
Slides/SidebarJS.html | 50 ++++++++++++++++++++++++++++---------------
4 files changed, 63 insertions(+), 44 deletions(-)
diff --git a/BuildSidebarJS.js b/BuildSidebarJS.js
index 683f422..4bfffe9 100644
--- a/BuildSidebarJS.js
+++ b/BuildSidebarJS.js
@@ -7,6 +7,21 @@ const execPromise = promisify(exec);
function wrapJS(sidebarJS) {
return `
`;
}
diff --git a/Docs/Sidebar.ts b/Docs/Sidebar.ts
index e8f3190..38506c6 100644
--- a/Docs/Sidebar.ts
+++ b/Docs/Sidebar.ts
@@ -4,20 +4,6 @@
///
///
-window.MathJax = {
- loader: { load: ['tex-svg', '[tex]/color'] },
- tex: { packages: { '[+]': ['color'] } },
- svg: {
- fontCache: 'none'
- },
- startup: {
- typeset: false // Prevent auto-typesetting
- },
- options: {
- enableAssistiveMml: false
- }
-};
-
// animation timeout ID
let runDots = -1;
diff --git a/Docs/SidebarJS.html b/Docs/SidebarJS.html
index 1590ac9..0f60b46 100644
--- a/Docs/SidebarJS.html
+++ b/Docs/SidebarJS.html
@@ -1,22 +1,24 @@
\ No newline at end of file
+
+
\ No newline at end of file
From 6ae74fbff19e0e6eb18e9c4631f8988ee80b9632 Mon Sep 17 00:00:00 2001
From: Siddhant Kameswar <115331356+grimsteel@users.noreply.github.com>
Date: Fri, 18 Jul 2025 17:07:26 -0500
Subject: [PATCH 11/11] slides: simplify element iteration code
- previously, we were iterating over the indices of elements. if a shape
was removed because it only had an equation, there was a chance we could
skip the next shape - this changes the code to iterate over an array of
elements that is fetched once, before the iteration happens
---
Slides/Code.ts | 35 +++++++----------------------------
1 file changed, 7 insertions(+), 28 deletions(-)
diff --git a/Slides/Code.ts b/Slides/Code.ts
index b49a558..1cf0a66 100644
--- a/Slides/Code.ts
+++ b/Slides/Code.ts
@@ -139,37 +139,19 @@ function replaceEquations(sizeRaw: string, delimiter: string) {
const slides = IntegratedApp.getBody();
const childCount = slides.length;
for (let slideNum = 0; slideNum < childCount; slideNum++) {
- for (let elementNum = 0; elementNum < slides[slideNum].getPageElements().length; elementNum++) {
- Common.debugLog("Slide Num: " + slideNum + " Num of shapes: " + slides[slideNum].getPageElements().length);
- let element = getElementFromIndices(slideNum, elementNum);
- if (element === null) continue;
+ const elements = slides[slideNum].getPageElements();
+ Common.debugLog("Slide Num: " + slideNum + " Num of shapes: " + elements.length);
+ for (const element of elements) {
+ const castedElement = castElement(element);
+ // if we don't recognize this element
+ if (castedElement === null) continue;
- c += renderElement(slideNum, element, renderOptions);
+ c += renderElement(slideNum, castedElement, renderOptions);
}
}
return Common.encodeFlag(0, c);
}
-/**
- * Returns the element iterating
- */
-function getElementFromIndices(slideNum: number, elementNum: number) {
- const doc = IntegratedApp.getBody();
- Common.assert(slideNum < doc.length, "slideNum < doc.length");
- const body = doc[slideNum];
- const elements = body.getPageElements();
- // elements = body.getPageElements();
- Common.assert(elementNum < elements.length, "elementNum (" + elementNum + ") < elements.length (" + elements.length + ")");
- let element: GoogleAppsScript.Slides.PageElement;
- if (elementNum < elements.length) {
- element = elements[elementNum];
- } else {
- return null;
- }
-
- return castElement(element);
-}
-
function castElement(element: GoogleAppsScript.Slides.PageElement) {
let elementType: GoogleAppsScript.Slides.PageElementType;
try {
@@ -283,9 +265,6 @@ function unwrapEQ(element: PageElement) {
*/
function findPos(slideNum: number, element: PageElement, renderOptions: AutoLatexCommon.RenderOptions) {
- // get the shape (elementNum) on the given slide (slideNum)
- // var element = getElementFromIndices(slideNum, elementNum);
- // debugLog("shape is: " + shape.getPageElementType())
let imagesPlaced = [];
if (!element)
imagesPlaced.push([0, 0]);