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.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);
}
});
}
/**
* 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 (Configuration.get('imageFormat') === 'svg') {
// SVG format.
// If SVG is encoded in base64 we need to convert the base64 bytes into a SVG string.
if (Configuration.get('saveMode') !== 'base64') {
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';
if (img.src.indexOf('data:image') !== -1) {
if (Configuration.get('imageFormat') === 'svg') {
// ...data:image/svg+xml;charset=utf8, = 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);
}
}
/**
* 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 [];
}
}