@wiris/mathtype-viewer 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # MathType Viewer
2
+
3
+ The MathType viewer renders all MathML and LaTeX-like formulas in a webpage using the MathType rendering engine.
4
+
5
+ ## Usage
6
+
7
+ To use the MathType viewer in your page, just include the script `dist/WIRISplugins.js`:
8
+
9
+ ```html
10
+ <script src="dist/WIRISplugins.js?viewer=image" defer></script>
11
+ ```
12
+
13
+ When loading the page now, all MathML and LaTeX formulas should be displayed as images.
14
+
15
+
16
+ ## Properties
17
+
18
+ The MathType viewer admits various properties.
19
+
20
+ They can be set like this:
21
+
22
+ ```html
23
+ <script src="dist/WIRISplugins.js&viewer=image" defer></script>
24
+ <script>
25
+ window.document.addEventListener('viewerLoaded', () => {
26
+ window.viewer.Properties.key1 = value1;
27
+ window.viewer.Properties.key2 = value2;
28
+ // ...
29
+ });
30
+ </script>
31
+ ```
32
+
33
+
34
+ ## Compilation
35
+
36
+ In case you want to compile the source code, you must install the NPM dependencies and then run Webpack.
37
+
38
+ First, run
39
+
40
+ ```sh
41
+ yarn
42
+ ```
43
+
44
+ to install the dependencies.
45
+ Then run
46
+
47
+ ```sh
48
+ yarn build
49
+ ```
50
+
51
+ to compile the package.
52
+ The output will be found in `dist/`.
53
+
54
+
55
+ ## Documentation
56
+
57
+ For the whole specification, see [here](docs/spec.md).
58
+
59
+
60
+ ## Privacy policy
61
+
62
+ The [MathType Privacy Policy](https://www.wiris.com/en/mathtype-privacy-policy/?utm_source=npmjs&utm_medium=referral) covers the data processing operations for the MathType users. It is an addendum of the company's general Privacy Policy and the [general Privacy Policy](https://www.wiris.com/en/privacy-policy?utm_source=npmjs&utm_medium=referral) still applies to MathType users.
@@ -0,0 +1,12 @@
1
+ module.exports = {
2
+ presets: [
3
+ [
4
+ '@babel/preset-env',
5
+ {
6
+ targets: {
7
+ node: 'current',
8
+ },
9
+ },
10
+ ],
11
+ ],
12
+ };
package/docs/spec.md ADDED
@@ -0,0 +1,118 @@
1
+ # Specification
2
+
3
+ ## Object
4
+
5
+ *What is the object being specified?*
6
+
7
+ The viewer consists of a single minified JavaScript file ready to be included in a website.
8
+ The file has been historically called WIRISplugins.js.
9
+
10
+
11
+ ## Distribution
12
+
13
+ *How can this object be obtained and used by our customers?*
14
+
15
+ The customers can include the JavaScript file using a `<script>` tag.
16
+ They have multiple options as to where this file resides.
17
+
18
+ - Directly from wiris.net. We serve the last two versions of the viewer as a static asset in wiris.net.
19
+ The historical URLs of this asset are https://www.wiris.net/demo/plugins/app/WIRISplugins.js (for the latest version) and https://www.wiris.net/client/plugins/app/WIRISplugins.js (for the second to last version).
20
+ These come, in fact, from the Java integration services that come with the WIRISplugins.js file included in them (see next bullet point).
21
+ - Distributed together with the integration services.
22
+ The backend component of our plugins comes together with the WIRISplugins.js file so that the customer can include it from their own backend directly.
23
+ - As an npm package. The file is distributed, together with its source code, as an npm package.
24
+ The publishing system can be the same as the one used for the rest of our npm packages that live in the html-integrations monorepo.
25
+
26
+
27
+ ## Behavior
28
+
29
+ *What should this object do and how should it do it?*
30
+
31
+ When included in a web page and configured properly, the script will transform certain mathematical formulas into rendered images.
32
+ Only the formulas inside the HTML element specified by the `element` property should be transformed.
33
+ Whether it transforms them into PNG or SVG images is determined by the property `wirisimageformat`.
34
+ Depending on the value of the property `viewer`, just MathML formulas (`<math>` elements), or just LaTeX formulas (LaTeX-like formulas surrounded by `$$`), or both will be transformed.
35
+
36
+
37
+ ### Architecture
38
+
39
+ The rendering process involves three pieces:
40
+
41
+ - The frontend script ("the viewer").
42
+ - The integration services.
43
+ A series of endpoints in the backend of the customer.
44
+ The customer can also choose not to host this part and use the integration services hosted in wiris.net instead as well, but then they won't be able to set the backend properties in the configuration.ini file.
45
+ - The editor services.
46
+ A series of endpoints in wiris.net.
47
+ These are the services that actually do the rendering and return the image content.
48
+
49
+ The viewer only ever calls the endpoints of the integration services, which in turn call the editor services.
50
+ Thus, throughout this document, all backend endpoints mentioned are part of the integration services.
51
+
52
+
53
+ ### Rendering algorithm
54
+
55
+ The algorithm for obtaining the rendered images depends on the `wirispluginperformance` property.
56
+
57
+ If set to `false`, every request to render an image will make a call directly to the createimage endpoint, which returns the data for the image directly.
58
+
59
+ Otherwise, the algorithm goes throught two cache steps.
60
+
61
+ 1. First, an MD5 hash of the formula is created.
62
+ Then, the MD5 is used to call the showimage service from the integration services.
63
+ The request formula's hash is included as URL parameters, so that the browser can use its cache to spare one call to the integration services.
64
+
65
+ 2. If the browser cache misses, then the browser does actually perform the call to showimage.
66
+ This might either return the image, if it is in the backend cache; or return a warning message, in which case the viewer will then call showimage again passing the whole formula via POST.
67
+ Finally, the response should contain the image.
68
+
69
+ Once the viewer has obtained the image data, it creates the HTML `<img>` element, adding the following properties:
70
+
71
+ - `src`: as obtained from showimage.
72
+ - `role`: with constant value `math`.
73
+ - `class`: with constant value `Wirisformula`.
74
+ - `alt`: with the alternative text for the formula, provided by the mathml2accessible service.
75
+ - An attribute whose name is the value of the parameter `wiriseditormathmlattribute` and whose value is the original MathML of the formula.
76
+
77
+ Finally, the viewer replaces the original formula with this new image object.
78
+
79
+
80
+ ### Properties
81
+
82
+ This section contains a reference of all the properties that affect the viewer's behavior.
83
+ There are a few different places where these properties can be set.
84
+ The place depends on each specific property.
85
+
86
+ - Backend.
87
+ Set in the configuration.ini file of the customer.
88
+ - Frontend.
89
+ There are two ways to set these properties.
90
+ - Either by modifying the `Properties` object via JavaScript:
91
+
92
+ ```html
93
+ <script src="dist/WIRISplugins.js" defer></script>
94
+ <script>
95
+ window.document.addEventListener('viewerLoaded', () => {
96
+ window.viewer.Properties.key1 = value1;
97
+ window.viewer.Properties.key2 = value2;
98
+ // ...
99
+ });
100
+ </script>
101
+ ```
102
+
103
+ - Or via URL in the included script (by appending `?key1=value1&key2=value2…`).
104
+ This method is discouraged and deprecated, and only kept for retrocompatibility purposes.
105
+
106
+ The following table contains a specification of each of the properties.
107
+
108
+ | Name | Description | Place | Possible values | Default value |
109
+ |----------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|--------------------------------------------------|-----------------------------------------|
110
+ | wiriseditormathmlattribute | The name of the HTML element attribute that will be used in the rendered img elements in order to store the original MathML. According to the HTML spec, it must begin with “data-” [ https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-* ]. | Backend | data-* | data-mathml |
111
+ | wirispluginperformance | Whether to use the browser cache to render formulas. | Backend | true, false | true |
112
+ | wirisimageformat | Whether to return PNG or SVG formulas. | Backend | svg, png | svg |
113
+ | editorServicesRoot | The URL base for the showimage and createimage integration services. If distributed together with the backend services, this URL is determined by the backend technology. If distributed with npm, it takes as a default value the integration services from wiris.net. In any case, this parameter can be set manually in the frontend. | Frontend | URL | https://www.wiris.net/demo/plugins/app/ |
114
+ | viewer | What should the viewer render exactly. none: do nothing image: render both MathML and LaTeX formulas mathml: render only MathML latex: render only LaTeX | Frontend | none, image, mathml, latex (mod capitalization) | none |
115
+ | element | A querySelector string that identifies the element in which the viewer should act. | Frontend | | document |
116
+ | lang | The language for the alt text. | Frontend | | en |
117
+ | 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 |
118
+ | zoom | The scale of the generated image. | Frontend | Positive floating point number | 1 |
package/index.html ADDED
@@ -0,0 +1,29 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <title> Testing Viewer MathML </title>
5
+ <meta charset="utf-8">
6
+ <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon">
7
+ <script src="dist/WIRISplugins.js?viewer=image" defer></script>
8
+ <script>
9
+ window.document.addEventListener('viewerLoaded', () => {
10
+ // window.viewer.Properties can be modified here
11
+ // Changes are reflected live
12
+ });
13
+ </script>
14
+ </head>
15
+ <body>
16
+ <p>
17
+ <math>
18
+ <mn>2</mn>
19
+ <mo>+</mo>
20
+ <mn>2</mn>
21
+ <mo>=</mo>
22
+ <mn>4</mn>
23
+ </math>
24
+ </p>
25
+ <p>
26
+ $$4 - 2 = 2$$
27
+ </p>
28
+ </body>
29
+ </html>
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@wiris/mathtype-viewer",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "app.js",
6
+ "scripts": {
7
+ "build": "webpack",
8
+ "serve": "webpack serve",
9
+ "prepack": "npm install && npm run build"
10
+ },
11
+ "keywords": [
12
+ "typescript",
13
+ "course"
14
+ ],
15
+ "author": "Integrations",
16
+ "license": "ISC",
17
+ "dependencies": {
18
+ "@wiris/mathtype-html-integration-devkit": "1.12.2"
19
+ },
20
+ "devDependencies": {
21
+ "babel-loader": "^9.1.2",
22
+ "lite-server": "^2.5.4",
23
+ "ts-loader": "^9.4.2",
24
+ "webpack": "^5.75.0",
25
+ "webpack-cli": "^4.0.0",
26
+ "webpack-dev-server": "^4.12.0"
27
+ }
28
+ }
package/src/app.ts ADDED
@@ -0,0 +1,73 @@
1
+ import { Properties } from './properties';
2
+ import { renderLatex } from './latex';
3
+ import { renderMathML } from './mathml';
4
+
5
+ // This should be the only code executed outside of a function
6
+ // and the only code containing browser globals (e.g. window)
7
+ // TODO try to set up the linter to check these two constraints
8
+ main(window);
9
+
10
+ /**
11
+ * Initial function called when loading the script.
12
+ * @param {Window} w - The window instance of the browser.
13
+ */
14
+ async function main(w: Window): Promise<void> {
15
+ // Expose the globals to the browser
16
+ (w as any).viewer = {
17
+ Properties,
18
+ };
19
+
20
+ const document = w.document;
21
+
22
+ /**
23
+ * Parse the DOM looking for LaTeX and <math> elements.
24
+ * Replaces them with the corresponding rendered images within the given element.
25
+ * @param {Document} document - The DOM document in which to search for the rendering root.
26
+ * @param {MutationObserver} observer - Mutation observer to activate or reactivate every time the rendering root changes.
27
+ */
28
+ Properties.render = async () => {
29
+ const element: HTMLElement = document.querySelector(Properties.element);
30
+ if (element) {
31
+ await renderLatex(element);
32
+ await renderMathML(element);
33
+ }
34
+ };
35
+
36
+ // Initial function to call once document is loaded
37
+ // Renders formulas and sets observer
38
+ const start = async () => {
39
+ // First render
40
+ Properties.render();
41
+
42
+ // Callback called every time there is a mutation in the watched DOM element
43
+ new MutationObserver(async (mutationList, observer) => {
44
+ for (const mutation of mutationList) {
45
+ for (const node of mutation.addedNodes) {
46
+ if (node instanceof HTMLElement) {
47
+ await Properties.render();
48
+ }
49
+ }
50
+ }
51
+ })
52
+ // We need to watch over the whole document, in case the Properties.element is inserted
53
+ // e.g. we set Properties.element = '#renderArea' and then we append <div id="renderArea">$$2+2=4$$</div> to the document
54
+ .observe(document, {
55
+ attributes: true, // In case an attribute is changed in a <math> node, for instance
56
+ childList: true, // In case a new <math> or $$latex$$ node is added, for instance
57
+ subtree: true, // In case a <math> node is added as a descendant of the observed element, for instance
58
+ });
59
+ };
60
+
61
+ // https://developer.mozilla.org/en-US/docs/Web/API/Document/DOMContentLoaded_event#checking_whether_loading_is_already_complete
62
+ if (document.readyState === "loading") {
63
+ // Loading hasn't finished yet
64
+ document.addEventListener("DOMContentLoaded", start);
65
+ } else {
66
+ // `DOMContentLoaded` has already fired
67
+ start();
68
+ }
69
+
70
+ // Dispatch an event notifying that the viewer has been loaded
71
+ document.dispatchEvent(new Event('viewerLoaded'));
72
+
73
+ }
package/src/latex.ts ADDED
@@ -0,0 +1,101 @@
1
+ import { latexToMathml } from './services';
2
+ import { Properties } from './properties';
3
+
4
+ interface LatexPosition {
5
+ start: number,
6
+ end: number,
7
+ }
8
+
9
+ /**
10
+ * Parse the DOM looking for LaTeX nodes and replaces them with the corresponding rendered images.
11
+ * @param {HTMLElement} root - Any DOM element that can contain MathML.
12
+ */
13
+ export async function renderLatex(root: HTMLElement) {
14
+
15
+ if (Properties.viewer !== 'image') {
16
+ return;
17
+ }
18
+
19
+ const latexNodes = findLatexTextNodes(root);
20
+
21
+ for (const latexNode of latexNodes) {
22
+ await replaceLatexInTextNode(latexNode);
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Replace LaTeX instances with MathML inside a given node.
28
+ * @param {Node} node - Text node in which to search and replace LaTeX instances.
29
+ */
30
+ async function replaceLatexInTextNode(node: Node) {
31
+ const textContent: string = node.textContent || '';
32
+ let pos: number = 0;
33
+
34
+ while (pos < textContent.length) {
35
+ const nextLatexPosition: LatexPosition = getNextLatexPos(pos, textContent);
36
+ if (nextLatexPosition) {
37
+ // Get left non LaTeX text.
38
+ const leftText: string = textContent.substring(pos, nextLatexPosition.start);
39
+ const leftTextNode = document.createTextNode(leftText);
40
+ // Create a node with left text.
41
+ node.parentNode?.insertBefore(leftTextNode, node);
42
+ node.nodeValue = node.nodeValue.substring(pos, nextLatexPosition.start);
43
+
44
+ // Get LaTeX text.
45
+ const latex = textContent.substring(nextLatexPosition.start + '$$'.length, nextLatexPosition.end);
46
+ // Convert LaTeX to mathml.
47
+ const response = await latexToMathml(latex, Properties.editorServicesRoot, Properties.editorServicesExtension);
48
+ // Insert mathml node.
49
+ const fragment = document.createRange().createContextualFragment(response.text);
50
+
51
+ node.parentNode?.insertBefore(fragment, node);
52
+ node.nodeValue = node.nodeValue.substring(nextLatexPosition.start, nextLatexPosition.end);
53
+
54
+ pos = nextLatexPosition.end + '$$'.length;
55
+ } else {
56
+ // No more LaTeX node found.
57
+ const text = textContent.substring(pos);
58
+ const textNode = document.createTextNode(text);
59
+ node.parentNode?.insertBefore(textNode, node);
60
+ node.nodeValue = '';
61
+ pos = textContent.length;
62
+ }
63
+ }
64
+
65
+ // Delete original text node.
66
+ node.parentNode?.removeChild(node);
67
+ }
68
+
69
+
70
+ /**
71
+ * Returns an array with all HTML LaTeX nodes.
72
+ * @param {HTMLElement} root - Any DOM element that can contain LaTeX.
73
+ * @returns {Node[]} Array with all HTML LaTeX nodes inside root.
74
+ */
75
+ function findLatexTextNodes(root: any): Node[] {
76
+ const nodeIterator: NodeIterator = document.createNodeIterator(
77
+ root,
78
+ NodeFilter.SHOW_TEXT,
79
+ node => /(\$\$)(.*)(\$\$)/.test(node.nodeValue || '') ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT
80
+ );
81
+ const latexNodes : Node[] = [];
82
+
83
+ let currentNode: Node | null;
84
+ while (currentNode = nodeIterator.nextNode()) {
85
+ latexNodes.push(currentNode);
86
+ }
87
+
88
+ return latexNodes;
89
+ }
90
+
91
+ /**
92
+ * Returns an object {start, end} with the start and end latex position.
93
+ * @param {number} pos - Current position inside the text.
94
+ * @param {string} text - Text where the next latex it will be searched.
95
+ * @
96
+ */
97
+ function getNextLatexPos(pos: number, text: string): LatexPosition {
98
+ const firstLatexTags = text.indexOf('$$', pos);
99
+ const secondLatexTags = firstLatexTags == -1 ? -1 : text.indexOf('$$', firstLatexTags + '$$'.length);
100
+ return firstLatexTags != -1 && secondLatexTags != -1 ? {'start': firstLatexTags, 'end': secondLatexTags} : null;
101
+ }
package/src/mathml.ts ADDED
@@ -0,0 +1,83 @@
1
+ import { Properties } from "./properties";
2
+ import { showImage, createImage, mathml2accessible, processJsonResponse } from './services';
3
+
4
+ /**
5
+ * Data obtained when rendering image. Data needed to set the formula image parameters.
6
+ */
7
+ interface FormulaData {
8
+ content: string,
9
+ baseline: string,
10
+ height: string,
11
+ width: string,
12
+ }
13
+
14
+ /**
15
+ * Parse the DOM looking for <math> elements and replace them with the corresponding rendered images within the given element.
16
+ * @param {HTMLElement} root - Any DOM element that can contain MathML.
17
+ */
18
+ export async function renderMathML(root: HTMLElement): Promise<void> {
19
+
20
+ if (Properties.viewer !== 'image') {
21
+ return;
22
+ }
23
+
24
+ for(const mathElement of [...root.getElementsByTagName('math')]) {
25
+ const mml = mathElement.outerHTML;
26
+
27
+ let result;
28
+
29
+ if (Properties.wirispluginperformance === 'true') {
30
+ // Transform mml to img.
31
+ result = await showImage(mml, Properties.lang, Properties.editorServicesRoot, Properties.editorServicesExtension);
32
+ } else {
33
+ // createimage returns the URL to showimage of the corresponding image
34
+ let url = await createImage(mml, Properties.lang, Properties.editorServicesRoot, Properties.editorServicesExtension);
35
+ // This line is necessary due to a bug in how the services interoperate.
36
+ // TODO fix the causing bug
37
+ url = url.replace('pluginsapp', 'plugins/app');
38
+ result = await processJsonResponse(fetch(url));
39
+ }
40
+
41
+ // Set img properties.
42
+ const img = await setImageProperties(result, mml, Properties.wiriseditormathmlattribute);
43
+ // const fragment = document.createRange().createContextualFragment(data.result.content);
44
+
45
+ // Replace the MathML for the generated formula image.
46
+ mathElement.parentNode?.replaceChild(img, mathElement);
47
+ }
48
+ };
49
+
50
+ /**
51
+ * Returns an image formula containing all MathType properties.
52
+ * @param {FormulaData} data - Object containing image values.
53
+ * @param {string} mml - The MathML of the formula image beeing created.
54
+ * @param {string} wiriseditormathmlattribute - The name of the HTML attribute to store the MathML in.
55
+ * @returns {Promise<HTMLImageElement>} - Formula image.
56
+ */
57
+ async function setImageProperties(data: FormulaData, mml: string, wiriseditormathmlattribute: string): Promise<HTMLImageElement> {
58
+
59
+ // Create imag element.
60
+ let img = document.createElement('img');
61
+
62
+ // Set image src. Encode the result svg.
63
+ img.src = `data:image/svg+xml;charset=utf8,${encodeURIComponent(data.content)}`;
64
+
65
+ // Set other image properties.
66
+ img.setAttribute(wiriseditormathmlattribute, mml);
67
+ img.setAttribute('class', 'Wirisformula');
68
+ img.setAttribute('role', 'math');
69
+
70
+ // If the render returns dimensions properties, set them to the image.
71
+ if (+data.height > 0) {
72
+ img.style.verticalAlign = "-" + (+data.height - +data.baseline) + "px";
73
+ img.height = +data.height;
74
+ img.width = +data.width;
75
+ }
76
+
77
+ // Set the alt text.
78
+ const { text } = await mathml2accessible(mml, Properties.lang, Properties.editorServicesRoot, Properties.editorServicesExtension);
79
+ img.alt = text;
80
+
81
+ return img;
82
+
83
+ }
@@ -0,0 +1,256 @@
1
+ import { configurationJson, StatusError } from './services';
2
+
3
+ // Helper types for Config below
4
+ type Viewer = 'image' | 'none';
5
+ type Wirispluginperformance = 'true' | 'false';
6
+
7
+ /**
8
+ * Type representing all the configuration for the viewer.
9
+ */
10
+ export type Config = {
11
+ editorServicesRoot?: string,
12
+ editorServicesExtension?: string,
13
+ backendConfig?: {
14
+ wirispluginperformance?: Wirispluginperformance,
15
+ wiriseditormathmlattribute?: string,
16
+ },
17
+ dpi?: number,
18
+ element?: string,
19
+ lang?: string,
20
+ viewer?: Viewer,
21
+ zoom?: number,
22
+ };
23
+
24
+ /**
25
+ * Fallback values for the configurations that are not set.
26
+ */
27
+ const defaultValues: Config = {
28
+ editorServicesRoot: 'https://www.wiris.net/demo/plugins/app/',
29
+ editorServicesExtension: '',
30
+ backendConfig: {
31
+ wirispluginperformance: 'true',
32
+ wiriseditormathmlattribute: 'data-mathml'
33
+ },
34
+ dpi: 96,
35
+ element: 'body',
36
+ lang: 'en',
37
+ viewer: 'none',
38
+ zoom: 1,
39
+ }
40
+
41
+ /**
42
+ * This class will handle the parameters defined by the user.
43
+ */
44
+ export abstract class Properties {
45
+
46
+ static render: () => Promise<void> = async () => {};
47
+
48
+ // Flag for the static parameters that access the backend.
49
+ private static backendObtained: boolean = false;
50
+
51
+ private static waitForBackend() {
52
+ while (!this.backendObtained);
53
+ }
54
+
55
+ // Get URL properties (retrocompatibility).
56
+ static config: Config = defaultValues;
57
+ static {
58
+
59
+ // Get URL parameters from <script>
60
+ const pluginName = 'WIRISplugins.js';
61
+ const script: HTMLScriptElement = document.querySelector(`script[src*="${pluginName}"]`);
62
+
63
+ if (!!script) {
64
+
65
+ const pluginNamePosition: number = script.src.lastIndexOf(pluginName);
66
+ const params: string = script.src.substring(pluginNamePosition + pluginName.length);
67
+ const urlParams = new URLSearchParams(params);
68
+
69
+ if (urlParams.get('dpi') !== null && urlParams.get('dpi') !== undefined) {
70
+ this.config.dpi = +urlParams.get('dpi');
71
+ }
72
+ if (urlParams.get('element') !== null && urlParams.get('element') !== undefined) {
73
+ this.config.element = urlParams.get('element');
74
+ }
75
+ if (urlParams.get('lang') !== null && urlParams.get('lang') !== undefined) {
76
+ this.config.lang = urlParams.get('lang');
77
+ }
78
+ if (urlParams.get('viewer') !== null && urlParams.get('viewer') !== undefined) {
79
+ this.config.viewer = (urlParams.get('viewer') as Viewer);
80
+ }
81
+ if (urlParams.get('zoom') !== null && urlParams.get('zoom') !== undefined) {
82
+ this.config.zoom = +urlParams.get('zoom');
83
+ }
84
+
85
+ }
86
+
87
+ // Get backend parameters calling the configurationjson service
88
+ (async () => {
89
+ try {
90
+ this.config.backendConfig = await configurationJson(
91
+ ['wirispluginperformance', 'wiriseditormathmlattribute'],
92
+ Properties.editorServicesRoot,
93
+ Properties.editorServicesExtension,
94
+ );
95
+ } catch(e) {
96
+ if (e instanceof StatusError) {
97
+ // Do nothing; leave default values.
98
+ console.error(e);
99
+ } else {
100
+ throw e;
101
+ }
102
+ } finally {
103
+ // Stop looking for the backend (even if the request fails)
104
+ this.backendObtained = true;
105
+ }
106
+ })();
107
+
108
+ }
109
+
110
+ /**
111
+ * Set the config values manually.
112
+ */
113
+ static init(config: Config) {
114
+ Properties.config = {...defaultValues, ...config};
115
+ }
116
+
117
+ static get editorServicesRoot(): string {
118
+ return this.config.editorServicesRoot ||
119
+ defaultValues.editorServicesRoot;
120
+ }
121
+
122
+ static set editorServicesRoot(editorServicesRoot: string) {
123
+ this.config.editorServicesRoot = editorServicesRoot;
124
+ this.render();
125
+ }
126
+
127
+ static get editorServicesExtension(): string {
128
+ return this.config.editorServicesExtension ||
129
+ defaultValues.editorServicesExtension;
130
+ }
131
+
132
+ static set editorServicesExtension(editorServicesExtension: string) {
133
+ this.config.editorServicesExtension = editorServicesExtension;
134
+ this.render();
135
+ }
136
+
137
+ /**
138
+ * Return the language.
139
+ * In order of priority, the first of the following that is set is returned:
140
+ * - The lang parameter set in the <script> (WIRISplugin.js?lang=...)
141
+ * - The HTML document language (<html lang=...>).
142
+ * - The language of the browser.
143
+ * - English, by default.
144
+ * @returns {string} Encoded language string.
145
+ */
146
+ static get lang(): string {
147
+ const configLang = (this.config.lang === 'inherit') ? null : this.config.lang;
148
+ return configLang ||
149
+ document.getElementsByTagName('html')[0].lang ||
150
+ navigator.language ||
151
+ defaultValues.lang;
152
+ }
153
+
154
+ static set lang(lang: string) {
155
+ this.config.lang = lang;
156
+ this.render();
157
+ }
158
+
159
+ /**
160
+ * Return the viewer mode for the MathML.
161
+ * In order of priority, the first of the following that is set is returned:
162
+ * - The viewer parameter set in the <script> (WIRISplugin.js?viewer=...)
163
+ * - none, by default.
164
+ */
165
+ static get viewer(): Viewer {
166
+ return this.config.viewer ||
167
+ defaultValues.viewer;
168
+ }
169
+
170
+ static set viewer(viewer: Viewer) {
171
+ this.config.viewer = viewer;
172
+ this.render();
173
+ }
174
+
175
+ /**
176
+ * Return the dpi of the images.
177
+ * In order of priority, the first of the following that is set is returned:
178
+ * - The dpi parameter set in the <script> (WIRISplugin.js?dpi=...)
179
+ * - 96, by default.
180
+ */
181
+ static get dpi(): number {
182
+ return this.config.dpi ||
183
+ defaultValues.dpi;
184
+ }
185
+
186
+ static set dpi(dpi: number) {
187
+ this.config.dpi = dpi;
188
+ this.render();
189
+ }
190
+
191
+ /**
192
+ * Return the zoom of the images.
193
+ * In order of priority, the first of the following that is set is returned:
194
+ * - The zoom parameter set in the <script> (WIRISplugin.js?zoom=...)
195
+ * - 1, by default.
196
+ */
197
+ static get zoom(): number {
198
+ return this.config.zoom ||
199
+ defaultValues.zoom;
200
+ }
201
+
202
+ static set zoom(zoom: number) {
203
+ this.config.zoom = zoom;
204
+ this.render();
205
+ }
206
+
207
+ /**
208
+ * Return the element in which to render formulas.
209
+ * In order of priority, the first of the following that is set is returned:
210
+ * - The zoom parameter set in the <script> (WIRISplugin.js?element=...)
211
+ * - 'body', by default.
212
+ */
213
+ static get element(): string {
214
+ return this.config.element ||
215
+ defaultValues.element;
216
+ }
217
+
218
+ static set element(element: string) {
219
+ this.config.element = element;
220
+ this.render();
221
+ }
222
+
223
+ /**
224
+ * Return the Wiris plugin performance.
225
+ * In order of priority, the first of the following that is set is returned:
226
+ * - The backend configuration of the parameter.
227
+ * - true, by default.
228
+ */
229
+ static get wirispluginperformance(): Wirispluginperformance {
230
+ this.waitForBackend();
231
+ return this.config.backendConfig.wirispluginperformance ||
232
+ defaultValues.backendConfig.wirispluginperformance;
233
+ }
234
+
235
+ static set wirispluginperformance(wirispluginperformance: Wirispluginperformance) {
236
+ this.config.backendConfig.wirispluginperformance = wirispluginperformance;
237
+ this.render();
238
+ }
239
+
240
+ /**
241
+ * Return the Wiris MathML attribute.
242
+ * In order of priority, the first of the following that is set is returned:
243
+ * - The backend configuration of the parameter.
244
+ * - data-mathml, by default.
245
+ */
246
+ static get wiriseditormathmlattribute(): string {
247
+ this.waitForBackend();
248
+ return this.config.backendConfig.wiriseditormathmlattribute ||
249
+ defaultValues.backendConfig.wiriseditormathmlattribute;
250
+ }
251
+
252
+ static set wiriseditormathmlattribute(wiriseditormathmlattribute: string) {
253
+ this.config.backendConfig.wiriseditormathmlattribute = wiriseditormathmlattribute;
254
+ this.render();
255
+ }
256
+ }
@@ -0,0 +1,184 @@
1
+ import Parser from '@wiris/mathtype-html-integration-devkit/src/parser';
2
+
3
+ enum MethodType {
4
+ Post = "POST",
5
+ Get = "GET",
6
+ }
7
+
8
+ /**
9
+ * Thrown when a service returns a JSON with a non-ok status value in its JSON body
10
+ */
11
+ export class StatusError extends Error {
12
+ constructor(message) {
13
+ super(message);
14
+ Object.setPrototypeOf(this, StatusError.prototype);
15
+ }
16
+ }
17
+
18
+ /**
19
+ * Helper function to process responses from the editor services.
20
+ * These usually come wrapped in a JSON with a status field that can be either "ok" or "warning".
21
+ * If status is "ok", return the result value along it. Otherwise, throw a StatusError.
22
+ * @param {Promise<Response>} response - The response given by the service.
23
+ * @returns {Promise<any>} The unwrapped result of the response, if valid.
24
+ * @throws {StatusError} Service responded with a non-ok status.
25
+ */
26
+ export async function processJsonResponse(response: Promise<Response>): Promise<any> {
27
+ try {
28
+ const { status, result } = await (await response).json();
29
+
30
+ if (status !== 'ok') {
31
+ throw new StatusError('Service responded with a non-ok status');
32
+ }
33
+
34
+ return result;
35
+ } catch(e) {
36
+ // TODO manage network and status non-ok errors
37
+ throw e;
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Calls the endpoint servicename and returns its response.
43
+ * @param {object} query - Object of parameters to pass as the body request or search parameters.
44
+ * @param {string} serviceName - Name of the service to be called.
45
+ * @param {string} serverURL - Url of the server where we want to call the service.
46
+ * @param {string} extension - Extension to add to the end of the serviceName (including the dot if necessary).
47
+ * @returns {Promise<Response>} The request response.
48
+ */
49
+ export async function callService(query: object, serviceName: string, method: MethodType, serverURL: string, extension: string) : Promise<any> {
50
+ try {
51
+ const url = new URL(serviceName + extension, serverURL);
52
+ const init: RequestInit = {
53
+ method,
54
+ headers: {
55
+ 'Content-type': 'application/x-www-form-urlencoded; charset=utf-8',
56
+ },
57
+ };
58
+
59
+ if (method === MethodType.Get) {
60
+ // Add the query as search params
61
+ for (const [key, value] of Object.entries(query)) {
62
+ url.searchParams.set(key, value);
63
+ }
64
+ } else {
65
+ // Add the query as the body of the request
66
+ init.body = new URLSearchParams({...query});
67
+ }
68
+
69
+ return fetch(url.toString(), init);
70
+ } catch(e) {
71
+ // TODO manage network and status non-ok errors
72
+ throw e;
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Returns the alt text of the MathML passed as parameter.
78
+ * @param {string} mml - MathML to be transformed into alt text.
79
+ * @param {string} lang - Language of the accessible text.
80
+ * @param {string} url - URL of the server where we want to call the service.
81
+ * @param {string} extension - Extension to add to the end of the serviceName (including the dot if necessary).
82
+ * @returns {Promise<Response>} The mathml2accessible service response.
83
+ */
84
+ export async function mathml2accessible(mml: string, lang: string, url: string, extension: string) : Promise<any> {
85
+ // Set the needed params to retrieve the alt text.
86
+ const params = {
87
+ 'service': 'mathml2accessible',
88
+ 'mml': mml,
89
+ 'metrics': 'true',
90
+ 'centerbaseline': 'false',
91
+ 'lang': lang,
92
+ 'ignoreStyles': 'true',
93
+ }
94
+
95
+ const response = callService(params, 'service', MethodType.Post, url, extension);
96
+ return processJsonResponse(response);
97
+ }
98
+
99
+ /**
100
+ * Calls the showImage service with the given MathML and returns the received Response object.
101
+ * @param {string} mml - MathML to render.
102
+ * @param {string} lang - Language.
103
+ * @param {string} url - URL of the server where we want to call the service.
104
+ * @param {string} extension - Extension to add to the end of the serviceName (including the dot if necessary).
105
+ * @returns {Promise<Response>} the Response object to the petition made to showImage
106
+ */
107
+ export async function showImage(mml: string, lang: string, url: string, extension: string): Promise<any> {
108
+ const params = {
109
+ 'mml': mml,
110
+ 'metrics': 'true',
111
+ 'centerbaseline': 'false',
112
+ 'lang':lang,
113
+ }
114
+
115
+ // Try to obtain the image via GET
116
+ const getParams = Parser.createShowImageSrcData({ mml }, lang);
117
+ const getResponse = callService(getParams, 'showimage', MethodType.Get, url, extension);
118
+ try {
119
+ return await processJsonResponse(getResponse);
120
+ } catch(e) {
121
+ if (e instanceof StatusError) {
122
+ // Formula was not in cache; proceed with calling showimage via POST
123
+ } else {
124
+ throw e;
125
+ }
126
+ }
127
+
128
+ // If GET request fails, it means that the formula was not in cache. Proceed with POST:
129
+ const response = callService(params, 'showimage', MethodType.Post, url, extension);
130
+ return processJsonResponse(response);
131
+
132
+ };
133
+
134
+ /**
135
+ * Calls the createImage service with the given MathML and returns the received Response object.
136
+ * @param {string} mml - MathML to render
137
+ * @param {string} lang - Language.
138
+ * @param {string} url - URL of the server where we want to call the service.
139
+ * @param {string} extension - Extension to add to the end of the serviceName (including the dot if necessary).
140
+ * @returns {Promise<Response>} the Response object to the petition made to showImage
141
+ */
142
+ export async function createImage(mml: string, lang: string, url: string, extension: string): Promise<any> {
143
+ const params = {
144
+ 'mml': mml,
145
+ 'metrics': 'true',
146
+ 'centerbaseline': 'false',
147
+ 'lang': lang,
148
+ }
149
+
150
+ const response = callService(params, 'createimage', MethodType.Get, url, extension);
151
+ return (await response).text();
152
+ };
153
+
154
+ /**
155
+ * Calls the latex2mathml service with the given LaTeX and returns the received Response object.
156
+ * @param {string} latex - LaTeX to render
157
+ * @param {string} url - Url of the server where we want to call the service.
158
+ * @param {string} extension - Extension to add to the end of the serviceName (including the dot if necessary).
159
+ * @returns {Promise<Response>} the Response object to the petition made to service
160
+ */
161
+ export async function latexToMathml(latex: string, url: string, extension: string): Promise<any> {
162
+ const params = {
163
+ 'service': 'latex2mathml',
164
+ 'latex': latex,
165
+ }
166
+
167
+ const response = callService(params, 'service', MethodType.Post, url, extension);
168
+ return processJsonResponse(response);
169
+ }
170
+
171
+ /**
172
+ * Returns the configuration from the backend.
173
+ * @param {string[]} variablekeys - List of the key names of the variables to fetch.
174
+ * @param {string} extension - Extension to add to the end of the serviceName (including the dot if necessary).
175
+ * @returns {Promise<Response>} The configurationjson service response.
176
+ */
177
+ export async function configurationJson(variablekeys: string[], url: string, extension: string) : Promise<any> {
178
+ const params = {
179
+ 'variablekeys': variablekeys.join(','),
180
+ }
181
+
182
+ const response = callService(params, 'configurationjson', MethodType.Get, url, extension);
183
+ return processJsonResponse(response);
184
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "compilerOptions": {
3
+ /* Basic Options */
4
+ "target": "ES5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
5
+ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
6
+ "lib": [
7
+ "dom",
8
+ "es6",
9
+ "dom.iterable",
10
+ "scripthost",
11
+ "es2017",
12
+ "es5",
13
+ "es2015",
14
+ ], /* Specify library files to be included in the compilation. */
15
+ // "allowJs": true, /* Allow javascript files to be compiled. */
16
+ // "checkJs": true, /* Report errors in .js files. */
17
+ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
18
+ "declaration": true, /* Generates corresponding '.d.ts' file. */
19
+ "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
20
+ "sourceMap": true, /* Generates corresponding '.map' file. */
21
+ // "outFile": "./", /* Concatenate and emit output to single file. */
22
+ "outDir": "./dist", /* Redirect output structure to the directory. */
23
+ "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
24
+ // "composite": true, /* Enable project compilation */es
25
+ "removeComments": true, /* Do not emit comments to output. */
26
+ // "noEmit": true, /* Do not emit outputs. */
27
+ // "importHelpers": true, /* Import emit helpers from 'tslib'. */
28
+ "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
29
+ // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
30
+ "noEmitOnError": true,
31
+
32
+ /* Strict Type-Checking Options */
33
+ // "strict": true, /* Enable all strict type-checking options. */
34
+ "noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */
35
+ // "strictNullChecks": true, /* Enable strict null checks. */
36
+ // "strictFunctionTypes": true, /* Enable strict checking of function types. */
37
+ // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
38
+ // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
39
+ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
40
+ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
41
+
42
+ /* Additional Checks */
43
+ "noUnusedLocals": false, /* Report errors on unused locals. */
44
+ "noUnusedParameters": false, /* Report errors on unused parameters. */
45
+ "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
46
+ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
47
+
48
+ /* Module Resolution Options */
49
+ "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
50
+ // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
51
+ // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
52
+ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
53
+ "typeRoots": [
54
+ "./typings",
55
+ "../node_modules/@types",
56
+ "./dist"
57
+ ], /* List of folders to include type definitions from. */
58
+ // "types": [], /* Type declaration files to be included in compilation. */
59
+ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
60
+ "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
61
+ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
62
+
63
+ /* Source Map Options */
64
+ // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
65
+ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
66
+ // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
67
+ // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
68
+
69
+ /* Experimental Options */
70
+ "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
71
+ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
72
+ },
73
+ "exclude": [
74
+ "node_modules", // would be the default
75
+ "./dist"
76
+ ]
77
+ }
@@ -0,0 +1,39 @@
1
+ const path = require('path');
2
+
3
+ module.exports = (config, context) => {
4
+ return {
5
+ entry: './src/app.ts',
6
+ mode: 'development',
7
+ devtool: 'inline-source-map',
8
+ module: {
9
+ rules: [
10
+ {
11
+ test: /\.tsx?$/,
12
+ use: 'ts-loader',
13
+ exclude: /node_modules/,
14
+ },
15
+ {
16
+ test: /\.js$/,
17
+ exclude: /node_modules/,
18
+ use: ['babel-loader']
19
+ },
20
+ ],
21
+ },
22
+ resolve: {
23
+ extensions: ['.tsx', '.ts', '.js'],
24
+ },
25
+ output: {
26
+ filename: 'WIRISplugins.js',
27
+ path: path.resolve(__dirname, 'dist'),
28
+ },
29
+ devServer: {
30
+ devMiddleware: {
31
+ writeToDisk: true,
32
+ },
33
+ static: './',
34
+ hot: true,
35
+ port: 8001,
36
+ open: true,
37
+ },
38
+ }
39
+ };