From 34c86a3c7ca5e3b4dc9086bd456835a377fc671d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B7=B1=E6=B8=8A=E5=8F=A3=E9=A6=99=E7=B3=96?= <5183454+yuanjianming666@user.noreply.gitee.com> Date: Tue, 5 Nov 2024 15:26:53 +0800 Subject: [PATCH 1/5] fix: init --- .eslintrc-auto-import.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.eslintrc-auto-import.json b/.eslintrc-auto-import.json index 61875e3b..a9f62599 100644 --- a/.eslintrc-auto-import.json +++ b/.eslintrc-auto-import.json @@ -3,11 +3,14 @@ "Component": true, "ComponentPublicInstance": true, "ComputedRef": true, + "DirectiveBinding": true, "EffectScope": true, "ExtractDefaultPropTypes": true, "ExtractPropTypes": true, "ExtractPublicPropTypes": true, "InjectionKey": true, + "MaybeRef": true, + "MaybeRefOrGetter": true, "PropType": true, "Ref": true, "VNode": true, @@ -41,6 +44,7 @@ "onServerPrefetch": true, "onUnmounted": true, "onUpdated": true, + "onWatcherCleanup": true, "provide": true, "reactive": true, "readonly": true, @@ -58,7 +62,10 @@ "useAttrs": true, "useCssModule": true, "useCssVars": true, + "useId": true, + "useModel": true, "useSlots": true, + "useTemplateRef": true, "watch": true, "watchEffect": true, "watchPostEffect": true, From 0f9a8e4865fb88d5de6bc7261db9232d17789192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B7=B1=E6=B8=8A=E5=8F=A3=E9=A6=99=E7=B3=96?= <5183454+yuanjianming666@user.noreply.gitee.com> Date: Tue, 12 Nov 2024 10:11:53 +0800 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20=E5=8F=98=E5=BD=A2=E6=96=87?= =?UTF-8?q?=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/index.ts | 4 +- packages/core/objects/ArcText.ts | 1166 +++++++++++++++++++++++ packages/core/objects/CustomTextbox.js | 2 +- packages/core/objects/utils/index.ts | 63 ++ src/api/material.ts | 4 +- src/components/attributeArcText.vue | 289 ++++++ src/components/attributeColor.vue | 1 + src/components/attributeFont.vue | 2 +- src/components/attributePostion.vue | 1 + src/components/attributeTextContent.vue | 2 +- src/components/fontStyle.vue | 1 + src/views/home/index.vue | 4 +- vite.config.ts | 1 + 13 files changed, 1533 insertions(+), 7 deletions(-) create mode 100644 packages/core/objects/ArcText.ts create mode 100644 packages/core/objects/utils/index.ts create mode 100644 src/components/attributeArcText.vue diff --git a/packages/core/index.ts b/packages/core/index.ts index c0ef129c..a21aa866 100644 --- a/packages/core/index.ts +++ b/packages/core/index.ts @@ -43,9 +43,11 @@ import EventType from './eventType'; import Utils from './utils/utils'; import CustomRect from './objects/CustomRect'; import CustomTextbox from './objects/CustomTextbox'; +import ArcText from './objects/ArcText'; + // import { extend } from 'dayjs'; -export { EventType, Utils, CustomRect, CustomTextbox }; +export { EventType, Utils, CustomRect, CustomTextbox, ArcText }; export default Editor; export * from './interface/Editor'; diff --git a/packages/core/objects/ArcText.ts b/packages/core/objects/ArcText.ts new file mode 100644 index 00000000..745097b2 --- /dev/null +++ b/packages/core/objects/ArcText.ts @@ -0,0 +1,1166 @@ +import { fabric } from 'fabric'; +import { sectorBoundingBox } from './utils/index' +const _dimensionAffectingProps = ['curvature', 'fontSize', 'fontWeight', 'fontFamily', 'fontStyle', 'lineHeight', 'text', 'charSpacing', 'textAlign', 'styles', 'color', 'canvas'] +const multipleSpacesRegex = / +/g // 多个空格正则匹配 +const NUM_FRACTION_DIGITS = 2 +const BaseRadius = 10000 +const { Color, util, Point } = fabric + + +export default fabric.util.createClass(fabric.IText, { + type: 'arc-text', + useRenderBoundingBoxes: true, + useBothRenderingMethod: true, + initialize: function (text: string, options: any) { + this.callSuper('initialize', text, options); + }, + _render: function (ctx: CanvasRenderingContext2D) { + ctx.save() + ctx.translate(-this._contentOffsetX, -this._contentOffsetY) + if (!this.__lineHeights) { + this.initDimensions(); + } + this._setTextStyles(ctx); + this._renderTextLinesBackground(ctx); + this._renderTextDecoration(ctx, 'underline'); + this._renderText(ctx); + this._renderTextDecoration(ctx, 'overline'); + this._renderTextDecoration(ctx, 'linethrough'); + ctx.restore() + }, + // 设置圆角 + setRadius: function (value: number) { + this.setCurvature(BaseRadius / value) + }, + // 设置弧度 + setCurvature: function (value: number) { + this.set('curvature', value) + // 手动触发对象修改事件 + this.canvas?.fire('object:modified') + }, + renderCharCallback: function (method: any, ctx: CanvasRenderingContext2D, lineIndex: number, charIndex: number, endCharIndex: number, left: number, top: number, fullDecl: any) { + for (let index = charIndex; index <= endCharIndex; index++) { + const tr = this._charTransformations[lineIndex][index] + ctx.textAlign = "center" + if (tr.char) { + let angle = this.curvature > 0 ? -tr.charAngle : -tr.charAngle - Math.PI + if (tr.contour && fullDecl.contourStroke) { + ctx.save(); + ctx.lineWidth = fullDecl.contourStrokeWidth + ctx.strokeStyle = fullDecl.contourStroke + ctx.beginPath() + ctx.moveTo(tr.contour.tl.x, tr.contour.tl.y) + ctx.lineTo(tr.contour.tr.x, tr.contour.tr.y) + ctx.lineTo(tr.contour.br.x, tr.contour.br.y) + ctx.lineTo(tr.contour.bl.x, tr.contour.bl.y) + ctx.closePath() + ctx.stroke() + ctx.restore() + } + this.runCharRendering(method, ctx, tr.char, tr.cl.x, tr.cl.y, angle, fullDecl, "center"); + } + } + }, + runCharRendering: function (method: any, ctx: CanvasRenderingContext2D, _char: string, left: number, top: number, angle: number, fullDecl: any, alignment?: string) { + if (ctx) { + ctx.save(); + ctx.translate(left, top) + ctx.rotate(angle) + } + this.defaultTextRender(method, ctx, _char, fullDecl); + if (ctx) { + ctx.restore() + } + }, + + getSelectionStartFromPointer: function (e: any) { + const mouseOffset = this.getLocalPointer(e) + let relX = mouseOffset.x + (-this.width / 2 + this._contentOffsetX) * this.scaleX, + relY = mouseOffset.y + (-this.height / 2 - this._curvingCenter.y + this._contentOffsetY) * this.scaleY, + angle = Math.atan2(-relX, -relY), + radius = Math.sqrt(relX * relX + relY * relY) / this.scaleY, + selectedLine = 0; + + if (this.curvature > 0) { + while (radius < this._linesRads[selectedLine]) { + selectedLine++; + } + } else { + if (angle < 0) angle += Math.PI * 2 + while (radius > this._linesRads[selectedLine]) { + selectedLine++; + } + } + if (selectedLine >= this._textLines.length) { + selectedLine = this._textLines.length - 1 + } + + let charIndex = 0; + for (let i = 0; i < selectedLine; i++) { + charIndex += this._textLines[i].length + this.missingNewlineOffset(i); + } + + let specials = this._specialArray && this._specialArray[selectedLine] + let specialsLen = 0; + let diff = Infinity, diff2, j + for (j = 0; j < (this._charTransformations[selectedLine] || []).length; j++) { + if (specials && specials[j] && specials[j] === specials[j - 1] || this._charTransformations[selectedLine][j].isDiacritic) { + specialsLen++ + continue; + } + diff2 = Math.abs(this._charTransformations[selectedLine][j].leftAngle - angle) % (Math.PI * 2) + if (diff < diff2) { + let result = charIndex + j - 1 - specialsLen + specialsLen = 0; + return result; + } + diff = diff2 + specialsLen = 0; + } + return charIndex + j - 1; + }, + _getLineLeftOffset: function (lineIndex: number, width?: number): number { + if (!width) return 0; + let lineWidth = this.getLineWidth(lineIndex); + if (this.textAlign === 'center') return (width - lineWidth) / 2; + if (this.textAlign === 'right') return width - lineWidth; + if (this.textAlign === 'justify-center' && this.isEndOfWrapping(lineIndex)) return (width - lineWidth) / 2; + if (this.textAlign === 'justify-right' && this.isEndOfWrapping(lineIndex)) return width - lineWidth; + return 0; + }, + + _renderTextDecoration: function (ctx: CanvasRenderingContext2D, type: any) { + if (!this.get(type) && !this.styleHas(type)) { + return; + } + let currentFill, _size, size, dy, _dy, lastFill, line, lastDecoration, charStart, currentDecoration; + ctx.save() + for (let i = 0, len = this._textLines.length; i < len; i++) { + if (!this.type && !this.styleHas(type, i)) { + continue; + } + charStart = 0 + lastDecoration = this.getValueOfPropertyAt(i, 0, type); + lastFill = this.getValueOfPropertyAt(i, 0, 'fill'); + size = this.getHeightOfChar(i, 0); + dy = this.getValueOfPropertyAt(i, 0, 'deltaY'); + let j; + for (j = 0; j < this._textLines[i].length; j++) { + currentDecoration = this.getValueOfPropertyAt(i, j, type); + currentFill = this.getValueOfPropertyAt(i, j, 'fill'); + _size = this.getHeightOfChar(i, j); + _dy = this.getValueOfPropertyAt(i, j, 'deltaY'); + + if (currentDecoration !== lastDecoration || currentFill !== lastFill || _size !== size || _dy !== dy) { + + if (lastDecoration && lastFill) { + let offset = this.offsets[type] * size + dy + this._drawTextLinesDecorationSector(ctx, lastFill, offset, i, charStart, j) + } + + lastDecoration = currentDecoration; + lastFill = currentFill; + size = _size; + dy = _dy; + charStart = j; + } + } + if (currentDecoration && currentFill) { + const offset = this.offsets[type] * size + dy + this._drawTextLinesDecorationSector(ctx, currentFill, offset, i, charStart, j) + } + } + ctx.restore() + this._removeShadow(ctx); + }, + + enArcLargeSpaces: function (width: number) { + let diffSpace, currentLineWidth, numberOfSpaces, accumulatedSpace, line, charBound, spaces; + for (let i = 0, len = this._textLines.length; i < len; i++) { + if (this.textAlign !== 'justify' && (i === len - 1 || this.isEndOfWrapping(i))) { + continue; + } + accumulatedSpace = 0; + line = this._textLines[i]; + currentLineWidth = this.getLineWidth(i); + if (currentLineWidth < width && (spaces = this.textLines[i].match(this._reSpacesAndTabs))) { + numberOfSpaces = spaces.length; + diffSpace = (width - currentLineWidth) / numberOfSpaces; + for (let j = 0, jlen = line.length; j <= jlen; j++) { + charBound = this.__charBounds[i][j]; + if (this._reSpaceAndTab.test(line[j])) { + charBound.width += diffSpace; + charBound.kernedWidth += diffSpace; + charBound.left += accumulatedSpace; + accumulatedSpace += diffSpace; + } else { + charBound.left += accumulatedSpace; + } + } + } + } + }, + + _getBaseLine: function (styleFontSize = 1) { + return (this.lineHeight * this.fontSize) - 0.9 * styleFontSize + }, + + + initDimensions: function () { + if (this.__skipDimension) { + return; + } + this.isEditing && this.initDelayedCursor(); + this.clearContextTop(); + this._splitText(); + this._clearCache(); + for (let li = 0, len = this._textLines.length; li < len; li++) { + this.getLineWidth(li); + this.getHeightOfLine(li); + } + const textHeight = this.calcTextHeight(); + const textWidth = this.calcTextWidth(); + const curvature = this.curvature !== undefined ? this.curvature : 1 + this.radius = BaseRadius / curvature + const cx = 0, cy = curvature > 0 ? textHeight / 2 + this.radius : -textHeight / 2 + this.radius + this._curvingCenter = new Point(cx, cy) + let globalOffset = 0 + if (curvature > 0) { + globalOffset = textHeight + } + this._linesRads = [] + if (this.textAlign.indexOf('justify') !== -1) { + this.enArcLargeSpaces(textWidth); + } + // 自定义_charTransformations 字符位置信息 + const cts: any[] = this._charTransformations = [] + + + let yMin = Infinity, yMax = -Infinity, xMin = Infinity, xMax = -Infinity + for (let i = 0; i < this.__charBounds.length; i++) { + cts[i] = [] + const row = this.__charBounds[i] + let currentLeft = -textWidth / 2 + this._getLineLeftOffset(i, textWidth) + if (this.__lineInfo) { + currentLeft += this.__lineInfo[i].renderedLeft + } + const heightOfLine = this.getHeightOfLine(i) + const charOffset = (heightOfLine - heightOfLine / this.lineHeight) + heightOfLine * this._fontSizeFraction / this.lineHeight + if (curvature > 0) { + globalOffset -= heightOfLine + } else { + globalOffset += heightOfLine + } + const rowOffset = Math.abs(this.radius) + globalOffset + this._linesRads.push(rowOffset) + for (let j = 0; j < row.length; j++) { + const bounds = row[j] + const decl = this.getCompleteStyleDeclaration(i, j); + const deltaY = decl && decl.deltaY || 0 + let bottomRadius, topRadius, charRadius, lineRadius, leftAngle, charAngle, rightAngle, renderLeftAngle, renderRightAngle; + if (curvature > 0) { + bottomRadius = deltaY + rowOffset + topRadius = deltaY + rowOffset + heightOfLine + charRadius = deltaY + rowOffset + charOffset + lineRadius = deltaY + rowOffset + heightOfLine - (heightOfLine / this.lineHeight) + + + const midRadius = (bottomRadius * 3 + topRadius * 2) / 5 + leftAngle = -(currentLeft + bounds.left) / midRadius + rightAngle = -(currentLeft + bounds.left + bounds.width) / midRadius + charAngle = -(currentLeft + bounds.left + bounds.width / 2) / midRadius + } else { + bottomRadius = deltaY + rowOffset + topRadius = deltaY + rowOffset - heightOfLine + charRadius = deltaY + rowOffset - charOffset + lineRadius = deltaY + rowOffset - heightOfLine + (heightOfLine / this.lineHeight) + + let midRadius = (bottomRadius * 2 + topRadius * 3) / 5 + leftAngle = Math.PI + (currentLeft + bounds.left) / midRadius + rightAngle = Math.PI + (currentLeft + bounds.left + bounds.width) / midRadius + charAngle = Math.PI + (currentLeft + bounds.left + bounds.width / 2) / midRadius + } + const rsin = Math.sin(rightAngle), + rcos = Math.cos(rightAngle), + lsin = Math.sin(leftAngle), + lcos = Math.cos(leftAngle), + csin = Math.sin(charAngle), + ccos = Math.cos(charAngle) + const ct = { + contour: bounds.contour && { + x: bounds.contour.x * decl.fontSize, + w: bounds.contour.w * decl.fontSize, + h: bounds.contour.h * decl.fontSize, + y: this._getBaseLine(decl.fontSize) + bounds.contour.y * decl.fontSize + }, + char: this._textLines[i][j], + charAngle, + leftAngle, + rightAngle, + charRadius, + bottomRadius, + topRadius, + lineRadius, + renderLeftAngle, + renderRightAngle, + bl: { x: cx - bottomRadius * lsin, y: cy - bottomRadius * lcos }, + br: { x: cx - bottomRadius * rsin, y: cy - bottomRadius * rcos }, + tl: { x: cx - topRadius * lsin, y: cy - topRadius * lcos }, + tr: { x: cx - topRadius * rsin, y: cy - topRadius * rcos }, + nl: { x: cx - lineRadius * lsin, y: cy - lineRadius * lcos }, + nr: { x: cx - lineRadius * rsin, y: cy - lineRadius * rcos }, + cl: { x: cx - charRadius * csin, y: cy - charRadius * ccos }, + lc: { x: cx - lineRadius * csin, y: cy - lineRadius * ccos } + } + + if (ct.char?.trim() && bounds.contour) { + let cos = (util as any).cos(-charAngle), sin = (util as any).sin(-charAngle); + let rotateMatrix = [cos, sin, -sin, cos, 0, 0] + let matrix = util.multiplyTransformMatrices([1, 0, 0, 1, ct.lc.x, ct.lc.y], rotateMatrix) + let y = ct.contour.y + if (curvature > 0) { + const x = ct.contour.x - this.__charBounds[i][j].width / 2 + ct.contour.br = (util as any).transformPoint({ x: x + ct.contour.w, y: -y }, matrix); + ct.contour.bl = (util as any).transformPoint({ x: x, y: -y }, matrix); + ct.contour.tl = (util as any).transformPoint({ x: x, y: -y - ct.contour.h }, matrix); + ct.contour.tr = (util as any).transformPoint({ x: x + ct.contour.w, y: -y - ct.contour.h }, matrix); + } else { + const x = - ct.contour.x + this.__charBounds[i][j].width / 2 + ct.contour.br = (util as any).transformPoint({ x: x - ct.contour.w, y: y }, matrix); + ct.contour.bl = (util as any).transformPoint({ x: x, y: y }, matrix); + ct.contour.tl = (util as any).transformPoint({ x: x, y: y + ct.contour.h }, matrix); + ct.contour.tr = (util as any).transformPoint({ x: x - ct.contour.w, y: y + ct.contour.h }, matrix); + } + xMin = Math.min(xMin, ct.contour.br.x, ct.contour.bl.x, ct.contour.tl.x, ct.contour.tr.x) + xMax = Math.max(xMax, ct.contour.br.x, ct.contour.bl.x, ct.contour.tl.x, ct.contour.tr.x) + yMin = Math.min(yMin, ct.contour.br.y, ct.contour.bl.y, ct.contour.tl.y, ct.contour.tr.y) + yMax = Math.max(yMax, ct.contour.br.y, ct.contour.bl.y, ct.contour.tl.y, ct.contour.tr.y) + } + cts[i][j] = ct + } + } + for (let i = 0; i < cts.length; i++) { + const ctsl = cts[i], cta = ctsl[0], ctb = ctsl[ctsl.length - 1] + let bbox, bbox2 + if (curvature > 0) { + bbox = sectorBoundingBox(cta.tl, ctb.tr, this._curvingCenter, this._linesRads[i] + this.__lineHeights[i]) + bbox2 = sectorBoundingBox(cta.nl, ctb.nr, this._curvingCenter, this._linesRads[i]) + } + else { + bbox = sectorBoundingBox(ctb.tr, cta.tl, this._curvingCenter, this._linesRads[i] - this.__lineHeights[i]) + bbox2 = sectorBoundingBox(ctb.nr, cta.nl, this._curvingCenter, this._linesRads[i]) + } + xMin = Math.min(xMin, bbox.x, bbox2.x) + xMax = Math.max(xMax, bbox.x + bbox.width, bbox2.x + bbox2.width) + yMin = Math.min(yMin, bbox.y, bbox2.y) + yMax = Math.max(yMax, bbox.y + bbox.height, bbox2.y + bbox2.height) + } + + this._enableDiacritics() + + const leftOverflow = -xMin - textWidth / 2 + const rightOverflow = xMax - textWidth / 2 + const topOverflow = -yMin - textHeight / 2 + const bottomOverflow = yMax - textHeight / 2 + + this._contentOffsetY = bottomOverflow / 2 - topOverflow / 2 + this._contentOffsetX = rightOverflow / 2 - leftOverflow / 2 + + if (this._contentOffsetX > 0) { + this.width = Math.max(textWidth + leftOverflow + rightOverflow, this.MIN_TEXT_WIDTH) + } + this.height = Math.max(textHeight + topOverflow + bottomOverflow, textHeight) + + }, + + _hasStyleChanged: function (prevStyle: string[], thisStyle: string[]) { + if (Object.keys(prevStyle).length !== Object.keys(thisStyle).length) { + return true + } + for (let prop in prevStyle) { + if (prevStyle[prop] !== thisStyle[prop]) { + return true; + } + } + return false; + }, + + interateTextChunks: function (lineIndex: number, foo: Function, iteratorFn?: Function) { + let actualStyle, nextStyle, firstChar = 0; + let specs = this._specialArray + let line = this._textLines[lineIndex] + let isJustify = this.textAlign.indexOf('justify') !== -1; + let shortCut = !isJustify && this.charSpacing === 0 && (!specs || !specs[lineIndex]) && this.isEmptyStyles(lineIndex); + + if (shortCut) { + foo(0, line.length, null) + return; + } + + let timeToRender; + + for (let i = 0, len = line.length - 1; i <= len; i++) { + timeToRender = i === len || this.charSpacing; + iteratorFn && iteratorFn(i) + if (isJustify && !timeToRender) { + if (this._reSpaceAndTab.test(line[i])) { + timeToRender = true; + } + } + if (!timeToRender) { + actualStyle = actualStyle || this.getCompleteStyleDeclaration(lineIndex, i); + nextStyle = this.getCompleteStyleDeclaration(lineIndex, i + 1); + timeToRender = (specs && specs[lineIndex] && specs[lineIndex][i] !== specs[lineIndex][i + 1]) || this._hasStyleChanged(actualStyle, nextStyle) + } + + if (timeToRender) { + foo(firstChar, i, actualStyle) + firstChar = i + 1; + actualStyle = nextStyle; + } + } + }, + + _enableDiacritics: function () { + const cts = this._charTransformations + //Fix Diacritics symbols on a curve + const diacritics = ['́', '̀', '̂', '̌', '̋', '̏', '̃', '̇', '̣', '·', '̈', 'ː', '̆', '̑', '͗', '̃', '҃', '̩', '̄', '̱', '⃓', '̷', '̵', '̊', '̓', '̒', '̔', '̉', '̛', '̦', '̧', '̡', '̢', '̨', '͝', '͡', '', '͞', '͠'] + for (let i in cts) { + for (let j in cts[i]) { + if (cts[i][j].char && diacritics.includes(cts[i][j].char)) { + // @ts-ignore + for (let k = j; k--;) { + if (cts[i][k].char) { + cts[i][k].char += cts[i][j].char + cts[i][j].isDiacritic = true; + delete cts[i][j].char + break; + } + } + } + } + } + }, + + _drawTextLinesDecorationSector: function (ctx: CanvasRenderingContext2D, currentColor: string, offset: number, line: number, charStart: number, charEnd: number) { + ctx.fillStyle = currentColor; + ctx.lineWidth = this.fontSize / 15 + let startChar = this._charTransformations[line][charStart] + let endChar = this._charTransformations[line][charEnd - 1] + ctx.beginPath() + if (this.curvature < 0) { + ctx.arc(this._curvingCenter.x, this._curvingCenter.y, startChar.charRadius + 1 + offset, -startChar.leftAngle - Math.PI / 2, -endChar.rightAngle - Math.PI / 2, true) + } else { + ctx.arc(this._curvingCenter.x, this._curvingCenter.y, startChar.charRadius - 1 - offset, -startChar.leftAngle - Math.PI / 2, -endChar.rightAngle - Math.PI / 2, false) + } + ctx.stroke() + }, + + _contextSelectBackgroundSector: function (ctx: CanvasRenderingContext2D, line: number, charStart: number, charEnd: number, fullLineRadius?: boolean) { + ctx.beginPath() + let startChar = this._charTransformations[line][charStart]; + let endChar = this._charTransformations[line][charEnd]; + ctx.moveTo(startChar.tl.x, startChar.tl.y) + let radius = fullLineRadius ? startChar.bottomRadius : startChar.lineRadius + const startStatus = this.curvature < 0 ? true : false + ctx.arc(this._curvingCenter.x, this._curvingCenter.y, radius, -startChar.leftAngle - Math.PI / 2, -endChar.rightAngle - Math.PI / 2, startStatus) + ctx.lineTo(endChar.tr.x, endChar.tr.y) + const endStatus = this.curvature < 0 ? false : true + ctx.arc(this._curvingCenter.x, this._curvingCenter.y, startChar.topRadius, -endChar.rightAngle - Math.PI / 2, -startChar.leftAngle - Math.PI / 2, endStatus) + ctx.closePath() + }, + // 设置文本背景颜色 + _renderTextLinesBackground: function (ctx: CanvasRenderingContext2D) { + if (!this.textBackgroundColor && !this.styleHas('textBackgroundColor')) return; + let originalFill = ctx.fillStyle, lastColor, charStart, currentColor; + for (let i = 0, len = this._textLines.length; i < len; i++) { + if (!this.textBackgroundColor && !this.styleHas('textBackgroundColor', i)) { + continue; + } + charStart = 0 + lastColor = this.getValueOfPropertyAt(i, 0, 'textBackgroundColor'); + let j = 0 + for (j = 0; j < this._textLines[i].length; j++) { + currentColor = this.getValueOfPropertyAt(i, j, 'textBackgroundColor'); + if (currentColor !== lastColor) { + if (lastColor) { + ctx.fillStyle = lastColor; + this._contextSelectBackgroundSector(ctx, i, charStart, j - 1) + ctx.fill() + } + charStart = j; + lastColor = currentColor; + } + } + if (currentColor) { + ctx.fillStyle = currentColor; + this._contextSelectBackgroundSector(ctx, i, charStart, j - 1) + ctx.fill() + } + } + ctx.fillStyle = originalFill; + this._removeShadow(ctx); + }, + + _set: function (key: any, value: any) { + this.callSuper('_set', key, value) + let needsDims = false; + if (typeof key === 'object') { + const keys = key + for (let _key in keys) { + needsDims = needsDims || _dimensionAffectingProps.indexOf(_key) !== -1; + } + } else { + needsDims = _dimensionAffectingProps.indexOf(key) !== -1; + } + + if (needsDims) { + this.initDimensions(); + this.setCoords(); + } + return this + }, + renderSelection: function (boundaries: any, ctx: CanvasRenderingContext2D) { + const selectionStart = this.inCompositionMode ? this.hiddenTextarea.selectionStart : this.selectionStart, + selectionEnd = this.inCompositionMode ? this.hiddenTextarea.selectionEnd : this.selectionEnd, + start = this.get2DCursorLocation(selectionStart), + end = this.get2DCursorLocation(selectionEnd), + startLine = start.lineIndex, + endLine = end.lineIndex, + startChar = start.charIndex < 0 ? 0 : start.charIndex, + endChar = end.charIndex < 0 ? 0 : end.charIndex; + + ctx.fillStyle = this.selectionColor; + ctx && ctx.translate(-this._contentOffsetX, -this._contentOffsetY) + + for (let i = startLine; i <= endLine; i++) { + let charStart = (i === startLine) ? startChar : 0, charEnd = (i >= startLine && i < endLine) ? this._textLines[i].length : endChar + this._contextSelectBackgroundSector(ctx, i, charStart, charEnd - 1, i !== endLine) + ctx.fill(); + } + }, + + _measureLine(lineIndex: number) { + let width = 0, charIndex, grapheme, line = this._textLines[lineIndex], prevGrapheme, + graphemeInfo, numOfSpaces = 0, lineBounds = new Array(line.length); + let renderedLeft = 0; + let renderedWidth = 0; + let renderedBottom = -Infinity; + let renderedTop = -Infinity + this.__charBounds[lineIndex] = lineBounds; + for (charIndex = 0; charIndex < line.length; charIndex++) { + grapheme = line[charIndex] + const style = this.getCompleteStyleDeclaration(lineIndex, charIndex) + graphemeInfo = this._getGraphemeBox(grapheme, lineIndex, charIndex, prevGrapheme); + lineBounds[charIndex] = graphemeInfo; + width += graphemeInfo.kernedWidth; + prevGrapheme = grapheme; + let contourX, contourW, contourH, contourY + if (this.useRenderBoundingBoxes && graphemeInfo.contour) { + contourX = graphemeInfo.contour.x * style.fontSize + contourW = graphemeInfo.contour.w * style.fontSize + contourH = graphemeInfo.contour.h * style.fontSize + contourY = this._getBaseLine(style.fontSize) + graphemeInfo.contour.y * style.fontSize + renderedLeft = Math.max(renderedLeft, -(graphemeInfo.left + contourX)) + renderedWidth = Math.max(renderedWidth, contourW + contourX + graphemeInfo.left) + renderedBottom = Math.max(renderedBottom, -contourY) + renderedTop = Math.max(renderedTop, contourY + contourH) + } + + } + lineBounds[charIndex] = { + left: graphemeInfo ? graphemeInfo.left + graphemeInfo.width : 0, + width: 0, + kernedWidth: 0, + height: this.fontSize + }; + const renderedRight = Math.max(0, renderedWidth - width) + return { + width, + numOfSpaces, + renderedLeft, + renderedBottom, + renderedRight, + renderedTop + }; + }, + + getLineWidth(lineIndex: number) { + if (this.__lineWidths[lineIndex]) { + return this.__lineWidths[lineIndex]; + } + let width, line = this._textLines[lineIndex], lineInfo; + if (line.length === 0) { + width = 0; + } + else { + lineInfo = this.measureLine(lineIndex); + if (this.useRenderBoundingBoxes) { + width = lineInfo.width + lineInfo.renderedRight + lineInfo.renderedLeft + this.__lineInfo[lineIndex] = lineInfo + } + else { + width = lineInfo.width; + } + } + this.__lineWidths[lineIndex] = width; + return width; + }, + // 覆盖 renderCursor + renderCursor(boundaries: any, ctx: CanvasRenderingContext2D) { + const cursorLocation = this.get2DCursorLocation(), + lineIndex = cursorLocation.lineIndex, + topOffset = boundaries.topOffset, + charIndex = cursorLocation.charIndex > 0 ? cursorLocation.charIndex - 1 : 0, + multiplier = this.scaleX * this.canvas.getZoom(), + charHeight = this.getValueOfPropertyAt(lineIndex, charIndex, 'fontSize'), + dy = this.getValueOfPropertyAt(lineIndex, charIndex, 'deltaY'), + cursorWidth = this.cursorWidth / multiplier; + if (this.inCompositionMode) { + this.renderSelection(boundaries, ctx); + } + + ctx.lineWidth = cursorWidth + ctx.strokeStyle = this.getValueOfPropertyAt(lineIndex, charIndex, 'fill'); + ctx.globalAlpha = this.__isMousedown ? 1 : this._currentCursorOpacity; + + if (this._charTransformations[lineIndex]?.[charIndex]) { + const tr = this._charTransformations[lineIndex][charIndex]; + ctx.translate(-this._contentOffsetX, -this._contentOffsetY) + ctx.beginPath() + ctx.moveTo(tr.nr.x, tr.nr.y) + ctx.lineTo(tr.tr.x, tr.tr.y) + ctx.stroke(); + } else { + ctx.fillRect( + boundaries.left + boundaries.leftOffset - cursorWidth / 2, + topOffset + boundaries.top + dy, + cursorWidth, + charHeight); + } + + }, + // 渐变颜色渲染 + _applyPatternGradientTransformText: function (filler: any) { + var pCanvas = fabric.util.createCanvasElement(), pCtx: any, + // TODO: verify compatibility with strokeUniform + width = this.width + this.strokeWidth, height = this.height + this.strokeWidth; + pCanvas.width = width; + pCanvas.height = height; + pCtx = pCanvas.getContext('2d'); + pCtx.beginPath(); pCtx.moveTo(0, 0); pCtx.lineTo(width, 0); pCtx.lineTo(width, height); + pCtx.lineTo(0, height); pCtx.closePath(); + pCtx.translate(-this._contentOffsetX, -this._contentOffsetY) + pCtx.fillStyle = filler.toLive(pCtx); + this._applyPatternGradientTransform(pCtx, filler); + pCtx.fill(); + return pCtx.createPattern(pCanvas, 'no-repeat'); + }, + + _renderTextCommon(ctx: CanvasRenderingContext2D, method: any) { + + ctx && ctx.save(); + let lineHeights = 0 + const left = this._getLeftOffset(), top = this._getTopOffset() + // _applyPatternGradientTransformText ? + const offsets = this._applyPatternGradientTransform(ctx, (method === 'fillText' ? this.fill : this.stroke)); + + + for (let i = 0, len = (this._textLines || []).length; i < len; i++) { + let lineOffsetX = 0, lineOffsetY = 0 + if (this.__lineInfo && this.__lineInfo[i]) { + + lineOffsetX = this.__lineInfo[i].renderedLeft + lineOffsetY = this.__renderOffsetTop + } + const heightOfLine = this.getHeightOfLine(i), + maxHeight = heightOfLine / this.lineHeight, + leftOffset = this._getLineLeftOffset(i); + this._renderTextLine( + method, + ctx, + this._textLines[i], + left + leftOffset - offsets.offsetX + lineOffsetX, + top + lineHeights + maxHeight - offsets.offsetY - lineOffsetY, + i + ); + lineHeights += heightOfLine; + } + ctx && ctx.restore(); + }, + + _renderText(ctx: CanvasRenderingContext2D) { + if (this.fill) { + ctx.fillStyle = this.fill; + } + + if (this.useBothRenderingMethod) { + return this._renderTextCommon(ctx, 'both'); + } + if (this.paintFirst === 'stroke') { + this._renderTextStroke(ctx); + this._renderTextFill(ctx); + } + else { + this._renderTextFill(ctx); + this._renderTextStroke(ctx); + } + }, + // 分割文本 + _splitText() { + let text = this.text + if (this.textTransform) { + if (this.textTransform === "uppercase") { + text = text.toUpperCase() + } + if (this.textTransform === "lowercase") { + text = text.toLowerCase() + } + if (this.textTransform === "capitalize") { + text = (util.string as any).capitalize(text) + } + } + let newLines = this._splitTextIntoLines(text); + this.textLines = newLines.lines; + this._textLines = newLines.graphemeLines; + this._unwrappedTextLines = newLines._unwrappedLines; + this._text = newLines.graphemeText; + + if (this.useRenderBoundingBoxes) { + this.__lineInfo = [] + } + return newLines; + }, + // // 计算文本高度 + calcTextHeight() { + let lineHeight, height = 0; + for (let i = 0, len = this._textLines.length; i < len; i++) { + lineHeight = this.getHeightOfLine(i); + height += (i === len - 1 ? lineHeight / this.lineHeight : lineHeight); + } + return height; + }, + // 渲染字符样式 + _renderChars(method: 'fillText' | 'strokeText', ctx: CanvasRenderingContext2D, line: Array, left: number, top: number, lineIndex: number) { + let lineHeight = this.getHeightOfLine(lineIndex), + charBox, + boxWidth = 0; + ctx && ctx.save(); + top -= lineHeight * this._fontSizeFraction / this.lineHeight; + this.interateTextChunks(lineIndex, + (charIndex: any, endCharIndex: any) => { + this._renderStr(method, ctx, lineIndex, charIndex, endCharIndex, left, top); + left += boxWidth; + boxWidth = 0; + }, + (i: any) => { + charBox = this.__charBounds[lineIndex][i]; + if (boxWidth === 0) { + left += charBox.kernedWidth - charBox.width; + boxWidth += charBox.width; + } else { + boxWidth += charBox.kernedWidth; + } + }) + ctx && ctx.restore(); + }, + + _renderStr(method: any, ctx: CanvasRenderingContext2D, lineIndex: number, charIndex: number, endCharIndex: number, left: number, top: number) { + const decl = this._getStyleDeclaration(lineIndex, charIndex), + fullDecl = this.getCompleteStyleDeclaration(lineIndex, charIndex), + shouldFill = method === 'fillText' && fullDecl.fill, + shouldStroke = method === 'strokeText' && fullDecl.stroke && fullDecl.strokeWidth; + let fillOffsets, strokeOffsets; + if (method !== "calc" && method !== "both" && !shouldStroke && !shouldFill) { + return; + } + ctx && decl && ctx.save() + // ctx && this._applyCharStyles(method, ctx, lineIndex, charIndex, fullDecl); + + shouldFill && (fillOffsets = this._setFillStyles(ctx, fullDecl)); + shouldStroke && (strokeOffsets = this._setStrokeStyles(ctx, fullDecl)); + + if (decl && decl.textBackgroundColor) { + this._removeShadow(ctx); + } + if (decl && decl.deltaY) { + top += decl.deltaY; + } + + fullDecl.special = this._specialArray && this._specialArray[lineIndex] && this._specialArray[lineIndex][charIndex]; + + if (this.renderCharCallback) { + this.renderCharCallback(method, ctx, lineIndex, charIndex, endCharIndex, left, top, fullDecl) + } + else { + const text = this._textLines[lineIndex].slice(charIndex, endCharIndex + 1).join("") + this.runCharRendering(method, ctx, text, left, top, 0, fullDecl); + } + ctx && decl && ctx.restore(); + }, + + _renderBackground(ctx: CanvasRenderingContext2D) { + if (!this.backgroundColor && !this.backgroundStroke) { + return; + } + let dim = this._getNonTransformedDimensions(); + if (this.backgroundColor) { + ctx.fillStyle = this.backgroundColor; + ctx.fillRect( + -dim.x / 2, + -dim.y / 2, + dim.x, + dim.y + ) + } + + if (this.backgroundStroke) { + this._setStrokeStyles(ctx, this.backgroundStroke); + ctx.strokeRect( + -dim.x / 2, + -dim.y / 2, + dim.x, + dim.y + ) + } + this._removeShadow(ctx); + }, + + defaultTextRender(method: string, ctx: CanvasRenderingContext2D, _char: string, decl: any) { + if (method === "both") { + if (decl.fill && this.paintFirst === 'fill') { + ctx.fillText(_char, 0, 0); + } + if (decl.stroke && decl.strokeWidth) { + if (this.shadow && !this.shadow.affectStroke) { + this._removeShadow(ctx); + } + ctx.save(); + this._setLineDash(ctx, this.strokeDashArray); + ctx.beginPath(); + ctx.strokeText(_char, 0, 0); + ctx.closePath(); + ctx.restore(); + } + if (decl.fill && this.paintFirst === 'stroke') { + ctx.fillText(_char, 0, 0); + } + } + else { + method === 'fillText' && decl.fill && ctx.fillText(_char, 0, 0); + method === 'strokeText' && decl.stroke && decl.strokeWidth && ctx.strokeText(_char, 0, 0); + } + return true; + }, + + getHeightOfLine(lineIndex: number) { + if (!this.__lineHeights) { + this.initDimensions(); + } + if (this.__lineHeights[lineIndex]) { + return this.__lineHeights[lineIndex]; + } + + const line = this._textLines[lineIndex] + let maxHeight = this.getHeightOfChar(lineIndex, 0); + for (let i = 1, len = line.length; i < len; i++) { + maxHeight = Math.max(this.getHeightOfChar(lineIndex, i), maxHeight); + } + return this.__lineHeights[lineIndex] = maxHeight * this.lineHeight * this._fontSizeMult; + }, + + cleanStyle(property: any) { + if (!this.styles || !property) return false + const obj = this.styles; + let letterCount, stylePropertyValue, graphemeCount = 0, stylesCount = 0, allStyleObjectPropertiesMatch = true + for (let p1 in obj) { + letterCount = 0; + for (let p2 in obj[p1]) { + const styleObject = obj[p1][p2], stylePropertyHasBeenSet = styleObject.hasOwnProperty(property); + stylesCount++; + if (stylePropertyHasBeenSet) { + if (!stylePropertyValue) { + stylePropertyValue = styleObject[property]; + } + else if (styleObject[property] !== stylePropertyValue) { + allStyleObjectPropertiesMatch = false; + } + if (styleObject[property] === this[property]) { + delete styleObject[property]; + } + } + else { + allStyleObjectPropertiesMatch = false; + } + if (Object.keys(styleObject).length !== 0) { + letterCount++; + } + else { + delete obj[p1][p2]; + } + } + if (letterCount === 0) { + delete obj[p1]; + } + } + for (let i = 0; i < this._textLines.length; i++) { + graphemeCount += this._textLines[i].length; + } + if (allStyleObjectPropertiesMatch && stylesCount === graphemeCount) { + if (stylePropertyValue !== undefined) { + this.set(property, stylePropertyValue) + } + this.removeStyle(property); + } + }, + + getLocalPointer(e: any, pointer?: any) { + pointer = pointer || this.canvas.getPointer(e); + let pClicked = new Point(pointer.x, pointer.y) + const objectLeftTop = this._getLeftTopCoords(); + if (this.angle) { + pClicked = util.rotatePoint(pClicked, objectLeftTop, util.degreesToRadians(-this.angle)); + } + return { + x: pClicked.x - objectLeftTop.x, + y: pClicked.y - objectLeftTop.y + }; + }, + + toSVG(reviver: any): any { + const imageData = this.toDataURL() + return `` + }, + _toSVG() { + const offsets = this._getSVGLeftTopOffsets() + const textAndBg = this._getSVGTextAndBg(offsets.textTop, offsets.textLeft); + return this._wrapSVGTextAndBg(textAndBg); + }, + + _getSVGLeftTopOffsets() { + return { + textLeft: -this.width / 2, + textTop: -this.height / 2, + lineTop: this.getHeightOfLine(0) + }; + }, + + _wrapSVGTextAndBg(textAndBg: any) { + const noShadow = true, textDecoration = this.getSvgTextDecoration(this); + return [ + textAndBg.textBgRects.join(''), + '\t\t', + textAndBg.textSpans.join(''), + '\n' + ]; + }, + + _setSVGBg(textBgRects: any) { + if (this.backgroundColor) { + textBgRects.push( + '\t\t\n'); + } + }, + + _getSVGTextAndBg(textTopOffset: any, textLeftOffset: any) { + let textSpans: any[] = [], + textBgRects: any[] = [], + height = textTopOffset, lineOffset; + this._setSVGBg(textBgRects); + + // text and text-background + for (let i = 0, len = this._textLines.length; i < len; i++) { + lineOffset = this._getLineLeftOffset(i); + if (this.textBackgroundColor || this.styleHas('textBackgroundColor', i)) { + this._setSVGTextLineBg(textBgRects, i, textLeftOffset + lineOffset, height); + } + this._setSVGTextLineText(textSpans, i, textLeftOffset + lineOffset, height); + height += this.getHeightOfLine(i); + } + + return { + textSpans: textSpans, + textBgRects: textBgRects + }; + }, + + _createTextCharSpan(_char: string, styleDecl: any, left: number, top: number, angle: number) { + const shouldUseWhitespace = _char !== _char.trim() || _char.match(multipleSpacesRegex) ? true : false, + styleProps = this.getSvgSpanStyles(styleDecl, shouldUseWhitespace), + fillStyles = styleProps ? 'style="' + styleProps + '"' : '', + dy = styleDecl.deltaY; + let dySpan = ''; + if (dy) { + dySpan = ' dy="' + util.toFixed(dy, NUM_FRACTION_DIGITS) + '" '; + } + return [ + '', + util.string.escapeXml(_char), + '' + ].join(''); + }, + + _hasStyleChangedForSvg(prevStyle: any, thisStyle: any) { + return this._hasStyleChanged(prevStyle, thisStyle) || + prevStyle.overline !== thisStyle.overline || + prevStyle.underline !== thisStyle.underline || + prevStyle.linethrough !== thisStyle.linethrough; + }, + + _setSVGTextLineText(textSpans: string[], lineIndex: number, textLeftOffset: number, textTopOffset: number) { + // set proper line offset + let lineHeight = this.getHeightOfLine(lineIndex), + isJustify = this.textAlign.indexOf('justify') !== -1, + actualStyle, + nextStyle, + charsToRender = '', + charBox, style, + boxWidth = 0, + line = this._textLines[lineIndex], + timeToRender; + + textTopOffset += lineHeight * (1 - this._fontSizeFraction) / this.lineHeight; + for (let i = 0, len = line.length - 1; i <= len; i++) { + timeToRender = i === len || this.charSpacing; + charsToRender += line[i]; + charBox = this._charTransformations[lineIndex][i]; + const angle = this.curvature > 0 ? -charBox.charAngle : -charBox.charAngle - Math.PI + if (boxWidth === 0) { + textLeftOffset += charBox.kernedWidth - charBox.width; + boxWidth += charBox.width; + } + else { + boxWidth += charBox.kernedWidth; + } + if (isJustify && !timeToRender) { + if (this._reSpaceAndTab.test(line[i])) { + timeToRender = true; + } + } + if (!timeToRender) { + actualStyle = actualStyle || this.getCompleteStyleDeclaration(lineIndex, i); + nextStyle = this.getCompleteStyleDeclaration(lineIndex, i + 1); + timeToRender = this._hasStyleChangedForSvg(actualStyle, nextStyle); + } + if (timeToRender) { + style = this._getStyleDeclaration(lineIndex, i) || {}; + // textSpans.push(this._createTextCharSpan(charsToRender, style, textLeftOffset, textTopOffset)); + const degress = angle * 180 / Math.PI + textSpans.push(this._createTextCharSpan(charsToRender, style, charBox.cl.x, charBox.cl.y, degress)); + charsToRender = ''; + actualStyle = nextStyle; + textLeftOffset += boxWidth; + boxWidth = 0; + } + } + }, + + _getCursorBoundariesOffsets(index: number) { + let topOffset = 0, + leftOffset = 0; + const { charIndex, lineIndex } = this.get2DCursorLocation(index); + + for (let i = 0; i < lineIndex; i++) { + topOffset += this.getHeightOfLine(i); + } + const lineLeftOffset = this._getLineLeftOffset(lineIndex); + const bound = this.__charBounds.length ? this.__charBounds[lineIndex][charIndex] : undefined; + bound && (leftOffset = bound.left); + if ( + this.charSpacing !== 0 && + charIndex === this._textLines[lineIndex].length + ) { + leftOffset -= this._getWidthOfCharSpacing(); + } + const boundaries = { + top: topOffset, + left: lineLeftOffset + (leftOffset > 0 ? leftOffset : 0), + }; + if (this.direction === 'rtl') { + if ( + this.textAlign === 'right' || + this.textAlign === 'justify' || + this.textAlign === 'justify_right' + ) { + boundaries.left *= -1; + } else if (this.textAlign === 'left' || this.textAlign === 'justify_left') { + boundaries.left = lineLeftOffset - (leftOffset > 0 ? leftOffset : 0); + } else if ( + this.textAlign === 'center' || + this.textAlign === 'justify_center' + ) { + boundaries.left = lineLeftOffset - (leftOffset > 0 ? leftOffset : 0); + } + } + return boundaries; + }, + + _setSVGTextLineBg(textBgRects: string[], i: number, leftOffset: number, textTopOffset: number) { + let line = this._textLines[i], + heightOfLine = this.getHeightOfLine(i) / this.lineHeight, + boxWidth = 0, + boxStart = 0, + charBox, currentColor, + lastColor = this.getValueOfPropertyAt(i, 0, 'textBackgroundColor'); + for (let j = 0, jlen = line.length; j < jlen; j++) { + charBox = this.__charBounds[i][j]; + currentColor = this.getValueOfPropertyAt(i, j, 'textBackgroundColor'); + if (currentColor !== lastColor) { + lastColor && this._pushTextBgRect(textBgRects, lastColor, leftOffset + boxStart, + textTopOffset, boxWidth, heightOfLine); + boxStart = charBox.left; + boxWidth = charBox.width; + lastColor = currentColor; + } + else { + boxWidth += charBox.kernedWidth; + } + } + currentColor && this._pushTextBgRect(textBgRects, currentColor, leftOffset + boxStart, + textTopOffset, boxWidth, heightOfLine); + }, + + _getFillAttributes(value: any) { + let fillColor: any = (value && typeof value === 'string') ? new Color(value) : ''; + if (!fillColor || !fillColor.getSource() || fillColor.getAlpha() === 1) { + return 'fill="' + value + '"'; + } + return 'opacity="' + fillColor.getAlpha() + '" fill="' + fillColor.setAlpha(1).toRgb() + '"'; + }, + + _getSVGLineTopOffset(lineIndex: number) { + let lineTopOffset = 0, lastHeight = 0, j = 0; + for (let j = 0; j < lineIndex; j++) { + lineTopOffset += this.getHeightOfLine(j); + } + lastHeight = this.getHeightOfLine(j); + return { + lineTop: lineTopOffset, + offset: (this._fontSizeMult - this._fontSizeFraction) * lastHeight / (this.lineHeight * this._fontSizeMult) + }; + }, + + getSvgStyles(skipShadow: boolean) { + const svgStyle = this.callSuper('getSvgStyles', skipShadow); + return svgStyle + ' white-space: pre;'; + } +}); diff --git a/packages/core/objects/CustomTextbox.js b/packages/core/objects/CustomTextbox.js index 4b765f37..1064f532 100644 --- a/packages/core/objects/CustomTextbox.js +++ b/packages/core/objects/CustomTextbox.js @@ -115,4 +115,4 @@ fabric.Textbox.fromObject = function (options, callback) { return callback(new fabric.Textbox(text, options)); }; -export default fabric.Rect; +export default fabric.Textbox; diff --git a/packages/core/objects/utils/index.ts b/packages/core/objects/utils/index.ts new file mode 100644 index 00000000..335b88af --- /dev/null +++ b/packages/core/objects/utils/index.ts @@ -0,0 +1,63 @@ +import { fabric } from 'fabric'; + +// 获取父级的缩放系数 +export const getParentScaleX = (el: any): number => { + return el.scaleX * (el.group ? getParentScaleX(el.group) : 1) +} + +export const notALeftClick = (e: MouseEvent) => { + return e.button && e.button !== 1; +} + +export function AngularDiffSigned(theta1: number, theta2: number) { + let dif = theta2 - theta1; + while (dif >= 2 * Math.PI) + dif -= 2 * Math.PI; + while (dif <= 0) + dif += 2 * Math.PI; + return dif; +} + +export function AnglesInClockwiseSequence(x: number, y: number, z: number) { + return AngularDiffSigned(x, y) + AngularDiffSigned(y, z) < 2 * Math.PI; +} + +export function sectorBoundingBox(E: fabric.Point, F: fabric.Point, C: fabric.Point, radius: number) { + // Put the endpoints into the bounding box: + let x1 = E.x; + let y1 = E.y; + let x2 = x1, y2 = y1; + if (F.x < x1) + x1 = F.x; + if (F.x > x2) + x2 = F.x; + if (F.y < y1) + y1 = F.y; + if (F.y > y2) + y2 = F.y; + + // Now consider the top/bottom/left/right extremities of the circle: + let thetaE = Math.atan2(E.y - C.y, E.x - C.x); + let thetaF = Math.atan2(F.y - C.y, F.x - C.x); + if (AnglesInClockwiseSequence(thetaE, 0/*right*/, thetaF)) { + let x = (C.x + radius); + if (x > x2) + x2 = x; + } + if (AnglesInClockwiseSequence(thetaE, Math.PI / 2/*bottom*/, thetaF)) { + let y = (C.y + radius); + if (y > y2) + y2 = y; + } + if (AnglesInClockwiseSequence(thetaE, Math.PI/*left*/, thetaF)) { + let x = (C.x - radius); + if (x < x1) + x1 = x; + } + if (AnglesInClockwiseSequence(thetaE, Math.PI * 3 / 2/*top*/, thetaF)) { + let y = (C.y - radius); + if (y < y1) + y1 = y; + } + return { x: x1, y: y1, width: x2 - x1, height: y2 - y1 } +} diff --git a/src/api/material.ts b/src/api/material.ts index ad331ff0..59282ae5 100644 --- a/src/api/material.ts +++ b/src/api/material.ts @@ -35,8 +35,8 @@ export const getFontStyleTypes = () => instance.get('/api/font-style-types'); export const getFontStyles = (data: any) => instance.get('/api/font-styles?' + data); // 获取根据分类获取字体样式列表 -export const getFontStyleListByType = (data: any) => - instance.get('/api/font-styles?' + qs.stringify(data)); +export const getFontStyleListByType = (data: any) => instance.get('/api/font-styles?' + qs.stringify(data)); + // 获取字体分类分类 export const getTmplTypes = () => instance.get('/api/templ-types'); diff --git a/src/components/attributeArcText.vue b/src/components/attributeArcText.vue new file mode 100644 index 00000000..b4e44851 --- /dev/null +++ b/src/components/attributeArcText.vue @@ -0,0 +1,289 @@ + + + + + diff --git a/src/components/attributeColor.vue b/src/components/attributeColor.vue index 269fb664..aa078851 100644 --- a/src/components/attributeColor.vue +++ b/src/components/attributeColor.vue @@ -68,6 +68,7 @@ const colorChange = (value) => { activeObject.height, value.angle ); + activeObject.set('fill', currentGradient, value.angle); activeObject.set(angleKey, value.angle); } diff --git a/src/components/attributeFont.vue b/src/components/attributeFont.vue index d421b536..1e2f31b7 100644 --- a/src/components/attributeFont.vue +++ b/src/components/attributeFont.vue @@ -133,7 +133,7 @@ import InputNumber from '@/components/inputNumber'; const update = getCurrentInstance(); // 文字元素 -const textType = ['i-text', 'textbox', 'text']; +const textType = ['i-text', 'textbox', 'text', 'arc-text']; const { canvasEditor, isMatchType, isOne } = useSelect(textType); // 属性值 diff --git a/src/components/attributePostion.vue b/src/components/attributePostion.vue index f7788b47..c0f0c87e 100644 --- a/src/components/attributePostion.vue +++ b/src/components/attributePostion.vue @@ -67,6 +67,7 @@ const baseType = [ 'line', 'arrow', 'thinTailArrow', + 'arc-text', ]; const { isMatchType, canvasEditor, isOne } = useSelect(baseType); diff --git a/src/components/attributeTextContent.vue b/src/components/attributeTextContent.vue index 48e1065b..d45fd0ef 100644 --- a/src/components/attributeTextContent.vue +++ b/src/components/attributeTextContent.vue @@ -3,7 +3,7 @@ import useSelect from '@/hooks/select'; import InputNumber from '@/components/inputNumber'; const update = getCurrentInstance(); -const { canvasEditor, isOne, isMatchType } = useSelect(['i-text']); +const { canvasEditor, isOne, isMatchType } = useSelect(['i-text', 'arc-text']); const baseAttr = reactive({ text: '', strokeWidth: 1, diff --git a/src/components/fontStyle.vue b/src/components/fontStyle.vue index 5276726f..cef87bcb 100644 --- a/src/components/fontStyle.vue +++ b/src/components/fontStyle.vue @@ -80,6 +80,7 @@ const addItem = async ({ info: item }) => { await canvasEditor.downFontByJSON(JSON.stringify(item.json)); const el = JSON.parse(JSON.stringify(item.json)); el.id = uuid(); + const elType = capitalizeFirstLetter(el.type); new fabric[elType].fromObject(el, (fabricEl) => { canvasEditor.addBaseType(fabricEl); diff --git a/src/views/home/index.vue b/src/views/home/index.vue index b53039c2..436db4bf 100644 --- a/src/views/home/index.vue +++ b/src/views/home/index.vue @@ -145,6 +145,8 @@ + + @@ -278,6 +280,7 @@ import Editor, { import Edit from '@/components/edit.vue'; import ClipImage from '@/components/clipImage.vue'; import AttributeTextContent from '@/components/attributeTextContent.vue'; +import AttributeArcText from '@/components/attributeArcText.vue'; // 创建编辑器 const canvasEditor = new Editor() as IEditor; @@ -352,7 +355,6 @@ onMounted(() => { // imageSmoothingEnabled: false, // 解决文字导出后不清晰问题 preserveObjectStacking: true, // 当选择画布中的对象时,让对象不在顶层。 }); - // 初始化编辑器 canvasEditor.init(canvas); canvasEditor diff --git a/vite.config.ts b/vite.config.ts index c20d4e98..03c38585 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -36,6 +36,7 @@ const config = ({ mode }) => { vueSetupExtend(), // 增加下面的配置项,这样在运行时就能检查eslint规范 eslintPlugin({ + fix: true, include: ['src/**/*.js', 'src/**/*.vue', 'src/*.js', 'src/*.vue'], }), vueJsx({ From 9937802489aec20766338b0c7431834020f7a2f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B7=B1=E6=B8=8A=E5=8F=A3=E9=A6=99=E7=B3=96?= <5183454+yuanjianming666@user.noreply.gitee.com> Date: Tue, 12 Nov 2024 10:12:10 +0800 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20=E5=8F=98=E5=BD=A2=E6=96=87?= =?UTF-8?q?=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/objects/ArcText.ts | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/packages/core/objects/ArcText.ts b/packages/core/objects/ArcText.ts index 745097b2..fa179a53 100644 --- a/packages/core/objects/ArcText.ts +++ b/packages/core/objects/ArcText.ts @@ -1,3 +1,4 @@ +/* eslint-disable */ import { fabric } from 'fabric'; import { sectorBoundingBox } from './utils/index' const _dimensionAffectingProps = ['curvature', 'fontSize', 'fontWeight', 'fontFamily', 'fontStyle', 'lineHeight', 'text', 'charSpacing', 'textAlign', 'styles', 'color', 'canvas'] @@ -640,22 +641,7 @@ export default fabric.util.createClass(fabric.IText, { } }, - // 渐变颜色渲染 - _applyPatternGradientTransformText: function (filler: any) { - var pCanvas = fabric.util.createCanvasElement(), pCtx: any, - // TODO: verify compatibility with strokeUniform - width = this.width + this.strokeWidth, height = this.height + this.strokeWidth; - pCanvas.width = width; - pCanvas.height = height; - pCtx = pCanvas.getContext('2d'); - pCtx.beginPath(); pCtx.moveTo(0, 0); pCtx.lineTo(width, 0); pCtx.lineTo(width, height); - pCtx.lineTo(0, height); pCtx.closePath(); - pCtx.translate(-this._contentOffsetX, -this._contentOffsetY) - pCtx.fillStyle = filler.toLive(pCtx); - this._applyPatternGradientTransform(pCtx, filler); - pCtx.fill(); - return pCtx.createPattern(pCanvas, 'no-repeat'); - }, + _renderTextCommon(ctx: CanvasRenderingContext2D, method: any) { From ec709f2902e3572f2545840afdf04402d19b9f49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B7=B1=E6=B8=8A=E5=8F=A3=E9=A6=99=E7=B3=96?= <5183454+yuanjianming666@user.noreply.gitee.com> Date: Tue, 12 Nov 2024 23:51:01 +0800 Subject: [PATCH 4/5] =?UTF-8?q?fix:=20=E6=96=B0=E5=A2=9E=20toObject?= =?UTF-8?q?=E5=92=8CfromObject?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc.js | 20 + packages/core/objects/ArcText.ts | 2488 +++++++++++++++++------------- 2 files changed, 1394 insertions(+), 1114 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index c7532ac6..a0036b50 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -39,6 +39,26 @@ module.exports = { 'vue/no-setup-props-destructure': 'off', 'vuejs-accessibility/anchor-has-content': 'off', '@typescript-eslint/no-non-null-assertion': 'off', + 'prefer-const': 'off', //如果一个变量从未被重新赋值,那么使用const声明会更好。 + '@typescript-eslint/ban-ts-comment': 'off', // 关闭禁止ts注释忽略校验 + '@typescript-eslint/ban-types': [ + //忽略默认的禁止类型 + 'error', + { + types: { + String: false, + Boolean: false, + Number: false, + Symbol: false, + '{}': false, + Object: false, + object: false, + Function: false, + }, + extendDefaults: true, + }, + ], + 'no-prototype-builtins': 'off', }, overrides: [ { diff --git a/packages/core/objects/ArcText.ts b/packages/core/objects/ArcText.ts index fa179a53..b1707c85 100644 --- a/packages/core/objects/ArcText.ts +++ b/packages/core/objects/ArcText.ts @@ -1,1152 +1,1412 @@ -/* eslint-disable */ +/*eslint prefer-const: "off"*/ import { fabric } from 'fabric'; -import { sectorBoundingBox } from './utils/index' -const _dimensionAffectingProps = ['curvature', 'fontSize', 'fontWeight', 'fontFamily', 'fontStyle', 'lineHeight', 'text', 'charSpacing', 'textAlign', 'styles', 'color', 'canvas'] -const multipleSpacesRegex = / +/g // 多个空格正则匹配 -const NUM_FRACTION_DIGITS = 2 -const BaseRadius = 10000 -const { Color, util, Point } = fabric - - -export default fabric.util.createClass(fabric.IText, { - type: 'arc-text', - useRenderBoundingBoxes: true, - useBothRenderingMethod: true, - initialize: function (text: string, options: any) { - this.callSuper('initialize', text, options); - }, - _render: function (ctx: CanvasRenderingContext2D) { - ctx.save() - ctx.translate(-this._contentOffsetX, -this._contentOffsetY) - if (!this.__lineHeights) { - this.initDimensions(); - } - this._setTextStyles(ctx); - this._renderTextLinesBackground(ctx); - this._renderTextDecoration(ctx, 'underline'); - this._renderText(ctx); - this._renderTextDecoration(ctx, 'overline'); - this._renderTextDecoration(ctx, 'linethrough'); - ctx.restore() - }, - // 设置圆角 - setRadius: function (value: number) { - this.setCurvature(BaseRadius / value) - }, - // 设置弧度 - setCurvature: function (value: number) { - this.set('curvature', value) - // 手动触发对象修改事件 - this.canvas?.fire('object:modified') - }, - renderCharCallback: function (method: any, ctx: CanvasRenderingContext2D, lineIndex: number, charIndex: number, endCharIndex: number, left: number, top: number, fullDecl: any) { - for (let index = charIndex; index <= endCharIndex; index++) { - const tr = this._charTransformations[lineIndex][index] - ctx.textAlign = "center" - if (tr.char) { - let angle = this.curvature > 0 ? -tr.charAngle : -tr.charAngle - Math.PI - if (tr.contour && fullDecl.contourStroke) { - ctx.save(); - ctx.lineWidth = fullDecl.contourStrokeWidth - ctx.strokeStyle = fullDecl.contourStroke - ctx.beginPath() - ctx.moveTo(tr.contour.tl.x, tr.contour.tl.y) - ctx.lineTo(tr.contour.tr.x, tr.contour.tr.y) - ctx.lineTo(tr.contour.br.x, tr.contour.br.y) - ctx.lineTo(tr.contour.bl.x, tr.contour.bl.y) - ctx.closePath() - ctx.stroke() - ctx.restore() - } - this.runCharRendering(method, ctx, tr.char, tr.cl.x, tr.cl.y, angle, fullDecl, "center"); - } - } - }, - runCharRendering: function (method: any, ctx: CanvasRenderingContext2D, _char: string, left: number, top: number, angle: number, fullDecl: any, alignment?: string) { - if (ctx) { - ctx.save(); - ctx.translate(left, top) - ctx.rotate(angle) - } - this.defaultTextRender(method, ctx, _char, fullDecl); - if (ctx) { - ctx.restore() - } - }, - - getSelectionStartFromPointer: function (e: any) { - const mouseOffset = this.getLocalPointer(e) - let relX = mouseOffset.x + (-this.width / 2 + this._contentOffsetX) * this.scaleX, - relY = mouseOffset.y + (-this.height / 2 - this._curvingCenter.y + this._contentOffsetY) * this.scaleY, - angle = Math.atan2(-relX, -relY), - radius = Math.sqrt(relX * relX + relY * relY) / this.scaleY, - selectedLine = 0; - - if (this.curvature > 0) { - while (radius < this._linesRads[selectedLine]) { - selectedLine++; - } - } else { - if (angle < 0) angle += Math.PI * 2 - while (radius > this._linesRads[selectedLine]) { - selectedLine++; - } - } - if (selectedLine >= this._textLines.length) { - selectedLine = this._textLines.length - 1 - } +import { sectorBoundingBox } from './utils/index'; +const _dimensionAffectingProps = [ + 'curvature', + 'fontSize', + 'fontWeight', + 'fontFamily', + 'fontStyle', + 'lineHeight', + 'text', + 'charSpacing', + 'textAlign', + 'styles', + 'color', + 'canvas', +]; +const multipleSpacesRegex = / +/g; // 多个空格正则匹配 +const NUM_FRACTION_DIGITS = 2; +const BaseRadius = 10000; +const { Color, util, Point } = fabric; + +const ArcText = fabric.util.createClass(fabric.IText, { + type: 'arc-text', + useRenderBoundingBoxes: true, + useBothRenderingMethod: true, + initialize: function (text: string, options: any) { + this.callSuper('initialize', text, options); + }, + toObject: function (propertiesToInclude: any) { + const obj = this.callSuper('toObject', propertiesToInclude); + obj.radius = this.radius; + obj.curvature = this.curvature; + return obj; + }, + _render: function (ctx: CanvasRenderingContext2D) { + ctx.save(); + ctx.translate(-this._contentOffsetX, -this._contentOffsetY); + if (!this.__lineHeights) { + this.initDimensions(); + } + this._setTextStyles(ctx); + this._renderTextLinesBackground(ctx); + this._renderTextDecoration(ctx, 'underline'); + this._renderText(ctx); + this._renderTextDecoration(ctx, 'overline'); + this._renderTextDecoration(ctx, 'linethrough'); + ctx.restore(); + }, + // 设置圆角 + setRadius: function (value: number) { + this.setCurvature(BaseRadius / value); + }, + // 设置弧度 + setCurvature: function (value: number) { + this.set('curvature', value); + // 手动触发对象修改事件 + this.canvas?.fire('object:modified'); + }, + renderCharCallback: function ( + method: any, + ctx: CanvasRenderingContext2D, + lineIndex: number, + charIndex: number, + endCharIndex: number, + left: number, + top: number, + fullDecl: any + ) { + for (let index = charIndex; index <= endCharIndex; index++) { + const tr = this._charTransformations[lineIndex][index]; + ctx.textAlign = 'center'; + if (tr.char) { + let angle = this.curvature > 0 ? -tr.charAngle : -tr.charAngle - Math.PI; + if (tr.contour && fullDecl.contourStroke) { + ctx.save(); + ctx.lineWidth = fullDecl.contourStrokeWidth; + ctx.strokeStyle = fullDecl.contourStroke; + ctx.beginPath(); + ctx.moveTo(tr.contour.tl.x, tr.contour.tl.y); + ctx.lineTo(tr.contour.tr.x, tr.contour.tr.y); + ctx.lineTo(tr.contour.br.x, tr.contour.br.y); + ctx.lineTo(tr.contour.bl.x, tr.contour.bl.y); + ctx.closePath(); + ctx.stroke(); + ctx.restore(); + } + this.runCharRendering(method, ctx, tr.char, tr.cl.x, tr.cl.y, angle, fullDecl, 'center'); + } + } + }, + runCharRendering: function ( + method: any, + ctx: CanvasRenderingContext2D, + _char: string, + left: number, + top: number, + angle: number, + fullDecl: any, + alignment?: string + ) { + if (ctx) { + ctx.save(); + ctx.translate(left, top); + ctx.rotate(angle); + } + this.defaultTextRender(method, ctx, _char, fullDecl); + if (ctx) { + ctx.restore(); + } + }, + + getSelectionStartFromPointer: function (e: any) { + const mouseOffset = this.getLocalPointer(e); + let relX = mouseOffset.x + (-this.width / 2 + this._contentOffsetX) * this.scaleX, + relY = + mouseOffset.y + + (-this.height / 2 - this._curvingCenter.y + this._contentOffsetY) * this.scaleY, + angle = Math.atan2(-relX, -relY), + radius = Math.sqrt(relX * relX + relY * relY) / this.scaleY, + selectedLine = 0; + + if (this.curvature > 0) { + while (radius < this._linesRads[selectedLine]) { + selectedLine++; + } + } else { + if (angle < 0) angle += Math.PI * 2; + while (radius > this._linesRads[selectedLine]) { + selectedLine++; + } + } + if (selectedLine >= this._textLines.length) { + selectedLine = this._textLines.length - 1; + } - let charIndex = 0; - for (let i = 0; i < selectedLine; i++) { - charIndex += this._textLines[i].length + this.missingNewlineOffset(i); - } + let charIndex = 0; + for (let i = 0; i < selectedLine; i++) { + charIndex += this._textLines[i].length + this.missingNewlineOffset(i); + } - let specials = this._specialArray && this._specialArray[selectedLine] - let specialsLen = 0; - let diff = Infinity, diff2, j - for (j = 0; j < (this._charTransformations[selectedLine] || []).length; j++) { - if (specials && specials[j] && specials[j] === specials[j - 1] || this._charTransformations[selectedLine][j].isDiacritic) { - specialsLen++ - continue; - } - diff2 = Math.abs(this._charTransformations[selectedLine][j].leftAngle - angle) % (Math.PI * 2) - if (diff < diff2) { - let result = charIndex + j - 1 - specialsLen - specialsLen = 0; - return result; - } - diff = diff2 - specialsLen = 0; - } - return charIndex + j - 1; - }, - _getLineLeftOffset: function (lineIndex: number, width?: number): number { - if (!width) return 0; - let lineWidth = this.getLineWidth(lineIndex); - if (this.textAlign === 'center') return (width - lineWidth) / 2; - if (this.textAlign === 'right') return width - lineWidth; - if (this.textAlign === 'justify-center' && this.isEndOfWrapping(lineIndex)) return (width - lineWidth) / 2; - if (this.textAlign === 'justify-right' && this.isEndOfWrapping(lineIndex)) return width - lineWidth; - return 0; - }, - - _renderTextDecoration: function (ctx: CanvasRenderingContext2D, type: any) { - if (!this.get(type) && !this.styleHas(type)) { - return; - } - let currentFill, _size, size, dy, _dy, lastFill, line, lastDecoration, charStart, currentDecoration; - ctx.save() - for (let i = 0, len = this._textLines.length; i < len; i++) { - if (!this.type && !this.styleHas(type, i)) { - continue; - } - charStart = 0 - lastDecoration = this.getValueOfPropertyAt(i, 0, type); - lastFill = this.getValueOfPropertyAt(i, 0, 'fill'); - size = this.getHeightOfChar(i, 0); - dy = this.getValueOfPropertyAt(i, 0, 'deltaY'); - let j; - for (j = 0; j < this._textLines[i].length; j++) { - currentDecoration = this.getValueOfPropertyAt(i, j, type); - currentFill = this.getValueOfPropertyAt(i, j, 'fill'); - _size = this.getHeightOfChar(i, j); - _dy = this.getValueOfPropertyAt(i, j, 'deltaY'); - - if (currentDecoration !== lastDecoration || currentFill !== lastFill || _size !== size || _dy !== dy) { - - if (lastDecoration && lastFill) { - let offset = this.offsets[type] * size + dy - this._drawTextLinesDecorationSector(ctx, lastFill, offset, i, charStart, j) - } - - lastDecoration = currentDecoration; - lastFill = currentFill; - size = _size; - dy = _dy; - charStart = j; - } - } - if (currentDecoration && currentFill) { - const offset = this.offsets[type] * size + dy - this._drawTextLinesDecorationSector(ctx, currentFill, offset, i, charStart, j) - } - } - ctx.restore() - this._removeShadow(ctx); - }, - - enArcLargeSpaces: function (width: number) { - let diffSpace, currentLineWidth, numberOfSpaces, accumulatedSpace, line, charBound, spaces; - for (let i = 0, len = this._textLines.length; i < len; i++) { - if (this.textAlign !== 'justify' && (i === len - 1 || this.isEndOfWrapping(i))) { - continue; - } - accumulatedSpace = 0; - line = this._textLines[i]; - currentLineWidth = this.getLineWidth(i); - if (currentLineWidth < width && (spaces = this.textLines[i].match(this._reSpacesAndTabs))) { - numberOfSpaces = spaces.length; - diffSpace = (width - currentLineWidth) / numberOfSpaces; - for (let j = 0, jlen = line.length; j <= jlen; j++) { - charBound = this.__charBounds[i][j]; - if (this._reSpaceAndTab.test(line[j])) { - charBound.width += diffSpace; - charBound.kernedWidth += diffSpace; - charBound.left += accumulatedSpace; - accumulatedSpace += diffSpace; - } else { - charBound.left += accumulatedSpace; - } - } - } - } - }, + let specials = this._specialArray && this._specialArray[selectedLine]; + let specialsLen = 0; + let diff = Infinity, + diff2, + j; + for (j = 0; j < (this._charTransformations[selectedLine] || []).length; j++) { + if ( + (specials && specials[j] && specials[j] === specials[j - 1]) || + this._charTransformations[selectedLine][j].isDiacritic + ) { + specialsLen++; + continue; + } + diff2 = + Math.abs(this._charTransformations[selectedLine][j].leftAngle - angle) % (Math.PI * 2); + if (diff < diff2) { + let result = charIndex + j - 1 - specialsLen; + specialsLen = 0; + return result; + } + diff = diff2; + specialsLen = 0; + } + return charIndex + j - 1; + }, + _getLineLeftOffset: function (lineIndex: number, width?: number): number { + if (!width) return 0; + let lineWidth = this.getLineWidth(lineIndex); + if (this.textAlign === 'center') return (width - lineWidth) / 2; + if (this.textAlign === 'right') return width - lineWidth; + if (this.textAlign === 'justify-center' && this.isEndOfWrapping(lineIndex)) + return (width - lineWidth) / 2; + if (this.textAlign === 'justify-right' && this.isEndOfWrapping(lineIndex)) + return width - lineWidth; + return 0; + }, + + _renderTextDecoration: function (ctx: CanvasRenderingContext2D, type: any) { + if (!this.get(type) && !this.styleHas(type)) { + return; + } + let currentFill, + _size, + size, + dy, + _dy, + lastFill, + line, + lastDecoration, + charStart, + currentDecoration; + ctx.save(); + for (let i = 0, len = this._textLines.length; i < len; i++) { + if (!this.type && !this.styleHas(type, i)) { + continue; + } + charStart = 0; + lastDecoration = this.getValueOfPropertyAt(i, 0, type); + lastFill = this.getValueOfPropertyAt(i, 0, 'fill'); + size = this.getHeightOfChar(i, 0); + dy = this.getValueOfPropertyAt(i, 0, 'deltaY'); + let j; + for (j = 0; j < this._textLines[i].length; j++) { + currentDecoration = this.getValueOfPropertyAt(i, j, type); + currentFill = this.getValueOfPropertyAt(i, j, 'fill'); + _size = this.getHeightOfChar(i, j); + _dy = this.getValueOfPropertyAt(i, j, 'deltaY'); - _getBaseLine: function (styleFontSize = 1) { - return (this.lineHeight * this.fontSize) - 0.9 * styleFontSize - }, + if ( + currentDecoration !== lastDecoration || + currentFill !== lastFill || + _size !== size || + _dy !== dy + ) { + if (lastDecoration && lastFill) { + let offset = this.offsets[type] * size + dy; + this._drawTextLinesDecorationSector(ctx, lastFill, offset, i, charStart, j); + } + + lastDecoration = currentDecoration; + lastFill = currentFill; + size = _size; + dy = _dy; + charStart = j; + } + } + if (currentDecoration && currentFill) { + const offset = this.offsets[type] * size + dy; + this._drawTextLinesDecorationSector(ctx, currentFill, offset, i, charStart, j); + } + } + ctx.restore(); + this._removeShadow(ctx); + }, + + enArcLargeSpaces: function (width: number) { + let diffSpace, currentLineWidth, numberOfSpaces, accumulatedSpace, line, charBound, spaces; + for (let i = 0, len = this._textLines.length; i < len; i++) { + if (this.textAlign !== 'justify' && (i === len - 1 || this.isEndOfWrapping(i))) { + continue; + } + accumulatedSpace = 0; + line = this._textLines[i]; + currentLineWidth = this.getLineWidth(i); + if (currentLineWidth < width && (spaces = this.textLines[i].match(this._reSpacesAndTabs))) { + numberOfSpaces = spaces.length; + diffSpace = (width - currentLineWidth) / numberOfSpaces; + for (let j = 0, jlen = line.length; j <= jlen; j++) { + charBound = this.__charBounds[i][j]; + if (this._reSpaceAndTab.test(line[j])) { + charBound.width += diffSpace; + charBound.kernedWidth += diffSpace; + charBound.left += accumulatedSpace; + accumulatedSpace += diffSpace; + } else { + charBound.left += accumulatedSpace; + } + } + } + } + }, + _getBaseLine: function (styleFontSize = 1) { + return this.lineHeight * this.fontSize - 0.9 * styleFontSize; + }, - initDimensions: function () { - if (this.__skipDimension) { - return; - } - this.isEditing && this.initDelayedCursor(); - this.clearContextTop(); - this._splitText(); - this._clearCache(); - for (let li = 0, len = this._textLines.length; li < len; li++) { - this.getLineWidth(li); - this.getHeightOfLine(li); - } - const textHeight = this.calcTextHeight(); - const textWidth = this.calcTextWidth(); - const curvature = this.curvature !== undefined ? this.curvature : 1 - this.radius = BaseRadius / curvature - const cx = 0, cy = curvature > 0 ? textHeight / 2 + this.radius : -textHeight / 2 + this.radius - this._curvingCenter = new Point(cx, cy) - let globalOffset = 0 + initDimensions: function () { + if (this.__skipDimension) { + return; + } + this.isEditing && this.initDelayedCursor(); + this.clearContextTop(); + this._splitText(); + this._clearCache(); + for (let li = 0, len = this._textLines.length; li < len; li++) { + this.getLineWidth(li); + this.getHeightOfLine(li); + } + const textHeight = this.calcTextHeight(); + const textWidth = this.calcTextWidth(); + const curvature = this.curvature !== undefined ? this.curvature : 1; + this.radius = BaseRadius / curvature; + const cx = 0, + cy = curvature > 0 ? textHeight / 2 + this.radius : -textHeight / 2 + this.radius; + this._curvingCenter = new Point(cx, cy); + let globalOffset = 0; + if (curvature > 0) { + globalOffset = textHeight; + } + this._linesRads = []; + if (this.textAlign.indexOf('justify') !== -1) { + this.enArcLargeSpaces(textWidth); + } + // 自定义_charTransformations 字符位置信息 + const cts: any[] = (this._charTransformations = []); + + let yMin = Infinity, + yMax = -Infinity, + xMin = Infinity, + xMax = -Infinity; + for (let i = 0; i < this.__charBounds.length; i++) { + cts[i] = []; + const row = this.__charBounds[i]; + let currentLeft = -textWidth / 2 + this._getLineLeftOffset(i, textWidth); + if (this.__lineInfo) { + currentLeft += this.__lineInfo[i].renderedLeft; + } + const heightOfLine = this.getHeightOfLine(i); + const charOffset = + heightOfLine - + heightOfLine / this.lineHeight + + (heightOfLine * this._fontSizeFraction) / this.lineHeight; + if (curvature > 0) { + globalOffset -= heightOfLine; + } else { + globalOffset += heightOfLine; + } + const rowOffset = Math.abs(this.radius) + globalOffset; + this._linesRads.push(rowOffset); + for (let j = 0; j < row.length; j++) { + const bounds = row[j]; + const decl = this.getCompleteStyleDeclaration(i, j); + const deltaY = (decl && decl.deltaY) || 0; + let bottomRadius, + topRadius, + charRadius, + lineRadius, + leftAngle, + charAngle, + rightAngle, + renderLeftAngle, + renderRightAngle; if (curvature > 0) { - globalOffset = textHeight - } - this._linesRads = [] - if (this.textAlign.indexOf('justify') !== -1) { - this.enArcLargeSpaces(textWidth); - } - // 自定义_charTransformations 字符位置信息 - const cts: any[] = this._charTransformations = [] - - - let yMin = Infinity, yMax = -Infinity, xMin = Infinity, xMax = -Infinity - for (let i = 0; i < this.__charBounds.length; i++) { - cts[i] = [] - const row = this.__charBounds[i] - let currentLeft = -textWidth / 2 + this._getLineLeftOffset(i, textWidth) - if (this.__lineInfo) { - currentLeft += this.__lineInfo[i].renderedLeft - } - const heightOfLine = this.getHeightOfLine(i) - const charOffset = (heightOfLine - heightOfLine / this.lineHeight) + heightOfLine * this._fontSizeFraction / this.lineHeight - if (curvature > 0) { - globalOffset -= heightOfLine - } else { - globalOffset += heightOfLine - } - const rowOffset = Math.abs(this.radius) + globalOffset - this._linesRads.push(rowOffset) - for (let j = 0; j < row.length; j++) { - const bounds = row[j] - const decl = this.getCompleteStyleDeclaration(i, j); - const deltaY = decl && decl.deltaY || 0 - let bottomRadius, topRadius, charRadius, lineRadius, leftAngle, charAngle, rightAngle, renderLeftAngle, renderRightAngle; - if (curvature > 0) { - bottomRadius = deltaY + rowOffset - topRadius = deltaY + rowOffset + heightOfLine - charRadius = deltaY + rowOffset + charOffset - lineRadius = deltaY + rowOffset + heightOfLine - (heightOfLine / this.lineHeight) - - - const midRadius = (bottomRadius * 3 + topRadius * 2) / 5 - leftAngle = -(currentLeft + bounds.left) / midRadius - rightAngle = -(currentLeft + bounds.left + bounds.width) / midRadius - charAngle = -(currentLeft + bounds.left + bounds.width / 2) / midRadius - } else { - bottomRadius = deltaY + rowOffset - topRadius = deltaY + rowOffset - heightOfLine - charRadius = deltaY + rowOffset - charOffset - lineRadius = deltaY + rowOffset - heightOfLine + (heightOfLine / this.lineHeight) - - let midRadius = (bottomRadius * 2 + topRadius * 3) / 5 - leftAngle = Math.PI + (currentLeft + bounds.left) / midRadius - rightAngle = Math.PI + (currentLeft + bounds.left + bounds.width) / midRadius - charAngle = Math.PI + (currentLeft + bounds.left + bounds.width / 2) / midRadius - } - const rsin = Math.sin(rightAngle), - rcos = Math.cos(rightAngle), - lsin = Math.sin(leftAngle), - lcos = Math.cos(leftAngle), - csin = Math.sin(charAngle), - ccos = Math.cos(charAngle) - const ct = { - contour: bounds.contour && { - x: bounds.contour.x * decl.fontSize, - w: bounds.contour.w * decl.fontSize, - h: bounds.contour.h * decl.fontSize, - y: this._getBaseLine(decl.fontSize) + bounds.contour.y * decl.fontSize - }, - char: this._textLines[i][j], - charAngle, - leftAngle, - rightAngle, - charRadius, - bottomRadius, - topRadius, - lineRadius, - renderLeftAngle, - renderRightAngle, - bl: { x: cx - bottomRadius * lsin, y: cy - bottomRadius * lcos }, - br: { x: cx - bottomRadius * rsin, y: cy - bottomRadius * rcos }, - tl: { x: cx - topRadius * lsin, y: cy - topRadius * lcos }, - tr: { x: cx - topRadius * rsin, y: cy - topRadius * rcos }, - nl: { x: cx - lineRadius * lsin, y: cy - lineRadius * lcos }, - nr: { x: cx - lineRadius * rsin, y: cy - lineRadius * rcos }, - cl: { x: cx - charRadius * csin, y: cy - charRadius * ccos }, - lc: { x: cx - lineRadius * csin, y: cy - lineRadius * ccos } - } - - if (ct.char?.trim() && bounds.contour) { - let cos = (util as any).cos(-charAngle), sin = (util as any).sin(-charAngle); - let rotateMatrix = [cos, sin, -sin, cos, 0, 0] - let matrix = util.multiplyTransformMatrices([1, 0, 0, 1, ct.lc.x, ct.lc.y], rotateMatrix) - let y = ct.contour.y - if (curvature > 0) { - const x = ct.contour.x - this.__charBounds[i][j].width / 2 - ct.contour.br = (util as any).transformPoint({ x: x + ct.contour.w, y: -y }, matrix); - ct.contour.bl = (util as any).transformPoint({ x: x, y: -y }, matrix); - ct.contour.tl = (util as any).transformPoint({ x: x, y: -y - ct.contour.h }, matrix); - ct.contour.tr = (util as any).transformPoint({ x: x + ct.contour.w, y: -y - ct.contour.h }, matrix); - } else { - const x = - ct.contour.x + this.__charBounds[i][j].width / 2 - ct.contour.br = (util as any).transformPoint({ x: x - ct.contour.w, y: y }, matrix); - ct.contour.bl = (util as any).transformPoint({ x: x, y: y }, matrix); - ct.contour.tl = (util as any).transformPoint({ x: x, y: y + ct.contour.h }, matrix); - ct.contour.tr = (util as any).transformPoint({ x: x - ct.contour.w, y: y + ct.contour.h }, matrix); - } - xMin = Math.min(xMin, ct.contour.br.x, ct.contour.bl.x, ct.contour.tl.x, ct.contour.tr.x) - xMax = Math.max(xMax, ct.contour.br.x, ct.contour.bl.x, ct.contour.tl.x, ct.contour.tr.x) - yMin = Math.min(yMin, ct.contour.br.y, ct.contour.bl.y, ct.contour.tl.y, ct.contour.tr.y) - yMax = Math.max(yMax, ct.contour.br.y, ct.contour.bl.y, ct.contour.tl.y, ct.contour.tr.y) - } - cts[i][j] = ct - } - } - for (let i = 0; i < cts.length; i++) { - const ctsl = cts[i], cta = ctsl[0], ctb = ctsl[ctsl.length - 1] - let bbox, bbox2 - if (curvature > 0) { - bbox = sectorBoundingBox(cta.tl, ctb.tr, this._curvingCenter, this._linesRads[i] + this.__lineHeights[i]) - bbox2 = sectorBoundingBox(cta.nl, ctb.nr, this._curvingCenter, this._linesRads[i]) - } - else { - bbox = sectorBoundingBox(ctb.tr, cta.tl, this._curvingCenter, this._linesRads[i] - this.__lineHeights[i]) - bbox2 = sectorBoundingBox(ctb.nr, cta.nl, this._curvingCenter, this._linesRads[i]) - } - xMin = Math.min(xMin, bbox.x, bbox2.x) - xMax = Math.max(xMax, bbox.x + bbox.width, bbox2.x + bbox2.width) - yMin = Math.min(yMin, bbox.y, bbox2.y) - yMax = Math.max(yMax, bbox.y + bbox.height, bbox2.y + bbox2.height) - } - - this._enableDiacritics() - - const leftOverflow = -xMin - textWidth / 2 - const rightOverflow = xMax - textWidth / 2 - const topOverflow = -yMin - textHeight / 2 - const bottomOverflow = yMax - textHeight / 2 + bottomRadius = deltaY + rowOffset; + topRadius = deltaY + rowOffset + heightOfLine; + charRadius = deltaY + rowOffset + charOffset; + lineRadius = deltaY + rowOffset + heightOfLine - heightOfLine / this.lineHeight; + + const midRadius = (bottomRadius * 3 + topRadius * 2) / 5; + leftAngle = -(currentLeft + bounds.left) / midRadius; + rightAngle = -(currentLeft + bounds.left + bounds.width) / midRadius; + charAngle = -(currentLeft + bounds.left + bounds.width / 2) / midRadius; + } else { + bottomRadius = deltaY + rowOffset; + topRadius = deltaY + rowOffset - heightOfLine; + charRadius = deltaY + rowOffset - charOffset; + lineRadius = deltaY + rowOffset - heightOfLine + heightOfLine / this.lineHeight; + + let midRadius = (bottomRadius * 2 + topRadius * 3) / 5; + leftAngle = Math.PI + (currentLeft + bounds.left) / midRadius; + rightAngle = Math.PI + (currentLeft + bounds.left + bounds.width) / midRadius; + charAngle = Math.PI + (currentLeft + bounds.left + bounds.width / 2) / midRadius; + } + const rsin = Math.sin(rightAngle), + rcos = Math.cos(rightAngle), + lsin = Math.sin(leftAngle), + lcos = Math.cos(leftAngle), + csin = Math.sin(charAngle), + ccos = Math.cos(charAngle); + const ct = { + contour: bounds.contour && { + x: bounds.contour.x * decl.fontSize, + w: bounds.contour.w * decl.fontSize, + h: bounds.contour.h * decl.fontSize, + y: this._getBaseLine(decl.fontSize) + bounds.contour.y * decl.fontSize, + }, + char: this._textLines[i][j], + charAngle, + leftAngle, + rightAngle, + charRadius, + bottomRadius, + topRadius, + lineRadius, + renderLeftAngle, + renderRightAngle, + bl: { x: cx - bottomRadius * lsin, y: cy - bottomRadius * lcos }, + br: { x: cx - bottomRadius * rsin, y: cy - bottomRadius * rcos }, + tl: { x: cx - topRadius * lsin, y: cy - topRadius * lcos }, + tr: { x: cx - topRadius * rsin, y: cy - topRadius * rcos }, + nl: { x: cx - lineRadius * lsin, y: cy - lineRadius * lcos }, + nr: { x: cx - lineRadius * rsin, y: cy - lineRadius * rcos }, + cl: { x: cx - charRadius * csin, y: cy - charRadius * ccos }, + lc: { x: cx - lineRadius * csin, y: cy - lineRadius * ccos }, + }; - this._contentOffsetY = bottomOverflow / 2 - topOverflow / 2 - this._contentOffsetX = rightOverflow / 2 - leftOverflow / 2 + if (ct.char?.trim() && bounds.contour) { + let cos = (util as any).cos(-charAngle), + sin = (util as any).sin(-charAngle); + let rotateMatrix = [cos, sin, -sin, cos, 0, 0]; + let matrix = util.multiplyTransformMatrices([1, 0, 0, 1, ct.lc.x, ct.lc.y], rotateMatrix); + let y = ct.contour.y; + if (curvature > 0) { + const x = ct.contour.x - this.__charBounds[i][j].width / 2; + ct.contour.br = (util as any).transformPoint({ x: x + ct.contour.w, y: -y }, matrix); + ct.contour.bl = (util as any).transformPoint({ x: x, y: -y }, matrix); + ct.contour.tl = (util as any).transformPoint({ x: x, y: -y - ct.contour.h }, matrix); + ct.contour.tr = (util as any).transformPoint( + { x: x + ct.contour.w, y: -y - ct.contour.h }, + matrix + ); + } else { + const x = -ct.contour.x + this.__charBounds[i][j].width / 2; + ct.contour.br = (util as any).transformPoint({ x: x - ct.contour.w, y: y }, matrix); + ct.contour.bl = (util as any).transformPoint({ x: x, y: y }, matrix); + ct.contour.tl = (util as any).transformPoint({ x: x, y: y + ct.contour.h }, matrix); + ct.contour.tr = (util as any).transformPoint( + { x: x - ct.contour.w, y: y + ct.contour.h }, + matrix + ); + } + xMin = Math.min(xMin, ct.contour.br.x, ct.contour.bl.x, ct.contour.tl.x, ct.contour.tr.x); + xMax = Math.max(xMax, ct.contour.br.x, ct.contour.bl.x, ct.contour.tl.x, ct.contour.tr.x); + yMin = Math.min(yMin, ct.contour.br.y, ct.contour.bl.y, ct.contour.tl.y, ct.contour.tr.y); + yMax = Math.max(yMax, ct.contour.br.y, ct.contour.bl.y, ct.contour.tl.y, ct.contour.tr.y); + } + cts[i][j] = ct; + } + } + for (let i = 0; i < cts.length; i++) { + const ctsl = cts[i], + cta = ctsl[0], + ctb = ctsl[ctsl.length - 1]; + let bbox, bbox2; + if (curvature > 0) { + bbox = sectorBoundingBox( + cta.tl, + ctb.tr, + this._curvingCenter, + this._linesRads[i] + this.__lineHeights[i] + ); + bbox2 = sectorBoundingBox(cta.nl, ctb.nr, this._curvingCenter, this._linesRads[i]); + } else { + bbox = sectorBoundingBox( + ctb.tr, + cta.tl, + this._curvingCenter, + this._linesRads[i] - this.__lineHeights[i] + ); + bbox2 = sectorBoundingBox(ctb.nr, cta.nl, this._curvingCenter, this._linesRads[i]); + } + xMin = Math.min(xMin, bbox.x, bbox2.x); + xMax = Math.max(xMax, bbox.x + bbox.width, bbox2.x + bbox2.width); + yMin = Math.min(yMin, bbox.y, bbox2.y); + yMax = Math.max(yMax, bbox.y + bbox.height, bbox2.y + bbox2.height); + } - if (this._contentOffsetX > 0) { - this.width = Math.max(textWidth + leftOverflow + rightOverflow, this.MIN_TEXT_WIDTH) - } - this.height = Math.max(textHeight + topOverflow + bottomOverflow, textHeight) + this._enableDiacritics(); - }, + const leftOverflow = -xMin - textWidth / 2; + const rightOverflow = xMax - textWidth / 2; + const topOverflow = -yMin - textHeight / 2; + const bottomOverflow = yMax - textHeight / 2; - _hasStyleChanged: function (prevStyle: string[], thisStyle: string[]) { - if (Object.keys(prevStyle).length !== Object.keys(thisStyle).length) { - return true - } - for (let prop in prevStyle) { - if (prevStyle[prop] !== thisStyle[prop]) { - return true; - } - } - return false; - }, - - interateTextChunks: function (lineIndex: number, foo: Function, iteratorFn?: Function) { - let actualStyle, nextStyle, firstChar = 0; - let specs = this._specialArray - let line = this._textLines[lineIndex] - let isJustify = this.textAlign.indexOf('justify') !== -1; - let shortCut = !isJustify && this.charSpacing === 0 && (!specs || !specs[lineIndex]) && this.isEmptyStyles(lineIndex); - - if (shortCut) { - foo(0, line.length, null) - return; - } + this._contentOffsetY = bottomOverflow / 2 - topOverflow / 2; + this._contentOffsetX = rightOverflow / 2 - leftOverflow / 2; - let timeToRender; + if (this._contentOffsetX > 0) { + this.width = Math.max(textWidth + leftOverflow + rightOverflow, this.MIN_TEXT_WIDTH); + } + this.height = Math.max(textHeight + topOverflow + bottomOverflow, textHeight); + }, - for (let i = 0, len = line.length - 1; i <= len; i++) { - timeToRender = i === len || this.charSpacing; - iteratorFn && iteratorFn(i) - if (isJustify && !timeToRender) { - if (this._reSpaceAndTab.test(line[i])) { - timeToRender = true; - } - } - if (!timeToRender) { - actualStyle = actualStyle || this.getCompleteStyleDeclaration(lineIndex, i); - nextStyle = this.getCompleteStyleDeclaration(lineIndex, i + 1); - timeToRender = (specs && specs[lineIndex] && specs[lineIndex][i] !== specs[lineIndex][i + 1]) || this._hasStyleChanged(actualStyle, nextStyle) - } + _hasStyleChanged: function (prevStyle: string[], thisStyle: string[]) { + if (Object.keys(prevStyle).length !== Object.keys(thisStyle).length) { + return true; + } + for (let prop in prevStyle) { + if (prevStyle[prop] !== thisStyle[prop]) { + return true; + } + } + return false; + }, + + interateTextChunks: function (lineIndex: number, foo: any, iteratorFn?: any) { + let actualStyle, + nextStyle, + firstChar = 0; + let specs = this._specialArray; + let line = this._textLines[lineIndex]; + let isJustify = this.textAlign.indexOf('justify') !== -1; + let shortCut = + !isJustify && + this.charSpacing === 0 && + (!specs || !specs[lineIndex]) && + this.isEmptyStyles(lineIndex); + + if (shortCut) { + foo(0, line.length, null); + return; + } - if (timeToRender) { - foo(firstChar, i, actualStyle) - firstChar = i + 1; - actualStyle = nextStyle; + let timeToRender; + + for (let i = 0, len = line.length - 1; i <= len; i++) { + timeToRender = i === len || this.charSpacing; + iteratorFn && iteratorFn(i); + if (isJustify && !timeToRender) { + if (this._reSpaceAndTab.test(line[i])) { + timeToRender = true; + } + } + if (!timeToRender) { + actualStyle = actualStyle || this.getCompleteStyleDeclaration(lineIndex, i); + nextStyle = this.getCompleteStyleDeclaration(lineIndex, i + 1); + timeToRender = + (specs && specs[lineIndex] && specs[lineIndex][i] !== specs[lineIndex][i + 1]) || + this._hasStyleChanged(actualStyle, nextStyle); + } + + if (timeToRender) { + foo(firstChar, i, actualStyle); + firstChar = i + 1; + actualStyle = nextStyle; + } + } + }, + + _enableDiacritics: function () { + const cts = this._charTransformations; + //Fix Diacritics symbols on a curve + const diacritics = [ + '́', + '̀', + '̂', + '̌', + '̋', + '̏', + '̃', + '̇', + '̣', + '·', + '̈', + 'ː', + '̆', + '̑', + '͗', + '̃', + '҃', + '̩', + '̄', + '̱', + '⃓', + '̷', + '̵', + '̊', + '̓', + '̒', + '̔', + '̉', + '̛', + '̦', + '̧', + '̡', + '̢', + '̨', + '͝', + '͡', + '', + '͞', + '͠', + ]; + for (let i in cts) { + for (let j in cts[i]) { + if (cts[i][j].char && diacritics.includes(cts[i][j].char)) { + for (let k: any = j; k--; ) { + if (cts[i][k].char) { + cts[i][k].char += cts[i][j].char; + cts[i][j].isDiacritic = true; + delete cts[i][j].char; + break; } + } } - }, - - _enableDiacritics: function () { - const cts = this._charTransformations - //Fix Diacritics symbols on a curve - const diacritics = ['́', '̀', '̂', '̌', '̋', '̏', '̃', '̇', '̣', '·', '̈', 'ː', '̆', '̑', '͗', '̃', '҃', '̩', '̄', '̱', '⃓', '̷', '̵', '̊', '̓', '̒', '̔', '̉', '̛', '̦', '̧', '̡', '̢', '̨', '͝', '͡', '', '͞', '͠'] - for (let i in cts) { - for (let j in cts[i]) { - if (cts[i][j].char && diacritics.includes(cts[i][j].char)) { - // @ts-ignore - for (let k = j; k--;) { - if (cts[i][k].char) { - cts[i][k].char += cts[i][j].char - cts[i][j].isDiacritic = true; - delete cts[i][j].char - break; - } - } - } - } + } + } + }, + + _drawTextLinesDecorationSector: function ( + ctx: CanvasRenderingContext2D, + currentColor: string, + offset: number, + line: number, + charStart: number, + charEnd: number + ) { + ctx.fillStyle = currentColor; + ctx.lineWidth = this.fontSize / 15; + let startChar = this._charTransformations[line][charStart]; + let endChar = this._charTransformations[line][charEnd - 1]; + ctx.beginPath(); + if (this.curvature < 0) { + ctx.arc( + this._curvingCenter.x, + this._curvingCenter.y, + startChar.charRadius + 1 + offset, + -startChar.leftAngle - Math.PI / 2, + -endChar.rightAngle - Math.PI / 2, + true + ); + } else { + ctx.arc( + this._curvingCenter.x, + this._curvingCenter.y, + startChar.charRadius - 1 - offset, + -startChar.leftAngle - Math.PI / 2, + -endChar.rightAngle - Math.PI / 2, + false + ); + } + ctx.stroke(); + }, + + _contextSelectBackgroundSector: function ( + ctx: CanvasRenderingContext2D, + line: number, + charStart: number, + charEnd: number, + fullLineRadius?: boolean + ) { + ctx.beginPath(); + let startChar = this._charTransformations[line][charStart]; + let endChar = this._charTransformations[line][charEnd]; + ctx.moveTo(startChar.tl.x, startChar.tl.y); + let radius = fullLineRadius ? startChar.bottomRadius : startChar.lineRadius; + const startStatus = this.curvature < 0 ? true : false; + ctx.arc( + this._curvingCenter.x, + this._curvingCenter.y, + radius, + -startChar.leftAngle - Math.PI / 2, + -endChar.rightAngle - Math.PI / 2, + startStatus + ); + ctx.lineTo(endChar.tr.x, endChar.tr.y); + const endStatus = this.curvature < 0 ? false : true; + ctx.arc( + this._curvingCenter.x, + this._curvingCenter.y, + startChar.topRadius, + -endChar.rightAngle - Math.PI / 2, + -startChar.leftAngle - Math.PI / 2, + endStatus + ); + ctx.closePath(); + }, + // 设置文本背景颜色 + _renderTextLinesBackground: function (ctx: CanvasRenderingContext2D) { + if (!this.textBackgroundColor && !this.styleHas('textBackgroundColor')) return; + let originalFill = ctx.fillStyle, + lastColor, + charStart, + currentColor; + for (let i = 0, len = this._textLines.length; i < len; i++) { + if (!this.textBackgroundColor && !this.styleHas('textBackgroundColor', i)) { + continue; + } + charStart = 0; + lastColor = this.getValueOfPropertyAt(i, 0, 'textBackgroundColor'); + let j = 0; + for (j = 0; j < this._textLines[i].length; j++) { + currentColor = this.getValueOfPropertyAt(i, j, 'textBackgroundColor'); + if (currentColor !== lastColor) { + if (lastColor) { + ctx.fillStyle = lastColor; + this._contextSelectBackgroundSector(ctx, i, charStart, j - 1); + ctx.fill(); + } + charStart = j; + lastColor = currentColor; } - }, - - _drawTextLinesDecorationSector: function (ctx: CanvasRenderingContext2D, currentColor: string, offset: number, line: number, charStart: number, charEnd: number) { + } + if (currentColor) { ctx.fillStyle = currentColor; - ctx.lineWidth = this.fontSize / 15 - let startChar = this._charTransformations[line][charStart] - let endChar = this._charTransformations[line][charEnd - 1] - ctx.beginPath() - if (this.curvature < 0) { - ctx.arc(this._curvingCenter.x, this._curvingCenter.y, startChar.charRadius + 1 + offset, -startChar.leftAngle - Math.PI / 2, -endChar.rightAngle - Math.PI / 2, true) - } else { - ctx.arc(this._curvingCenter.x, this._curvingCenter.y, startChar.charRadius - 1 - offset, -startChar.leftAngle - Math.PI / 2, -endChar.rightAngle - Math.PI / 2, false) - } - ctx.stroke() - }, - - _contextSelectBackgroundSector: function (ctx: CanvasRenderingContext2D, line: number, charStart: number, charEnd: number, fullLineRadius?: boolean) { - ctx.beginPath() - let startChar = this._charTransformations[line][charStart]; - let endChar = this._charTransformations[line][charEnd]; - ctx.moveTo(startChar.tl.x, startChar.tl.y) - let radius = fullLineRadius ? startChar.bottomRadius : startChar.lineRadius - const startStatus = this.curvature < 0 ? true : false - ctx.arc(this._curvingCenter.x, this._curvingCenter.y, radius, -startChar.leftAngle - Math.PI / 2, -endChar.rightAngle - Math.PI / 2, startStatus) - ctx.lineTo(endChar.tr.x, endChar.tr.y) - const endStatus = this.curvature < 0 ? false : true - ctx.arc(this._curvingCenter.x, this._curvingCenter.y, startChar.topRadius, -endChar.rightAngle - Math.PI / 2, -startChar.leftAngle - Math.PI / 2, endStatus) - ctx.closePath() - }, - // 设置文本背景颜色 - _renderTextLinesBackground: function (ctx: CanvasRenderingContext2D) { - if (!this.textBackgroundColor && !this.styleHas('textBackgroundColor')) return; - let originalFill = ctx.fillStyle, lastColor, charStart, currentColor; - for (let i = 0, len = this._textLines.length; i < len; i++) { - if (!this.textBackgroundColor && !this.styleHas('textBackgroundColor', i)) { - continue; - } - charStart = 0 - lastColor = this.getValueOfPropertyAt(i, 0, 'textBackgroundColor'); - let j = 0 - for (j = 0; j < this._textLines[i].length; j++) { - currentColor = this.getValueOfPropertyAt(i, j, 'textBackgroundColor'); - if (currentColor !== lastColor) { - if (lastColor) { - ctx.fillStyle = lastColor; - this._contextSelectBackgroundSector(ctx, i, charStart, j - 1) - ctx.fill() - } - charStart = j; - lastColor = currentColor; - } - } - if (currentColor) { - ctx.fillStyle = currentColor; - this._contextSelectBackgroundSector(ctx, i, charStart, j - 1) - ctx.fill() - } - } - ctx.fillStyle = originalFill; - this._removeShadow(ctx); - }, - - _set: function (key: any, value: any) { - this.callSuper('_set', key, value) - let needsDims = false; - if (typeof key === 'object') { - const keys = key - for (let _key in keys) { - needsDims = needsDims || _dimensionAffectingProps.indexOf(_key) !== -1; - } - } else { - needsDims = _dimensionAffectingProps.indexOf(key) !== -1; - } + this._contextSelectBackgroundSector(ctx, i, charStart, j - 1); + ctx.fill(); + } + } + ctx.fillStyle = originalFill; + this._removeShadow(ctx); + }, + + _set: function (key: any, value: any) { + this.callSuper('_set', key, value); + let needsDims = false; + if (typeof key === 'object') { + const keys = key; + for (let _key in keys) { + needsDims = needsDims || _dimensionAffectingProps.indexOf(_key) !== -1; + } + } else { + needsDims = _dimensionAffectingProps.indexOf(key) !== -1; + } - if (needsDims) { - this.initDimensions(); - this.setCoords(); - } - return this - }, - renderSelection: function (boundaries: any, ctx: CanvasRenderingContext2D) { - const selectionStart = this.inCompositionMode ? this.hiddenTextarea.selectionStart : this.selectionStart, - selectionEnd = this.inCompositionMode ? this.hiddenTextarea.selectionEnd : this.selectionEnd, - start = this.get2DCursorLocation(selectionStart), - end = this.get2DCursorLocation(selectionEnd), - startLine = start.lineIndex, - endLine = end.lineIndex, - startChar = start.charIndex < 0 ? 0 : start.charIndex, - endChar = end.charIndex < 0 ? 0 : end.charIndex; - - ctx.fillStyle = this.selectionColor; - ctx && ctx.translate(-this._contentOffsetX, -this._contentOffsetY) - - for (let i = startLine; i <= endLine; i++) { - let charStart = (i === startLine) ? startChar : 0, charEnd = (i >= startLine && i < endLine) ? this._textLines[i].length : endChar - this._contextSelectBackgroundSector(ctx, i, charStart, charEnd - 1, i !== endLine) - ctx.fill(); - } - }, - - _measureLine(lineIndex: number) { - let width = 0, charIndex, grapheme, line = this._textLines[lineIndex], prevGrapheme, - graphemeInfo, numOfSpaces = 0, lineBounds = new Array(line.length); - let renderedLeft = 0; - let renderedWidth = 0; - let renderedBottom = -Infinity; - let renderedTop = -Infinity - this.__charBounds[lineIndex] = lineBounds; - for (charIndex = 0; charIndex < line.length; charIndex++) { - grapheme = line[charIndex] - const style = this.getCompleteStyleDeclaration(lineIndex, charIndex) - graphemeInfo = this._getGraphemeBox(grapheme, lineIndex, charIndex, prevGrapheme); - lineBounds[charIndex] = graphemeInfo; - width += graphemeInfo.kernedWidth; - prevGrapheme = grapheme; - let contourX, contourW, contourH, contourY - if (this.useRenderBoundingBoxes && graphemeInfo.contour) { - contourX = graphemeInfo.contour.x * style.fontSize - contourW = graphemeInfo.contour.w * style.fontSize - contourH = graphemeInfo.contour.h * style.fontSize - contourY = this._getBaseLine(style.fontSize) + graphemeInfo.contour.y * style.fontSize - renderedLeft = Math.max(renderedLeft, -(graphemeInfo.left + contourX)) - renderedWidth = Math.max(renderedWidth, contourW + contourX + graphemeInfo.left) - renderedBottom = Math.max(renderedBottom, -contourY) - renderedTop = Math.max(renderedTop, contourY + contourH) - } + if (needsDims) { + this.initDimensions(); + this.setCoords(); + } + return this; + }, + renderSelection: function (boundaries: any, ctx: CanvasRenderingContext2D) { + const selectionStart = this.inCompositionMode + ? this.hiddenTextarea.selectionStart + : this.selectionStart, + selectionEnd = this.inCompositionMode ? this.hiddenTextarea.selectionEnd : this.selectionEnd, + start = this.get2DCursorLocation(selectionStart), + end = this.get2DCursorLocation(selectionEnd), + startLine = start.lineIndex, + endLine = end.lineIndex, + startChar = start.charIndex < 0 ? 0 : start.charIndex, + endChar = end.charIndex < 0 ? 0 : end.charIndex; + + ctx.fillStyle = this.selectionColor; + ctx && ctx.translate(-this._contentOffsetX, -this._contentOffsetY); + + for (let i = startLine; i <= endLine; i++) { + let charStart = i === startLine ? startChar : 0, + charEnd = i >= startLine && i < endLine ? this._textLines[i].length : endChar; + this._contextSelectBackgroundSector(ctx, i, charStart, charEnd - 1, i !== endLine); + ctx.fill(); + } + }, + + _measureLine(lineIndex: number) { + let width = 0, + charIndex, + grapheme, + line = this._textLines[lineIndex], + prevGrapheme, + graphemeInfo, + numOfSpaces = 0, + lineBounds = new Array(line.length); + let renderedLeft = 0; + let renderedWidth = 0; + let renderedBottom = -Infinity; + let renderedTop = -Infinity; + this.__charBounds[lineIndex] = lineBounds; + for (charIndex = 0; charIndex < line.length; charIndex++) { + grapheme = line[charIndex]; + const style = this.getCompleteStyleDeclaration(lineIndex, charIndex); + graphemeInfo = this._getGraphemeBox(grapheme, lineIndex, charIndex, prevGrapheme); + lineBounds[charIndex] = graphemeInfo; + width += graphemeInfo.kernedWidth; + prevGrapheme = grapheme; + let contourX, contourW, contourH, contourY; + if (this.useRenderBoundingBoxes && graphemeInfo.contour) { + contourX = graphemeInfo.contour.x * style.fontSize; + contourW = graphemeInfo.contour.w * style.fontSize; + contourH = graphemeInfo.contour.h * style.fontSize; + contourY = this._getBaseLine(style.fontSize) + graphemeInfo.contour.y * style.fontSize; + renderedLeft = Math.max(renderedLeft, -(graphemeInfo.left + contourX)); + renderedWidth = Math.max(renderedWidth, contourW + contourX + graphemeInfo.left); + renderedBottom = Math.max(renderedBottom, -contourY); + renderedTop = Math.max(renderedTop, contourY + contourH); + } + } + lineBounds[charIndex] = { + left: graphemeInfo ? graphemeInfo.left + graphemeInfo.width : 0, + width: 0, + kernedWidth: 0, + height: this.fontSize, + }; + const renderedRight = Math.max(0, renderedWidth - width); + return { + width, + numOfSpaces, + renderedLeft, + renderedBottom, + renderedRight, + renderedTop, + }; + }, + + getLineWidth(lineIndex: number) { + if (this.__lineWidths[lineIndex]) { + return this.__lineWidths[lineIndex]; + } + let width, + line = this._textLines[lineIndex], + lineInfo; + if (line.length === 0) { + width = 0; + } else { + lineInfo = this.measureLine(lineIndex); + if (this.useRenderBoundingBoxes) { + width = lineInfo.width + lineInfo.renderedRight + lineInfo.renderedLeft; + this.__lineInfo[lineIndex] = lineInfo; + } else { + width = lineInfo.width; + } + } + this.__lineWidths[lineIndex] = width; + return width; + }, + // 覆盖 renderCursor + renderCursor(boundaries: any, ctx: CanvasRenderingContext2D) { + const cursorLocation = this.get2DCursorLocation(), + lineIndex = cursorLocation.lineIndex, + topOffset = boundaries.topOffset, + charIndex = cursorLocation.charIndex > 0 ? cursorLocation.charIndex - 1 : 0, + multiplier = this.scaleX * this.canvas.getZoom(), + charHeight = this.getValueOfPropertyAt(lineIndex, charIndex, 'fontSize'), + dy = this.getValueOfPropertyAt(lineIndex, charIndex, 'deltaY'), + cursorWidth = this.cursorWidth / multiplier; + if (this.inCompositionMode) { + this.renderSelection(boundaries, ctx); + } - } - lineBounds[charIndex] = { - left: graphemeInfo ? graphemeInfo.left + graphemeInfo.width : 0, - width: 0, - kernedWidth: 0, - height: this.fontSize - }; - const renderedRight = Math.max(0, renderedWidth - width) - return { - width, - numOfSpaces, - renderedLeft, - renderedBottom, - renderedRight, - renderedTop - }; - }, + ctx.lineWidth = cursorWidth; + ctx.strokeStyle = this.getValueOfPropertyAt(lineIndex, charIndex, 'fill'); + ctx.globalAlpha = this.__isMousedown ? 1 : this._currentCursorOpacity; + + if (this._charTransformations[lineIndex]?.[charIndex]) { + const tr = this._charTransformations[lineIndex][charIndex]; + ctx.translate(-this._contentOffsetX, -this._contentOffsetY); + ctx.beginPath(); + ctx.moveTo(tr.nr.x, tr.nr.y); + ctx.lineTo(tr.tr.x, tr.tr.y); + ctx.stroke(); + } else { + ctx.fillRect( + boundaries.left + boundaries.leftOffset - cursorWidth / 2, + topOffset + boundaries.top + dy, + cursorWidth, + charHeight + ); + } + }, + + _renderTextCommon(ctx: CanvasRenderingContext2D, method: any) { + ctx && ctx.save(); + let lineHeights = 0; + const left = this._getLeftOffset(), + top = this._getTopOffset(); + // _applyPatternGradientTransformText ? + const offsets = this._applyPatternGradientTransform( + ctx, + method === 'fillText' ? this.fill : this.stroke + ); + + for (let i = 0, len = (this._textLines || []).length; i < len; i++) { + let lineOffsetX = 0, + lineOffsetY = 0; + if (this.__lineInfo && this.__lineInfo[i]) { + lineOffsetX = this.__lineInfo[i].renderedLeft; + lineOffsetY = this.__renderOffsetTop; + } + const heightOfLine = this.getHeightOfLine(i), + maxHeight = heightOfLine / this.lineHeight, + leftOffset = this._getLineLeftOffset(i); + this._renderTextLine( + method, + ctx, + this._textLines[i], + left + leftOffset - offsets.offsetX + lineOffsetX, + top + lineHeights + maxHeight - offsets.offsetY - lineOffsetY, + i + ); + lineHeights += heightOfLine; + } + ctx && ctx.restore(); + }, - getLineWidth(lineIndex: number) { - if (this.__lineWidths[lineIndex]) { - return this.__lineWidths[lineIndex]; - } - let width, line = this._textLines[lineIndex], lineInfo; - if (line.length === 0) { - width = 0; - } - else { - lineInfo = this.measureLine(lineIndex); - if (this.useRenderBoundingBoxes) { - width = lineInfo.width + lineInfo.renderedRight + lineInfo.renderedLeft - this.__lineInfo[lineIndex] = lineInfo - } - else { - width = lineInfo.width; - } - } - this.__lineWidths[lineIndex] = width; - return width; - }, - // 覆盖 renderCursor - renderCursor(boundaries: any, ctx: CanvasRenderingContext2D) { - const cursorLocation = this.get2DCursorLocation(), - lineIndex = cursorLocation.lineIndex, - topOffset = boundaries.topOffset, - charIndex = cursorLocation.charIndex > 0 ? cursorLocation.charIndex - 1 : 0, - multiplier = this.scaleX * this.canvas.getZoom(), - charHeight = this.getValueOfPropertyAt(lineIndex, charIndex, 'fontSize'), - dy = this.getValueOfPropertyAt(lineIndex, charIndex, 'deltaY'), - cursorWidth = this.cursorWidth / multiplier; - if (this.inCompositionMode) { - this.renderSelection(boundaries, ctx); - } + _renderText(ctx: CanvasRenderingContext2D) { + if (this.fill) { + ctx.fillStyle = this.fill; + } - ctx.lineWidth = cursorWidth - ctx.strokeStyle = this.getValueOfPropertyAt(lineIndex, charIndex, 'fill'); - ctx.globalAlpha = this.__isMousedown ? 1 : this._currentCursorOpacity; - - if (this._charTransformations[lineIndex]?.[charIndex]) { - const tr = this._charTransformations[lineIndex][charIndex]; - ctx.translate(-this._contentOffsetX, -this._contentOffsetY) - ctx.beginPath() - ctx.moveTo(tr.nr.x, tr.nr.y) - ctx.lineTo(tr.tr.x, tr.tr.y) - ctx.stroke(); + if (this.useBothRenderingMethod) { + return this._renderTextCommon(ctx, 'both'); + } + if (this.paintFirst === 'stroke') { + this._renderTextStroke(ctx); + this._renderTextFill(ctx); + } else { + this._renderTextFill(ctx); + this._renderTextStroke(ctx); + } + }, + // 分割文本 + _splitText() { + let text = this.text; + if (this.textTransform) { + if (this.textTransform === 'uppercase') { + text = text.toUpperCase(); + } + if (this.textTransform === 'lowercase') { + text = text.toLowerCase(); + } + if (this.textTransform === 'capitalize') { + text = (util.string as any).capitalize(text); + } + } + let newLines = this._splitTextIntoLines(text); + this.textLines = newLines.lines; + this._textLines = newLines.graphemeLines; + this._unwrappedTextLines = newLines._unwrappedLines; + this._text = newLines.graphemeText; + + if (this.useRenderBoundingBoxes) { + this.__lineInfo = []; + } + return newLines; + }, + // // 计算文本高度 + calcTextHeight() { + let lineHeight, + height = 0; + for (let i = 0, len = this._textLines.length; i < len; i++) { + lineHeight = this.getHeightOfLine(i); + height += i === len - 1 ? lineHeight / this.lineHeight : lineHeight; + } + return height; + }, + // 渲染字符样式 + _renderChars( + method: 'fillText' | 'strokeText', + ctx: CanvasRenderingContext2D, + line: Array, + left: number, + top: number, + lineIndex: number + ) { + let lineHeight = this.getHeightOfLine(lineIndex), + charBox, + boxWidth = 0; + ctx && ctx.save(); + top -= (lineHeight * this._fontSizeFraction) / this.lineHeight; + this.interateTextChunks( + lineIndex, + (charIndex: any, endCharIndex: any) => { + this._renderStr(method, ctx, lineIndex, charIndex, endCharIndex, left, top); + left += boxWidth; + boxWidth = 0; + }, + (i: any) => { + charBox = this.__charBounds[lineIndex][i]; + if (boxWidth === 0) { + left += charBox.kernedWidth - charBox.width; + boxWidth += charBox.width; } else { - ctx.fillRect( - boundaries.left + boundaries.leftOffset - cursorWidth / 2, - topOffset + boundaries.top + dy, - cursorWidth, - charHeight); - } - - }, - - - _renderTextCommon(ctx: CanvasRenderingContext2D, method: any) { - - ctx && ctx.save(); - let lineHeights = 0 - const left = this._getLeftOffset(), top = this._getTopOffset() - // _applyPatternGradientTransformText ? - const offsets = this._applyPatternGradientTransform(ctx, (method === 'fillText' ? this.fill : this.stroke)); - - - for (let i = 0, len = (this._textLines || []).length; i < len; i++) { - let lineOffsetX = 0, lineOffsetY = 0 - if (this.__lineInfo && this.__lineInfo[i]) { - - lineOffsetX = this.__lineInfo[i].renderedLeft - lineOffsetY = this.__renderOffsetTop - } - const heightOfLine = this.getHeightOfLine(i), - maxHeight = heightOfLine / this.lineHeight, - leftOffset = this._getLineLeftOffset(i); - this._renderTextLine( - method, - ctx, - this._textLines[i], - left + leftOffset - offsets.offsetX + lineOffsetX, - top + lineHeights + maxHeight - offsets.offsetY - lineOffsetY, - i - ); - lineHeights += heightOfLine; - } - ctx && ctx.restore(); - }, - - _renderText(ctx: CanvasRenderingContext2D) { - if (this.fill) { - ctx.fillStyle = this.fill; - } - - if (this.useBothRenderingMethod) { - return this._renderTextCommon(ctx, 'both'); - } - if (this.paintFirst === 'stroke') { - this._renderTextStroke(ctx); - this._renderTextFill(ctx); - } - else { - this._renderTextFill(ctx); - this._renderTextStroke(ctx); - } - }, - // 分割文本 - _splitText() { - let text = this.text - if (this.textTransform) { - if (this.textTransform === "uppercase") { - text = text.toUpperCase() - } - if (this.textTransform === "lowercase") { - text = text.toLowerCase() - } - if (this.textTransform === "capitalize") { - text = (util.string as any).capitalize(text) - } - } - let newLines = this._splitTextIntoLines(text); - this.textLines = newLines.lines; - this._textLines = newLines.graphemeLines; - this._unwrappedTextLines = newLines._unwrappedLines; - this._text = newLines.graphemeText; - - if (this.useRenderBoundingBoxes) { - this.__lineInfo = [] - } - return newLines; - }, - // // 计算文本高度 - calcTextHeight() { - let lineHeight, height = 0; - for (let i = 0, len = this._textLines.length; i < len; i++) { - lineHeight = this.getHeightOfLine(i); - height += (i === len - 1 ? lineHeight / this.lineHeight : lineHeight); - } - return height; - }, - // 渲染字符样式 - _renderChars(method: 'fillText' | 'strokeText', ctx: CanvasRenderingContext2D, line: Array, left: number, top: number, lineIndex: number) { - let lineHeight = this.getHeightOfLine(lineIndex), - charBox, - boxWidth = 0; - ctx && ctx.save(); - top -= lineHeight * this._fontSizeFraction / this.lineHeight; - this.interateTextChunks(lineIndex, - (charIndex: any, endCharIndex: any) => { - this._renderStr(method, ctx, lineIndex, charIndex, endCharIndex, left, top); - left += boxWidth; - boxWidth = 0; - }, - (i: any) => { - charBox = this.__charBounds[lineIndex][i]; - if (boxWidth === 0) { - left += charBox.kernedWidth - charBox.width; - boxWidth += charBox.width; - } else { - boxWidth += charBox.kernedWidth; - } - }) - ctx && ctx.restore(); - }, - - _renderStr(method: any, ctx: CanvasRenderingContext2D, lineIndex: number, charIndex: number, endCharIndex: number, left: number, top: number) { - const decl = this._getStyleDeclaration(lineIndex, charIndex), - fullDecl = this.getCompleteStyleDeclaration(lineIndex, charIndex), - shouldFill = method === 'fillText' && fullDecl.fill, - shouldStroke = method === 'strokeText' && fullDecl.stroke && fullDecl.strokeWidth; - let fillOffsets, strokeOffsets; - if (method !== "calc" && method !== "both" && !shouldStroke && !shouldFill) { - return; - } - ctx && decl && ctx.save() - // ctx && this._applyCharStyles(method, ctx, lineIndex, charIndex, fullDecl); - - shouldFill && (fillOffsets = this._setFillStyles(ctx, fullDecl)); - shouldStroke && (strokeOffsets = this._setStrokeStyles(ctx, fullDecl)); - - if (decl && decl.textBackgroundColor) { - this._removeShadow(ctx); - } - if (decl && decl.deltaY) { - top += decl.deltaY; - } + boxWidth += charBox.kernedWidth; + } + } + ); + ctx && ctx.restore(); + }, + + _renderStr( + method: any, + ctx: CanvasRenderingContext2D, + lineIndex: number, + charIndex: number, + endCharIndex: number, + left: number, + top: number + ) { + const decl = this._getStyleDeclaration(lineIndex, charIndex), + fullDecl = this.getCompleteStyleDeclaration(lineIndex, charIndex), + shouldFill = method === 'fillText' && fullDecl.fill, + shouldStroke = method === 'strokeText' && fullDecl.stroke && fullDecl.strokeWidth; + let fillOffsets, strokeOffsets; + if (method !== 'calc' && method !== 'both' && !shouldStroke && !shouldFill) { + return; + } + ctx && decl && ctx.save(); + // ctx && this._applyCharStyles(method, ctx, lineIndex, charIndex, fullDecl); - fullDecl.special = this._specialArray && this._specialArray[lineIndex] && this._specialArray[lineIndex][charIndex]; + shouldFill && (fillOffsets = this._setFillStyles(ctx, fullDecl)); + shouldStroke && (strokeOffsets = this._setStrokeStyles(ctx, fullDecl)); - if (this.renderCharCallback) { - this.renderCharCallback(method, ctx, lineIndex, charIndex, endCharIndex, left, top, fullDecl) - } - else { - const text = this._textLines[lineIndex].slice(charIndex, endCharIndex + 1).join("") - this.runCharRendering(method, ctx, text, left, top, 0, fullDecl); - } - ctx && decl && ctx.restore(); - }, + if (decl && decl.textBackgroundColor) { + this._removeShadow(ctx); + } + if (decl && decl.deltaY) { + top += decl.deltaY; + } - _renderBackground(ctx: CanvasRenderingContext2D) { - if (!this.backgroundColor && !this.backgroundStroke) { - return; - } - let dim = this._getNonTransformedDimensions(); - if (this.backgroundColor) { - ctx.fillStyle = this.backgroundColor; - ctx.fillRect( - -dim.x / 2, - -dim.y / 2, - dim.x, - dim.y - ) - } + fullDecl.special = + this._specialArray && + this._specialArray[lineIndex] && + this._specialArray[lineIndex][charIndex]; - if (this.backgroundStroke) { - this._setStrokeStyles(ctx, this.backgroundStroke); - ctx.strokeRect( - -dim.x / 2, - -dim.y / 2, - dim.x, - dim.y - ) - } - this._removeShadow(ctx); - }, + if (this.renderCharCallback) { + this.renderCharCallback(method, ctx, lineIndex, charIndex, endCharIndex, left, top, fullDecl); + } else { + const text = this._textLines[lineIndex].slice(charIndex, endCharIndex + 1).join(''); + this.runCharRendering(method, ctx, text, left, top, 0, fullDecl); + } + ctx && decl && ctx.restore(); + }, - defaultTextRender(method: string, ctx: CanvasRenderingContext2D, _char: string, decl: any) { - if (method === "both") { - if (decl.fill && this.paintFirst === 'fill') { - ctx.fillText(_char, 0, 0); - } - if (decl.stroke && decl.strokeWidth) { - if (this.shadow && !this.shadow.affectStroke) { - this._removeShadow(ctx); - } - ctx.save(); - this._setLineDash(ctx, this.strokeDashArray); - ctx.beginPath(); - ctx.strokeText(_char, 0, 0); - ctx.closePath(); - ctx.restore(); - } - if (decl.fill && this.paintFirst === 'stroke') { - ctx.fillText(_char, 0, 0); - } - } - else { - method === 'fillText' && decl.fill && ctx.fillText(_char, 0, 0); - method === 'strokeText' && decl.stroke && decl.strokeWidth && ctx.strokeText(_char, 0, 0); - } - return true; - }, + _renderBackground(ctx: CanvasRenderingContext2D) { + if (!this.backgroundColor && !this.backgroundStroke) { + return; + } + let dim = this._getNonTransformedDimensions(); + if (this.backgroundColor) { + ctx.fillStyle = this.backgroundColor; + ctx.fillRect(-dim.x / 2, -dim.y / 2, dim.x, dim.y); + } - getHeightOfLine(lineIndex: number) { - if (!this.__lineHeights) { - this.initDimensions(); - } - if (this.__lineHeights[lineIndex]) { - return this.__lineHeights[lineIndex]; - } + if (this.backgroundStroke) { + this._setStrokeStyles(ctx, this.backgroundStroke); + ctx.strokeRect(-dim.x / 2, -dim.y / 2, dim.x, dim.y); + } + this._removeShadow(ctx); + }, + + defaultTextRender(method: string, ctx: CanvasRenderingContext2D, _char: string, decl: any) { + if (method === 'both') { + if (decl.fill && this.paintFirst === 'fill') { + ctx.fillText(_char, 0, 0); + } + if (decl.stroke && decl.strokeWidth) { + if (this.shadow && !this.shadow.affectStroke) { + this._removeShadow(ctx); + } + ctx.save(); + this._setLineDash(ctx, this.strokeDashArray); + ctx.beginPath(); + ctx.strokeText(_char, 0, 0); + ctx.closePath(); + ctx.restore(); + } + if (decl.fill && this.paintFirst === 'stroke') { + ctx.fillText(_char, 0, 0); + } + } else { + method === 'fillText' && decl.fill && ctx.fillText(_char, 0, 0); + method === 'strokeText' && decl.stroke && decl.strokeWidth && ctx.strokeText(_char, 0, 0); + } + return true; + }, - const line = this._textLines[lineIndex] - let maxHeight = this.getHeightOfChar(lineIndex, 0); - for (let i = 1, len = line.length; i < len; i++) { - maxHeight = Math.max(this.getHeightOfChar(lineIndex, i), maxHeight); - } - return this.__lineHeights[lineIndex] = maxHeight * this.lineHeight * this._fontSizeMult; - }, - - cleanStyle(property: any) { - if (!this.styles || !property) return false - const obj = this.styles; - let letterCount, stylePropertyValue, graphemeCount = 0, stylesCount = 0, allStyleObjectPropertiesMatch = true - for (let p1 in obj) { - letterCount = 0; - for (let p2 in obj[p1]) { - const styleObject = obj[p1][p2], stylePropertyHasBeenSet = styleObject.hasOwnProperty(property); - stylesCount++; - if (stylePropertyHasBeenSet) { - if (!stylePropertyValue) { - stylePropertyValue = styleObject[property]; - } - else if (styleObject[property] !== stylePropertyValue) { - allStyleObjectPropertiesMatch = false; - } - if (styleObject[property] === this[property]) { - delete styleObject[property]; - } - } - else { - allStyleObjectPropertiesMatch = false; - } - if (Object.keys(styleObject).length !== 0) { - letterCount++; - } - else { - delete obj[p1][p2]; - } - } - if (letterCount === 0) { - delete obj[p1]; - } - } - for (let i = 0; i < this._textLines.length; i++) { - graphemeCount += this._textLines[i].length; - } - if (allStyleObjectPropertiesMatch && stylesCount === graphemeCount) { - if (stylePropertyValue !== undefined) { - this.set(property, stylePropertyValue) - } - this.removeStyle(property); - } - }, - - getLocalPointer(e: any, pointer?: any) { - pointer = pointer || this.canvas.getPointer(e); - let pClicked = new Point(pointer.x, pointer.y) - const objectLeftTop = this._getLeftTopCoords(); - if (this.angle) { - pClicked = util.rotatePoint(pClicked, objectLeftTop, util.degreesToRadians(-this.angle)); - } - return { - x: pClicked.x - objectLeftTop.x, - y: pClicked.y - objectLeftTop.y - }; - }, - - toSVG(reviver: any): any { - const imageData = this.toDataURL() - return `` - }, - _toSVG() { - const offsets = this._getSVGLeftTopOffsets() - const textAndBg = this._getSVGTextAndBg(offsets.textTop, offsets.textLeft); - return this._wrapSVGTextAndBg(textAndBg); - }, - - _getSVGLeftTopOffsets() { - return { - textLeft: -this.width / 2, - textTop: -this.height / 2, - lineTop: this.getHeightOfLine(0) - }; - }, - - _wrapSVGTextAndBg(textAndBg: any) { - const noShadow = true, textDecoration = this.getSvgTextDecoration(this); - return [ - textAndBg.textBgRects.join(''), - '\t\t', - textAndBg.textSpans.join(''), - '\n' - ]; - }, - - _setSVGBg(textBgRects: any) { - if (this.backgroundColor) { - textBgRects.push( - '\t\t\n'); - } - }, - - _getSVGTextAndBg(textTopOffset: any, textLeftOffset: any) { - let textSpans: any[] = [], - textBgRects: any[] = [], - height = textTopOffset, lineOffset; - this._setSVGBg(textBgRects); - - // text and text-background - for (let i = 0, len = this._textLines.length; i < len; i++) { - lineOffset = this._getLineLeftOffset(i); - if (this.textBackgroundColor || this.styleHas('textBackgroundColor', i)) { - this._setSVGTextLineBg(textBgRects, i, textLeftOffset + lineOffset, height); - } - this._setSVGTextLineText(textSpans, i, textLeftOffset + lineOffset, height); - height += this.getHeightOfLine(i); - } + getHeightOfLine(lineIndex: number) { + if (!this.__lineHeights) { + this.initDimensions(); + } + if (this.__lineHeights[lineIndex]) { + return this.__lineHeights[lineIndex]; + } - return { - textSpans: textSpans, - textBgRects: textBgRects - }; - }, - - _createTextCharSpan(_char: string, styleDecl: any, left: number, top: number, angle: number) { - const shouldUseWhitespace = _char !== _char.trim() || _char.match(multipleSpacesRegex) ? true : false, - styleProps = this.getSvgSpanStyles(styleDecl, shouldUseWhitespace), - fillStyles = styleProps ? 'style="' + styleProps + '"' : '', - dy = styleDecl.deltaY; - let dySpan = ''; - if (dy) { - dySpan = ' dy="' + util.toFixed(dy, NUM_FRACTION_DIGITS) + '" '; + const line = this._textLines[lineIndex]; + let maxHeight = this.getHeightOfChar(lineIndex, 0); + for (let i = 1, len = line.length; i < len; i++) { + maxHeight = Math.max(this.getHeightOfChar(lineIndex, i), maxHeight); + } + return (this.__lineHeights[lineIndex] = maxHeight * this.lineHeight * this._fontSizeMult); + }, + + cleanStyle(property: any) { + if (!this.styles || !property) return false; + const obj = this.styles; + let letterCount, + stylePropertyValue, + graphemeCount = 0, + stylesCount = 0, + allStyleObjectPropertiesMatch = true; + for (let p1 in obj) { + letterCount = 0; + for (let p2 in obj[p1]) { + const styleObject = obj[p1][p2], + stylePropertyHasBeenSet = Object.prototype.hasOwnProperty.call(styleObject, property); + stylesCount++; + if (stylePropertyHasBeenSet) { + if (!stylePropertyValue) { + stylePropertyValue = styleObject[property]; + } else if (styleObject[property] !== stylePropertyValue) { + allStyleObjectPropertiesMatch = false; + } + if (styleObject[property] === this[property]) { + delete styleObject[property]; + } + } else { + allStyleObjectPropertiesMatch = false; } - return [ - '', - util.string.escapeXml(_char), - '' - ].join(''); - }, - - _hasStyleChangedForSvg(prevStyle: any, thisStyle: any) { - return this._hasStyleChanged(prevStyle, thisStyle) || - prevStyle.overline !== thisStyle.overline || - prevStyle.underline !== thisStyle.underline || - prevStyle.linethrough !== thisStyle.linethrough; - }, - - _setSVGTextLineText(textSpans: string[], lineIndex: number, textLeftOffset: number, textTopOffset: number) { - // set proper line offset - let lineHeight = this.getHeightOfLine(lineIndex), - isJustify = this.textAlign.indexOf('justify') !== -1, - actualStyle, - nextStyle, - charsToRender = '', - charBox, style, - boxWidth = 0, - line = this._textLines[lineIndex], - timeToRender; - - textTopOffset += lineHeight * (1 - this._fontSizeFraction) / this.lineHeight; - for (let i = 0, len = line.length - 1; i <= len; i++) { - timeToRender = i === len || this.charSpacing; - charsToRender += line[i]; - charBox = this._charTransformations[lineIndex][i]; - const angle = this.curvature > 0 ? -charBox.charAngle : -charBox.charAngle - Math.PI - if (boxWidth === 0) { - textLeftOffset += charBox.kernedWidth - charBox.width; - boxWidth += charBox.width; - } - else { - boxWidth += charBox.kernedWidth; - } - if (isJustify && !timeToRender) { - if (this._reSpaceAndTab.test(line[i])) { - timeToRender = true; - } - } - if (!timeToRender) { - actualStyle = actualStyle || this.getCompleteStyleDeclaration(lineIndex, i); - nextStyle = this.getCompleteStyleDeclaration(lineIndex, i + 1); - timeToRender = this._hasStyleChangedForSvg(actualStyle, nextStyle); - } - if (timeToRender) { - style = this._getStyleDeclaration(lineIndex, i) || {}; - // textSpans.push(this._createTextCharSpan(charsToRender, style, textLeftOffset, textTopOffset)); - const degress = angle * 180 / Math.PI - textSpans.push(this._createTextCharSpan(charsToRender, style, charBox.cl.x, charBox.cl.y, degress)); - charsToRender = ''; - actualStyle = nextStyle; - textLeftOffset += boxWidth; - boxWidth = 0; - } + if (Object.keys(styleObject).length !== 0) { + letterCount++; + } else { + delete obj[p1][p2]; } - }, - - _getCursorBoundariesOffsets(index: number) { - let topOffset = 0, - leftOffset = 0; - const { charIndex, lineIndex } = this.get2DCursorLocation(index); + } + if (letterCount === 0) { + delete obj[p1]; + } + } + for (let i = 0; i < this._textLines.length; i++) { + graphemeCount += this._textLines[i].length; + } + if (allStyleObjectPropertiesMatch && stylesCount === graphemeCount) { + if (stylePropertyValue !== undefined) { + this.set(property, stylePropertyValue); + } + this.removeStyle(property); + } + }, + + getLocalPointer(e: any, pointer?: any) { + pointer = pointer || this.canvas.getPointer(e); + let pClicked = new Point(pointer.x, pointer.y); + const objectLeftTop = this._getLeftTopCoords(); + if (this.angle) { + pClicked = util.rotatePoint(pClicked, objectLeftTop, util.degreesToRadians(-this.angle)); + } + return { + x: pClicked.x - objectLeftTop.x, + y: pClicked.y - objectLeftTop.y, + }; + }, + + toSVG(reviver: any): any { + const imageData = this.toDataURL(); + return ``; + }, + _toSVG() { + const offsets = this._getSVGLeftTopOffsets(); + const textAndBg = this._getSVGTextAndBg(offsets.textTop, offsets.textLeft); + return this._wrapSVGTextAndBg(textAndBg); + }, + + _getSVGLeftTopOffsets() { + return { + textLeft: -this.width / 2, + textTop: -this.height / 2, + lineTop: this.getHeightOfLine(0), + }; + }, + + _wrapSVGTextAndBg(textAndBg: any) { + const noShadow = true, + textDecoration = this.getSvgTextDecoration(this); + return [ + textAndBg.textBgRects.join(''), + '\t\t', + textAndBg.textSpans.join(''), + '\n', + ]; + }, + + _setSVGBg(textBgRects: any) { + if (this.backgroundColor) { + textBgRects.push( + '\t\t\n' + ); + } + }, + + _getSVGTextAndBg(textTopOffset: any, textLeftOffset: any) { + let textSpans: any[] = [], + textBgRects: any[] = [], + height = textTopOffset, + lineOffset; + this._setSVGBg(textBgRects); + + // text and text-background + for (let i = 0, len = this._textLines.length; i < len; i++) { + lineOffset = this._getLineLeftOffset(i); + if (this.textBackgroundColor || this.styleHas('textBackgroundColor', i)) { + this._setSVGTextLineBg(textBgRects, i, textLeftOffset + lineOffset, height); + } + this._setSVGTextLineText(textSpans, i, textLeftOffset + lineOffset, height); + height += this.getHeightOfLine(i); + } - for (let i = 0; i < lineIndex; i++) { - topOffset += this.getHeightOfLine(i); - } - const lineLeftOffset = this._getLineLeftOffset(lineIndex); - const bound = this.__charBounds.length ? this.__charBounds[lineIndex][charIndex] : undefined; - bound && (leftOffset = bound.left); - if ( - this.charSpacing !== 0 && - charIndex === this._textLines[lineIndex].length - ) { - leftOffset -= this._getWidthOfCharSpacing(); - } - const boundaries = { - top: topOffset, - left: lineLeftOffset + (leftOffset > 0 ? leftOffset : 0), - }; - if (this.direction === 'rtl') { - if ( - this.textAlign === 'right' || - this.textAlign === 'justify' || - this.textAlign === 'justify_right' - ) { - boundaries.left *= -1; - } else if (this.textAlign === 'left' || this.textAlign === 'justify_left') { - boundaries.left = lineLeftOffset - (leftOffset > 0 ? leftOffset : 0); - } else if ( - this.textAlign === 'center' || - this.textAlign === 'justify_center' - ) { - boundaries.left = lineLeftOffset - (leftOffset > 0 ? leftOffset : 0); - } - } - return boundaries; - }, - - _setSVGTextLineBg(textBgRects: string[], i: number, leftOffset: number, textTopOffset: number) { - let line = this._textLines[i], - heightOfLine = this.getHeightOfLine(i) / this.lineHeight, - boxWidth = 0, - boxStart = 0, - charBox, currentColor, - lastColor = this.getValueOfPropertyAt(i, 0, 'textBackgroundColor'); - for (let j = 0, jlen = line.length; j < jlen; j++) { - charBox = this.__charBounds[i][j]; - currentColor = this.getValueOfPropertyAt(i, j, 'textBackgroundColor'); - if (currentColor !== lastColor) { - lastColor && this._pushTextBgRect(textBgRects, lastColor, leftOffset + boxStart, - textTopOffset, boxWidth, heightOfLine); - boxStart = charBox.left; - boxWidth = charBox.width; - lastColor = currentColor; - } - else { - boxWidth += charBox.kernedWidth; - } - } - currentColor && this._pushTextBgRect(textBgRects, currentColor, leftOffset + boxStart, - textTopOffset, boxWidth, heightOfLine); - }, - - _getFillAttributes(value: any) { - let fillColor: any = (value && typeof value === 'string') ? new Color(value) : ''; - if (!fillColor || !fillColor.getSource() || fillColor.getAlpha() === 1) { - return 'fill="' + value + '"'; - } - return 'opacity="' + fillColor.getAlpha() + '" fill="' + fillColor.setAlpha(1).toRgb() + '"'; - }, + return { + textSpans: textSpans, + textBgRects: textBgRects, + }; + }, + + _createTextCharSpan(_char: string, styleDecl: any, left: number, top: number, angle: number) { + const shouldUseWhitespace = + _char !== _char.trim() || _char.match(multipleSpacesRegex) ? true : false, + styleProps = this.getSvgSpanStyles(styleDecl, shouldUseWhitespace), + fillStyles = styleProps ? 'style="' + styleProps + '"' : '', + dy = styleDecl.deltaY; + let dySpan = ''; + if (dy) { + dySpan = ' dy="' + util.toFixed(dy, NUM_FRACTION_DIGITS) + '" '; + } + return [ + '', + util.string.escapeXml(_char), + '', + ].join(''); + }, + + _hasStyleChangedForSvg(prevStyle: any, thisStyle: any) { + return ( + this._hasStyleChanged(prevStyle, thisStyle) || + prevStyle.overline !== thisStyle.overline || + prevStyle.underline !== thisStyle.underline || + prevStyle.linethrough !== thisStyle.linethrough + ); + }, + + _setSVGTextLineText( + textSpans: string[], + lineIndex: number, + textLeftOffset: number, + textTopOffset: number + ) { + // set proper line offset + let lineHeight = this.getHeightOfLine(lineIndex), + isJustify = this.textAlign.indexOf('justify') !== -1, + actualStyle, + nextStyle, + charsToRender = '', + charBox, + style, + boxWidth = 0, + line = this._textLines[lineIndex], + timeToRender; + + textTopOffset += (lineHeight * (1 - this._fontSizeFraction)) / this.lineHeight; + for (let i = 0, len = line.length - 1; i <= len; i++) { + timeToRender = i === len || this.charSpacing; + charsToRender += line[i]; + charBox = this._charTransformations[lineIndex][i]; + const angle = this.curvature > 0 ? -charBox.charAngle : -charBox.charAngle - Math.PI; + if (boxWidth === 0) { + textLeftOffset += charBox.kernedWidth - charBox.width; + boxWidth += charBox.width; + } else { + boxWidth += charBox.kernedWidth; + } + if (isJustify && !timeToRender) { + if (this._reSpaceAndTab.test(line[i])) { + timeToRender = true; + } + } + if (!timeToRender) { + actualStyle = actualStyle || this.getCompleteStyleDeclaration(lineIndex, i); + nextStyle = this.getCompleteStyleDeclaration(lineIndex, i + 1); + timeToRender = this._hasStyleChangedForSvg(actualStyle, nextStyle); + } + if (timeToRender) { + style = this._getStyleDeclaration(lineIndex, i) || {}; + // textSpans.push(this._createTextCharSpan(charsToRender, style, textLeftOffset, textTopOffset)); + const degress = (angle * 180) / Math.PI; + textSpans.push( + this._createTextCharSpan(charsToRender, style, charBox.cl.x, charBox.cl.y, degress) + ); + charsToRender = ''; + actualStyle = nextStyle; + textLeftOffset += boxWidth; + boxWidth = 0; + } + } + }, - _getSVGLineTopOffset(lineIndex: number) { - let lineTopOffset = 0, lastHeight = 0, j = 0; - for (let j = 0; j < lineIndex; j++) { - lineTopOffset += this.getHeightOfLine(j); - } - lastHeight = this.getHeightOfLine(j); - return { - lineTop: lineTopOffset, - offset: (this._fontSizeMult - this._fontSizeFraction) * lastHeight / (this.lineHeight * this._fontSizeMult) - }; - }, + _getCursorBoundariesOffsets(index: number) { + let topOffset = 0, + leftOffset = 0; + const { charIndex, lineIndex } = this.get2DCursorLocation(index); - getSvgStyles(skipShadow: boolean) { - const svgStyle = this.callSuper('getSvgStyles', skipShadow); - return svgStyle + ' white-space: pre;'; + for (let i = 0; i < lineIndex; i++) { + topOffset += this.getHeightOfLine(i); + } + const lineLeftOffset = this._getLineLeftOffset(lineIndex); + const bound = this.__charBounds.length ? this.__charBounds[lineIndex][charIndex] : undefined; + bound && (leftOffset = bound.left); + if (this.charSpacing !== 0 && charIndex === this._textLines[lineIndex].length) { + leftOffset -= this._getWidthOfCharSpacing(); + } + const boundaries = { + top: topOffset, + left: lineLeftOffset + (leftOffset > 0 ? leftOffset : 0), + }; + if (this.direction === 'rtl') { + if ( + this.textAlign === 'right' || + this.textAlign === 'justify' || + this.textAlign === 'justify_right' + ) { + boundaries.left *= -1; + } else if (this.textAlign === 'left' || this.textAlign === 'justify_left') { + boundaries.left = lineLeftOffset - (leftOffset > 0 ? leftOffset : 0); + } else if (this.textAlign === 'center' || this.textAlign === 'justify_center') { + boundaries.left = lineLeftOffset - (leftOffset > 0 ? leftOffset : 0); + } } + return boundaries; + }, + + _setSVGTextLineBg(textBgRects: string[], i: number, leftOffset: number, textTopOffset: number) { + let line = this._textLines[i], + heightOfLine = this.getHeightOfLine(i) / this.lineHeight, + boxWidth = 0, + boxStart = 0, + charBox, + currentColor, + lastColor = this.getValueOfPropertyAt(i, 0, 'textBackgroundColor'); + for (let j = 0, jlen = line.length; j < jlen; j++) { + charBox = this.__charBounds[i][j]; + currentColor = this.getValueOfPropertyAt(i, j, 'textBackgroundColor'); + if (currentColor !== lastColor) { + lastColor && + this._pushTextBgRect( + textBgRects, + lastColor, + leftOffset + boxStart, + textTopOffset, + boxWidth, + heightOfLine + ); + boxStart = charBox.left; + boxWidth = charBox.width; + lastColor = currentColor; + } else { + boxWidth += charBox.kernedWidth; + } + } + currentColor && + this._pushTextBgRect( + textBgRects, + currentColor, + leftOffset + boxStart, + textTopOffset, + boxWidth, + heightOfLine + ); + }, + + _getFillAttributes(value: any) { + let fillColor: any = value && typeof value === 'string' ? new Color(value) : ''; + if (!fillColor || !fillColor.getSource() || fillColor.getAlpha() === 1) { + return 'fill="' + value + '"'; + } + return 'opacity="' + fillColor.getAlpha() + '" fill="' + fillColor.setAlpha(1).toRgb() + '"'; + }, + + _getSVGLineTopOffset(lineIndex: number) { + let lineTopOffset = 0, + lastHeight = 0, + j = 0; + for (let j = 0; j < lineIndex; j++) { + lineTopOffset += this.getHeightOfLine(j); + } + lastHeight = this.getHeightOfLine(j); + return { + lineTop: lineTopOffset, + offset: + ((this._fontSizeMult - this._fontSizeFraction) * lastHeight) / + (this.lineHeight * this._fontSizeMult), + }; + }, + + getSvgStyles(skipShadow: boolean) { + const svgStyle = this.callSuper('getSvgStyles', skipShadow); + return svgStyle + ' white-space: pre;'; + }, }); + +function _fromObject(klass: any, object: any, callback: any, extraParam: string) { + (util as any).enlivenPatterns([object.fill, object.stroke], function (patterns: any[]) { + if (typeof patterns[0] !== 'undefined') { + object.fill = patterns[0]; + } + if (typeof patterns[1] !== 'undefined') { + object.stroke = patterns[1]; + } + (util as any).enlivenObjectEnlivables(object, object, function () { + const instance = extraParam ? new klass(object[extraParam], object) : new klass(object); + callback && callback(instance); + }); + }); +} + +ArcText.fromObject = function (object: any, callback: any) { + const clone = fabric.util.object.clone as (object: any, deep: boolean) => any; + const options = clone(object, true); + if (!options.radius) options.radius = 311; + if (!options.curvature) options.curvature = Math.floor(BaseRadius / options.radius); + _fromObject(ArcText, options, callback, 'text'); +}; + +export default ArcText; From 68bc22cfee6c10c64cd95155be3630433d102af0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B7=B1=E6=B8=8A=E5=8F=A3=E9=A6=99=E7=B3=96?= <5183454+yuanjianming666@user.noreply.gitee.com> Date: Tue, 12 Nov 2024 23:51:43 +0800 Subject: [PATCH 5/5] =?UTF-8?q?fix:=20=E6=96=B0=E5=A2=9E=20toObject?= =?UTF-8?q?=E5=92=8CfromObject?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc.js | 1 - 1 file changed, 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index a0036b50..c6eab674 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -58,7 +58,6 @@ module.exports = { extendDefaults: true, }, ], - 'no-prototype-builtins': 'off', }, overrides: [ {