UNPKG

@wiris/mathtype-html-integration-devkit

Version:

Allows to integrate MathType Web into any JavaScript HTML WYSIWYG rich text editor.

289 lines (269 loc) 9.87 kB
import Configuration from "./configuration"; import Util from "./util"; /** * @classdesc * This class represents MathType Image class. Contains all the logic related * to MathType images manipulation. * All MathType images are generated using the appropriate MathType * integration service: showimage or createimage. * * There are two available image formats: * - svg (default) * - png * * There are two formats for the image src attribute: * - A data-uri scheme containing the URL-encoded SVG or a PNG's base64. * - A link to the showimage service. */ export default class Image { /** * Removes data attributes from an image. * @param {HTMLImageElement} img - Image where remove data attributes. */ static removeImgDataAttributes(img) { const attributesToRemove = []; const { attributes } = img; Object.keys(attributes).forEach((key) => { const attribute = attributes[key]; if (attribute !== undefined && attribute.name !== undefined && attribute.name.indexOf("data-") === 0) { // Is preferred keep an array and remove after the search // because when attribute is removed the array of attributes // is modified. attributesToRemove.push(attribute.name); } }); attributesToRemove.forEach((attribute) => { img.removeAttribute(attribute); }); } /** * @static * Clones all MathType image attributes from a HTMLImageElement to another. * @param {HTMLImageElement} originImg - The original image. * @param {HTMLImageElement} destImg - The destination image. */ static clone(originImg, destImg) { const customEditorAttributeName = Configuration.get("imageCustomEditorName"); if (!originImg.hasAttribute(customEditorAttributeName)) { destImg.removeAttribute(customEditorAttributeName); } const mathmlAttributeName = Configuration.get("imageMathmlAttribute"); const imgAttributes = [ mathmlAttributeName, customEditorAttributeName, "alt", "height", "width", "style", "src", "role", ]; imgAttributes.forEach((iterator) => { const originAttribute = originImg.getAttribute(iterator); if (originAttribute) { destImg.setAttribute(iterator, originAttribute); } }); } /** * Determines whether an img src contains an SVG. * @param {HTMLImageElement} img the img element to inspect * @returns true if the img src contains an SVG, false otherwise */ static isSvg(img) { return img.src.startsWith("data:image/svg+xml;"); } /** * Determines whether an img src is encoded in base64 or not. * @param {HTMLImageElement} img the img element to inspect * @returns true if the img src is encoded in base64, false otherwise */ static isBase64(img) { return img.src.startsWith("data:image/svg+xml;base64,") || img.src.startsWith("data:image/png;base64,"); } /** * Calculates the metrics of a MathType image given the the service response and the image format. * @param {HTMLImageElement} img - The HTMLImageElement. * @param {String} uri - The URI generated by the image service: can be a data URI scheme or a URL. * @param {Boolean} jsonResponse - True the response of the image service is a * JSON object. False otherwise. */ static setImgSize(img, uri, jsonResponse) { let ar; let base64String; let bytes; let svgString; if (jsonResponse) { // Cleaning data:image/png;base64. if (Image.isSvg(img)) { // SVG format. // If SVG is encoded in base64 we need to convert the base64 bytes into a SVG string. if (!Image.isBase64(img)) { ar = Image.getMetricsFromSvgString(uri); } else { base64String = img.src.substr(img.src.indexOf("base64,") + 7, img.src.length); svgString = ""; bytes = Util.b64ToByteArray(base64String, base64String.length); for (let i = 0; i < bytes.length; i += 1) { svgString += String.fromCharCode(bytes[i]); } ar = Image.getMetricsFromSvgString(svgString); } // PNG format: we store all metrics information in the first 88 bytes. } else { base64String = img.src.substr(img.src.indexOf("base64,") + 7, img.src.length); bytes = Util.b64ToByteArray(base64String, 88); ar = Image.getMetricsFromBytes(bytes); } // Backwards compatibility: we store the metrics into createimage response. } else { ar = Util.urlToAssArray(uri); } let width = ar.cw; if (!width) { return; } let height = ar.ch; let baseline = ar.cb; const { dpi } = ar; if (dpi) { width = (width * 96) / dpi; height = (height * 96) / dpi; baseline = (baseline * 96) / dpi; } img.width = width; img.height = height; img.style.verticalAlign = `-${height - baseline}px`; } /** * Calculates the metrics of an image which has been resized. Is used to restore the original * metrics of a resized image. * @param {HTMLImageElement } img - The resized HTMLImageElement. */ static fixAfterResize(img) { img.removeAttribute("style"); img.removeAttribute("width"); img.removeAttribute("height"); // In order to avoid resize with max-width css property. img.style.maxWidth = "none"; const processImg = (img) => { if (img.src.indexOf("data:image") !== -1) { if (img.src.indexOf("data:image/svg+xml") !== -1) { // Image is in base64: decode it in order to calculate the size, and then bring it back to base64 // This is a bit of an ugly hack used to recycle the logic of Image.setImgSize instead of rewriting it // (which would actually make more sense for readibility and efficiency). if (img.src.indexOf("data:image/svg+xml;base64,") !== -1) { // 'data:image/svg+xml;base64,'.length === 26 const base64String = img.getAttribute("src").substring(26); const svgString = window.atob(base64String); const encodedSvgString = encodeURIComponent(svgString); img.setAttribute("src", `data:image/svg+xml;charset=utf8,${encodedSvgString}`); // 'data:image/svg+xml;charset=utf8,'.length === 32. const svg = decodeURIComponent(img.src.substring(32, img.src.length)); Image.setImgSize(img, svg, true); // Return src to base64! img.setAttribute("src", `data:image/svg+xml;base64,${base64String}`); } else { // 'data:image/svg+xml;charset=utf8,'.length === 32. const svg = decodeURIComponent(img.src.substring(32, img.src.length)); Image.setImgSize(img, svg, true); } } else { // 'data:image/png;base64,' === 22. const base64 = img.src.substring(22, img.src.length); Image.setImgSize(img, base64, true); } } else { Image.setImgSize(img, img.src); } }; // If the image doesn't contain a blob, just process it normally if (img.src.indexOf("blob:") === -1) { processImg(img); // if it does contain a blob, then read that, replace the src with the decoded content, and process it } else { const reader = new FileReader(); reader.onload = function () { img.setAttribute("src", reader.result); processImg(img); }; fetch(img.src) .then((r) => r.blob()) .then((blob) => { reader.readAsDataURL(blob); }); } } /** * Returns the metrics (height, width and baseline) contained in a SVG image generated * by the MathType image service. This image contains as an extra custom attribute: * the baseline (wrs:baseline). * @param {String} svgString - The SVG image. * @return {Array} - The image metrics. */ static getMetricsFromSvgString(svgString) { let first = svgString.indexOf('height="'); let last = svgString.indexOf('"', first + 8, svgString.length); const height = svgString.substring(first + 8, last); first = svgString.indexOf('width="'); last = svgString.indexOf('"', first + 7, svgString.length); const width = svgString.substring(first + 7, last); first = svgString.indexOf('wrs:baseline="'); last = svgString.indexOf('"', first + 14, svgString.length); const baseline = svgString.substring(first + 14, last); if (typeof width !== "undefined") { const arr = []; arr.cw = width; arr.ch = height; if (typeof baseline !== "undefined") { arr.cb = baseline; } return arr; } return []; } /** * Returns the metrics (width, height, baseline and dpi) contained in a PNG byte array. * @param {Array.<Bytes>} bytes - png byte array. * @return {Array} The png metrics. */ static getMetricsFromBytes(bytes) { Util.readBytes(bytes, 0, 8); let width; let height; let typ; let baseline; let dpi; while (bytes.length >= 4) { typ = Util.readInt32(bytes); if (typ === 0x49484452) { width = Util.readInt32(bytes); height = Util.readInt32(bytes); // Read 5 bytes. Util.readInt32(bytes); Util.readByte(bytes); } else if (typ === 0x62615345) { // Baseline: 'baSE'. baseline = Util.readInt32(bytes); } else if (typ === 0x70485973) { // Dpis: 'pHYs'. dpi = Util.readInt32(bytes); dpi = Math.round(dpi / 39.37); Util.readInt32(bytes); Util.readByte(bytes); } Util.readInt32(bytes); } if (typeof width !== "undefined") { const arr = []; arr.cw = width; arr.ch = height; arr.dpi = dpi; if (baseline) { arr.cb = baseline; } return arr; } return []; } }