import debounce from 'lodash/debounce'; import isInteger from 'lodash/isInteger'; import { CanvasObject } from './CanvasObject'; import { TextInterface } from '../../../CanvasInterface/Objects/Text'; import { Logger } from '../../../Utils/Logger'; import HTML2Paper from '../Utils/HTML2Paper'; import { collectStyleParams } from '../Utils/StyleParams'; import { AbstractCanvas } from '../../../CanvasInterface/Canvas'; import { updateFontStyle } from '../Utils/updateFontStyle'; const logger = new Logger('Text'); export class Text extends CanvasObject implements TextInterface { public static type = 'Text'; protected _content = ''; protected _style: any = {}; protected _fontSize = 16; protected _fontSizeMin = 8; protected _isBulkName = false; protected _textAlign = 'left'; protected _fontItem: any = {}; constructor(options?: any) { super(); this.initOptions(options); if (options.content) { this.content = options.content; } } setScaling(scaling: number) { super.setScaling(scaling); this.transformScalingToFontSize(); } /** * This method runs with a delay after the setScaling has been called. */ transformScalingToFontSize = debounce(() => { const scaling = this.scaling; if (scaling !== 1) { this._scaling = 1; const fontSize = Math.floor(this._fontSize * scaling); if (isInteger(fontSize)) { this.fontSize = fontSize; } } }, 500); get content() { return this._content; } set content(value: string) { // create a wrapper element and assign it to the contentHTML, it does the rest const element = document.createElement('div'); element.innerHTML = value; // if it has only one child then use that directly this.contentHTML = element.children.length === 1 ? element.firstChild : element; } set contentHTML(element: HTMLElement) { const prevState = this.toObject(); // store the outerHTML to get the default values as well this._content = element.outerHTML; this.build(); this.triggerChangeEvent(prevState); } get contentHTML() { return this._content; } get style() { return this._style; } set style(style: any) { //skipping explicit update as style is computed in contentHTML setter } set fontSize(value: number | string) { const prevState = this.toObject(); const fontSize = parseInt(value); if (!fontSize) { throw new Error('The given value is not a number!'); } if (fontSize === this._fontSize) { return; } this._fontSize = Math.max(this._fontSizeMin, fontSize); this.build(); this.triggerChangeEvent(prevState); } get fontSize() { return this._fontSize; } get fontSizeMin() { return this._fontSizeMin; } set fontSizeMin(value: number) { const prevState = this.toObject(); this._fontSizeMin = value; // update fontSize to resize it if smaller than the minimum this.fontSize = this._fontSize; this.build(); this.triggerChangeEvent(prevState); } build(options = {}) { const { triggerChange } = options; const converter = new HTML2Paper(); const { canvas } = this; // to do the build correctly for some browsers (eg. edge) the canvas has to be added to the DOM // test browser info: Microsoft Edge 44.18362.449.0 const un = canvas && canvas.addToContainer(); this.wrappedObject = converter.importHTML( this.content, this.wrappedObject, false, true, this.getExplicitStyleParams() ); this.updateStyleParams(); un && canvas.removeFromContainer(); if (triggerChange) { const prevState = this.toObject(); this.triggerChangeEvent(prevState); } } updateStyleParams() { this._style = { ...collectStyleParams(this._content), ...this.getExplicitStyleParams(), }; } /** * Triggers the build function if the canvas is available, so the content can be imported correctly * @param canvas */ set canvas(canvas: AbstractCanvas) { super.canvas = canvas; this.build(); } // the getter should be always overridden in pair with the setter // otherwise an access to this.canvas returns undefined (see Object.defineProperty for details) get canvas() { return this._canvas; } getExplicitStyleParams() { const { fontSize, textAlign, fontItem } = this; return { fontSize, textAlign, fontItem, }; } get isBulkName() { return this._isBulkName; } set isBulkName(value: boolean) { const prevState = this.toObject(); this._isBulkName = value; this.triggerChangeEvent(prevState); } get textAlign() { return this._textAlign; } set textAlign(value: string) { this._textAlign = value; this.build(); } get fontItem() { return this._fontItem; } set fontItem(fontItem) { this._fontItem = fontItem; this.content = updateFontStyle(this.content, fontItem); } getState() { const { content, fontSize, fontSizeMin, textAlign, fontItem } = this; return { ...super.getState(), content, fontSize, fontSizeMin, textAlign, fontItem, }; } toObject(...args) { const { content, style, fontSize, fontSizeMin, isBulkName, textAlign, fontItem } = this; return { ...super.toObject(...args), content, style, fontSize, fontSizeMin, isBulkName, textAlign, fontItem, }; } }