fabric 7.1.0 → 7.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.husky/pre-commit +1 -0
- package/CHANGELOG.md +13 -0
- package/dist/extensions/cropping_controls/croppingControls.d.ts +12 -8
- package/dist/extensions/cropping_controls/croppingControls.d.ts.map +1 -1
- package/dist/extensions/cropping_controls/croppingHandlers.d.ts +19 -1
- package/dist/extensions/cropping_controls/croppingHandlers.d.ts.map +1 -1
- package/dist/extensions/cropping_controls/enterCropMode.d.ts.map +1 -1
- package/dist/index.js +189 -160
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/index.min.mjs +1 -1
- package/dist/index.min.mjs.map +1 -1
- package/dist/index.mjs +189 -160
- package/dist/index.mjs.map +1 -1
- package/dist/index.node.cjs +189 -160
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.mjs +189 -160
- package/dist/index.node.mjs.map +1 -1
- package/dist/package.json.min.mjs +1 -1
- package/dist/package.json.mjs +1 -1
- package/dist/src/EventTypeDefs.d.ts +3 -0
- package/dist/src/EventTypeDefs.d.ts.map +1 -1
- package/dist/src/Pattern/Pattern.d.ts.map +1 -1
- package/dist/src/Pattern/Pattern.min.mjs +1 -1
- package/dist/src/Pattern/Pattern.min.mjs.map +1 -1
- package/dist/src/Pattern/Pattern.mjs +2 -1
- package/dist/src/Pattern/Pattern.mjs.map +1 -1
- package/dist/src/Shadow.d.ts +1 -1
- package/dist/src/Shadow.d.ts.map +1 -1
- package/dist/src/Shadow.min.mjs +1 -1
- package/dist/src/Shadow.min.mjs.map +1 -1
- package/dist/src/Shadow.mjs +5 -4
- package/dist/src/Shadow.mjs.map +1 -1
- package/dist/src/canvas/CanvasOptions.d.ts.map +1 -1
- package/dist/src/canvas/CanvasOptions.min.mjs.map +1 -1
- package/dist/src/canvas/CanvasOptions.mjs.map +1 -1
- package/dist/src/canvas/SelectableCanvas.d.ts +2 -0
- package/dist/src/canvas/SelectableCanvas.d.ts.map +1 -1
- package/dist/src/canvas/SelectableCanvas.min.mjs +1 -1
- package/dist/src/canvas/SelectableCanvas.min.mjs.map +1 -1
- package/dist/src/canvas/SelectableCanvas.mjs +6 -1
- package/dist/src/canvas/SelectableCanvas.mjs.map +1 -1
- package/dist/src/canvas/StaticCanvas.d.ts.map +1 -1
- package/dist/src/canvas/StaticCanvas.min.mjs +1 -1
- package/dist/src/canvas/StaticCanvas.min.mjs.map +1 -1
- package/dist/src/canvas/StaticCanvas.mjs +3 -1
- package/dist/src/canvas/StaticCanvas.mjs.map +1 -1
- package/dist/src/canvas/StaticCanvasOptions.d.ts.map +1 -1
- package/dist/src/canvas/StaticCanvasOptions.min.mjs.map +1 -1
- package/dist/src/canvas/StaticCanvasOptions.mjs.map +1 -1
- package/dist/src/controls/Control.d.ts +9 -1
- package/dist/src/controls/Control.d.ts.map +1 -1
- package/dist/src/controls/Control.min.mjs +1 -1
- package/dist/src/controls/Control.min.mjs.map +1 -1
- package/dist/src/controls/Control.mjs +8 -0
- package/dist/src/controls/Control.mjs.map +1 -1
- package/dist/src/gradient/Gradient.d.ts.map +1 -1
- package/dist/src/gradient/Gradient.min.mjs +1 -1
- package/dist/src/gradient/Gradient.min.mjs.map +1 -1
- package/dist/src/gradient/Gradient.mjs +19 -6
- package/dist/src/gradient/Gradient.mjs.map +1 -1
- package/dist/src/shapes/Circle.d.ts.map +1 -1
- package/dist/src/shapes/Circle.min.mjs +1 -1
- package/dist/src/shapes/Circle.min.mjs.map +1 -1
- package/dist/src/shapes/Circle.mjs +10 -7
- package/dist/src/shapes/Circle.mjs.map +1 -1
- package/dist/src/shapes/Ellipse.d.ts.map +1 -1
- package/dist/src/shapes/Ellipse.min.mjs +1 -1
- package/dist/src/shapes/Ellipse.min.mjs.map +1 -1
- package/dist/src/shapes/Ellipse.mjs +2 -1
- package/dist/src/shapes/Ellipse.mjs.map +1 -1
- package/dist/src/shapes/Group.d.ts.map +1 -1
- package/dist/src/shapes/Group.min.mjs +1 -1
- package/dist/src/shapes/Group.min.mjs.map +1 -1
- package/dist/src/shapes/Group.mjs +2 -1
- package/dist/src/shapes/Group.mjs.map +1 -1
- package/dist/src/shapes/IText/IText.d.ts.map +1 -1
- package/dist/src/shapes/IText/IText.min.mjs.map +1 -1
- package/dist/src/shapes/IText/IText.mjs.map +1 -1
- package/dist/src/shapes/Image.d.ts +1 -1
- package/dist/src/shapes/Image.d.ts.map +1 -1
- package/dist/src/shapes/Image.min.mjs +1 -1
- package/dist/src/shapes/Image.min.mjs.map +1 -1
- package/dist/src/shapes/Image.mjs +4 -3
- package/dist/src/shapes/Image.mjs.map +1 -1
- package/dist/src/shapes/Line.d.ts.map +1 -1
- package/dist/src/shapes/Line.min.mjs +1 -1
- package/dist/src/shapes/Line.min.mjs.map +1 -1
- package/dist/src/shapes/Line.mjs +6 -10
- package/dist/src/shapes/Line.mjs.map +1 -1
- package/dist/src/shapes/Object/FabricObjectSVGExportMixin.d.ts.map +1 -1
- package/dist/src/shapes/Object/FabricObjectSVGExportMixin.min.mjs +1 -1
- package/dist/src/shapes/Object/FabricObjectSVGExportMixin.min.mjs.map +1 -1
- package/dist/src/shapes/Object/FabricObjectSVGExportMixin.mjs +5 -4
- package/dist/src/shapes/Object/FabricObjectSVGExportMixin.mjs.map +1 -1
- package/dist/src/shapes/Object/InteractiveObject.d.ts.map +1 -1
- package/dist/src/shapes/Object/InteractiveObject.min.mjs.map +1 -1
- package/dist/src/shapes/Object/InteractiveObject.mjs.map +1 -1
- package/dist/src/shapes/Object/Object.d.ts.map +1 -1
- package/dist/src/shapes/Object/Object.min.mjs +1 -1
- package/dist/src/shapes/Object/Object.min.mjs.map +1 -1
- package/dist/src/shapes/Object/Object.mjs +3 -0
- package/dist/src/shapes/Object/Object.mjs.map +1 -1
- package/dist/src/shapes/Object/types/FabricObjectProps.d.ts.map +1 -1
- package/dist/src/shapes/Object/types/ObjectProps.d.ts.map +1 -1
- package/dist/src/shapes/Path.d.ts.map +1 -1
- package/dist/src/shapes/Path.min.mjs.map +1 -1
- package/dist/src/shapes/Path.mjs +1 -2
- package/dist/src/shapes/Path.mjs.map +1 -1
- package/dist/src/shapes/Polyline.d.ts.map +1 -1
- package/dist/src/shapes/Polyline.min.mjs +1 -1
- package/dist/src/shapes/Polyline.min.mjs.map +1 -1
- package/dist/src/shapes/Polyline.mjs +10 -6
- package/dist/src/shapes/Polyline.mjs.map +1 -1
- package/dist/src/shapes/Rect.d.ts.map +1 -1
- package/dist/src/shapes/Rect.min.mjs +1 -1
- package/dist/src/shapes/Rect.min.mjs.map +1 -1
- package/dist/src/shapes/Rect.mjs +2 -1
- package/dist/src/shapes/Rect.mjs.map +1 -1
- package/dist/src/shapes/Text/Text.d.ts.map +1 -1
- package/dist/src/shapes/Text/Text.min.mjs.map +1 -1
- package/dist/src/shapes/Text/Text.mjs.map +1 -1
- package/dist/src/shapes/Text/TextSVGExportMixin.min.mjs +1 -1
- package/dist/src/shapes/Text/TextSVGExportMixin.min.mjs.map +1 -1
- package/dist/src/shapes/Text/TextSVGExportMixin.mjs +5 -5
- package/dist/src/shapes/Text/TextSVGExportMixin.mjs.map +1 -1
- package/dist/src/shapes/Textbox.d.ts.map +1 -1
- package/dist/src/shapes/Textbox.min.mjs.map +1 -1
- package/dist/src/shapes/Textbox.mjs.map +1 -1
- package/dist/src/shapes/Triangle.d.ts.map +1 -1
- package/dist/src/shapes/Triangle.min.mjs.map +1 -1
- package/dist/src/shapes/Triangle.mjs.map +1 -1
- package/dist/src/util/lang_string.d.ts +1 -1
- package/dist/src/util/lang_string.d.ts.map +1 -1
- package/dist/src/util/lang_string.min.mjs +1 -1
- package/dist/src/util/lang_string.min.mjs.map +1 -1
- package/dist/src/util/lang_string.mjs +1 -1
- package/dist/src/util/lang_string.mjs.map +1 -1
- package/dist/src/util/misc/svgParsing.d.ts.map +1 -1
- package/dist/src/util/misc/svgParsing.min.mjs +1 -1
- package/dist/src/util/misc/svgParsing.min.mjs.map +1 -1
- package/dist/src/util/misc/svgParsing.mjs +2 -1
- package/dist/src/util/misc/svgParsing.mjs.map +1 -1
- package/dist-extensions/cropping_controls/croppingControls.mjs +39 -9
- package/dist-extensions/cropping_controls/croppingControls.mjs.map +1 -1
- package/dist-extensions/cropping_controls/croppingHandlers.mjs +84 -2
- package/dist-extensions/cropping_controls/croppingHandlers.mjs.map +1 -1
- package/dist-extensions/cropping_controls/enterCropMode.mjs +7 -2
- package/dist-extensions/cropping_controls/enterCropMode.mjs.map +1 -1
- package/dist-extensions/extensions/cropping_controls/croppingControls.d.ts +12 -8
- package/dist-extensions/extensions/cropping_controls/croppingControls.d.ts.map +1 -1
- package/dist-extensions/extensions/cropping_controls/croppingHandlers.d.ts +19 -1
- package/dist-extensions/extensions/cropping_controls/croppingHandlers.d.ts.map +1 -1
- package/dist-extensions/extensions/cropping_controls/enterCropMode.d.ts.map +1 -1
- package/dist-extensions/fabric-extensions.min.js +1 -1
- package/dist-extensions/fabric-extensions.min.js.map +1 -1
- package/dist-extensions/src/EventTypeDefs.d.ts +3 -0
- package/dist-extensions/src/EventTypeDefs.d.ts.map +1 -1
- package/dist-extensions/src/Pattern/Pattern.d.ts.map +1 -1
- package/dist-extensions/src/Shadow.d.ts +1 -1
- package/dist-extensions/src/Shadow.d.ts.map +1 -1
- package/dist-extensions/src/canvas/CanvasOptions.d.ts.map +1 -1
- package/dist-extensions/src/canvas/SelectableCanvas.d.ts +2 -0
- package/dist-extensions/src/canvas/SelectableCanvas.d.ts.map +1 -1
- package/dist-extensions/src/canvas/StaticCanvas.d.ts.map +1 -1
- package/dist-extensions/src/canvas/StaticCanvasOptions.d.ts.map +1 -1
- package/dist-extensions/src/controls/Control.d.ts +9 -1
- package/dist-extensions/src/controls/Control.d.ts.map +1 -1
- package/dist-extensions/src/gradient/Gradient.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Circle.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Ellipse.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Group.d.ts.map +1 -1
- package/dist-extensions/src/shapes/IText/IText.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Image.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Line.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Object/FabricObjectSVGExportMixin.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Object/InteractiveObject.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Object/Object.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Object/types/FabricObjectProps.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Object/types/ObjectProps.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Path.d.ts +1 -1
- package/dist-extensions/src/shapes/Path.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Polyline.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Rect.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Text/Text.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Textbox.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Triangle.d.ts.map +1 -1
- package/dist-extensions/src/util/lang_string.d.ts +1 -1
- package/dist-extensions/src/util/lang_string.d.ts.map +1 -1
- package/dist-extensions/src/util/misc/svgParsing.d.ts.map +1 -1
- package/eslint.config.mjs +2 -0
- package/extensions/cropping_controls/croppingControls.spec.ts +65 -27
- package/extensions/cropping_controls/croppingControls.ts +40 -8
- package/extensions/cropping_controls/croppingHandlers.spec.ts +355 -46
- package/extensions/cropping_controls/croppingHandlers.ts +123 -0
- package/extensions/cropping_controls/enterCropMode.ts +6 -2
- package/package.json +17 -8
- package/src/ClassRegistry.spec.ts +18 -19
- package/src/EventTypeDefs.ts +13 -11
- package/src/Pattern/Pattern.spec.ts +12 -0
- package/src/Pattern/Pattern.ts +3 -2
- package/src/Shadow.ts +9 -8
- package/src/brushes/PencilBrush.spec.ts +11 -11
- package/src/canvas/Canvas-dispose.spec.ts +8 -7
- package/src/canvas/Canvas.spec.ts +27 -29
- package/src/canvas/CanvasOptions.ts +2 -1
- package/src/canvas/SelectableCanvas.ts +11 -4
- package/src/canvas/StaticCanvas.spec.ts +20 -0
- package/src/canvas/StaticCanvas.ts +7 -4
- package/src/canvas/StaticCanvasOptions.ts +1 -3
- package/src/controls/Control.ts +24 -1
- package/src/gradient/Gradient.spec.ts +101 -46
- package/src/gradient/Gradient.ts +27 -14
- package/src/shapes/Circle.spec.ts +10 -39
- package/src/shapes/Circle.ts +11 -11
- package/src/shapes/Ellipse.spec.ts +8 -37
- package/src/shapes/Ellipse.ts +7 -7
- package/src/shapes/Group.ts +3 -3
- package/src/shapes/IText/IText-click-behavior.spec.ts +36 -49
- package/src/shapes/IText/IText.ts +5 -6
- package/src/shapes/IText/__snapshots__/ITextBehavior.test.ts.snap +6 -6
- package/src/shapes/Image.spec.ts +17 -33
- package/src/shapes/Image.ts +15 -11
- package/src/shapes/Line.spec.ts +4 -30
- package/src/shapes/Line.ts +11 -16
- package/src/shapes/Object/FabricObjectSVGExportMixin.ts +11 -4
- package/src/shapes/Object/InteractiveObject.ts +4 -4
- package/src/shapes/Object/Object.ts +6 -5
- package/src/shapes/Object/objectSvgExport.spec.ts +112 -0
- package/src/shapes/Object/types/FabricObjectProps.ts +1 -4
- package/src/shapes/Object/types/ObjectProps.ts +1 -3
- package/src/shapes/Path.spec.ts +4 -27
- package/src/shapes/Path.ts +2 -4
- package/src/shapes/Polygon.spec.ts +4 -31
- package/src/shapes/Polyline.spec.ts +4 -31
- package/src/shapes/Polyline.ts +11 -12
- package/src/shapes/Rect.spec.ts +25 -33
- package/src/shapes/Rect.ts +7 -7
- package/src/shapes/Text/Text.spec.ts +3 -32
- package/src/shapes/Text/Text.ts +5 -6
- package/src/shapes/Text/TextSVGExportMixin.ts +14 -14
- package/src/shapes/Text/__snapshots__/Text.spec.ts.snap +1 -1
- package/src/shapes/Textbox.spec.ts +5 -5
- package/src/shapes/Textbox.ts +6 -5
- package/src/shapes/Triangle.ts +4 -4
- package/src/shapes/__snapshots__/Image.spec.ts.snap +4 -4
- package/src/shapes/__snapshots__/Textbox.spec.ts.snap +5 -5
- package/src/util/lang_string.ts +3 -2
- package/src/util/misc/svgParsing.ts +2 -1
- package/tsconfig.spec.json +1 -0
- package/vitest.config.ts +12 -2
- package/vitest.extend.ts +6 -2
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import type { Transform } from 'fabric';
|
|
2
|
-
import { FabricImage, Canvas } from 'fabric';
|
|
2
|
+
import { FabricImage, Canvas, Control, Point } from 'fabric';
|
|
3
|
+
import { createImageCroppingControls } from './croppingControls';
|
|
3
4
|
import {
|
|
4
5
|
changeImageWidth,
|
|
5
|
-
changeCropWidth,
|
|
6
6
|
changeImageHeight,
|
|
7
|
-
changeCropHeight,
|
|
8
7
|
changeImageCropX,
|
|
9
|
-
changeCropX,
|
|
10
8
|
changeImageCropY,
|
|
11
|
-
|
|
9
|
+
cropPanMoveHandler,
|
|
10
|
+
ghostScalePositionHandler,
|
|
11
|
+
scaleEquallyCropGenerator,
|
|
12
|
+
renderGhostImage,
|
|
12
13
|
} from './croppingHandlers';
|
|
13
14
|
|
|
14
|
-
import { describe, expect, test, beforeEach, afterEach } from 'vitest';
|
|
15
|
+
import { describe, expect, test, beforeEach, afterEach, vi } from 'vitest';
|
|
15
16
|
|
|
16
17
|
describe('croppingHandlers', () => {
|
|
17
18
|
let canvas: Canvas;
|
|
@@ -26,7 +27,7 @@ describe('croppingHandlers', () => {
|
|
|
26
27
|
corner,
|
|
27
28
|
originX: origin.x,
|
|
28
29
|
originY: origin.y,
|
|
29
|
-
} as Transform;
|
|
30
|
+
} as unknown as Transform;
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
function createMockImage(
|
|
@@ -57,6 +58,8 @@ describe('croppingHandlers', () => {
|
|
|
57
58
|
cropX,
|
|
58
59
|
cropY,
|
|
59
60
|
});
|
|
61
|
+
img.controls = createImageCroppingControls();
|
|
62
|
+
|
|
60
63
|
return img;
|
|
61
64
|
}
|
|
62
65
|
|
|
@@ -65,7 +68,7 @@ describe('croppingHandlers', () => {
|
|
|
65
68
|
image = createMockImage();
|
|
66
69
|
canvas.add(image);
|
|
67
70
|
eventData = {};
|
|
68
|
-
transform = prepareTransform(image, '
|
|
71
|
+
transform = prepareTransform(image, 'mrc');
|
|
69
72
|
});
|
|
70
73
|
|
|
71
74
|
afterEach(() => {
|
|
@@ -85,7 +88,7 @@ describe('croppingHandlers', () => {
|
|
|
85
88
|
// Image element is 200px wide, cropX is 0, so max available is 200
|
|
86
89
|
image = createMockImage({ width: 100, cropX: 50, elementWidth: 200 });
|
|
87
90
|
canvas.add(image);
|
|
88
|
-
transform = prepareTransform(image, '
|
|
91
|
+
transform = prepareTransform(image, 'mrc');
|
|
89
92
|
|
|
90
93
|
// Try to set width beyond available (200 - 50 = 150 available)
|
|
91
94
|
changeImageWidth(eventData, transform, 500, 50);
|
|
@@ -94,7 +97,7 @@ describe('croppingHandlers', () => {
|
|
|
94
97
|
|
|
95
98
|
test('constrains width to minimum of 1 (lower limit)', () => {
|
|
96
99
|
image = createMockImage({ width: 100, cropX: 50, elementWidth: 200 });
|
|
97
|
-
transform = prepareTransform(image, '
|
|
100
|
+
transform = prepareTransform(image, 'mrc');
|
|
98
101
|
changeImageWidth(eventData, transform, 0.1, 50);
|
|
99
102
|
expect(image.width).toBe(1);
|
|
100
103
|
});
|
|
@@ -105,7 +108,7 @@ describe('croppingHandlers', () => {
|
|
|
105
108
|
cropX: 50,
|
|
106
109
|
elementWidth: 200,
|
|
107
110
|
});
|
|
108
|
-
transform = prepareTransform(image, '
|
|
111
|
+
transform = prepareTransform(image, 'mrc');
|
|
109
112
|
const changed = changeImageWidth(eventData, transform, 200, 50);
|
|
110
113
|
expect(changed).toBe(true);
|
|
111
114
|
const changed2 = changeImageWidth(eventData, transform, 200, 50);
|
|
@@ -113,17 +116,6 @@ describe('croppingHandlers', () => {
|
|
|
113
116
|
});
|
|
114
117
|
});
|
|
115
118
|
|
|
116
|
-
describe('changeCropWidth', () => {
|
|
117
|
-
test('is wrapped with wrapWithFireEvent and wrapWithFixedAnchor', () => {
|
|
118
|
-
// Re-import to trigger the wrapping
|
|
119
|
-
// Since the module is already loaded, we verify the export is a function
|
|
120
|
-
expect(typeof changeCropWidth).toBe('function');
|
|
121
|
-
|
|
122
|
-
// The wrapped function should be different from the base function
|
|
123
|
-
expect(changeCropWidth).not.toBe(changeImageWidth);
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
|
|
127
119
|
describe('changeImageHeight', () => {
|
|
128
120
|
beforeEach(() => {
|
|
129
121
|
image = createMockImage({
|
|
@@ -131,7 +123,7 @@ describe('croppingHandlers', () => {
|
|
|
131
123
|
cropY: 50,
|
|
132
124
|
elementHeight: 200,
|
|
133
125
|
});
|
|
134
|
-
transform = prepareTransform(image, '
|
|
126
|
+
transform = prepareTransform(image, 'mbc');
|
|
135
127
|
});
|
|
136
128
|
|
|
137
129
|
test('changes height normally when within bounds', () => {
|
|
@@ -161,14 +153,6 @@ describe('croppingHandlers', () => {
|
|
|
161
153
|
});
|
|
162
154
|
});
|
|
163
155
|
|
|
164
|
-
describe('changeCropHeight', () => {
|
|
165
|
-
test('is wrapped with wrapWithFireEvent and wrapWithFixedAnchor', () => {
|
|
166
|
-
// The wrapped function should be different from the base function
|
|
167
|
-
expect(typeof changeCropHeight).toBe('function');
|
|
168
|
-
expect(changeCropHeight).not.toBe(changeImageHeight);
|
|
169
|
-
});
|
|
170
|
-
});
|
|
171
|
-
|
|
172
156
|
describe('changeImageCropX', () => {
|
|
173
157
|
beforeEach(() => {
|
|
174
158
|
image = createMockImage({
|
|
@@ -177,7 +161,7 @@ describe('croppingHandlers', () => {
|
|
|
177
161
|
elementWidth: 200,
|
|
178
162
|
});
|
|
179
163
|
// Use 'ml' corner for cropX - changing left side moves cropX
|
|
180
|
-
transform = prepareTransform(image, '
|
|
164
|
+
transform = prepareTransform(image, 'mlc');
|
|
181
165
|
});
|
|
182
166
|
|
|
183
167
|
test('changes cropX and width together', () => {
|
|
@@ -189,7 +173,7 @@ describe('croppingHandlers', () => {
|
|
|
189
173
|
|
|
190
174
|
test('constrains cropX to minimum of 0 and adjusts width accordingly', () => {
|
|
191
175
|
image = createMockImage({ width: 100, cropX: 10, elementWidth: 200 });
|
|
192
|
-
transform = prepareTransform(image, '
|
|
176
|
+
transform = prepareTransform(image, 'mlc');
|
|
193
177
|
|
|
194
178
|
changeImageCropX(eventData, transform, -10, 50);
|
|
195
179
|
|
|
@@ -215,13 +199,6 @@ describe('croppingHandlers', () => {
|
|
|
215
199
|
});
|
|
216
200
|
});
|
|
217
201
|
|
|
218
|
-
describe('changeCropX', () => {
|
|
219
|
-
test('is wrapped with wrapWithFireEvent and wrapWithFixedAnchor', () => {
|
|
220
|
-
expect(typeof changeCropX).toBe('function');
|
|
221
|
-
expect(changeCropX).not.toBe(changeImageCropX);
|
|
222
|
-
});
|
|
223
|
-
});
|
|
224
|
-
|
|
225
202
|
describe('changeImageCropY', () => {
|
|
226
203
|
beforeEach(() => {
|
|
227
204
|
image = createMockImage({
|
|
@@ -230,7 +207,7 @@ describe('croppingHandlers', () => {
|
|
|
230
207
|
elementHeight: 200,
|
|
231
208
|
});
|
|
232
209
|
// Use 'mt' corner for cropY - changing top side moves cropY
|
|
233
|
-
transform = prepareTransform(image, '
|
|
210
|
+
transform = prepareTransform(image, 'mtc');
|
|
234
211
|
});
|
|
235
212
|
|
|
236
213
|
test('changes cropY and height together', () => {
|
|
@@ -245,7 +222,7 @@ describe('croppingHandlers', () => {
|
|
|
245
222
|
test('constrains cropY to minimum of 0 and adjusts height accordingly', () => {
|
|
246
223
|
image = createMockImage({ height: 100, cropY: 10, elementHeight: 200 });
|
|
247
224
|
canvas.add(image);
|
|
248
|
-
transform = prepareTransform(image, '
|
|
225
|
+
transform = prepareTransform(image, 'mtc');
|
|
249
226
|
|
|
250
227
|
changeImageCropY(eventData, transform, 50, -30);
|
|
251
228
|
|
|
@@ -261,10 +238,342 @@ describe('croppingHandlers', () => {
|
|
|
261
238
|
});
|
|
262
239
|
});
|
|
263
240
|
|
|
264
|
-
describe('
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
241
|
+
describe('cropPanMoveHandler', () => {
|
|
242
|
+
beforeEach(() => {
|
|
243
|
+
image = createMockImage({
|
|
244
|
+
width: 100,
|
|
245
|
+
height: 100,
|
|
246
|
+
cropX: 50,
|
|
247
|
+
cropY: 50,
|
|
248
|
+
elementWidth: 300,
|
|
249
|
+
elementHeight: 300,
|
|
250
|
+
});
|
|
251
|
+
canvas.add(image);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test('pans the image by adjusting cropX and cropY', () => {
|
|
255
|
+
const original = {
|
|
256
|
+
left: image.left,
|
|
257
|
+
top: image.top,
|
|
258
|
+
cropX: image.cropX,
|
|
259
|
+
cropY: image.cropY,
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
// Simulate moving the image 10px to the right and 10px down
|
|
263
|
+
image.left = original.left + 10;
|
|
264
|
+
image.top = original.top + 10;
|
|
265
|
+
|
|
266
|
+
const moveEvent = {
|
|
267
|
+
transform: {
|
|
268
|
+
target: image,
|
|
269
|
+
original,
|
|
270
|
+
} as unknown as Transform,
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
cropPanMoveHandler(moveEvent as any);
|
|
274
|
+
|
|
275
|
+
// cropX should decrease (panning right means showing more of the left side)
|
|
276
|
+
expect(image.cropX).toBeLessThan(original.cropX);
|
|
277
|
+
// cropY should decrease (panning down means showing more of the top)
|
|
278
|
+
expect(image.cropY).toBeLessThan(original.cropY);
|
|
279
|
+
// Position should be restored to original
|
|
280
|
+
expect(image.left).toBe(original.left);
|
|
281
|
+
expect(image.top).toBe(original.top);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
test('constrains cropX to minimum of 0', () => {
|
|
285
|
+
const original = {
|
|
286
|
+
left: image.left,
|
|
287
|
+
top: image.top,
|
|
288
|
+
cropX: 10,
|
|
289
|
+
cropY: 50,
|
|
290
|
+
};
|
|
291
|
+
image.cropX = 10;
|
|
292
|
+
|
|
293
|
+
// Move far right to try to get negative cropX
|
|
294
|
+
image.left = original.left + 100;
|
|
295
|
+
image.top = original.top;
|
|
296
|
+
|
|
297
|
+
const moveEvent = {
|
|
298
|
+
transform: {
|
|
299
|
+
target: image,
|
|
300
|
+
original,
|
|
301
|
+
} as unknown as Transform,
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
cropPanMoveHandler(moveEvent as any);
|
|
305
|
+
|
|
306
|
+
expect(image.cropX).toBeGreaterThanOrEqual(0);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
test('constrains cropY to minimum of 0', () => {
|
|
310
|
+
const original = {
|
|
311
|
+
left: image.left,
|
|
312
|
+
top: image.top,
|
|
313
|
+
cropX: 50,
|
|
314
|
+
cropY: 10,
|
|
315
|
+
};
|
|
316
|
+
image.cropY = 10;
|
|
317
|
+
|
|
318
|
+
// Move far down to try to get negative cropY
|
|
319
|
+
image.left = original.left;
|
|
320
|
+
image.top = original.top + 100;
|
|
321
|
+
|
|
322
|
+
const moveEvent = {
|
|
323
|
+
transform: {
|
|
324
|
+
target: image,
|
|
325
|
+
original,
|
|
326
|
+
} as unknown as Transform,
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
cropPanMoveHandler(moveEvent as any);
|
|
330
|
+
|
|
331
|
+
expect(image.cropY).toBeGreaterThanOrEqual(0);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
test('constrains cropX so crop area stays within element bounds', () => {
|
|
335
|
+
const original = {
|
|
336
|
+
left: image.left,
|
|
337
|
+
top: image.top,
|
|
338
|
+
cropX: 150, // Near the right edge (element is 300px wide)
|
|
339
|
+
cropY: 50,
|
|
340
|
+
};
|
|
341
|
+
image.cropX = 150;
|
|
342
|
+
|
|
343
|
+
// Move far left to try to exceed element width
|
|
344
|
+
image.left = original.left - 200;
|
|
345
|
+
image.top = original.top;
|
|
346
|
+
|
|
347
|
+
const moveEvent = {
|
|
348
|
+
transform: {
|
|
349
|
+
target: image,
|
|
350
|
+
original,
|
|
351
|
+
} as unknown as Transform,
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
cropPanMoveHandler(moveEvent as any);
|
|
355
|
+
|
|
356
|
+
// cropX + width should not exceed element width
|
|
357
|
+
expect(image.cropX + image.width).toBeLessThanOrEqual(300);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
test('constrains cropY so crop area stays within element bounds', () => {
|
|
361
|
+
const original = {
|
|
362
|
+
left: image.left,
|
|
363
|
+
top: image.top,
|
|
364
|
+
cropX: 50,
|
|
365
|
+
cropY: 150, // Near the bottom edge (element is 300px tall)
|
|
366
|
+
};
|
|
367
|
+
image.cropY = 150;
|
|
368
|
+
|
|
369
|
+
// Move far up to try to exceed element height
|
|
370
|
+
image.left = original.left;
|
|
371
|
+
image.top = original.top - 200;
|
|
372
|
+
|
|
373
|
+
const moveEvent = {
|
|
374
|
+
transform: {
|
|
375
|
+
target: image,
|
|
376
|
+
original,
|
|
377
|
+
} as unknown as Transform,
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
cropPanMoveHandler(moveEvent as any);
|
|
381
|
+
|
|
382
|
+
// cropY + height should not exceed element height
|
|
383
|
+
expect(image.cropY + image.height).toBeLessThanOrEqual(300);
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
describe('ghostScalePositionHandler', () => {
|
|
388
|
+
beforeEach(() => {
|
|
389
|
+
image = createMockImage({
|
|
390
|
+
width: 100,
|
|
391
|
+
height: 100,
|
|
392
|
+
cropX: 50,
|
|
393
|
+
cropY: 50,
|
|
394
|
+
elementWidth: 300,
|
|
395
|
+
elementHeight: 300,
|
|
396
|
+
});
|
|
397
|
+
canvas.add(image);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
test('positions top-left corner control correctly', () => {
|
|
401
|
+
const control = new Control({ x: -0.5, y: -0.5 });
|
|
402
|
+
const result = ghostScalePositionHandler.call(
|
|
403
|
+
control,
|
|
404
|
+
new Point(100, 100),
|
|
405
|
+
[1, 2, 3, 4, 5, 6], // this matrix is not used
|
|
406
|
+
image,
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
expect(result).toEqual({ x: -50, y: -50 });
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
test('positions bottom-right corner control correctly', () => {
|
|
413
|
+
const control = new Control({ x: 0.5, y: 0.5 });
|
|
414
|
+
const result = ghostScalePositionHandler.call(
|
|
415
|
+
control,
|
|
416
|
+
new Point(100, 100),
|
|
417
|
+
[1, 2, 3, 4, 5, 6], // this matrix is not used
|
|
418
|
+
image,
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
expect(result).toEqual({ x: 250, y: 250 });
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
test('positions top-right corner control correctly', () => {
|
|
425
|
+
const control = new Control({ x: 0.5, y: -0.5 });
|
|
426
|
+
const result = ghostScalePositionHandler.call(
|
|
427
|
+
control,
|
|
428
|
+
new Point(100, 100),
|
|
429
|
+
[1, 2, 3, 4, 5, 6], // this matrix is not used
|
|
430
|
+
image,
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
expect(result).toEqual({ x: 250, y: -50 });
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
test('positions bottom-left corner control correctly', () => {
|
|
437
|
+
const control = new Control({ x: -0.5, y: 0.5 });
|
|
438
|
+
const result = ghostScalePositionHandler.call(
|
|
439
|
+
control,
|
|
440
|
+
new Point(100, 100),
|
|
441
|
+
[1, 2, 3, 4, 5, 6], // this matrix is not used
|
|
442
|
+
image,
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
expect(result).toEqual({ x: -50, y: 250 });
|
|
446
|
+
});
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
describe('scaleEquallyCropGenerator', () => {
|
|
450
|
+
beforeEach(() => {
|
|
451
|
+
image = createMockImage({
|
|
452
|
+
width: 100,
|
|
453
|
+
height: 100,
|
|
454
|
+
cropX: 50,
|
|
455
|
+
cropY: 50,
|
|
456
|
+
elementWidth: 300,
|
|
457
|
+
elementHeight: 300,
|
|
458
|
+
});
|
|
459
|
+
canvas.add(image);
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
test('returns a TransformActionHandler function', () => {
|
|
463
|
+
const handler = scaleEquallyCropGenerator(-0.5, -0.5);
|
|
464
|
+
expect(typeof handler).toBe('function');
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
test('scales image uniformly from top-left corner', () => {
|
|
468
|
+
const handler = scaleEquallyCropGenerator(-0.5, -0.5);
|
|
469
|
+
transform = prepareTransform(image, 'tls');
|
|
470
|
+
expect(image.scaleX).toBe(1);
|
|
471
|
+
// Simulate dragging to scale up
|
|
472
|
+
const result = handler(eventData, transform, -400, -400);
|
|
473
|
+
|
|
474
|
+
// The handler should return a boolean
|
|
475
|
+
expect(result).toBe(true);
|
|
476
|
+
expect(image.scaleX.toFixed(2)).toBe('2.17');
|
|
477
|
+
expect(image.scaleX).toBe(image.scaleY);
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
test('scales image uniformly from bottom-right corner', () => {
|
|
481
|
+
const handler = scaleEquallyCropGenerator(0.5, 0.5);
|
|
482
|
+
transform = prepareTransform(image, 'brs');
|
|
483
|
+
expect(image.scaleX).toBe(1);
|
|
484
|
+
const result = handler(eventData, transform, 400, 400);
|
|
485
|
+
expect(result).toBe(true);
|
|
486
|
+
expect(image.scaleX).toBe(1.5);
|
|
487
|
+
expect(image.scaleX).toBe(image.scaleY);
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
test('returns false when scaling would exceed element bounds', () => {
|
|
491
|
+
// Set up image near the edge of element
|
|
492
|
+
image = createMockImage({
|
|
493
|
+
width: 250,
|
|
494
|
+
height: 250,
|
|
495
|
+
cropX: 25,
|
|
496
|
+
cropY: 25,
|
|
497
|
+
elementWidth: 300,
|
|
498
|
+
elementHeight: 300,
|
|
499
|
+
});
|
|
500
|
+
canvas.add(image);
|
|
501
|
+
|
|
502
|
+
const handler = scaleEquallyCropGenerator(-0.5, -0.5);
|
|
503
|
+
transform = prepareTransform(image, 'tls');
|
|
504
|
+
|
|
505
|
+
// Try to scale down significantly which might push bounds
|
|
506
|
+
const result = handler(eventData, transform, 10, 10);
|
|
507
|
+
|
|
508
|
+
expect(result).toBe(false);
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
test('adjusts cropX and cropY when scaling from negative corner', () => {
|
|
512
|
+
image = createMockImage({
|
|
513
|
+
width: 90,
|
|
514
|
+
height: 90,
|
|
515
|
+
cropX: 25,
|
|
516
|
+
cropY: 25,
|
|
517
|
+
elementWidth: 300,
|
|
518
|
+
elementHeight: 300,
|
|
519
|
+
});
|
|
520
|
+
canvas.add(image);
|
|
521
|
+
const handler = scaleEquallyCropGenerator(-0.5, -0.5);
|
|
522
|
+
transform = prepareTransform(image, 'tls');
|
|
523
|
+
expect(image.cropX).toBe(25);
|
|
524
|
+
expect(image.cropY).toBe(25);
|
|
525
|
+
const result = handler(eventData, transform, 5, 5);
|
|
526
|
+
expect(result).toBe(true);
|
|
527
|
+
// When scaling from top-left, cropX and cropY should be recalculated
|
|
528
|
+
expect(image.cropX).toBe(0);
|
|
529
|
+
expect(image.cropY).toBe(0);
|
|
530
|
+
});
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
describe('renderGhostImage', () => {
|
|
534
|
+
beforeEach(() => {
|
|
535
|
+
image = createMockImage({
|
|
536
|
+
width: 100,
|
|
537
|
+
height: 100,
|
|
538
|
+
cropX: 50,
|
|
539
|
+
cropY: 50,
|
|
540
|
+
elementWidth: 300,
|
|
541
|
+
elementHeight: 300,
|
|
542
|
+
});
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
test('draws image at correct position based on crop values', () => {
|
|
546
|
+
const mockCtx = {
|
|
547
|
+
globalAlpha: 1,
|
|
548
|
+
drawImage: vi.fn(),
|
|
549
|
+
} as unknown as CanvasRenderingContext2D;
|
|
550
|
+
|
|
551
|
+
renderGhostImage.call(image, { ctx: mockCtx });
|
|
552
|
+
|
|
553
|
+
// Should draw at (-width/2 - cropX, -height/2 - cropY)
|
|
554
|
+
// = (-50 - 50, -50 - 50) = (-100, -100)
|
|
555
|
+
expect(mockCtx.drawImage).toHaveBeenCalledWith(
|
|
556
|
+
image._element,
|
|
557
|
+
-100,
|
|
558
|
+
-100,
|
|
559
|
+
);
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
test('temporarily reduces globalAlpha by 50%', () => {
|
|
563
|
+
let alphaWhenDrawing: number | undefined;
|
|
564
|
+
const mockCtx = {
|
|
565
|
+
globalAlpha: 0.8,
|
|
566
|
+
drawImage: vi.fn(() => {
|
|
567
|
+
alphaWhenDrawing = mockCtx.globalAlpha;
|
|
568
|
+
}),
|
|
569
|
+
} as unknown as CanvasRenderingContext2D;
|
|
570
|
+
|
|
571
|
+
renderGhostImage.call(image, { ctx: mockCtx });
|
|
572
|
+
|
|
573
|
+
// During draw, alpha should be 0.8 * 0.5 = 0.4
|
|
574
|
+
expect(alphaWhenDrawing).toBe(0.4);
|
|
575
|
+
// After render, alpha should be restored
|
|
576
|
+
expect(mockCtx.globalAlpha).toBe(0.8);
|
|
268
577
|
});
|
|
269
578
|
});
|
|
270
579
|
});
|
|
@@ -4,6 +4,8 @@ import type {
|
|
|
4
4
|
TransformActionHandler,
|
|
5
5
|
FabricImage,
|
|
6
6
|
ObjectEvents,
|
|
7
|
+
Control,
|
|
8
|
+
TMat2D,
|
|
7
9
|
} from 'fabric';
|
|
8
10
|
import { controlsUtils, Point, util } from 'fabric';
|
|
9
11
|
|
|
@@ -160,3 +162,124 @@ export const cropPanMoveHandler = ({ transform }: ObjectEvents['moving']) => {
|
|
|
160
162
|
fabricImage.left = original.left;
|
|
161
163
|
fabricImage.top = original.top;
|
|
162
164
|
};
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* This position handler works only for this specific use case.
|
|
168
|
+
* It does not support padding nor offset, and it reduces all possible positions
|
|
169
|
+
* to the main 4 corners only.
|
|
170
|
+
* Any position that is < 0 is the extreme left/top, the rest are right/bottom
|
|
171
|
+
*/
|
|
172
|
+
export function ghostScalePositionHandler(
|
|
173
|
+
this: Control,
|
|
174
|
+
dim: Point, // currentDimension
|
|
175
|
+
finalMatrix: TMat2D,
|
|
176
|
+
fabricObject: FabricImage,
|
|
177
|
+
// currentControl: Control,
|
|
178
|
+
) {
|
|
179
|
+
const matrix = fabricObject.calcTransformMatrix();
|
|
180
|
+
const vpt = fabricObject.getViewportTransform();
|
|
181
|
+
const _finalMatrix = util.multiplyTransformMatrices(vpt, matrix);
|
|
182
|
+
|
|
183
|
+
let x = 0;
|
|
184
|
+
let y = 0;
|
|
185
|
+
if (this.x < 0) {
|
|
186
|
+
x = -fabricObject.width / 2 - fabricObject.cropX;
|
|
187
|
+
} else {
|
|
188
|
+
x =
|
|
189
|
+
fabricObject.getElement().width -
|
|
190
|
+
fabricObject.width / 2 -
|
|
191
|
+
fabricObject.cropX;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (this.y < 0) {
|
|
195
|
+
y = -fabricObject.height / 2 - fabricObject.cropY;
|
|
196
|
+
} else {
|
|
197
|
+
y =
|
|
198
|
+
fabricObject.getElement().height -
|
|
199
|
+
fabricObject.height / 2 -
|
|
200
|
+
fabricObject.cropY;
|
|
201
|
+
}
|
|
202
|
+
return new Point(x, y).transform(_finalMatrix);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const calcScale = (currentPoint: Point, height: number, width: number) =>
|
|
206
|
+
Math.min(Math.abs(currentPoint.x / width), Math.abs(currentPoint.y / height));
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Action handler generator that handles scaling of an image in crop mode.
|
|
210
|
+
* The goal is to keep the current bounding box steady.
|
|
211
|
+
* So this action handler has its own calculations for a dynamic anchor point
|
|
212
|
+
*/
|
|
213
|
+
export const scaleEquallyCropGenerator =
|
|
214
|
+
(cx: number, cy: number): TransformActionHandler =>
|
|
215
|
+
(eventData, transform, x, y) => {
|
|
216
|
+
const { target } = transform as unknown as { target: FabricImage };
|
|
217
|
+
const { width: fullWidth, height: fullHeight } = target.getElement();
|
|
218
|
+
const remainderX = fullWidth - target.width - target.cropX;
|
|
219
|
+
const remainderY = fullHeight - target.height - target.cropY;
|
|
220
|
+
const anchorOriginX =
|
|
221
|
+
cx < 0 ? 1 + remainderX / target.width : -target.cropX / target.width;
|
|
222
|
+
const anchorOriginY =
|
|
223
|
+
cy < 0 ? 1 + remainderY / target.height : -target.cropY / target.height;
|
|
224
|
+
const constraint = target.translateToOriginPoint(
|
|
225
|
+
target.getCenterPoint(),
|
|
226
|
+
anchorOriginX,
|
|
227
|
+
anchorOriginY,
|
|
228
|
+
);
|
|
229
|
+
const newPoint = controlsUtils.getLocalPoint(
|
|
230
|
+
transform,
|
|
231
|
+
anchorOriginX,
|
|
232
|
+
anchorOriginY,
|
|
233
|
+
x,
|
|
234
|
+
y,
|
|
235
|
+
);
|
|
236
|
+
const scale = calcScale(newPoint, fullHeight, fullWidth);
|
|
237
|
+
const scaleChangeX = scale / target.scaleX;
|
|
238
|
+
const scaleChangeY = scale / target.scaleY;
|
|
239
|
+
const scaledRemainderX = remainderX / scaleChangeX;
|
|
240
|
+
const scaledRemainderY = remainderY / scaleChangeY;
|
|
241
|
+
const newWidth = target.width / scaleChangeX;
|
|
242
|
+
const newHeight = target.height / scaleChangeY;
|
|
243
|
+
const newCropX =
|
|
244
|
+
cx < 0
|
|
245
|
+
? fullWidth - newWidth - scaledRemainderX
|
|
246
|
+
: target.cropX / scaleChangeX;
|
|
247
|
+
const newCropY =
|
|
248
|
+
cy < 0
|
|
249
|
+
? fullHeight - newHeight - scaledRemainderY
|
|
250
|
+
: target.cropY / scaleChangeY;
|
|
251
|
+
|
|
252
|
+
if (
|
|
253
|
+
(cx < 0 ? scaledRemainderX : newCropX) + newWidth > fullWidth ||
|
|
254
|
+
(cy < 0 ? scaledRemainderY : newCropY) + newHeight > fullHeight
|
|
255
|
+
) {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
target.scaleX = scale;
|
|
260
|
+
target.scaleY = scale;
|
|
261
|
+
target.width = newWidth;
|
|
262
|
+
target.height = newHeight;
|
|
263
|
+
target.cropX = newCropX;
|
|
264
|
+
target.cropY = newCropY;
|
|
265
|
+
const newAnchorOriginX =
|
|
266
|
+
cx < 0 ? 1 + scaledRemainderX / newWidth : -newCropX / newWidth;
|
|
267
|
+
const newAnchorOriginY =
|
|
268
|
+
cy < 0 ? 1 + scaledRemainderY / newHeight : -newCropY / newHeight;
|
|
269
|
+
target.setPositionByOrigin(constraint, newAnchorOriginX, newAnchorOriginY);
|
|
270
|
+
return true;
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
export function renderGhostImage(
|
|
274
|
+
this: FabricImage,
|
|
275
|
+
{ ctx }: { ctx: CanvasRenderingContext2D },
|
|
276
|
+
) {
|
|
277
|
+
const alpha = ctx.globalAlpha;
|
|
278
|
+
ctx.globalAlpha *= 0.5;
|
|
279
|
+
ctx.drawImage(
|
|
280
|
+
this._element,
|
|
281
|
+
-this.width / 2 - this.cropX,
|
|
282
|
+
-this.height / 2 - this.cropY,
|
|
283
|
+
);
|
|
284
|
+
ctx.globalAlpha = alpha;
|
|
285
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type FabricImage, type TPointerEventInfo } from 'fabric';
|
|
2
2
|
import { createImageCroppingControls } from './croppingControls';
|
|
3
|
-
import { cropPanMoveHandler } from './croppingHandlers';
|
|
3
|
+
import { cropPanMoveHandler, renderGhostImage } from './croppingHandlers';
|
|
4
4
|
/**
|
|
5
5
|
* Coordinates the change to image to enter crop mode and returns
|
|
6
6
|
* a function to exit crop mode
|
|
@@ -10,12 +10,16 @@ export const enterCropMode = function enterCropMode(
|
|
|
10
10
|
{ target }: TPointerEventInfo,
|
|
11
11
|
) {
|
|
12
12
|
const fabricImage = target as FabricImage;
|
|
13
|
-
const { controls } = fabricImage;
|
|
13
|
+
const { controls, padding } = fabricImage;
|
|
14
|
+
fabricImage.padding = 0;
|
|
14
15
|
fabricImage.controls = createImageCroppingControls();
|
|
15
16
|
fabricImage.on('moving', cropPanMoveHandler);
|
|
17
|
+
fabricImage.on('before:render', renderGhostImage);
|
|
16
18
|
fabricImage.setCoords();
|
|
17
19
|
const exitCropMode = () => {
|
|
20
|
+
fabricImage.padding = padding;
|
|
18
21
|
fabricImage.off('moving', cropPanMoveHandler);
|
|
22
|
+
fabricImage.off('before:render', renderGhostImage);
|
|
19
23
|
fabricImage.controls = controls;
|
|
20
24
|
fabricImage.setCoords();
|
|
21
25
|
fabricImage.once('mousedblclick', enterCropMode);
|