UNPKG

fabric

Version:

Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.

1 lines 22.8 kB
{"version":3,"file":"Textbox.min.mjs","names":[],"sources":["../../../src/shapes/Textbox.ts"],"sourcesContent":["import type { TClassProperties, TOptions } from '../typedefs';\nimport { IText } from './IText/IText';\nimport { classRegistry } from '../ClassRegistry';\nimport { createTextboxDefaultControls } from '../controls/commonControls';\nimport { JUSTIFY } from './Text/constants';\nimport type { TextStyleDeclaration } from './Text/StyledText';\nimport type { SerializedITextProps, ITextProps } from './IText/IText';\nimport type { ITextEvents } from './IText/ITextBehavior';\nimport type { TextLinesInfo } from './Text/Text';\nimport type { Control } from '../controls/Control';\n\n// @TODO: Many things here are configuration related and shouldn't be on the class nor prototype\n// regexes, list of properties that are not suppose to change by instances, magic consts.\n// this will be a separated effort\nexport const textboxDefaultValues: Partial<TClassProperties<Textbox>> = {\n minWidth: 20,\n dynamicMinWidth: 2,\n lockScalingFlip: true,\n noScaleCache: false,\n _wordJoiners: /[ \\t\\r]/,\n splitByGrapheme: false,\n};\n\nexport type GraphemeData = {\n wordsData: {\n word: string[];\n width: number;\n }[][];\n largestWordWidth: number;\n};\n\nexport type StyleMap = Record<string, { line: number; offset: number }>;\n\n// @TODO this is not complete\ninterface UniqueTextboxProps {\n minWidth: number;\n splitByGrapheme: boolean;\n dynamicMinWidth: number;\n _wordJoiners: RegExp;\n}\n\nexport interface SerializedTextboxProps\n extends\n SerializedITextProps,\n Pick<UniqueTextboxProps, 'minWidth' | 'splitByGrapheme'> {}\n\nexport interface TextboxProps extends ITextProps, UniqueTextboxProps {}\n\n/**\n * Textbox class, based on IText, allows the user to resize the text rectangle\n * and wraps lines automatically. Textboxes have their Y scaling locked, the\n * user can only change width. Height is adjusted automatically based on the\n * wrapping of lines.\n */\nexport class Textbox<\n Props extends TOptions<TextboxProps> = Partial<TextboxProps>,\n SProps extends SerializedTextboxProps = SerializedTextboxProps,\n EventSpec extends ITextEvents = ITextEvents,\n>\n extends IText<Props, SProps, EventSpec>\n implements UniqueTextboxProps\n{\n /**\n * Minimum width of textbox, in pixels.\n * @type Number\n */\n declare minWidth: number;\n\n /**\n * Minimum calculated width of a textbox, in pixels.\n * fixed to 2 so that an empty textbox cannot go to 0\n * and is still selectable without text.\n * @type Number\n */\n declare dynamicMinWidth: number;\n\n /**\n * Use this boolean property in order to split strings that have no white space concept.\n * this is a cheap way to help with chinese/japanese\n * @type Boolean\n * @since 2.6.0\n */\n declare splitByGrapheme: boolean;\n\n declare _wordJoiners: RegExp;\n\n declare _styleMap: StyleMap;\n\n declare isWrapping: boolean;\n\n static type = 'Textbox';\n\n static textLayoutProperties = [...IText.textLayoutProperties, 'width'];\n\n static ownDefaults = textboxDefaultValues;\n\n static getDefaults(): Record<string, any> {\n return {\n ...super.getDefaults(),\n ...Textbox.ownDefaults,\n };\n }\n\n /**\n * Constructor\n * @param {String} text Text string\n * @param {Object} [options] Options object\n */\n constructor(text: string, options?: Props) {\n super(text, { ...Textbox.ownDefaults, ...options } as Props);\n }\n\n /**\n * Creates the default control object.\n * If you prefer to have on instance of controls shared among all objects\n * make this function return an empty object and add controls to the ownDefaults object\n */\n static createControls(): { controls: Record<string, Control> } {\n return { controls: createTextboxDefaultControls() };\n }\n\n /**\n * Unlike superclass's version of this function, Textbox does not update\n * its width.\n * @private\n * @override\n */\n initDimensions() {\n if (!this.initialized) {\n return;\n }\n this.isEditing && this.initDelayedCursor();\n this._clearCache();\n // clear dynamicMinWidth as it will be different after we re-wrap line\n this.dynamicMinWidth = 0;\n // wrap lines\n this._styleMap = this._generateStyleMap(this._splitText());\n // if after wrapping, the width is smaller than dynamicMinWidth, change the width and re-wrap\n if (this.dynamicMinWidth > this.width) {\n this._set('width', this.dynamicMinWidth);\n }\n if (this.textAlign.includes(JUSTIFY)) {\n // once text is measured we need to make space fatter to make justified text.\n this.enlargeSpaces();\n }\n // clear cache and re-calculate height\n this.height = this.calcTextHeight();\n }\n\n /**\n * Generate an object that translates the style object so that it is\n * broken up by visual lines (new lines and automatic wrapping).\n * The original text styles object is broken up by actual lines (new lines only),\n * which is only sufficient for Text / IText\n * @private\n */\n _generateStyleMap(textInfo: TextLinesInfo): StyleMap {\n let realLineCount = 0,\n realLineCharCount = 0,\n charCount = 0;\n const map: StyleMap = {};\n\n for (let i = 0; i < textInfo.graphemeLines.length; i++) {\n if (textInfo.graphemeText[charCount] === '\\n' && i > 0) {\n realLineCharCount = 0;\n charCount++;\n realLineCount++;\n } else if (\n !this.splitByGrapheme &&\n this._reSpaceAndTab.test(textInfo.graphemeText[charCount]) &&\n i > 0\n ) {\n // this case deals with space's that are removed from end of lines when wrapping\n realLineCharCount++;\n charCount++;\n }\n\n map[i] = { line: realLineCount, offset: realLineCharCount };\n\n charCount += textInfo.graphemeLines[i].length;\n realLineCharCount += textInfo.graphemeLines[i].length;\n }\n\n return map;\n }\n\n /**\n * Returns true if object has a style property or has it on a specified line\n * @param {Number} lineIndex\n * @return {Boolean}\n */\n styleHas(property: keyof TextStyleDeclaration, lineIndex: number): boolean {\n if (this._styleMap && !this.isWrapping) {\n const map = this._styleMap[lineIndex];\n if (map) {\n lineIndex = map.line;\n }\n }\n return super.styleHas(property, lineIndex);\n }\n\n /**\n * Returns true if object has no styling or no styling in a line\n * @param {Number} lineIndex , lineIndex is on wrapped lines.\n * @return {Boolean}\n */\n isEmptyStyles(lineIndex: number): boolean {\n if (!this.styles) {\n return true;\n }\n let offset = 0,\n nextLineIndex = lineIndex + 1,\n nextOffset: number,\n shouldLimit = false;\n const map = this._styleMap[lineIndex],\n mapNextLine = this._styleMap[lineIndex + 1];\n if (map) {\n lineIndex = map.line;\n offset = map.offset;\n }\n if (mapNextLine) {\n nextLineIndex = mapNextLine.line;\n shouldLimit = nextLineIndex === lineIndex;\n nextOffset = mapNextLine.offset;\n }\n const obj =\n typeof lineIndex === 'undefined'\n ? this.styles\n : { line: this.styles[lineIndex] };\n for (const p1 in obj) {\n for (const p2 in obj[p1]) {\n const p2Number = parseInt(p2, 10);\n if (p2Number >= offset && (!shouldLimit || p2Number < nextOffset!)) {\n for (const p3 in obj[p1][p2]) {\n return false;\n }\n }\n }\n }\n return true;\n }\n\n /**\n * @protected\n * @param {Number} lineIndex\n * @param {Number} charIndex\n * @return {TextStyleDeclaration} a style object reference to the existing one or a new empty object when undefined\n */\n _getStyleDeclaration(\n lineIndex: number,\n charIndex: number,\n ): TextStyleDeclaration {\n if (this._styleMap && !this.isWrapping) {\n const map = this._styleMap[lineIndex];\n if (!map) {\n return {};\n }\n lineIndex = map.line;\n charIndex = map.offset + charIndex;\n }\n return super._getStyleDeclaration(lineIndex, charIndex);\n }\n\n /**\n * @param {Number} lineIndex\n * @param {Number} charIndex\n * @param {Object} style\n * @private\n */\n protected _setStyleDeclaration(\n lineIndex: number,\n charIndex: number,\n style: object,\n ) {\n const map = this._styleMap[lineIndex];\n super._setStyleDeclaration(map.line, map.offset + charIndex, style);\n }\n\n /**\n * @param {Number} lineIndex\n * @param {Number} charIndex\n * @private\n */\n protected _deleteStyleDeclaration(lineIndex: number, charIndex: number) {\n const map = this._styleMap[lineIndex];\n super._deleteStyleDeclaration(map.line, map.offset + charIndex);\n }\n\n /**\n * probably broken need a fix\n * Returns the real style line that correspond to the wrapped lineIndex line\n * Used just to verify if the line does exist or not.\n * @param {Number} lineIndex\n * @returns {Boolean} if the line exists or not\n * @private\n */\n protected _getLineStyle(lineIndex: number): boolean {\n const map = this._styleMap[lineIndex];\n return !!this.styles[map.line];\n }\n\n /**\n * Set the line style to an empty object so that is initialized\n * @param {Number} lineIndex\n * @param {Object} style\n * @private\n */\n protected _setLineStyle(lineIndex: number) {\n const map = this._styleMap[lineIndex];\n super._setLineStyle(map.line);\n }\n\n /**\n * Wraps text using the 'width' property of Textbox. First this function\n * splits text on newlines, so we preserve newlines entered by the user.\n * Then it wraps each line using the width of the Textbox by calling\n * _wrapLine().\n * @param {Array} lines The string array of text that is split into lines\n * @param {Number} desiredWidth width you want to wrap to\n * @returns {Array} Array of lines\n */\n _wrapText(lines: string[], desiredWidth: number): string[][] {\n this.isWrapping = true;\n // extract all thewords and the widths to optimally wrap lines.\n const data = this.getGraphemeDataForRender(lines);\n const wrapped: string[][] = [];\n for (let i = 0; i < data.wordsData.length; i++) {\n wrapped.push(...this._wrapLine(i, desiredWidth, data));\n }\n this.isWrapping = false;\n return wrapped;\n }\n\n /**\n * For each line of text terminated by an hard line stop,\n * measure each word width and extract the largest word from all.\n * The returned words here are the one that at the end will be rendered.\n * @param {string[]} lines the lines we need to measure\n *\n */\n getGraphemeDataForRender(lines: string[]): GraphemeData {\n const splitByGrapheme = this.splitByGrapheme,\n infix = splitByGrapheme ? '' : ' ';\n\n let largestWordWidth = 0;\n\n const data = lines.map((line, lineIndex) => {\n let offset = 0;\n const wordsOrGraphemes = splitByGrapheme\n ? this.graphemeSplit(line)\n : this.wordSplit(line);\n\n if (wordsOrGraphemes.length === 0) {\n return [{ word: [], width: 0 }];\n }\n\n return wordsOrGraphemes.map((word: string) => {\n // if using splitByGrapheme words are already in graphemes.\n const graphemeArray = splitByGrapheme\n ? [word]\n : this.graphemeSplit(word);\n const width = this._measureWord(graphemeArray, lineIndex, offset);\n largestWordWidth = Math.max(width, largestWordWidth);\n offset += graphemeArray.length + infix.length;\n return { word: graphemeArray, width };\n });\n });\n\n return {\n wordsData: data,\n largestWordWidth,\n };\n }\n\n /**\n * Helper function to measure a string of text, given its lineIndex and charIndex offset\n * It gets called when charBounds are not available yet.\n * Override if necessary\n * Use with {@link Textbox#wordSplit}\n *\n * @param {CanvasRenderingContext2D} ctx\n * @param {String} text\n * @param {number} lineIndex\n * @param {number} charOffset\n * @returns {number}\n */\n _measureWord(word: string[], lineIndex: number, charOffset = 0): number {\n let width = 0,\n prevGrapheme;\n const skipLeft = true;\n for (let i = 0, len = word.length; i < len; i++) {\n const box = this._getGraphemeBox(\n word[i],\n lineIndex,\n i + charOffset,\n prevGrapheme,\n skipLeft,\n );\n width += box.kernedWidth;\n prevGrapheme = word[i];\n }\n return width;\n }\n\n /**\n * Override this method to customize word splitting\n * Use with {@link Textbox#_measureWord}\n * @param {string} value\n * @returns {string[]} array of words\n */\n wordSplit(value: string): string[] {\n return value.split(this._wordJoiners);\n }\n\n /**\n * Wraps a line of text using the width of the Textbox as desiredWidth\n * and leveraging the known width o words from GraphemeData\n * @private\n * @param {Number} lineIndex\n * @param {Number} desiredWidth width you want to wrap the line to\n * @param {GraphemeData} graphemeData an object containing all the lines' words width.\n * @param {Number} reservedSpace space to remove from wrapping for custom functionalities\n * @returns {Array} Array of line(s) into which the given text is wrapped\n * to.\n */\n _wrapLine(\n lineIndex: number,\n desiredWidth: number,\n { largestWordWidth, wordsData }: GraphemeData,\n reservedSpace = 0,\n ): string[][] {\n const additionalSpace = this._getWidthOfCharSpacing(),\n splitByGrapheme = this.splitByGrapheme,\n graphemeLines = [],\n infix = splitByGrapheme ? '' : ' ';\n\n let lineWidth = 0,\n line: string[] = [],\n // spaces in different languages?\n offset = 0,\n infixWidth = 0,\n lineJustStarted = true;\n\n desiredWidth -= reservedSpace;\n\n const maxWidth = Math.max(\n desiredWidth,\n largestWordWidth,\n this.dynamicMinWidth,\n );\n // layout words\n const data = wordsData[lineIndex];\n offset = 0;\n let i;\n for (i = 0; i < data.length; i++) {\n const { word, width: wordWidth } = data[i];\n offset += word.length;\n\n lineWidth += infixWidth + wordWidth - additionalSpace;\n if (lineWidth > maxWidth && !lineJustStarted) {\n graphemeLines.push(line);\n line = [];\n lineWidth = wordWidth;\n lineJustStarted = true;\n } else {\n lineWidth += additionalSpace;\n }\n\n if (!lineJustStarted && !splitByGrapheme) {\n line.push(infix);\n }\n line = line.concat(word);\n\n infixWidth = splitByGrapheme\n ? 0\n : this._measureWord([infix], lineIndex, offset);\n offset++;\n lineJustStarted = false;\n }\n\n i && graphemeLines.push(line);\n\n // TODO: this code is probably not necessary anymore.\n // it can be moved out of this function since largestWordWidth is now\n // known in advance\n if (largestWordWidth + reservedSpace > this.dynamicMinWidth) {\n this.dynamicMinWidth = largestWordWidth - additionalSpace + reservedSpace;\n }\n return graphemeLines;\n }\n\n /**\n * Detect if the text line is ended with an hard break\n * text and itext do not have wrapping, return false\n * @param {Number} lineIndex text to split\n * @return {Boolean}\n */\n isEndOfWrapping(lineIndex: number): boolean {\n if (!this._styleMap[lineIndex + 1]) {\n // is last line, return true;\n return true;\n }\n if (this._styleMap[lineIndex + 1].line !== this._styleMap[lineIndex].line) {\n // this is last line before a line break, return true;\n return true;\n }\n return false;\n }\n\n /**\n * Detect if a line has a linebreak and so we need to account for it when moving\n * and counting style.\n * This is important only for splitByGrapheme at the end of wrapping.\n * If we are not wrapping the offset is always 1\n * @return Number\n */\n missingNewlineOffset(lineIndex: number, skipWrapping?: boolean): 0 | 1 {\n if (this.splitByGrapheme && !skipWrapping) {\n return this.isEndOfWrapping(lineIndex) ? 1 : 0;\n }\n return 1;\n }\n\n /**\n * Gets lines of text to render in the Textbox. This function calculates\n * text wrapping on the fly every time it is called.\n * @param {String} text text to split\n * @returns {Array} Array of lines in the Textbox.\n * @override\n */\n _splitTextIntoLines(text: string) {\n const newText = super._splitTextIntoLines(text),\n graphemeLines = this._wrapText(newText.lines, this.width),\n lines = new Array(graphemeLines.length);\n for (let i = 0; i < graphemeLines.length; i++) {\n lines[i] = graphemeLines[i].join('');\n }\n newText.lines = lines;\n newText.graphemeLines = graphemeLines;\n return newText;\n }\n\n getMinWidth() {\n return Math.max(this.minWidth, this.dynamicMinWidth);\n }\n\n _removeExtraneousStyles() {\n const linesToKeep = new Map();\n for (const prop in this._styleMap) {\n const propNumber = parseInt(prop, 10);\n if (this._textLines[propNumber]) {\n const lineIndex = this._styleMap[prop].line;\n linesToKeep.set(`${lineIndex}`, true);\n }\n }\n for (const prop in this.styles) {\n if (!linesToKeep.has(prop)) {\n delete this.styles[prop];\n }\n }\n }\n\n /**\n * Returns object representation of an instance\n * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output\n * @return {Object} object representation of an instance\n */\n toObject<\n T extends Omit<Props & TClassProperties<this>, keyof SProps>,\n K extends keyof T = never,\n >(propertiesToInclude: K[] = []): Pick<T, K> & SProps {\n return super.toObject<T, K>([\n 'minWidth',\n 'splitByGrapheme',\n ...propertiesToInclude,\n ] as K[]);\n }\n}\n\nclassRegistry.setClass(Textbox);\n"],"mappings":"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"}