diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a69a02a983..f7e299f9594 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## [next] +- feat(IText): expose getCursorRenderingData() function. [#10204](https://github.com/fabricjs/fabric.js/pull/10204) - fix(Canvas): allowTouchScrolling interactions [#10078](https://github.com/fabricjs/fabric.js/pull/10078) - update(IText): Add method enterEditingImpl/exitEditingImpl that executes the logic of enterEditing/exitEditing without events [#10187](https://github.com/fabricjs/fabric.js/issues/10187) - fix(FabricObject): Fix clipPath blurryness with scale [#9774](https://github.com/fabricjs/fabric.js/pull/9774) diff --git a/fabric.ts b/fabric.ts index 9c39c58ef45..b0ac64e9ebb 100644 --- a/fabric.ts +++ b/fabric.ts @@ -116,6 +116,8 @@ export { export type { ITextProps, SerializedITextProps, + CursorRenderingData, + CursorBoundaries, } from './src/shapes/IText/IText'; export { IText } from './src/shapes/IText/IText'; export type { diff --git a/src/shapes/IText/IText.ts b/src/shapes/IText/IText.ts index aba9905e839..58975dad9f2 100644 --- a/src/shapes/IText/IText.ts +++ b/src/shapes/IText/IText.ts @@ -19,13 +19,22 @@ import { import { CENTER, FILL, LEFT, RIGHT } from '../../constants'; import type { ObjectToCanvasElementOptions } from '../Object/Object'; -type CursorBoundaries = { +export type CursorBoundaries = { left: number; top: number; leftOffset: number; topOffset: number; }; +export type CursorRenderingData = { + color: string; + opacity: number; + left: number; + top: number; + width: number; + height: number; +}; + // Declare IText protected properties to workaround TS const protectedDefaultValues = { _selectionDirection: null, @@ -380,7 +389,7 @@ export class IText< return; } const boundaries = this._getCursorBoundaries(); - if (this.selectionStart === this.selectionEnd) { + if (this.selectionStart === this.selectionEnd && !this.inCompositionMode) { this.renderCursor(ctx, boundaries); } else { this.renderSelection(ctx, boundaries); @@ -482,8 +491,11 @@ export class IText< * If contextTop is not available, do nothing. */ renderCursorAt(selectionStart: number) { - const boundaries = this._getCursorBoundaries(selectionStart, true); - this._renderCursor(this.canvas!.contextTop, boundaries, selectionStart); + this._renderCursor( + this.canvas!.contextTop, + this._getCursorBoundaries(selectionStart, true), + selectionStart, + ); } /** @@ -495,11 +507,16 @@ export class IText< this._renderCursor(ctx, boundaries, this.selectionStart); } - _renderCursor( - ctx: CanvasRenderingContext2D, - boundaries: CursorBoundaries, - selectionStart: number, - ) { + /** + * Return the data needed to render the cursor for given selection start + * The left,top are relative to the object, while width and height are prescaled + * to look think with canvas zoom and object scaling, + * so they depend on canvas and object scaling + */ + getCursorRenderingData( + selectionStart: number = this.selectionStart, + boundaries: CursorBoundaries = this._getCursorBoundaries(selectionStart), + ): CursorRenderingData { const cursorLocation = this.get2DCursorLocation(selectionStart), lineIndex = cursorLocation.lineIndex, charIndex = @@ -514,21 +531,32 @@ export class IText< this.lineHeight - charHeight * (1 - this._fontSizeFraction); - if (this.inCompositionMode) { - // TODO: investigate why there isn't a return inside the if, - // and why can't happen at the top of the function - this.renderSelection(ctx, boundaries); - } - ctx.fillStyle = - this.cursorColor || - (this.getValueOfPropertyAt(lineIndex, charIndex, FILL) as string); - ctx.globalAlpha = this._currentCursorOpacity; - ctx.fillRect( - boundaries.left + boundaries.leftOffset - cursorWidth / 2, - topOffset + boundaries.top + dy, - cursorWidth, - charHeight, - ); + return { + color: + this.cursorColor || + (this.getValueOfPropertyAt(lineIndex, charIndex, 'fill') as string), + opacity: this._currentCursorOpacity, + left: boundaries.left + boundaries.leftOffset - cursorWidth / 2, + top: topOffset + boundaries.top + dy, + width: cursorWidth, + height: charHeight, + }; + } + + /** + * Render the cursor at the given selectionStart. + * + */ + _renderCursor( + ctx: CanvasRenderingContext2D, + boundaries: CursorBoundaries, + selectionStart: number, + ) { + const { color, opacity, left, top, width, height } = + this.getCursorRenderingData(selectionStart, boundaries); + ctx.fillStyle = color; + ctx.globalAlpha = opacity; + ctx.fillRect(left, top, width, height); } /**