import { Utils } from '../common/Utils'
import { MathUtils } from '../math/MathUtils';
import { AlignType } from '../const';
const __ = {
private: Symbol('private'),
}
// #region Private Functions
// #endregion
/**
* @class CanvasRenderer
* The canvas renderer.
* @memberof THING
*/
class CanvasRenderer {
/**
* The renderer to draw on canvas.
* @param {Object} param The initial parameters.
*/
constructor(param = {}) {
this[__.private] = {};
let _private = this[__.private];
_private.renderer = null;
let width = param['width'] || 1;
let height = param['height'] || 1;
let renderer = Utils.createObject('CanvasRenderer');
renderer.resize(width, height);
_private.renderer = renderer;
}
// #region Private
_limitSize(size) {
let renderCapabilities = Utils.getCurrentApp().renderCapabilities;
let maxTextureSize = renderCapabilities.maxTextureSize;
let width = size[0];
let height = size[1];
if (width > maxTextureSize || height > maxTextureSize) {
// Keep size ratio
if (width > height) {
width = Math.min(maxTextureSize, MathUtils.ceilPowerOfTwo(width));
height = Math.floor(height * (width / size[0]));
}
else {
height = Math.min(maxTextureSize, MathUtils.ceilPowerOfTwo(height));
width = Math.floor(width * (height / size[1]));
}
}
return [width, height];
}
_scaleRow(row, scaleFactor) {
row.height *= scaleFactor;
let metrics = row.metrics;
if (metrics.actualBoundingBoxAscent) {
metrics.actualBoundingBoxAscent *= scaleFactor;
}
if (metrics.actualBoundingBoxDescent) {
metrics.actualBoundingBoxDescent *= scaleFactor;
}
if (metrics.fontBoundingBoxAscent) {
metrics.fontBoundingBoxAscent *= scaleFactor;
}
if (metrics.fontBoundingBoxDescent) {
metrics.fontBoundingBoxDescent *= scaleFactor;
}
if (metrics.actualBoundingBoxLeft) {
metrics.actualBoundingBoxLeft *= scaleFactor;
}
if (metrics.actualBoundingBoxRight) {
metrics.actualBoundingBoxRight *= scaleFactor;
}
if (metrics.width) {
metrics.width *= scaleFactor;
}
let rowOptions = row.options;
rowOptions.size *= scaleFactor;
}
// #endregion
/**
* Clear canvas.
* @private
*/
clear() {
let _private = this[__.private];
_private.renderer.clear();
}
/**
* Resize.
* @param {Number} width The width.
* @param {Number} height The height.
* @private
*/
resize(width, height) {
let _private = this[__.private];
_private.renderer.resize(width, height);
}
/**
* Draw text.
* @param {String} text The text string.
* @param {Object} options The options.
* @param {Number} options.size The font size.
* @param {String} options.type The font type.
* @param {Number} options.lineWidth The font text width each line.
* @param {Number} options.lineHeight The font text height each line.
* @param {Number|String|Array<Number>} options.color The font color.
* @param {Number|String|Array<Number>} options.shadowColor The font shadow color.
* @param {Number} options.shadowAlpha The font shadow alpha.
* @param {Number} options.shadowAngle The font shadow angle.
* @param {Number} options.shadowBlur The font shadow blur.
* @param {Number} options.shadowDistance The font shadow distance.
* @param {AlignType} options.alignType The font align type.
* @param {Boolean} [options.richText=false] True indicates use rich text.
* @param {Boolean} [options.stroke=false] True indicates use storke mode.
* @param {Boolean} [options.powerOfTwo=false] True indicates use power of 2 size to fit.
* @returns {Boolean}
* @private
*/
drawText(text, options) {
let _private = this[__.private];
let renderer = _private.renderer;
let result = renderer.getTextRows(text, options);
if (!result) {
return false;
}
// Limit it with max texture size
let size = this._limitSize(result.size);
// If the size has changed then need to re-fit text by scale factor
if (size[0] != result.size[0] || size[1] != result.size[1]) {
// Get the scale factor
let scaleFactor = size[0] / result.size[0];
result.rows.forEach(row => {
this._scaleRow(row, scaleFactor);
});
result.size = size;
}
// Resize it by max texture size
renderer.resize(size[0], size[1]);
// Draw text with scale factor
let drawingOptions = Object.assign({}, options);
renderer.drawText(result, drawingOptions);
return true;
}
/**
* Fill rect with color.
* @param {Number} x The left in pixel.
* @param {Number} y The top in pixel.
* @param {Number} width The width in pixel.
* @param {Number} height The height in pixel.
* @param {Array<Number>} color The color.
* @private
*/
fillRect(x, y, width, height, color) {
let _private = this[__.private];
_private.renderer.fillRect(x, y, width, height, Utils.parseColor(color));
}
/**
* Convert to image.
* @param {Number} width The width in pixel.
* @param {Number} height The height in pixel.
* @returns {Object}
* @private
*/
convertToImage(width, height) {
let _private = this[__.private];
return _private.renderer.convertToImage(width, height);
}
/**
* Save as image.
* @param {String} fileName The file name.
* @param {Number} width The width in pixel.
* @param {Number} height The height in pixel.
* @returns {Boolean}
* @private
*/
saveAsImage(fileName, width, height) {
let image = this.convertToImage(width, height);
if (!image) {
return false;
}
return Utils.saveAsFile(fileName, image);
}
/**
* Get canvas context.
* @returns {Object}
* @private
*/
getContext() {
let _private = this[__.private];
return _private.renderer.getContext();
}
/**
* @typedef {Object} ImageDataResult
* @property {Number} width The width in pixel.
* @property {Number} height The height in pixel.
* @property {Uint8Array} data The pixel data.
* @private
*/
/**
* Get image data.
* @returns {ImageDataResult}
* @private
*/
getImageData() {
let _private = this[__.private];
let context = _private.renderer.getContext();
let imageData = _private.renderer.getImageData(0, 0, context.width, context.height);
return {
width: context.width,
height: context.height,
data: imageData
};
}
/**
* Check class type.
* @type {Boolean}
* @private
*/
get isCanvasRenderer() {
return true;
}
/**
* Render from the html text.
* @param {String} html The string of DOM Node object to render.
* @param {Object} options - Rendering options
* @param {String} options.bgcolor - color for the background, any valid CSS color value.
* @param {Function} options.filter Should return true if passed node should be included in the output
* (excluding node means excluding it's children as well). Not called on the root node.
* @return {Promise} A promise that is fulfilled with a canvas object.
*/
renderFromHTML(html, options) {
let _private = this[__.private];
return _private.renderer.renderFromHTML(html, options);
}
}
export { CanvasRenderer }