Zoom:
diff --git a/demos/html/viewer/src/app.js b/demos/html/viewer/src/app.js
index 79a3aa4b9..d35bb5c6b 100644
--- a/demos/html/viewer/src/app.js
+++ b/demos/html/viewer/src/app.js
@@ -30,10 +30,12 @@ document.getElementById("viewer_form").addEventListener("submit", () => {
lang: lang.value,
viewer: viewer.value,
zoom: zoom.value,
+ vieweroffset:vieweroffset.value,
+ simultaneousmml:simultaneousmml.value,
+ simultaneouslatex:simultaneouslatex.value,
};
window.viewer.properties.config = config;
document.getElementsByTagName("main")[0].innerHTML = document.getElementById("content").value;
- window.com.wiris.js.JsPluginViewer.parseElement(document.getElementsByTagName("main")[0]);
});
diff --git a/demos/html/viewer/src/static/style.css b/demos/html/viewer/src/static/style.css
index e12aaaea9..75bbc1891 100644
--- a/demos/html/viewer/src/static/style.css
+++ b/demos/html/viewer/src/static/style.css
@@ -3,6 +3,7 @@ body {
}
input[type="text"],
+input[type="number"],
select,
textarea {
width: 100%;
diff --git a/packages/viewer/docs/spec.md b/packages/viewer/docs/spec.md
index d7c314ff9..6698585e2 100644
--- a/packages/viewer/docs/spec.md
+++ b/packages/viewer/docs/spec.md
@@ -112,6 +112,9 @@ The following table contains a specification of each of the properties.
| lang | The language for the alt text. | Frontend | | en |
| dpi | Resolution in dots per inch of the generated image. This feature scales the formula with a factor of dpi/96. | Frontend | Positive integer | 96 |
| zoom | The scale of the generated image. | Frontend | Positive floating point number | 1 |
+| vieweroffset | We render only the formulas on screen. This parameter defines the number of pixels to render in advance. | Frontend | Positive floating point number | 200 |
+| simultaneousmml | Max number of possible simultaneous MathML rendering petitions at the same time. | Frontend | Positive floating point number | 50 |
+| simultaneouslatex | Max number of possible simultaneous LaTeX rendering petitions at the same time. | Frontend | Positive floating point number | 50 |
## API
diff --git a/packages/viewer/src/app.ts b/packages/viewer/src/app.ts
index bfbc5ee69..1b12e57ed 100644
--- a/packages/viewer/src/app.ts
+++ b/packages/viewer/src/app.ts
@@ -1,6 +1,6 @@
import { Properties } from "./properties";
import { renderLatex } from "./latex";
-import { renderMathML } from "./mathml";
+import { renderAMathML } from "./mathml";
import { bypassEncapsulation } from "./retro";
import packageInformation from "../../../node_modules/@wiris/mathtype-viewer/package.json";
@@ -41,49 +41,85 @@ async function main(w: Window): Promise
{
const document = w.document;
- /**
- * Parse the DOM looking for LaTeX and elements.
- * Replaces them with the corresponding rendered images within the given element.
- * @param {Document} document - The DOM document in which to search for the rendering root.
- * @param {MutationObserver} observer - Mutation observer to activate or reactivate every time the rendering root changes.
- */
- properties.render = async () => {
- const element: HTMLElement = document.querySelector(properties.element);
- if (element) {
- await renderLatex(properties, element);
- await renderMathML(properties, element);
- }
- };
-
// Initial function to call once document is loaded
// Renders formulas and sets observer
const start = async () => {
- // Check if the viewer is alredy loaded
+ // Check if the viewer is already loaded
if (w.viewer.isLoaded) return;
w.viewer.isLoaded = true;
- // First render
- properties.render();
+ // Interaction Observer
+ const mathobserver = new IntersectionObserver(async (entries, observer) => {
+
+ let latexPromises = [];
+
+ const maxMmlPetition = properties.simultaneousmml;
+ let mmlPromises = [];
+ for (const entry of entries) {
+
+ if (entry.isIntersecting && entry.target instanceof MathMLElement && entry.target.matches('math')) {
+ const promise = renderAMathML(properties, entry.target);
+ mmlPromises.push(promise);
+ observer.unobserve(entry.target);
+ // To not saturate service, not make all render petition at once
+ if (mmlPromises.length >= maxMmlPetition) {
+ await Promise.all(mmlPromises);
+ mmlPromises = [];
+ }
+ }
+
+ if (entry.isIntersecting && entry.target instanceof HTMLElement) {
+ const promise = renderLatex(properties, entry.target);
+ latexPromises.push(promise);
+ observer.unobserve(entry.target);
+ }
+ }
+ await Promise.all(latexPromises);
+ await Promise.all(mmlPromises);
+ }, {
+ rootMargin: `${properties.vieweroffset}px`
+ });
+
+ const allElements = document.querySelectorAll('*');
+ window.addEventListener("load", function () {
+ allElements.forEach(function (element) {
+ mathobserver.observe(element);
+ });
+ });
+
+ //MutationObserver
// Callback called every time there is a mutation in the watched DOM element
- // Feature temporarily disabled due to KB-37834
- // new MutationObserver(async (mutationList, observer) => {
- // for (const mutation of mutationList) {
- // for (const node of mutation.addedNodes) {
- // if (node instanceof HTMLElement) {
- // await properties.render();
- // }
- // }
- // }
- // })
- // // We need to watch over the whole document, in case the Properties.element is inserted
- // // e.g. we set Properties.element = '#renderArea' and then we append $$2+2=4$$
to the document
- // .observe(document, {
- // attributes: true, // In case an attribute is changed in a node, for instance
- // childList: true, // In case a new or $$latex$$ node is added, for instance
- // subtree: true, // In case a node is added as a descendant of the observed element, for instance
- // });
+ new MutationObserver(async (mutationList) => {
+ for (const mutation of mutationList) {
+ for (const node of mutation.addedNodes) {
+ // If was a htmlelement, decompose to child element to find posible latex or mathml
+ if (node instanceof HTMLElement) {
+ const allElements = node.querySelectorAll('*');
+
+ allElements.forEach(function (element) {
+ mathobserver.observe(element);
+ });
+ }
+ // Observe mathml
+ else if (node instanceof MathMLElement) {
+ mathobserver.observe(node);
+ }
+ // If it was a pure text, observe their parent in order to find posible latex
+ else if (node.nodeType == Node.TEXT_NODE) {
+ mathobserver.observe(node.parentElement);
+ }
+ }
+ }
+ })
+ // We need to watch over the whole document, in case the Properties.element is inserted
+ // e.g. we set Properties.element = '#renderArea' and then we append $$2+2=4$$
to the document
+ .observe(document, {
+ attributes: true, // In case an attribute is changed in a node, for instance
+ childList: true, // In case a new or $$latex$$ node is added, for instance
+ subtree: true, // In case a node is added as a descendant of the observed element, for instance
+ });
};
// https://developer.mozilla.org/en-US/docs/Web/API/Document/DOMContentLoaded_event#checking_whether_loading_is_already_complete
diff --git a/packages/viewer/src/latex.ts b/packages/viewer/src/latex.ts
index 94cba0197..f74692098 100644
--- a/packages/viewer/src/latex.ts
+++ b/packages/viewer/src/latex.ts
@@ -18,9 +18,21 @@ export async function renderLatex(properties: Properties, root: HTMLElement) {
const latexNodes = findLatexTextNodes(root);
+ const maxLatexPetition = properties.simultaneouslatex;
+
+ let promises = [];
+
for (const latexNode of latexNodes) {
- await replaceLatexInTextNode(properties, latexNode);
+ promises.push(replaceLatexInTextNode(properties, latexNode));
+
+ // To not saturate service, not make all render petition at once
+ if (promises.length >= maxLatexPetition) {
+ await Promise.all(promises);
+ promises = [];
+ }
}
+
+ await Promise.all(promises);
}
/**
diff --git a/packages/viewer/src/mathml.ts b/packages/viewer/src/mathml.ts
index 7f5344df7..fc56f8759 100644
--- a/packages/viewer/src/mathml.ts
+++ b/packages/viewer/src/mathml.ts
@@ -48,6 +48,41 @@ function decodeSafeMathML(root: HTMLElement) {
}
}
+export async function renderAMathML(properties: Properties, mathElement: MathMLElement): Promise {
+ if (properties.viewer !== "image" && properties.viewer !== "mathml") {
+ return;
+ }
+
+ const mml = serializeHtmlToXml(mathElement.outerHTML);
+
+ try {
+ let imgSource;
+
+ // Transform mml to img.
+ if (properties.wirispluginperformance === "true") {
+ imgSource = await showImage(
+ mml,
+ properties.lang,
+ properties.editorServicesRoot,
+ properties.editorServicesExtension,
+ );
+ } else {
+ imgSource = await createImage(
+ mml,
+ properties.lang,
+ properties.editorServicesRoot,
+ properties.editorServicesExtension,
+ );
+ }
+
+ // Set img properties.
+ const img = await setImageProperties(properties, imgSource, mml);
+ // Replace the MathML for the generated formula image.
+ mathElement.parentNode?.replaceChild(img, mathElement);
+ } catch {
+ console.error(`Cannot render ${mml}: invalid MathML format.`);
+ }
+}
/**
* Parse the DOM looking for elements and replace them with the corresponding rendered images within the given element.
* @param {Properties} properties - Properties of the viewer.
@@ -60,37 +95,44 @@ export async function renderMathML(properties: Properties, root: HTMLElement): P
decodeSafeMathML(root);
+ const promises = [];
+
for (const mathElement of [...root.getElementsByTagName("math")]) {
const mml = serializeHtmlToXml(mathElement.outerHTML);
- try {
- let imgSource;
-
- // Transform mml to img.
- if (properties.wirispluginperformance === "true") {
- imgSource = await showImage(
- mml,
- properties.lang,
- properties.editorServicesRoot,
- properties.editorServicesExtension,
- );
- } else {
- imgSource = await createImage(
- mml,
- properties.lang,
- properties.editorServicesRoot,
- properties.editorServicesExtension,
- );
+ const promise = (async () => {
+ try {
+ let imgSource;
+
+ // Transform mml to img.
+ if (properties.wirispluginperformance === "true") {
+ imgSource = await showImage(
+ mml,
+ properties.lang,
+ properties.editorServicesRoot,
+ properties.editorServicesExtension,
+ );
+ } else {
+ imgSource = await createImage(
+ mml,
+ properties.lang,
+ properties.editorServicesRoot,
+ properties.editorServicesExtension,
+ );
+ }
+
+ // Set img properties.
+ const img = await setImageProperties(properties, imgSource, mml);
+ // Replace the MathML for the generated formula image.
+ mathElement.parentNode?.replaceChild(img, mathElement);
+ } catch {
+ console.error(`Cannot render ${mml}: invalid MathML format.`);
}
+ })();
- // Set img properties.
- const img = await setImageProperties(properties, imgSource, mml);
- // Replace the MathML for the generated formula image.
- mathElement.parentNode?.replaceChild(img, mathElement);
- } catch {
- console.error(`Cannot render ${mml}: invalid MathML format.`);
- continue;
- }
+ promises.push(promise);
}
+
+ await Promise.all(promises);
}
/**
diff --git a/packages/viewer/src/properties.ts b/packages/viewer/src/properties.ts
index 6d43c4bb8..526d4aa32 100644
--- a/packages/viewer/src/properties.ts
+++ b/packages/viewer/src/properties.ts
@@ -21,6 +21,9 @@ export type Config = {
lang?: string;
viewer?: Viewer;
zoom?: number;
+ vieweroffset?: number,
+ simultaneousmml?: number;
+ simultaneouslatex?: number;
};
/**
@@ -39,6 +42,9 @@ const defaultValues: Config = {
lang: "en",
viewer: "none",
zoom: 1,
+ vieweroffset: 200,
+ simultaneousmml: 50,
+ simultaneouslatex: 50,
};
/**
@@ -47,7 +53,7 @@ const defaultValues: Config = {
export class Properties {
private static instance: Properties | null = null;
- render: () => Promise = async () => {};
+ render: () => Promise = async () => { };
// Get URL properties (retrocompatibility).
config: Config = defaultValues;
@@ -56,7 +62,6 @@ export class Properties {
* Do not use this method. Instead, use {@link Properties.generate}.
* Constructors cannot be async so we make it private and force instantiation through an alternative static method.
*/
- private new() {}
static getInstance(): Properties {
if (!Properties.instance) {
@@ -94,6 +99,17 @@ export class Properties {
if (urlParams.get("zoom") !== null && urlParams.get("zoom") !== undefined) {
Properties.instance.config.zoom = +urlParams.get("zoom");
}
+ if (urlParams.get("viewer-offset") !== null && urlParams.get("viewer-offset") !== undefined) {
+ Properties.instance.config.vieweroffset = +urlParams.get("viewer-offset");
+ }
+ if (urlParams.get("simultaneous-mml") !== null && urlParams.get("simultaneous-mml") !== undefined) {
+ Properties.instance.config.simultaneousmml = +urlParams.get("simultaneous-mml");
+ Math.min(Properties.instance.config.simultaneousmml, 50);
+ }
+ if (urlParams.get("simultaneous-latex") !== null && urlParams.get("simultaneous-latex") !== undefined) {
+ Properties.instance.config.simultaneouslatex = +urlParams.get("simultaneous-latex");
+ Math.min(Properties.instance.config.simultaneouslatex, 50);
+ }
}
Properties.instance.checkServices();
@@ -266,4 +282,37 @@ export class Properties {
this.config.backendConfig.wiriseditormathmlattribute = wiriseditormathmlattribute;
this.render();
}
+
+ /**
+ * Return the max number of simultaneous MathML rendering petition can make at the same time.
+ */
+ get simultaneousmml(): number {
+ return this.config.simultaneousmml || defaultValues.simultaneousmml;
+ }
+
+ set simultaneousmml(simultaneousmml: number) {
+ this.config.simultaneousmml = simultaneousmml;
+ this.render();
+ }
+ /**
+ * Return the max number of simultaneous LaTeX rendering petition can make at the same time.
+ */
+ get simultaneouslatex(): number {
+ return this.config.simultaneouslatex || defaultValues.simultaneouslatex;
+ }
+ set simultaneouslatex(simultaneouslatex: number) {
+ this.config.simultaneouslatex = simultaneouslatex;
+ this.render();
+ }
+
+ /**
+ * Return value serves to grow or shrink each side of the root element's bounding box before computing intersections.
+ */
+ get vieweroffset(): number {
+ return this.config.vieweroffset || defaultValues.vieweroffset;
+ }
+ set vieweroffset(vieweroffset: number) {
+ this.config.vieweroffset = vieweroffset;
+ this.render();
+ }
}