Comparing version
@@ -1,2 +0,2 @@ | ||
import type { CanvasEvents, DragEventData, ObjectEvents, TPointerEvent, TPointerEventNames, Transform } from '../EventTypeDefs'; | ||
import type { CanvasEvents, DragEventData, ObjectEvents, TEventsExtraData, TPointerEvent, TPointerEventNames, Transform } from '../EventTypeDefs'; | ||
import { Point } from '../Point'; | ||
@@ -47,2 +47,8 @@ import type { FabricObject } from '../shapes/Object/FabricObject'; | ||
private _dropTarget; | ||
/** | ||
* a boolean that keeps track of the click state during a cycle of mouse down/up. | ||
* If a mouse move occurs it becomes false. | ||
* Is true by default, turns false on mouse move. | ||
* Used to determine if a mouseUp is a click | ||
*/ | ||
private _isClick; | ||
@@ -147,3 +153,3 @@ textEditingManager: TextEditingManager; | ||
*/ | ||
private _onDoubleClick; | ||
private _onClick; | ||
/** | ||
@@ -212,3 +218,3 @@ * Return a the id of an event. | ||
*/ | ||
_handleEvent<T extends TPointerEventNames>(e: TPointerEvent, eventType: T): void; | ||
_handleEvent<T extends TPointerEventNames>(e: TPointerEvent, eventType: T, extraData?: TEventsExtraData[T]): void; | ||
/** | ||
@@ -215,0 +221,0 @@ * @private |
@@ -211,8 +211,22 @@ import type { Control } from './controls/Control'; | ||
type WithBeforeSuffix<T extends string> = T | BeforeSuffix<T>; | ||
type TPointerEvents<Prefix extends string> = Record<`${Prefix}${WithBeforeSuffix<'down'> | WithBeforeSuffix<'move'> | 'dblclick'}`, TPointerEventInfo> & Record<`${Prefix}${WithBeforeSuffix<'up'>}`, TPointerEventInfo & { | ||
type TPointerEvents<Prefix extends string> = Record<`${Prefix}${WithBeforeSuffix<'down'> | WithBeforeSuffix<'move'> | 'dblclick' | 'tripleclick'}`, TPointerEventInfo> & Record<`${Prefix}down`, TPointerEventInfo & { | ||
/** | ||
* Indicates if the target or current target where already selected | ||
* before the cycle of mouse down -> mouse up started | ||
*/ | ||
alreadySelected: boolean; | ||
}> & Record<`${Prefix}${WithBeforeSuffix<'up'>}`, TPointerEventInfo & { | ||
isClick: boolean; | ||
/** | ||
* The targets at the moment of mouseup that could be different from the | ||
* target at the moment of mouse down in case of a drag action for example | ||
*/ | ||
currentTarget?: FabricObject; | ||
/** | ||
* The subtargets at the moment of mouseup that could be different from the | ||
* target at the moment of mouse down in case of a drag action for example | ||
*/ | ||
currentSubTargets: FabricObject[]; | ||
}> & Record<`${Prefix}wheel`, TPointerEventInfo<WheelEvent>> & Record<`${Prefix}over`, TPointerEventInfo & InEvent> & Record<`${Prefix}out`, TPointerEventInfo & OutEvent>; | ||
export type TPointerEventNames = WithBeforeSuffix<'down'> | WithBeforeSuffix<'move'> | WithBeforeSuffix<'up'> | 'dblclick' | 'wheel'; | ||
export type TPointerEventNames = WithBeforeSuffix<'down'> | WithBeforeSuffix<'move'> | WithBeforeSuffix<'up'> | 'dblclick' | 'tripleclick' | 'wheel'; | ||
export type ObjectPointerEvents = TPointerEvents<'mouse'>; | ||
@@ -286,3 +300,6 @@ export type CanvasPointerEvents = TPointerEvents<'mouse:'>; | ||
} | ||
export type TEventsExtraData = Record<PropertyKey, Record<PropertyKey, never>> & Record<'down', { | ||
alreadySelected: boolean; | ||
}>; | ||
export {}; | ||
//# sourceMappingURL=EventTypeDefs.d.ts.map |
export type TKeyMapIText = Record<KeyboardEvent['keyCode'], CursorHandlingMethods>; | ||
export type CursorHandlingMethods = 'moveCursorUp' | 'moveCursorDown' | 'moveCursorLeft' | 'moveCursorRight' | 'exitEditing' | 'copy' | 'cut' | 'selectAll'; | ||
export type CursorHandlingMethods = 'moveCursorUp' | 'moveCursorDown' | 'moveCursorLeft' | 'moveCursorRight' | 'exitEditing' | 'copy' | 'cut' | 'cmdAll'; | ||
export declare const keysMap: TKeyMapIText; | ||
@@ -4,0 +4,0 @@ export declare const keysMapRtl: TKeyMapIText; |
@@ -6,2 +6,3 @@ import type { ITextEvents } from './ITextBehavior'; | ||
import type { ObjectToCanvasElementOptions } from '../Object/Object'; | ||
import type { FabricObject } from '../Object/FabricObject'; | ||
export type CursorBoundaries = { | ||
@@ -233,2 +234,10 @@ left: number; | ||
/** | ||
* Finds and returns an array of clip paths that are applied to the parent | ||
* group(s) of the current FabricObject instance. The object's hierarchy is | ||
* traversed upwards (from the current object towards the root of the canvas), | ||
* checking each parent object for the presence of a `clipPath` that is not | ||
* absolutely positioned. | ||
*/ | ||
findAncestorsWithClipPath(): FabricObject[]; | ||
/** | ||
* Returns cursor boundaries (left, top, leftOffset, topOffset) | ||
@@ -282,3 +291,3 @@ * left/top are left/top of entire text box | ||
* Render the cursor at the given selectionStart. | ||
* | ||
* @param {CanvasRenderingContext2D} ctx transformed context to draw on | ||
*/ | ||
@@ -285,0 +294,0 @@ _renderCursor(ctx: CanvasRenderingContext2D, boundaries: CursorBoundaries, selectionStart: number): void; |
@@ -1,2 +0,2 @@ | ||
import type { ObjectEvents, TPointerEvent, TPointerEventInfo } from '../../EventTypeDefs'; | ||
import type { ObjectEvents, TPointerEvent } from '../../EventTypeDefs'; | ||
import type { FabricObject } from '../Object/FabricObject'; | ||
@@ -15,3 +15,2 @@ import { FabricText } from '../Text/Text'; | ||
}; | ||
tripleclick: TPointerEventInfo; | ||
'editing:entered': never | { | ||
@@ -44,2 +43,6 @@ e: TPointerEvent; | ||
protected __selectionStartOnMouseDown: number; | ||
/** | ||
* Keeps track if the IText object was selected before the actual click. | ||
* This because we want to delay enter editing by a click. | ||
*/ | ||
protected selected: boolean; | ||
@@ -114,2 +117,6 @@ protected cursorOffsetCache: { | ||
/** | ||
* Selects entire text and updates the visual state | ||
*/ | ||
cmdAll(): void; | ||
/** | ||
* Returns selected text | ||
@@ -151,4 +158,3 @@ * @return {String} | ||
/** | ||
* TODO fix: selectionStart set as 0 will be ignored? | ||
* Selects a word based on the index | ||
* Selects the word that contains the char at index selectionStart | ||
* @param {Number} selectionStart Index of a character | ||
@@ -158,7 +164,6 @@ */ | ||
/** | ||
* TODO fix: selectionStart set as 0 will be ignored? | ||
* Selects a line based on the index | ||
* Selects the line that contains selectionStart | ||
* @param {Number} selectionStart Index of a character | ||
*/ | ||
selectLine(selectionStart?: number): this; | ||
selectLine(selectionStart?: number): void; | ||
/** | ||
@@ -165,0 +170,0 @@ * Enters editing state |
@@ -1,3 +0,2 @@ | ||
import type { TPointerEvent, TPointerEventInfo } from '../../EventTypeDefs'; | ||
import type { XY } from '../../Point'; | ||
import type { ObjectPointerEvents, TPointerEvent, TPointerEventInfo } from '../../EventTypeDefs'; | ||
import { DraggableTextDelegate } from './DraggableTextDelegate'; | ||
@@ -9,7 +8,2 @@ import type { ITextEvents } from './ITextBehavior'; | ||
export declare abstract class ITextClickBehavior<Props extends TOptions<TextProps> = Partial<TextProps>, SProps extends SerializedTextProps = SerializedTextProps, EventSpec extends ITextEvents = ITextEvents> extends ITextKeyBehavior<Props, SProps, EventSpec> { | ||
private __lastSelected; | ||
private __lastClickTime; | ||
private __lastLastClickTime; | ||
private __lastPointer; | ||
private __newClickTime; | ||
protected draggableTextDelegate: DraggableTextDelegate; | ||
@@ -37,8 +31,2 @@ initBehavior(): void; | ||
/** | ||
* Default event handler to simulate triple click | ||
* @private | ||
*/ | ||
onMouseDown(options: TPointerEventInfo): void; | ||
isTripleClick(newPointer: XY): boolean; | ||
/** | ||
* Default handler for double click, select a word | ||
@@ -59,14 +47,8 @@ */ | ||
*/ | ||
_mouseDownHandler({ e }: TPointerEventInfo): void; | ||
_mouseDownHandler({ e, alreadySelected }: ObjectPointerEvents['mousedown']): void; | ||
/** | ||
* Default event handler for the basic functionalities needed on mousedown:before | ||
* can be overridden to do something different. | ||
* Scope of this implementation is: verify the object is already selected when mousing down | ||
*/ | ||
_mouseDownHandlerBefore({ e }: TPointerEventInfo): void; | ||
/** | ||
* standard handler for mouse up, overridable | ||
* @private | ||
*/ | ||
mouseUpHandler({ e, transform }: TPointerEventInfo): void; | ||
mouseUpHandler({ e, transform }: ObjectPointerEvents['mouseup']): void; | ||
/** | ||
@@ -73,0 +55,0 @@ * Changes cursor location in a text depending on passed pointer (x/y) object |
@@ -13,3 +13,4 @@ export { cos } from './misc/cos'; | ||
export { toFixed } from './misc/toFixed'; | ||
export { matrixToSVG, parsePreserveAspectRatioAttribute, parseUnit, getSvgAttributes, } from './misc/svgParsing'; | ||
export { parsePreserveAspectRatioAttribute, parseUnit, getSvgAttributes, } from './misc/svgParsing'; | ||
export { matrixToSVG } from './misc/svgExport'; | ||
export { groupSVGElements } from './misc/groupSVGElements'; | ||
@@ -16,0 +17,0 @@ export { findScaleToFit, findScaleToCover } from './misc/findScaleTo'; |
@@ -18,2 +18,7 @@ import type { ImageFormat, TSize } from '../../typedefs'; | ||
export declare const copyCanvasElement: (canvas: HTMLCanvasElement) => HTMLCanvasElement; | ||
/** | ||
* Creates a canvas element as big as another | ||
* @param {CanvasElement} canvas to copy size and content of | ||
* @return {CanvasElement} initialized canvas element | ||
*/ | ||
export declare const createCanvasElementFor: (canvas: HTMLCanvasElement | ImageData | HTMLImageElement | TSize) => HTMLCanvasElement; | ||
@@ -25,3 +30,3 @@ /** | ||
* @param {String} format 'jpeg' or 'png', in some browsers 'webp' is ok too | ||
* @param {Number} quality <= 1 and > 0 | ||
* @param {number} quality <= 1 and > 0 | ||
* @return {String} data url | ||
@@ -28,0 +33,0 @@ */ |
@@ -1,2 +0,2 @@ | ||
import type { TBBox, TMat2D, SVGElementName } from '../../typedefs'; | ||
import type { TBBox, SVGElementName } from '../../typedefs'; | ||
/** | ||
@@ -31,8 +31,2 @@ * Returns array of attributes for given svg that fabric parses | ||
/** | ||
* given an array of 6 number returns something like `"matrix(...numbers)"` | ||
* @param {TMat2D} transform an array with 6 numbers | ||
* @return {String} transform matrix for svg | ||
*/ | ||
export declare const matrixToSVG: (transform: TMat2D) => string; | ||
/** | ||
* Adobe Illustrator (at least CS5) is unable to render rgba()-based fill values | ||
@@ -39,0 +33,0 @@ * we work around it by "moving" alpha channel into opacity attribute and setting fill's alpha to 1 |
@@ -1,2 +0,2 @@ | ||
import type { CanvasEvents, DragEventData, ObjectEvents, TPointerEvent, TPointerEventNames, Transform } from '../EventTypeDefs'; | ||
import type { CanvasEvents, DragEventData, ObjectEvents, TEventsExtraData, TPointerEvent, TPointerEventNames, Transform } from '../EventTypeDefs'; | ||
import { Point } from '../Point'; | ||
@@ -47,2 +47,8 @@ import type { FabricObject } from '../shapes/Object/FabricObject'; | ||
private _dropTarget; | ||
/** | ||
* a boolean that keeps track of the click state during a cycle of mouse down/up. | ||
* If a mouse move occurs it becomes false. | ||
* Is true by default, turns false on mouse move. | ||
* Used to determine if a mouseUp is a click | ||
*/ | ||
private _isClick; | ||
@@ -147,3 +153,3 @@ textEditingManager: TextEditingManager; | ||
*/ | ||
private _onDoubleClick; | ||
private _onClick; | ||
/** | ||
@@ -212,3 +218,3 @@ * Return a the id of an event. | ||
*/ | ||
_handleEvent<T extends TPointerEventNames>(e: TPointerEvent, eventType: T): void; | ||
_handleEvent<T extends TPointerEventNames>(e: TPointerEvent, eventType: T, extraData?: TEventsExtraData[T]): void; | ||
/** | ||
@@ -215,0 +221,0 @@ * @private |
@@ -211,8 +211,22 @@ import type { Control } from './controls/Control'; | ||
type WithBeforeSuffix<T extends string> = T | BeforeSuffix<T>; | ||
type TPointerEvents<Prefix extends string> = Record<`${Prefix}${WithBeforeSuffix<'down'> | WithBeforeSuffix<'move'> | 'dblclick'}`, TPointerEventInfo> & Record<`${Prefix}${WithBeforeSuffix<'up'>}`, TPointerEventInfo & { | ||
type TPointerEvents<Prefix extends string> = Record<`${Prefix}${WithBeforeSuffix<'down'> | WithBeforeSuffix<'move'> | 'dblclick' | 'tripleclick'}`, TPointerEventInfo> & Record<`${Prefix}down`, TPointerEventInfo & { | ||
/** | ||
* Indicates if the target or current target where already selected | ||
* before the cycle of mouse down -> mouse up started | ||
*/ | ||
alreadySelected: boolean; | ||
}> & Record<`${Prefix}${WithBeforeSuffix<'up'>}`, TPointerEventInfo & { | ||
isClick: boolean; | ||
/** | ||
* The targets at the moment of mouseup that could be different from the | ||
* target at the moment of mouse down in case of a drag action for example | ||
*/ | ||
currentTarget?: FabricObject; | ||
/** | ||
* The subtargets at the moment of mouseup that could be different from the | ||
* target at the moment of mouse down in case of a drag action for example | ||
*/ | ||
currentSubTargets: FabricObject[]; | ||
}> & Record<`${Prefix}wheel`, TPointerEventInfo<WheelEvent>> & Record<`${Prefix}over`, TPointerEventInfo & InEvent> & Record<`${Prefix}out`, TPointerEventInfo & OutEvent>; | ||
export type TPointerEventNames = WithBeforeSuffix<'down'> | WithBeforeSuffix<'move'> | WithBeforeSuffix<'up'> | 'dblclick' | 'wheel'; | ||
export type TPointerEventNames = WithBeforeSuffix<'down'> | WithBeforeSuffix<'move'> | WithBeforeSuffix<'up'> | 'dblclick' | 'tripleclick' | 'wheel'; | ||
export type ObjectPointerEvents = TPointerEvents<'mouse'>; | ||
@@ -286,3 +300,6 @@ export type CanvasPointerEvents = TPointerEvents<'mouse:'>; | ||
} | ||
export type TEventsExtraData = Record<PropertyKey, Record<PropertyKey, never>> & Record<'down', { | ||
alreadySelected: boolean; | ||
}>; | ||
export {}; | ||
//# sourceMappingURL=EventTypeDefs.d.ts.map |
export type TKeyMapIText = Record<KeyboardEvent['keyCode'], CursorHandlingMethods>; | ||
export type CursorHandlingMethods = 'moveCursorUp' | 'moveCursorDown' | 'moveCursorLeft' | 'moveCursorRight' | 'exitEditing' | 'copy' | 'cut' | 'selectAll'; | ||
export type CursorHandlingMethods = 'moveCursorUp' | 'moveCursorDown' | 'moveCursorLeft' | 'moveCursorRight' | 'exitEditing' | 'copy' | 'cut' | 'cmdAll'; | ||
export declare const keysMap: TKeyMapIText; | ||
@@ -4,0 +4,0 @@ export declare const keysMapRtl: TKeyMapIText; |
@@ -6,2 +6,3 @@ import type { ITextEvents } from './ITextBehavior'; | ||
import type { ObjectToCanvasElementOptions } from '../Object/Object'; | ||
import type { FabricObject } from '../Object/FabricObject'; | ||
export type CursorBoundaries = { | ||
@@ -233,2 +234,10 @@ left: number; | ||
/** | ||
* Finds and returns an array of clip paths that are applied to the parent | ||
* group(s) of the current FabricObject instance. The object's hierarchy is | ||
* traversed upwards (from the current object towards the root of the canvas), | ||
* checking each parent object for the presence of a `clipPath` that is not | ||
* absolutely positioned. | ||
*/ | ||
findAncestorsWithClipPath(): FabricObject[]; | ||
/** | ||
* Returns cursor boundaries (left, top, leftOffset, topOffset) | ||
@@ -282,3 +291,3 @@ * left/top are left/top of entire text box | ||
* Render the cursor at the given selectionStart. | ||
* | ||
* @param {CanvasRenderingContext2D} ctx transformed context to draw on | ||
*/ | ||
@@ -285,0 +294,0 @@ _renderCursor(ctx: CanvasRenderingContext2D, boundaries: CursorBoundaries, selectionStart: number): void; |
@@ -1,2 +0,2 @@ | ||
import type { ObjectEvents, TPointerEvent, TPointerEventInfo } from '../../EventTypeDefs'; | ||
import type { ObjectEvents, TPointerEvent } from '../../EventTypeDefs'; | ||
import type { FabricObject } from '../Object/FabricObject'; | ||
@@ -15,3 +15,2 @@ import { FabricText } from '../Text/Text'; | ||
}; | ||
tripleclick: TPointerEventInfo; | ||
'editing:entered': never | { | ||
@@ -44,2 +43,6 @@ e: TPointerEvent; | ||
protected __selectionStartOnMouseDown: number; | ||
/** | ||
* Keeps track if the IText object was selected before the actual click. | ||
* This because we want to delay enter editing by a click. | ||
*/ | ||
protected selected: boolean; | ||
@@ -114,2 +117,6 @@ protected cursorOffsetCache: { | ||
/** | ||
* Selects entire text and updates the visual state | ||
*/ | ||
cmdAll(): void; | ||
/** | ||
* Returns selected text | ||
@@ -151,4 +158,3 @@ * @return {String} | ||
/** | ||
* TODO fix: selectionStart set as 0 will be ignored? | ||
* Selects a word based on the index | ||
* Selects the word that contains the char at index selectionStart | ||
* @param {Number} selectionStart Index of a character | ||
@@ -158,7 +164,6 @@ */ | ||
/** | ||
* TODO fix: selectionStart set as 0 will be ignored? | ||
* Selects a line based on the index | ||
* Selects the line that contains selectionStart | ||
* @param {Number} selectionStart Index of a character | ||
*/ | ||
selectLine(selectionStart?: number): this; | ||
selectLine(selectionStart?: number): void; | ||
/** | ||
@@ -165,0 +170,0 @@ * Enters editing state |
@@ -1,3 +0,2 @@ | ||
import type { TPointerEvent, TPointerEventInfo } from '../../EventTypeDefs'; | ||
import type { XY } from '../../Point'; | ||
import type { ObjectPointerEvents, TPointerEvent, TPointerEventInfo } from '../../EventTypeDefs'; | ||
import { DraggableTextDelegate } from './DraggableTextDelegate'; | ||
@@ -9,7 +8,2 @@ import type { ITextEvents } from './ITextBehavior'; | ||
export declare abstract class ITextClickBehavior<Props extends TOptions<TextProps> = Partial<TextProps>, SProps extends SerializedTextProps = SerializedTextProps, EventSpec extends ITextEvents = ITextEvents> extends ITextKeyBehavior<Props, SProps, EventSpec> { | ||
private __lastSelected; | ||
private __lastClickTime; | ||
private __lastLastClickTime; | ||
private __lastPointer; | ||
private __newClickTime; | ||
protected draggableTextDelegate: DraggableTextDelegate; | ||
@@ -37,8 +31,2 @@ initBehavior(): void; | ||
/** | ||
* Default event handler to simulate triple click | ||
* @private | ||
*/ | ||
onMouseDown(options: TPointerEventInfo): void; | ||
isTripleClick(newPointer: XY): boolean; | ||
/** | ||
* Default handler for double click, select a word | ||
@@ -59,14 +47,8 @@ */ | ||
*/ | ||
_mouseDownHandler({ e }: TPointerEventInfo): void; | ||
_mouseDownHandler({ e, alreadySelected }: ObjectPointerEvents['mousedown']): void; | ||
/** | ||
* Default event handler for the basic functionalities needed on mousedown:before | ||
* can be overridden to do something different. | ||
* Scope of this implementation is: verify the object is already selected when mousing down | ||
*/ | ||
_mouseDownHandlerBefore({ e }: TPointerEventInfo): void; | ||
/** | ||
* standard handler for mouse up, overridable | ||
* @private | ||
*/ | ||
mouseUpHandler({ e, transform }: TPointerEventInfo): void; | ||
mouseUpHandler({ e, transform }: ObjectPointerEvents['mouseup']): void; | ||
/** | ||
@@ -73,0 +55,0 @@ * Changes cursor location in a text depending on passed pointer (x/y) object |
@@ -13,3 +13,4 @@ export { cos } from './misc/cos'; | ||
export { toFixed } from './misc/toFixed'; | ||
export { matrixToSVG, parsePreserveAspectRatioAttribute, parseUnit, getSvgAttributes, } from './misc/svgParsing'; | ||
export { parsePreserveAspectRatioAttribute, parseUnit, getSvgAttributes, } from './misc/svgParsing'; | ||
export { matrixToSVG } from './misc/svgExport'; | ||
export { groupSVGElements } from './misc/groupSVGElements'; | ||
@@ -16,0 +17,0 @@ export { findScaleToFit, findScaleToCover } from './misc/findScaleTo'; |
@@ -18,2 +18,7 @@ import type { ImageFormat, TSize } from '../../typedefs'; | ||
export declare const copyCanvasElement: (canvas: HTMLCanvasElement) => HTMLCanvasElement; | ||
/** | ||
* Creates a canvas element as big as another | ||
* @param {CanvasElement} canvas to copy size and content of | ||
* @return {CanvasElement} initialized canvas element | ||
*/ | ||
export declare const createCanvasElementFor: (canvas: HTMLCanvasElement | ImageData | HTMLImageElement | TSize) => HTMLCanvasElement; | ||
@@ -25,3 +30,3 @@ /** | ||
* @param {String} format 'jpeg' or 'png', in some browsers 'webp' is ok too | ||
* @param {Number} quality <= 1 and > 0 | ||
* @param {number} quality <= 1 and > 0 | ||
* @return {String} data url | ||
@@ -28,0 +33,0 @@ */ |
@@ -1,2 +0,2 @@ | ||
import type { TBBox, TMat2D, SVGElementName } from '../../typedefs'; | ||
import type { TBBox, SVGElementName } from '../../typedefs'; | ||
/** | ||
@@ -31,8 +31,2 @@ * Returns array of attributes for given svg that fabric parses | ||
/** | ||
* given an array of 6 number returns something like `"matrix(...numbers)"` | ||
* @param {TMat2D} transform an array with 6 numbers | ||
* @return {String} transform matrix for svg | ||
*/ | ||
export declare const matrixToSVG: (transform: TMat2D) => string; | ||
/** | ||
* Adobe Illustrator (at least CS5) is unable to render rgba()-based fill values | ||
@@ -39,0 +33,0 @@ * we work around it by "moving" alpha channel into opacity attribute and setting fill's alpha to 1 |
@@ -5,3 +5,3 @@ { | ||
"homepage": "http://fabricjs.com/", | ||
"version": "6.6.1", | ||
"version": "6.6.2", | ||
"author": "Juriy Zaytsev <kangax@gmail.com>", | ||
@@ -113,7 +113,7 @@ "contributors": [ | ||
"moment": "^2.29.1", | ||
"nyc": "^15.1.0", | ||
"nyc": "^17.x", | ||
"pixelmatch": "^4.0.2", | ||
"prettier": "^3.3.1", | ||
"ps-list": "^8.1.0", | ||
"qunit": "^2.17.2", | ||
"qunit": "^2.24.1", | ||
"rollup": "^4.20.0", | ||
@@ -120,0 +120,0 @@ "semver": "^7.3.8", |
@@ -57,5 +57,9 @@ /* eslint-disable no-restricted-globals */ | ||
canvas.setViewportTransform(genericVpt); | ||
canvas | ||
.getSelectionElement() | ||
.dispatchEvent(new MouseEvent(type, { clientX: 50, clientY: 50 })); | ||
canvas.getSelectionElement().dispatchEvent( | ||
new MouseEvent(type, { | ||
clientX: 50, | ||
clientY: 50, | ||
detail: type === 'dblclick' ? 2 : undefined, | ||
}), | ||
); | ||
expect(spy.mock.calls).toMatchSnapshot(snapshotOptions); | ||
@@ -62,0 +66,0 @@ }, |
@@ -7,2 +7,3 @@ import { classRegistry } from '../ClassRegistry'; | ||
ObjectEvents, | ||
TEventsExtraData, | ||
TPointerEvent, | ||
@@ -114,2 +115,8 @@ TPointerEventNames, | ||
/** | ||
* a boolean that keeps track of the click state during a cycle of mouse down/up. | ||
* If a mouse move occurs it becomes false. | ||
* Is true by default, turns false on mouse move. | ||
* Used to determine if a mouseUp is a click | ||
*/ | ||
private _isClick: boolean; | ||
@@ -139,3 +146,3 @@ | ||
'_onContextMenu', | ||
'_onDoubleClick', | ||
'_onClick', | ||
'_onDragStart', | ||
@@ -180,3 +187,5 @@ '_onDragEnd', | ||
functor(canvasElement, 'contextmenu', this._onContextMenu); | ||
functor(canvasElement, 'dblclick', this._onDoubleClick); | ||
functor(canvasElement, 'click', this._onClick); | ||
// decide if to remove in fabric 7.0 | ||
functor(canvasElement, 'dblclick', this._onClick); | ||
functor(canvasElement, 'dragstart', this._onDragStart); | ||
@@ -551,5 +560,8 @@ functor(canvasElement, 'dragend', this._onDragEnd); | ||
*/ | ||
private _onDoubleClick(e: TPointerEvent) { | ||
private _onClick(e: TPointerEvent) { | ||
const clicks = e.detail; | ||
if (clicks > 3 || clicks < 2) return; | ||
this._cacheTransformEventData(e); | ||
this._handleEvent(e, 'dblclick'); | ||
clicks == 2 && e.type === 'dblclick' && this._handleEvent(e, 'dblclick'); | ||
clicks == 3 && this._handleEvent(e, 'tripleclick'); | ||
this._resetTransformEventData(); | ||
@@ -919,3 +931,7 @@ } | ||
*/ | ||
_handleEvent<T extends TPointerEventNames>(e: TPointerEvent, eventType: T) { | ||
_handleEvent<T extends TPointerEventNames>( | ||
e: TPointerEvent, | ||
eventType: T, | ||
extraData?: TEventsExtraData[T], | ||
) { | ||
const target = this._target, | ||
@@ -937,2 +953,5 @@ targets = this.targets || [], | ||
: {}), | ||
...(eventType === 'down:before' || eventType === 'down' | ||
? extraData | ||
: {}), | ||
} as CanvasEvents[`mouse:${T}`]; | ||
@@ -961,3 +980,3 @@ this.fire(`mouse:${eventType}`, options); | ||
this.freeDrawingBrush.onMouseDown(pointer, { e, pointer }); | ||
this._handleEvent(e, 'down'); | ||
this._handleEvent(e, 'down', { alreadySelected: false }); | ||
} | ||
@@ -1015,3 +1034,3 @@ | ||
let target: FabricObject | undefined = this._target; | ||
let alreadySelected = !!target && target === this._activeObject; | ||
// if right/middle click just fire events | ||
@@ -1022,3 +1041,5 @@ const { button } = e as MouseEvent; | ||
(this.fireRightClick && button === 2)) && | ||
this._handleEvent(e, 'down'); | ||
this._handleEvent(e, 'down', { | ||
alreadySelected, | ||
}); | ||
this._resetTransformEventData(); | ||
@@ -1074,4 +1095,5 @@ return; | ||
// check again because things could have changed | ||
alreadySelected = !!target && target === this._activeObject; | ||
if (target) { | ||
const alreadySelected = target === this._activeObject; | ||
if (target.selectable && target.activeOn === 'down') { | ||
@@ -1103,3 +1125,3 @@ this.setActiveObject(target, e); | ||
shouldRender && (this._objectsToRender = undefined); | ||
this._handleEvent(e, 'down'); | ||
this._handleEvent(e, 'down', { alreadySelected: alreadySelected }); | ||
// we must renderAll so that we update the visuals | ||
@@ -1106,0 +1128,0 @@ shouldRender && this.requestRenderAll(); |
@@ -296,4 +296,4 @@ import type { ModifierKey, TOptionalModifierKey } from '../EventTypeDefs'; | ||
containerClass: 'canvas-container', | ||
// turn to true for fabric 7.0 | ||
preserveObjectStacking: false, | ||
}; |
@@ -37,3 +37,3 @@ import { config } from '../config'; | ||
import { pick } from '../util/misc/pick'; | ||
import { matrixToSVG } from '../util/misc/svgParsing'; | ||
import { matrixToSVG } from '../util/misc/svgExport'; | ||
import { toFixed } from '../util/misc/toFixed'; | ||
@@ -40,0 +40,0 @@ import { isFiller, isPattern, isTextObject } from '../util/typeAssertions'; |
@@ -261,10 +261,29 @@ import type { Control } from './controls/Control'; | ||
| WithBeforeSuffix<'move'> | ||
| 'dblclick'}`, | ||
| 'dblclick' | ||
| 'tripleclick'}`, | ||
TPointerEventInfo | ||
> & | ||
Record< | ||
`${Prefix}down`, | ||
TPointerEventInfo & { | ||
/** | ||
* Indicates if the target or current target where already selected | ||
* before the cycle of mouse down -> mouse up started | ||
*/ | ||
alreadySelected: boolean; | ||
} | ||
> & | ||
Record< | ||
`${Prefix}${WithBeforeSuffix<'up'>}`, | ||
TPointerEventInfo & { | ||
isClick: boolean; | ||
/** | ||
* The targets at the moment of mouseup that could be different from the | ||
* target at the moment of mouse down in case of a drag action for example | ||
*/ | ||
currentTarget?: FabricObject; | ||
/** | ||
* The subtargets at the moment of mouseup that could be different from the | ||
* target at the moment of mouse down in case of a drag action for example | ||
*/ | ||
currentSubTargets: FabricObject[]; | ||
@@ -282,2 +301,3 @@ } | ||
| 'dblclick' | ||
| 'tripleclick' | ||
| 'wheel'; | ||
@@ -356,1 +376,4 @@ | ||
} | ||
export type TEventsExtraData = Record<PropertyKey, Record<PropertyKey, never>> & | ||
Record<'down', { alreadySelected: boolean }>; |
@@ -8,3 +8,3 @@ import { Color } from '../color/Color'; | ||
import { pick } from '../util/misc/pick'; | ||
import { matrixToSVG } from '../util/misc/svgParsing'; | ||
import { matrixToSVG } from '../util/misc/svgExport'; | ||
import { linearDefaultCoords, radialDefaultCoords } from './constants'; | ||
@@ -11,0 +11,0 @@ import { parseColorStops } from './parser/parseColorStops'; |
import { FabricImage } from './Image'; | ||
import { Shadow } from '../Shadow'; | ||
import { Brightness } from '../filters/Brightness'; | ||
import { loadSVGFromString } from '../parser/loadSVGFromString'; | ||
const mockImage = new Image(100, 100); | ||
jest.mock('../util/misc/objectEnlive', () => { | ||
const all = jest.requireActual('../util/misc/objectEnlive'); | ||
return { | ||
...all, | ||
loadImage: jest.fn(async (src) => { | ||
const img = mockImage; | ||
img.src = src; | ||
return img; | ||
}), | ||
}; | ||
}); | ||
const mockApplyFilter = jest.fn(); | ||
@@ -50,2 +65,30 @@ | ||
}); | ||
describe('SVG import', () => { | ||
it('can import images when xlink:href attribute is set', async () => { | ||
const { objects } = | ||
await loadSVGFromString(`<svg viewBox="0 0 745 1040" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" | ||
xml:space="preserve"> | ||
<image zaparoo-no-print="true" xlink:href="https://design.zaparoo.org/ZapTradingCard.png" width="745" height="1040"> | ||
</image> | ||
</svg>`); | ||
const image = objects[0] as FabricImage; | ||
expect(image instanceof FabricImage).toBe(true); | ||
expect((image._originalElement as HTMLImageElement).src).toBe( | ||
'https://design.zaparoo.org/ZapTradingCard.png', | ||
); | ||
}); | ||
it('can import images when href attribute has no xlink', async () => { | ||
const { objects } = | ||
await loadSVGFromString(`<svg viewBox="0 0 745 1040" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" | ||
xml:space="preserve"> | ||
<image zaparoo-no-print="true" href="https://design.zaparoo.org/ZapTradingCard.png" width="745" height="1040"> | ||
</image> | ||
</svg>`); | ||
const image = objects[0] as FabricImage; | ||
expect(image instanceof FabricImage).toBe(true); | ||
expect((image._originalElement as HTMLImageElement).src).toBe( | ||
'https://design.zaparoo.org/ZapTradingCard.png', | ||
); | ||
}); | ||
}); | ||
}); |
@@ -784,2 +784,3 @@ import { getFabricDocument, getEnv } from '../env'; | ||
'xlink:href', | ||
'href', | ||
'crossOrigin', | ||
@@ -855,3 +856,3 @@ 'image-rendering', | ||
return this.fromURL( | ||
parsedAttributes['xlink:href'], | ||
parsedAttributes['xlink:href'] || parsedAttributes['href'], | ||
options, | ||
@@ -858,0 +859,0 @@ parsedAttributes, |
@@ -14,3 +14,3 @@ export type TKeyMapIText = Record< | ||
| 'cut' | ||
| 'selectAll'; | ||
| 'cmdAll'; | ||
@@ -65,3 +65,3 @@ const MOVE_CURSOR_UP: CursorHandlingMethods = 'moveCursorUp'; | ||
export const ctrlKeysMapDown: TKeyMapIText = { | ||
65: 'selectAll', | ||
65: 'cmdAll', | ||
}; |
@@ -1,2 +0,2 @@ | ||
import type { Canvas } from '../../canvas/Canvas'; | ||
import { Canvas } from '../../canvas/Canvas'; | ||
import '../../../jest.extend'; | ||
@@ -43,2 +43,13 @@ import { Group } from '../Group'; | ||
}); | ||
describe('Interaction with mouse and editing', () => { | ||
it('_mouseDownHandlerBefore set up selected property', () => { | ||
const iText = new IText('test need some word\nsecond line'); | ||
iText.canvas = new Canvas(); | ||
expect(iText.selected).toBe(undefined); | ||
iText._mouseDownHandler({ e: { button: 0 }, alreadySelected: false }); | ||
expect(iText.selected).toBe(false); | ||
iText._mouseDownHandler({ e: {}, alreadySelected: true }); | ||
expect(iText.selected).toBe(true); | ||
}); | ||
}); | ||
}); |
@@ -21,2 +21,5 @@ import { Canvas } from '../../canvas/Canvas'; | ||
import type { ObjectToCanvasElementOptions } from '../Object/Object'; | ||
import type { FabricObject } from '../Object/FabricObject'; | ||
import { createCanvasElementFor } from '../../util/misc/dom'; | ||
import { applyCanvasTransform } from '../../util/internals/applyCanvasTransform'; | ||
@@ -385,3 +388,3 @@ export type CursorBoundaries = { | ||
renderCursorOrSelection() { | ||
if (!this.isEditing) { | ||
if (!this.isEditing || !this.canvas) { | ||
return; | ||
@@ -394,8 +397,49 @@ } | ||
const boundaries = this._getCursorBoundaries(); | ||
const ancestors = this.findAncestorsWithClipPath(); | ||
const hasAncestorsWithClipping = ancestors.length > 0; | ||
let drawingCtx: CanvasRenderingContext2D = ctx; | ||
let drawingCanvas: HTMLCanvasElement | undefined = undefined; | ||
if (hasAncestorsWithClipping) { | ||
// we have some clipPath, we need to draw the selection on an intermediate layer. | ||
drawingCanvas = createCanvasElementFor(ctx.canvas); | ||
drawingCtx = drawingCanvas.getContext('2d')!; | ||
applyCanvasTransform(drawingCtx, this.canvas); | ||
const m = this.calcTransformMatrix(); | ||
drawingCtx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); | ||
} | ||
if (this.selectionStart === this.selectionEnd && !this.inCompositionMode) { | ||
this.renderCursor(ctx, boundaries); | ||
this.renderCursor(drawingCtx, boundaries); | ||
} else { | ||
this.renderSelection(ctx, boundaries); | ||
this.renderSelection(drawingCtx, boundaries); | ||
} | ||
this.canvas!.contextTopDirty = true; | ||
if (hasAncestorsWithClipping) { | ||
// we need a neutral context. | ||
// this won't work for nested clippaths in which a clippath | ||
// has its own clippath | ||
for (const ancestor of ancestors) { | ||
const clipPath = ancestor.clipPath!; | ||
const clippingCanvas = createCanvasElementFor(ctx.canvas); | ||
const clippingCtx = clippingCanvas.getContext('2d')!; | ||
applyCanvasTransform(clippingCtx, this.canvas); | ||
// position the ctx in the center of the outer ancestor | ||
if (!clipPath.absolutePositioned) { | ||
const m = ancestor.calcTransformMatrix(); | ||
clippingCtx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); | ||
} | ||
clipPath.transform(clippingCtx); | ||
// we assign an empty drawing context, we don't plan to have this working for nested clippaths for now | ||
clipPath.drawObject(clippingCtx, true, {}); | ||
this.drawClipPathOnCache(drawingCtx, clipPath, clippingCanvas); | ||
} | ||
} | ||
if (hasAncestorsWithClipping) { | ||
ctx.setTransform(1, 0, 0, 1, 0, 0); | ||
ctx.drawImage(drawingCanvas!, 0, 0); | ||
} | ||
this.canvas.contextTopDirty = true; | ||
ctx.restore(); | ||
@@ -405,2 +449,23 @@ } | ||
/** | ||
* Finds and returns an array of clip paths that are applied to the parent | ||
* group(s) of the current FabricObject instance. The object's hierarchy is | ||
* traversed upwards (from the current object towards the root of the canvas), | ||
* checking each parent object for the presence of a `clipPath` that is not | ||
* absolutely positioned. | ||
*/ | ||
findAncestorsWithClipPath(): FabricObject[] { | ||
const clipPathAncestors: FabricObject[] = []; | ||
// eslint-disable-next-line @typescript-eslint/no-this-alias | ||
let obj: FabricObject | undefined = this; | ||
while (obj) { | ||
if (obj.clipPath) { | ||
clipPathAncestors.push(obj); | ||
} | ||
obj = obj.parent; | ||
} | ||
return clipPathAncestors; | ||
} | ||
/** | ||
* Returns cursor boundaries (left, top, leftOffset, topOffset) | ||
@@ -552,3 +617,3 @@ * left/top are left/top of entire text box | ||
* Render the cursor at the given selectionStart. | ||
* | ||
* @param {CanvasRenderingContext2D} ctx transformed context to draw on | ||
*/ | ||
@@ -555,0 +620,0 @@ _renderCursor( |
@@ -190,3 +190,3 @@ import { roundSnapshotOptions } from '../../../jest.extend'; | ||
_tickMock.mockClear(); | ||
iText.__lastSelected = true; | ||
iText.selected = true; | ||
iText.mouseUpHandler({ | ||
@@ -193,0 +193,0 @@ e: { |
@@ -1,6 +0,2 @@ | ||
import type { | ||
ObjectEvents, | ||
TPointerEvent, | ||
TPointerEventInfo, | ||
} from '../../EventTypeDefs'; | ||
import type { ObjectEvents, TPointerEvent } from '../../EventTypeDefs'; | ||
import { Point } from '../../Point'; | ||
@@ -37,3 +33,2 @@ import type { FabricObject } from '../Object/FabricObject'; | ||
changed: never | { index: number; action: string }; | ||
tripleclick: TPointerEventInfo; | ||
'editing:entered': never | { e: TPointerEvent }; | ||
@@ -74,2 +69,6 @@ 'editing:exited': never; | ||
/** | ||
* Keeps track if the IText object was selected before the actual click. | ||
* This because we want to delay enter editing by a click. | ||
*/ | ||
protected declare selected: boolean; | ||
@@ -230,2 +229,10 @@ protected declare cursorOffsetCache: { left?: number; top?: number }; | ||
/** | ||
* Selects entire text and updates the visual state | ||
*/ | ||
cmdAll() { | ||
this.selectAll(); | ||
this.renderCursorOrSelection(); | ||
} | ||
/** | ||
* Returns selected text | ||
@@ -348,8 +355,7 @@ * @return {String} | ||
/** | ||
* TODO fix: selectionStart set as 0 will be ignored? | ||
* Selects a word based on the index | ||
* Selects the word that contains the char at index selectionStart | ||
* @param {Number} selectionStart Index of a character | ||
*/ | ||
selectWord(selectionStart?: number) { | ||
selectionStart = selectionStart || this.selectionStart; | ||
selectionStart = selectionStart ?? this.selectionStart; | ||
// search backwards | ||
@@ -367,2 +373,3 @@ const newSelectionStart = this.searchWordBoundary(selectionStart, -1), | ||
this._updateTextarea(); | ||
// remove next major, for now it renders twice :( | ||
this.renderCursorOrSelection(); | ||
@@ -372,8 +379,7 @@ } | ||
/** | ||
* TODO fix: selectionStart set as 0 will be ignored? | ||
* Selects a line based on the index | ||
* Selects the line that contains selectionStart | ||
* @param {Number} selectionStart Index of a character | ||
*/ | ||
selectLine(selectionStart?: number) { | ||
selectionStart = selectionStart || this.selectionStart; | ||
selectionStart = selectionStart ?? this.selectionStart; | ||
const newSelectionStart = this.findLineBoundaryLeft(selectionStart), | ||
@@ -386,3 +392,2 @@ newSelectionEnd = this.findLineBoundaryRight(selectionStart); | ||
this._updateTextarea(); | ||
return this; | ||
} | ||
@@ -389,0 +394,0 @@ |
@@ -1,5 +0,7 @@ | ||
import type { TPointerEvent, TPointerEventInfo } from '../../EventTypeDefs'; | ||
import type { XY } from '../../Point'; | ||
import type { | ||
ObjectPointerEvents, | ||
TPointerEvent, | ||
TPointerEventInfo, | ||
} from '../../EventTypeDefs'; | ||
import { Point } from '../../Point'; | ||
import { stopEvent } from '../../util/dom_event'; | ||
import { invertTransform } from '../../util/misc/matrix'; | ||
@@ -22,8 +24,2 @@ import { DraggableTextDelegate } from './DraggableTextDelegate'; | ||
> extends ITextKeyBehavior<Props, SProps, EventSpec> { | ||
private declare __lastSelected: boolean; | ||
private declare __lastClickTime: number; | ||
private declare __lastLastClickTime: number; | ||
private declare __lastPointer: XY | Record<string, never>; | ||
private declare __newClickTime: number; | ||
protected draggableTextDelegate: DraggableTextDelegate; | ||
@@ -34,14 +30,6 @@ | ||
this.on('mousedown', this._mouseDownHandler); | ||
this.on('mousedown:before', this._mouseDownHandlerBefore); | ||
this.on('mouseup', this.mouseUpHandler); | ||
this.on('mousedblclick', this.doubleClickHandler); | ||
this.on('tripleclick', this.tripleClickHandler); | ||
this.on('mousetripleclick', this.tripleClickHandler); | ||
// Initializes "dbclick" event handler | ||
this.__lastClickTime = +new Date(); | ||
// for triple click | ||
this.__lastLastClickTime = +new Date(); | ||
this.__lastPointer = {}; | ||
this.on('mousedown', this.onMouseDown); | ||
this.draggableTextDelegate = new DraggableTextDelegate( | ||
@@ -83,31 +71,2 @@ this as unknown as IText, | ||
/** | ||
* Default event handler to simulate triple click | ||
* @private | ||
*/ | ||
onMouseDown(options: TPointerEventInfo) { | ||
if (!this.canvas) { | ||
return; | ||
} | ||
this.__newClickTime = +new Date(); | ||
const newPointer = options.pointer; | ||
if (this.isTripleClick(newPointer)) { | ||
this.fire('tripleclick', options); | ||
stopEvent(options.e); | ||
} | ||
this.__lastLastClickTime = this.__lastClickTime; | ||
this.__lastClickTime = this.__newClickTime; | ||
this.__lastPointer = newPointer; | ||
this.__lastSelected = this.selected && !this.getActiveControl(); | ||
} | ||
isTripleClick(newPointer: XY) { | ||
return ( | ||
this.__newClickTime - this.__lastClickTime < 500 && | ||
this.__lastClickTime - this.__lastLastClickTime < 500 && | ||
this.__lastPointer.x === newPointer.x && | ||
this.__lastPointer.y === newPointer.y | ||
); | ||
} | ||
/** | ||
* Default handler for double click, select a word | ||
@@ -120,2 +79,3 @@ */ | ||
this.selectWord(this.getSelectionStartFromPointer(options.e)); | ||
this.renderCursorOrSelection(); | ||
} | ||
@@ -131,2 +91,3 @@ | ||
this.selectLine(this.getSelectionStartFromPointer(options.e)); | ||
this.renderCursorOrSelection(); | ||
} | ||
@@ -142,3 +103,3 @@ | ||
*/ | ||
_mouseDownHandler({ e }: TPointerEventInfo) { | ||
_mouseDownHandler({ e, alreadySelected }: ObjectPointerEvents['mousedown']) { | ||
if ( | ||
@@ -159,3 +120,3 @@ !this.canvas || | ||
if (this.selected) { | ||
if (alreadySelected) { | ||
this.inCompositionMode = false; | ||
@@ -172,24 +133,12 @@ this.setCursorByClick(e); | ||
} | ||
this.selected ||= alreadySelected || this.isEditing; | ||
} | ||
/** | ||
* Default event handler for the basic functionalities needed on mousedown:before | ||
* can be overridden to do something different. | ||
* Scope of this implementation is: verify the object is already selected when mousing down | ||
*/ | ||
_mouseDownHandlerBefore({ e }: TPointerEventInfo) { | ||
if (!this.canvas || !this.editable || notALeftClick(e)) { | ||
return; | ||
} | ||
// we want to avoid that an object that was selected and then becomes unselectable, | ||
// may trigger editing mode in some way. | ||
this.selected = this === this.canvas._activeObject; | ||
} | ||
/** | ||
* standard handler for mouse up, overridable | ||
* @private | ||
*/ | ||
mouseUpHandler({ e, transform }: TPointerEventInfo) { | ||
mouseUpHandler({ e, transform }: ObjectPointerEvents['mouseup']) { | ||
const didDrag = this.draggableTextDelegate.end(e); | ||
if (this.canvas) { | ||
@@ -206,2 +155,3 @@ this.canvas.textEditingManager.unregister(this); | ||
} | ||
if ( | ||
@@ -217,5 +167,3 @@ !this.editable || | ||
if (this.__lastSelected && !this.getActiveControl()) { | ||
this.selected = false; | ||
this.__lastSelected = false; | ||
if (this.selected && !this.getActiveControl()) { | ||
this.enterEditing(e); | ||
@@ -227,4 +175,2 @@ if (this.selectionStart === this.selectionEnd) { | ||
} | ||
} else { | ||
this.selected = true; | ||
} | ||
@@ -231,0 +177,0 @@ } |
@@ -176,2 +176,3 @@ import { config } from '../../config'; | ||
const fromPaste = this.fromPaste; | ||
const { value, selectionStart, selectionEnd } = this.hiddenTextarea; | ||
this.fromPaste = false; | ||
@@ -196,10 +197,8 @@ e && e.stopPropagation(); | ||
// decisions about style changes. | ||
const nextText = this._splitTextIntoLines( | ||
this.hiddenTextarea.value, | ||
).graphemeText, | ||
const nextText = this._splitTextIntoLines(value).graphemeText, | ||
charCount = this._text.length, | ||
nextCharCount = nextText.length, | ||
selectionStart = this.selectionStart, | ||
selectionEnd = this.selectionEnd, | ||
selection = selectionStart !== selectionEnd; | ||
_selectionStart = this.selectionStart, | ||
_selectionEnd = this.selectionEnd, | ||
selection = _selectionStart !== _selectionEnd; | ||
let copiedStyle: TextStyleDeclaration[] | undefined, | ||
@@ -212,18 +211,18 @@ removedText, | ||
const textareaSelection = this.fromStringToGraphemeSelection( | ||
this.hiddenTextarea.selectionStart, | ||
this.hiddenTextarea.selectionEnd, | ||
this.hiddenTextarea.value, | ||
selectionStart, | ||
selectionEnd, | ||
value, | ||
); | ||
const backDelete = selectionStart > textareaSelection.selectionStart; | ||
const backDelete = _selectionStart > textareaSelection.selectionStart; | ||
if (selection) { | ||
removedText = this._text.slice(selectionStart, selectionEnd); | ||
charDiff += selectionEnd - selectionStart; | ||
removedText = this._text.slice(_selectionStart, _selectionEnd); | ||
charDiff += _selectionEnd - _selectionStart; | ||
} else if (nextCharCount < charCount) { | ||
if (backDelete) { | ||
removedText = this._text.slice(selectionEnd + charDiff, selectionEnd); | ||
removedText = this._text.slice(_selectionEnd + charDiff, _selectionEnd); | ||
} else { | ||
removedText = this._text.slice( | ||
selectionStart, | ||
selectionStart - charDiff, | ||
_selectionStart, | ||
_selectionStart - charDiff, | ||
); | ||
@@ -242,4 +241,4 @@ } | ||
copiedStyle = this.getSelectionStyles( | ||
selectionStart, | ||
selectionStart + 1, | ||
_selectionStart, | ||
_selectionStart + 1, | ||
false, | ||
@@ -256,11 +255,11 @@ ); | ||
if (selection) { | ||
removeFrom = selectionStart; | ||
removeTo = selectionEnd; | ||
removeFrom = _selectionStart; | ||
removeTo = _selectionEnd; | ||
} else if (backDelete) { | ||
// detect differences between forwardDelete and backDelete | ||
removeFrom = selectionEnd - removedText.length; | ||
removeTo = selectionEnd; | ||
removeFrom = _selectionEnd - removedText.length; | ||
removeTo = _selectionEnd; | ||
} else { | ||
removeFrom = selectionEnd; | ||
removeTo = selectionEnd + removedText.length; | ||
removeFrom = _selectionEnd; | ||
removeTo = _selectionEnd + removedText.length; | ||
} | ||
@@ -278,3 +277,3 @@ this.removeStyleFromTo(removeFrom, removeTo); | ||
} | ||
this.insertNewStyleBlock(insertedText, selectionStart, copiedStyle); | ||
this.insertNewStyleBlock(insertedText, _selectionStart, copiedStyle); | ||
} | ||
@@ -281,0 +280,0 @@ updateAndFire(); |
import type { TSVGReviver } from '../../typedefs'; | ||
import { uid } from '../../util/internals/uid'; | ||
import { colorPropToSVG, matrixToSVG } from '../../util/misc/svgParsing'; | ||
import { colorPropToSVG } from '../../util/misc/svgParsing'; | ||
import { FILL, NONE, STROKE } from '../../constants'; | ||
import type { FabricObject } from './FabricObject'; | ||
import { isFiller } from '../../util/typeAssertions'; | ||
import { matrixToSVG } from '../../util/misc/svgExport'; | ||
@@ -8,0 +9,0 @@ export class FabricObjectSVGExportMixin { |
@@ -826,3 +826,2 @@ import { cache } from '../../cache'; | ||
ctx.setTransform(1, 0, 0, 1, 0, 0); | ||
//ctx.scale(1 / 2, 1 / 2); | ||
ctx.drawImage(canvasWithClipPath, 0, 0); | ||
@@ -829,0 +828,0 @@ ctx.restore(); |
import { roundSnapshotOptions } from '../../../jest.extend'; | ||
import { cache } from '../../cache'; | ||
import { config } from '../../config'; | ||
import { Path } from '../Path'; | ||
import { FabricText } from './Text'; | ||
@@ -55,2 +56,15 @@ | ||
it('toSVG with a path', async () => { | ||
const path = new Path('M 10 10 H 50 V 60', { fill: '', stroke: 'red' }); | ||
const text = new FabricText( | ||
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', | ||
{ scaleX: 2, scaleY: 2 }, | ||
); | ||
const plainSvg = text.toSVG(); | ||
text.path = path; | ||
const svg = text.toSVG(); | ||
expect(svg).toMatchSnapshot(); | ||
expect(svg.includes(plainSvg)).toBe(false); | ||
}); | ||
it('subscript/superscript', async () => { | ||
@@ -57,0 +71,0 @@ const text = await FabricText.fromObject({ |
@@ -10,4 +10,8 @@ import { config } from '../../config'; | ||
import { JUSTIFY } from '../Text/constants'; | ||
import type { FabricText } from './Text'; | ||
import type { FabricText, GraphemeBBox } from './Text'; | ||
import { STROKE, FILL } from '../../constants'; | ||
import { createRotateMatrix } from '../../util/misc/matrix'; | ||
import { radiansToDegrees } from '../../util/misc/radiansDegreesConversion'; | ||
import { Point } from '../../Point'; | ||
import { matrixToSVG } from '../../util/misc/svgExport'; | ||
@@ -35,7 +39,19 @@ const multipleSpacesRegex = / +/g; | ||
toSVG(this: TextSVGExportMixin & FabricText, reviver?: TSVGReviver): string { | ||
return this._createBaseSVGMarkup(this._toSVG(), { | ||
reviver, | ||
noStyle: true, | ||
withShadow: true, | ||
}); | ||
const textSvg = this._createBaseSVGMarkup(this._toSVG(), { | ||
reviver, | ||
noStyle: true, | ||
withShadow: true, | ||
}), | ||
path = this.path; | ||
if (path) { | ||
return ( | ||
textSvg + | ||
path._createBaseSVGMarkup(path._toSVG(), { | ||
reviver, | ||
withShadow: true, | ||
additionalTransform: matrixToSVG(this.calcOwnMatrix()), | ||
}) | ||
); | ||
} | ||
return textSvg; | ||
} | ||
@@ -147,3 +163,5 @@ | ||
top: number, | ||
charBox: GraphemeBBox, | ||
) { | ||
const numFractionDigit = config.NUM_FRACTION_DIGITS; | ||
const styleProps = this.getSvgSpanStyles( | ||
@@ -155,11 +173,21 @@ styleDecl, | ||
dy = styleDecl.deltaY, | ||
dySpan = dy ? ` dy="${toFixed(dy, config.NUM_FRACTION_DIGITS)}" ` : ''; | ||
dySpan = dy ? ` dy="${toFixed(dy, numFractionDigit)}" ` : '', | ||
{ angle, renderLeft, renderTop, width } = charBox; | ||
let angleAttr = ''; | ||
if (renderLeft !== undefined) { | ||
const wBy2 = width / 2; | ||
angle && | ||
(angleAttr = ` rotate="${toFixed(radiansToDegrees(angle), numFractionDigit)}"`); | ||
const m = createRotateMatrix({ angle: radiansToDegrees(angle!) }); | ||
m[4] = renderLeft!; | ||
m[5] = renderTop!; | ||
const renderPoint = new Point(-wBy2, 0).transform(m); | ||
left = renderPoint.x; | ||
top = renderPoint.y; | ||
} | ||
return `<tspan x="${toFixed( | ||
left, | ||
config.NUM_FRACTION_DIGITS, | ||
)}" y="${toFixed( | ||
return `<tspan x="${toFixed(left, numFractionDigit)}" y="${toFixed( | ||
top, | ||
config.NUM_FRACTION_DIGITS, | ||
)}" ${dySpan}${fillStyles}>${escapeXml(char)}</tspan>`; | ||
numFractionDigit, | ||
)}" ${dySpan}${angleAttr}${fillStyles}>${escapeXml(char)}</tspan>`; | ||
} | ||
@@ -188,3 +216,3 @@ | ||
for (let i = 0, len = line.length - 1; i <= len; i++) { | ||
timeToRender = i === len || this.charSpacing; | ||
timeToRender = i === len || this.charSpacing || this.path; | ||
charsToRender += line[i]; | ||
@@ -204,3 +232,3 @@ charBox = this.__charBounds[lineIndex][i]; | ||
if (!timeToRender) { | ||
// if we have charSpacing, we render char by char | ||
// if we have charSpacing or a path, we render char by char | ||
actualStyle = | ||
@@ -219,2 +247,3 @@ actualStyle || this.getCompleteStyleDeclaration(lineIndex, i); | ||
textTopOffset, | ||
charBox, | ||
), | ||
@@ -221,0 +250,0 @@ ); |
@@ -58,3 +58,2 @@ export { cos } from './misc/cos'; | ||
export { | ||
matrixToSVG, | ||
parsePreserveAspectRatioAttribute, | ||
@@ -64,2 +63,3 @@ parseUnit, | ||
} from './misc/svgParsing'; | ||
export { matrixToSVG } from './misc/svgExport'; | ||
export { groupSVGElements } from './misc/groupSVGElements'; | ||
@@ -66,0 +66,0 @@ export { findScaleToFit, findScaleToCover } from './misc/findScaleTo'; |
@@ -36,2 +36,7 @@ import { getFabricDocument } from '../../env'; | ||
/** | ||
* Creates a canvas element as big as another | ||
* @param {CanvasElement} canvas to copy size and content of | ||
* @return {CanvasElement} initialized canvas element | ||
*/ | ||
export const createCanvasElementFor = ( | ||
@@ -51,3 +56,3 @@ canvas: HTMLCanvasElement | ImageData | HTMLImageElement | TSize, | ||
* @param {String} format 'jpeg' or 'png', in some browsers 'webp' is ok too | ||
* @param {Number} quality <= 1 and > 0 | ||
* @param {number} quality <= 1 and > 0 | ||
* @return {String} data url | ||
@@ -54,0 +59,0 @@ */ |
import { Color } from '../../color/Color'; | ||
import { config } from '../../config'; | ||
import { DEFAULT_SVG_FONT_SIZE, FILL, NONE } from '../../constants'; | ||
import type { | ||
TBBox, | ||
TMat2D, | ||
SVGElementName, | ||
SupportedSVGUnit, | ||
} from '../../typedefs'; | ||
import type { TBBox, SVGElementName, SupportedSVGUnit } from '../../typedefs'; | ||
import { toFixed } from './toFixed'; | ||
@@ -124,14 +119,2 @@ | ||
/** | ||
* given an array of 6 number returns something like `"matrix(...numbers)"` | ||
* @param {TMat2D} transform an array with 6 numbers | ||
* @return {String} transform matrix for svg | ||
*/ | ||
export const matrixToSVG = (transform: TMat2D) => | ||
'matrix(' + | ||
transform | ||
.map((value) => toFixed(value, config.NUM_FRACTION_DIGITS)) | ||
.join(' ') + | ||
')'; | ||
/** | ||
* Adobe Illustrator (at least CS5) is unable to render rgba()-based fill values | ||
@@ -138,0 +121,0 @@ * we work around it by "moving" alpha channel into opacity attribute and setting fill's alpha to 1 |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
24894466
0.36%2321
0.78%219498
0.34%