From 65e4b587c5920947bbbb1fb699527d7a10aa9e4b Mon Sep 17 00:00:00 2001 From: Siddhant Kameswar <115331356+grimsteel@users.noreply.github.com> Date: Sat, 24 May 2025 13:20:26 -0500 Subject: [PATCH 01/11] MathJax preparation: clean up Docs render code - use enum and defined interface type instead of tuple/magic numbers - move all render options into a single interface --- Common/Code.ts | 13 ++- Docs/Code.ts | 188 ++++++++++++++++++++++------------ types/common-types/LICENSE | 2 +- types/common-types/index.d.ts | 17 +++ 4 files changed, 153 insertions(+), 67 deletions(-) diff --git a/Common/Code.ts b/Common/Code.ts index ef0bdb0..5e8710a 100644 --- a/Common/Code.ts +++ b/Common/Code.ts @@ -2,7 +2,7 @@ const DEBUG = true; //doing ctrl + m to get key to see errors is still needed; D /** * An array which defines a renderer - * + * * Note: clasp-types is not compatible with type aliases, so this is defined as an interface instead. * @public */ @@ -14,7 +14,16 @@ interface Renderer { 4: string; 5: string; 6: string; -}; +} + +/** + * Options for rendering. Currently not actually used in Common.renderEquation + * + * @public + */ +interface RenderOptions { + quality: number, size: number, defaultSize: number, inline: boolean , delim: Delimiter +} /** * @public diff --git a/Docs/Code.ts b/Docs/Code.ts index 830a0d1..b27ed55 100644 --- a/Docs/Code.ts +++ b/Docs/Code.ts @@ -7,6 +7,23 @@ var DEBUG = false; //doing ctrl + m to get key to see errors is still needed; DEBUG is for all nondiagnostic information +const enum DocsEquationRenderStatus { + // error types + NoDocument, + NoStartDelimiter, + NoEndDelimiter, + EmptyEquation, + AllRenderersFailed, + + Success +} + +interface DocsEquationRenderResult { + status: DocsEquationRenderStatus, + equationSize?: number, + nextStartElement?: GoogleAppsScript.Document.RangeElement +} + const DocsApp = { getUi: function(){ let activeUi = DocumentApp.getUi(); @@ -92,7 +109,6 @@ function replaceEquations(sizeRaw: string, delimiter: string) { const delim = Common.getDelimiters(delimiter); Common.savePrefs(sizeRaw, delimiter); let c = 0; //counter - let defaultSize = 11; let allEmpty = 0; Common.reportDeltaTime(146); let body: GoogleAppsScript.Document.Document; @@ -102,27 +118,54 @@ function replaceEquations(sizeRaw: string, delimiter: string) { console.error(error); return Common.encodeFlag(-1, 0); } - - let childCount = body.getBody().getParent().getNumChildren(); + + const baseRenderOptions: AutoLatexCommon.RenderOptions = { + quality, + size, + defaultSize: 11, + inline: isInline, + delim + }; + + const childCount = body.getBody().getParent().getNumChildren(); Common.reportDeltaTime(156); - for (var index = 0; index < childCount; index++) { + for (let index = 0; index < childCount; index++) { let failedStartElemIfIsEmpty = null; while (true) { // prevFailedStartElemIfIsEmpty is here so when $$$$ fails again and again, it doesn't get stuck there and moves on. - let [gotSize, returnedFailedStartElemIfIsEmpty] = findPos(index, delim, quality, size, defaultSize, isInline, failedStartElemIfIsEmpty); //or: "\\\$\\\$", "\\\$\\\$" - allEmpty = returnedFailedStartElemIfIsEmpty ? allEmpty + 1 : 0; - failedStartElemIfIsEmpty = returnedFailedStartElemIfIsEmpty; - + const { + status, + equationSize, + nextStartElement + } = findPos(index, baseRenderOptions, failedStartElemIfIsEmpty); //or: "\\\$\\\$", "\\\$\\\$" + + if (nextStartElement) failedStartElemIfIsEmpty = nextStartElement; + // if we found an actual equation, update the default size + if (equationSize) baseRenderOptions.defaultSize = equationSize; + + // count consecutive empty equations + if (status == DocsEquationRenderStatus.EmptyEquation) { + allEmpty++; + } else { + allEmpty = 0; + } + if (allEmpty > 10) break; //Assume we quit on 10 consecutive empty equations. - - if (gotSize == -100000) - // means all renderers didn't return/bugged out. - return Common.encodeFlag(-2, c); // instead, return pair of number and bool flag in list but whatever - - if (gotSize == 0) break; // finished with renders in this section - - defaultSize = gotSize; - c = returnedFailedStartElemIfIsEmpty ? c : c + 1; // # of equations += 1 except empty equations + + // quit if all renderers failed + if (status == DocsEquationRenderStatus.AllRenderersFailed) { + return Common.encodeFlag(-2, 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) { + break; + } + + if (status != DocsEquationRenderStatus.EmptyEquation) { + c++; + } console.log("Rendered equations: " + c); } } @@ -142,43 +185,57 @@ function replaceEquations(sizeRaw: string, delimiter: string) { 1 if eqn is "" and 0 if not. Assume we close on 4 consecutive empty ones. */ -function findPos(index: number, delim: AutoLatexCommon.Delimiter, quality: number, size: number, defaultSize: number, isInline: boolean, prevFailedStartElemIfIsEmpty = null): [number, GoogleAppsScript.Document.RangeElement | null] { +function findPos(index: number, renderOptions: AutoLatexCommon.RenderOptions, prevFailedStartElemIfIsEmpty = null): DocsEquationRenderResult { Common.debugLog("Checking document section index # ", index); Common.reportDeltaTime(195); const docBody = getBodyFromIndex(index); if (docBody == null) { - return [0, null]; + return { + status: DocsEquationRenderStatus.NoDocument + }; } - let startElement = docBody.findText(delim[2]); + let startElement = docBody.findText(renderOptions.delim[2]); if (prevFailedStartElemIfIsEmpty) { - startElement = docBody.findText(delim[2], prevFailedStartElemIfIsEmpty); + startElement = docBody.findText(renderOptions.delim[2], prevFailedStartElemIfIsEmpty); + } + if (startElement == null) { + return { + status: DocsEquationRenderStatus.NoStartDelimiter + }; } - if (startElement == null) return [0, null]; //didn't find first delimiter const placeHolderStart = startElement.getStartOffset(); //position of image insertion - const endElement = docBody.findText(delim[3], startElement); - if (endElement == null) return [0, null]; //didn't find end delimiter (maybe make error different?) + const endElement = docBody.findText(renderOptions.delim[3], startElement); + // could not find the ending delimiter after the start + if (endElement == null) { + return { + status: DocsEquationRenderStatus.NoEndDelimiter + }; + } const placeHolderEnd = endElement.getEndOffsetInclusive(); //text between placeHolderStart and placeHolderEnd will be permanently deleted - Common.debugLog(delim[2], " single escaped delimiters ", placeHolderEnd - placeHolderStart, " characters long"); + Common.debugLog(renderOptions.delim[2], " single escaped delimiters ", placeHolderEnd - placeHolderStart, " characters long"); Common.reportDeltaTime(214); if (placeHolderEnd - placeHolderStart == 2.0) { // empty equation console.log("Empty equation! In index " + index + " and offset " + placeHolderStart); - return [defaultSize, endElement]; // default behavior of placeImage + + return { + // start from the end element next time to avoid an infinite loop + nextStartElement: endElement, + status: DocsEquationRenderStatus.EmptyEquation + }; } - return placeImage(startElement, placeHolderStart, placeHolderEnd, quality, size, defaultSize, delim, isInline); + return placeImage(startElement, placeHolderStart, placeHolderEnd, renderOptions); } -function getEquation(paragraph: GoogleAppsScript.Document.Paragraph, childIndex: number, start: number, end: number, delimiters: AutoLatexCommon.Delimiter) { +function getEquation(textElement: GoogleAppsScript.Document.Text, start: number, end: number, delimiters: AutoLatexCommon.Delimiter) { const equationOriginal = []; Common.reportDeltaTime(284); - Common.debugLog("See text", paragraph.getChild(childIndex).asText().getText(), paragraph.getChild(childIndex).asText().getText().length); - const equation = paragraph - .getChild(childIndex) - .asText() + 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); @@ -190,19 +247,17 @@ function getEquation(paragraph: GoogleAppsScript.Document.Paragraph, childIndex: } //retrieve size from text -function setSize(size: number, defaultSize: number, paragraph: GoogleAppsScript.Document.Paragraph, childIndex: number, start: number) { +function setSize(size: number, defaultSize: number, textElement: GoogleAppsScript.Document.Text, start: number) { //GET SIZE let newSize = size; if (size == 0) { try { - newSize = paragraph - .getChild(childIndex) + newSize = textElement .asText() .editAsText() .getFontSize(start + 3); //Fix later: Change from 3 to 1 } catch (err) { - newSize = paragraph - .getChild(childIndex) + newSize = textElement .asText() .editAsText() .getFontSize(start + 1); //Fix later: Change from 3 to 1 @@ -230,7 +285,7 @@ function setSize(size: number, defaultSize: number, paragraph: GoogleAppsScript. * @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, quality: number, size: number, defaultSize: number, delim: AutoLatexCommon.Delimiter, isInline: boolean): [number, GoogleAppsScript.Document.RangeElement | null] { +function placeImage(startElement: GoogleAppsScript.Document.RangeElement, start: number, end: number, renderOptions: AutoLatexCommon.RenderOptions): DocsEquationRenderResult { Common.reportDeltaTime(411); Common.reportDeltaTime(413); // GET VARIABLES @@ -238,16 +293,23 @@ 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 - size = setSize(size, defaultSize, paragraph, childIndex, start); - const equationOriginal = getEquation(paragraph, childIndex, start, end, delim); + const size = setSize(renderOptions.size, renderOptions.defaultSize, textElement, start); + const equationOriginal = getEquation(textElement, start, end, renderOptions.delim); if (equationOriginal == "") { console.log("No equation but undetected start and end as ", start, " ", end); - return [defaultSize, startElement]; + + return { + status: DocsEquationRenderStatus.EmptyEquation, + // TODO: this _should_ be impossible - empty equations should be detected in findPos() + nextStartElement: startElement + }; } - let { resp, renderer, rendererType, worked, equation } = Common.renderEquation(equationOriginal, quality, delim, isInline, 0, 0, 0); - if (worked > Common.capableRenderers) return [-100000, null]; + let { resp, renderer, rendererType, worked, equation } = Common.renderEquation(equationOriginal, renderOptions.quality, renderOptions.delim, renderOptions.inline, 0, 0, 0); + if (worked > Common.capableRenderers) return { + status: DocsEquationRenderStatus.AllRenderersFailed + }; // SAVING FORMATTING Common.reportDeltaTime(511); if (escape(resp.getBlob().getDataAsString()).substring(0, 50) == Common.invalidEquationHashCodecogsFirst50) { @@ -264,29 +326,24 @@ function placeImage(startElement: GoogleAppsScript.Document.RangeElement, start: 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?) const logoBlob = resp.getBlob(); Common.reportDeltaTime(526); - - try { - paragraph.insertInlineImage(childIndex + 1, logoBlob); // TODO ISSUE: sometimes fails because it times out and yeets - const returnParams = repairImage(paragraph, childIndex, size, defaultSize, renderer, delim, textCopy, resp, rendererType, equation, equationOriginal); - return returnParams; - } catch (err) { - console.log("Could not insert image try 1"); - console.error(err); - } - Common.reportDeltaTime(536); - try { - Utilities.sleep(1000); - paragraph.insertInlineImage(childIndex + 1, logoBlob); // TODO ISSUE: sometimes fails because it times out and yeets - const returnParams = repairImage(paragraph, childIndex, size, defaultSize, renderer, delim, textCopy, resp, rendererType, equation, equationOriginal); - return returnParams; - } catch (err) { - console.log("Could not insert image try 2 after 1000ms"); - console.error(err); + + // 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, renderOptions.defaultSize, renderer, renderOptions.delim, textCopy, resp, rendererType, equation, equationOriginal); + } catch (err) { + console.log(`Could not insert image try ${tryNum}`); + console.error(err); + + Utilities.sleep(1000); + } } + 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): [number, null] { +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 { let attemptsToSetImageUrl = 3; Common.reportDeltaTime(552); // 3 seconds!! inserting an inline image takes time while (attemptsToSetImageUrl > 0) { @@ -345,8 +402,11 @@ function repairImage(paragraph: GoogleAppsScript.Document.Paragraph, childIndex: size = Math.round(height * multiple); Common.reportDeltaTime(595); Common.sizeImage(DocsApp, paragraph, childIndex + 1, size, Math.round(width * multiple)); - defaultSize = oldSize; - return [defaultSize, null]; + + return { + status: DocsEquationRenderStatus.Success, + equationSize: oldSize + }; } function getBodyFromIndex(index: number) { diff --git a/types/common-types/LICENSE b/types/common-types/LICENSE index 88dccf9..303fd68 100644 --- a/types/common-types/LICENSE +++ b/types/common-types/LICENSE @@ -1,7 +1,7 @@ MIT License -Copyright (c) 2023 +Copyright (c) 2025 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/types/common-types/index.d.ts b/types/common-types/index.d.ts index c55816e..6235848 100644 --- a/types/common-types/index.d.ts +++ b/types/common-types/index.d.ts @@ -91,6 +91,23 @@ declare namespace AutoLatexCommon { } + /** + * Options for rendering. Currently not actually used in Common.renderEquation + */ + export interface RenderOptions { + + defaultSize: number; + + delim: Delimiter; + + inline: boolean; + + quality: number; + + size: number; + + } + /** * An array which defines a renderer * From efd0e8377d07bb67a4601d2e28cbbe7db5d0a838 Mon Sep 17 00:00:00 2001 From: Siddhant Kameswar <115331356+grimsteel@users.noreply.github.com> Date: Sun, 1 Jun 2025 18:43:02 -0500 Subject: [PATCH 02/11] client mathjax render code --- BuildSidebarJS.js | 7 +- Common/Code.ts | 26 +++++- Docs/ALEStylesheet.html | 5 ++ Docs/Code.ts | 6 ++ Docs/Sidebar.ts | 143 +++++++++++++++++++++++-------- Docs/SidebarJS.html | 155 ++++++++++++++++++++++++---------- package-lock.json | 14 +++ package.json | 1 + types/common-types/index.d.ts | 29 ++++++- types/docs-types/index.d.ts | 20 ++++- 10 files changed, 319 insertions(+), 87 deletions(-) diff --git a/BuildSidebarJS.js b/BuildSidebarJS.js index e301199..683f422 100644 --- a/BuildSidebarJS.js +++ b/BuildSidebarJS.js @@ -7,13 +7,14 @@ const execPromise = promisify(exec); function wrapJS(sidebarJS) { return ` `; +${sidebarJS} +`; } async function compileTS() { try { - await execPromise("npx tsc --preserveConstEnums Sidebar.ts"); - } catch { + await execPromise("npx tsc --preserveConstEnums Sidebar.ts -t es2020 --lib es2020"); + } catch (err) { // typescript complains about conflicting types between DOM and Google Apps Script; ignore } } diff --git a/Common/Code.ts b/Common/Code.ts index 5e8710a..bb29a9e 100644 --- a/Common/Code.ts +++ b/Common/Code.ts @@ -16,13 +16,33 @@ interface Renderer { 6: string; } +interface CommonRenderOptions { + size: number; + inline: boolean; + // color + r: number, g: number, b: number; + // needed for constructing the derendering string + delim: Delimiter; +} + /** - * Options for rendering. Currently not actually used in Common.renderEquation + * Options for rendering on the server - these are general settings for all equations * * @public */ -interface RenderOptions { - quality: number, size: number, defaultSize: number, inline: boolean , delim: Delimiter +interface RenderOptions extends CommonRenderOptions { + defaultSize: number; + clientRender: boolean; +} + +/** +* Options/state for rendering on the client - these are settings for a specific equation +* +* @public +*/ +interface ClientRenderOptions extends CommonRenderOptions { + rangeId: string; + equation: string; } /** diff --git a/Docs/ALEStylesheet.html b/Docs/ALEStylesheet.html index 76e50c2..60c0c7a 100644 --- a/Docs/ALEStylesheet.html +++ b/Docs/ALEStylesheet.html @@ -90,4 +90,9 @@ margin: 3px 8px 0 0; /* Alternatively: padding: 0 5px; */ width: 248px; } + + .mathjax-equation-hidden-render { + visibility: hidden; + position: absolute; + } diff --git a/Docs/Code.ts b/Docs/Code.ts index b27ed55..8dbba7a 100644 --- a/Docs/Code.ts +++ b/Docs/Code.ts @@ -7,6 +7,9 @@ var DEBUG = false; //doing ctrl + m to get key to see errors is still needed; DEBUG is for all nondiagnostic information +/** + * @public + */ const enum DocsEquationRenderStatus { // error types NoDocument, @@ -15,6 +18,9 @@ const enum DocsEquationRenderStatus { EmptyEquation, AllRenderersFailed, + // equation should be rendered on the client side (MathJax) + ClientRender, + Success } diff --git a/Docs/Sidebar.ts b/Docs/Sidebar.ts index 4cc530d..af163d8 100644 --- a/Docs/Sidebar.ts +++ b/Docs/Sidebar.ts @@ -4,6 +4,82 @@ /// /// +window.MathJax = { + loader: {load: ['tex-svg']}, + svg: { + fontCache: 'none' + }, + startup: { + typeset: false // Prevent auto-typesetting + }, + options: { + enableAssistiveMml: false + } +}; + +async function renderMathJaxEquation(renderOptions: AutoLatexCommon.ClientRenderOptions) { + // newline becomes \\ + const equation = renderOptions.equation.replace(/\n|\r|\r\n/g, "\\\\"); + + const result = await window.MathJax.tex2svgPromise(equation, { + display: !renderOptions.inline, + em: renderOptions.size + }); + const svg: SVGSVGElement = result.querySelector("svg"); + + // calculate width and height by rendering this svg with the specified font size + 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; + + svg.remove(); + + // set width/height explicitly on the svg + svg.setAttribute("width", `${width}px`); + svg.setAttribute("height", `${height}px`); + + const styles = MathJax.svgStylesheet().outerHTML; + + // create a URL for this svg + const svgString = new XMLSerializer().serializeToString(svg) + // inject css + .replace("", "" + 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. */ @@ -69,6 +145,12 @@ function loadPreferences(choicePrefs: {size: string, delim: string}) { $('#edit-text').prop("disabled", false); $('#undo-all').prop("disabled", false); } + +function makeStatusText(successCount: number) { + 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; @@ -79,40 +161,33 @@ function insertText(){ google.script.run .withSuccessHandler( - function(returnSuccess: number, element) { - $("#loading").html(''); - clearInterval(runDots); - element.disabled = false; - console.log(returnSuccess); - let flag = 0; - let renderCount = 1; - if(returnSuccess < -1){ - flag = -2; - renderCount = -2 - returnSuccess; - } - else if(returnSuccess == -1){ - flag = -1; - renderCount = 0; + 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); + } } - else{ - flag = 0; - renderCount = returnSuccess; - } - // 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) { @@ -284,4 +359,4 @@ function showError(msg1: any, msg2: any) {//CHANGE TO OTHER DIV WHEN PUBLISHING var div = $('
' + msg1 + '
'); $('#loading').after(div); $('#loading').html(msg2); -} \ No newline at end of file +} diff --git a/Docs/SidebarJS.html b/Docs/SidebarJS.html index 67b94ad..aacf25b 100644 --- a/Docs/SidebarJS.html +++ b/Docs/SidebarJS.html @@ -4,6 +4,69 @@ /// /// /// +window.MathJax = { + loader: { load: ['tex-svg'] }, + svg: { + fontCache: 'none' + }, + startup: { + typeset: false // Prevent auto-typesetting + }, + options: { + enableAssistiveMml: false + } +}; +async function renderMathJaxEquation(renderOptions) { + // newline becomes \\ + const equation = renderOptions.equation.replace(/\n|\r|\r\n/g, "\\\\"); + const result = await window.MathJax.tex2svgPromise(equation, { + display: !renderOptions.inline, + em: renderOptions.size + }); + const svg = result.querySelector("svg"); + // calculate width and height by rendering this svg with the specified font size + 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; + svg.remove(); + // set width/height explicitly on the svg + svg.setAttribute("width", `${width}px`); + svg.setAttribute("height", `${height}px`); + const styles = MathJax.svgStylesheet().outerHTML; + // create a URL for this svg + const svgString = new XMLSerializer().serializeToString(svg) + // inject css + .replace("", "" + 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
- - + +
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]);