diff --git a/.codesandbox/templates/vanilla/src/index.ts b/.codesandbox/templates/vanilla/src/index.ts
index f0bcbd8a7a3..cafc616d9d6 100644
--- a/.codesandbox/templates/vanilla/src/index.ts
+++ b/.codesandbox/templates/vanilla/src/index.ts
@@ -1,6 +1,6 @@
import * as fabric from 'fabric';
import './styles.css';
-import { testCase } from './testcases/responsive';
+import { testCase } from './testcases/loadingSvgs';
const el = document.getElementById('canvas');
const canvas = (window.canvas = new fabric.Canvas(el));
diff --git a/.codesandbox/templates/vanilla/src/testcases/loadingSvgs.ts b/.codesandbox/templates/vanilla/src/testcases/loadingSvgs.ts
new file mode 100644
index 00000000000..8799e53312b
--- /dev/null
+++ b/.codesandbox/templates/vanilla/src/testcases/loadingSvgs.ts
@@ -0,0 +1,24 @@
+import * as fabric from 'fabric';
+
+const svgString = ``;
+
+export async function testCase(canvas: fabric.Canvas) {
+ const svg = await fabric.loadSVGFromString(svgString);
+ canvas.add(...(svg.objects.filter((obj) => !!obj) as fabric.FabricObject[]));
+}
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 6a50e61bd69..488518dfc49 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -40,7 +40,7 @@ jobs:
uses: ./.github/actions/build-fabric-cached
- name: Run ${{ matrix.suite }} tests with coverage
if: matrix.suite == 'unit'
- run: npm run test:coverage
+ run: npm run test:coverage && sleep 5
- name: Run ${{ matrix.suite }} tests with coverage
if: matrix.suite == 'visual'
run: npm run test:visual:coverage
@@ -49,6 +49,7 @@ jobs:
with:
name: coverage-${{ matrix.suite }}
path: .nyc_output/*.json
+
browser:
needs: [prime-build]
name: ${{ matrix.target }} ${{ matrix.suite }} tests
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3ad99f9b466..195b25b79c7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,12 @@
# Changelog
+## [next]
+
+- fix(FabricObject): Fix clipPath blurryness with scale [#9774](https://github.com/fabricjs/fabric.js/pull/9774)
+
## [6.4.3]
+- fix(FabricObject): Render clipPath as sharp as the object [#9774](https://github.com/fabricjs/fabric.js/pull/9774)
- fix(Controls): changeWidth can change width with decimals [#10186](https://github.com/fabricjs/fabric.js/pull/10186)
- ci(): Add some prebuilt fabric in the dist folder [#10178](https://github.com/fabricjs/fabric.js/pull/10178)
- chore(): Add more generic font families to FabricText.genericFonts [#10167](https://github.com/fabricjs/fabric.js/pull/10167)
diff --git a/src/canvas/StaticCanvas.ts b/src/canvas/StaticCanvas.ts
index c757be231e3..8100043063c 100644
--- a/src/canvas/StaticCanvas.ts
+++ b/src/canvas/StaticCanvas.ts
@@ -26,7 +26,7 @@ import {
} from '../util/animation/AnimationFrameProvider';
import { runningAnimations } from '../util/animation/AnimationRegistry';
import { uid } from '../util/internals/uid';
-import { createCanvasElement, toDataURL } from '../util/misc/dom';
+import { createCanvasElementFor, toDataURL } from '../util/misc/dom';
import { invertTransform, transformPoint } from '../util/misc/matrix';
import type { EnlivenObjectOptions } from '../util/misc/objectEnlive';
import {
@@ -585,9 +585,10 @@ export class StaticCanvas<
if (path) {
path._set('canvas', this);
// needed to setup a couple of variables
+ // todo migrate to the newer one
path.shouldCache();
path._transformDone = true;
- path.renderCache({ forClipping: true });
+ (path as TCachedFabricObject).renderCache({ forClipping: true });
this.drawClipPathOnCanvas(ctx, path as TCachedFabricObject);
}
this._renderOverlay(ctx);
@@ -1334,9 +1335,7 @@ export class StaticCanvas<
* This essentially copies canvas dimensions since loadFromJSON does not affect canvas size.
*/
cloneWithoutData() {
- const el = createCanvasElement();
- el.width = this.width;
- el.height = this.height;
+ const el = createCanvasElementFor(this);
return new (this.constructor as Constructor)(el);
}
@@ -1425,12 +1424,13 @@ export class StaticCanvas<
translateY = (vp[5] - (top || 0)) * multiplier,
newVp = [newZoom, 0, 0, newZoom, translateX, translateY] as TMat2D,
originalRetina = this.enableRetinaScaling,
- canvasEl = createCanvasElement(),
+ canvasEl = createCanvasElementFor({
+ width: scaledWidth,
+ height: scaledHeight,
+ }),
objectsToRender = filter
? this._objects.filter((obj) => filter(obj))
: this._objects;
- canvasEl.width = scaledWidth;
- canvasEl.height = scaledHeight;
this.enableRetinaScaling = false;
this.viewportTransform = newVp;
this.width = scaledWidth;
diff --git a/src/filters/BaseFilter.ts b/src/filters/BaseFilter.ts
index eded9631ee2..6305f31dca5 100644
--- a/src/filters/BaseFilter.ts
+++ b/src/filters/BaseFilter.ts
@@ -1,5 +1,4 @@
import { getEnv } from '../env';
-import { createCanvasElement } from '../util/misc/dom';
import type {
T2DPipelineState,
TWebGLAttributeLocationMap,
@@ -15,6 +14,7 @@ import {
} from './shaders/baseFilter';
import type { Abortable } from '../typedefs';
import { FabricError } from '../util/internals/console';
+import { createCanvasElementFor } from '../util/misc/dom';
const regex = new RegExp(highPsourceCode, 'g');
@@ -368,9 +368,11 @@ export class BaseFilter<
*/
createHelpLayer(options: T2DPipelineState) {
if (!options.helpLayer) {
- const helpLayer = createCanvasElement();
- helpLayer.width = options.sourceWidth;
- helpLayer.height = options.sourceHeight;
+ const { sourceWidth, sourceHeight } = options;
+ const helpLayer = createCanvasElementFor({
+ width: sourceWidth,
+ height: sourceHeight,
+ });
options.helpLayer = helpLayer;
}
}
diff --git a/src/filters/WebGLFilterBackend.ts b/src/filters/WebGLFilterBackend.ts
index cddd27c380e..aaa575712da 100644
--- a/src/filters/WebGLFilterBackend.ts
+++ b/src/filters/WebGLFilterBackend.ts
@@ -1,5 +1,5 @@
import { config } from '../config';
-import { createCanvasElement } from '../util/misc/dom';
+import { createCanvasElementFor } from '../util/misc/dom';
import type {
TWebGLPipelineState,
TProgramCache,
@@ -72,9 +72,7 @@ export class WebGLFilterBackend {
* class properties to the GLFilterBackend class.
*/
createWebGLCanvas(width: number, height: number): void {
- const canvas = createCanvasElement();
- canvas.width = width;
- canvas.height = height;
+ const canvas = createCanvasElementFor({ width, height });
const glOptions = {
alpha: true,
premultipliedAlpha: false,
diff --git a/src/filters/utils.ts b/src/filters/utils.ts
index 39d9d634a94..f1ae1dbfa36 100644
--- a/src/filters/utils.ts
+++ b/src/filters/utils.ts
@@ -1,5 +1,5 @@
import { getFabricWindow } from '../env';
-import { createCanvasElement } from '../util/misc/dom';
+import { createCanvasElement, createCanvasElementFor } from '../util/misc/dom';
import { WebGLFilterBackend } from './WebGLFilterBackend';
import type { TWebGLPipelineState, T2DPipelineState } from './typedefs';
@@ -16,7 +16,7 @@ export const isWebGLPipelineState = (
* putImageData is faster than drawImage for that specific operation.
*/
export const isPutImageFaster = (width: number, height: number): boolean => {
- const targetCanvas = createCanvasElement();
+ const targetCanvas = createCanvasElementFor({ width, height });
const sourceCanvas = createCanvasElement();
const gl = sourceCanvas.getContext('webgl')!;
// eslint-disable-next-line no-undef
@@ -31,8 +31,6 @@ export const isPutImageFaster = (width: number, height: number): boolean => {
targetCanvas: targetCanvas,
} as unknown as TWebGLPipelineState;
let startTime;
- targetCanvas.width = width;
- targetCanvas.height = height;
startTime = getFabricWindow().performance.now();
WebGLFilterBackend.prototype.copyGLTo2D.call(
diff --git a/src/parser/elements_parser.ts b/src/parser/elements_parser.ts
index ae313baceac..436862e20b1 100644
--- a/src/parser/elements_parser.ts
+++ b/src/parser/elements_parser.ts
@@ -135,7 +135,11 @@ export class ElementsParser {
// TODO: resolveClipPath could be run once per clippath with minor work per object.
// is a refactor that i m not sure is worth on this code
- async resolveClipPath(obj: NotParsedFabricObject, usingElement: Element) {
+ async resolveClipPath(
+ obj: NotParsedFabricObject,
+ usingElement: Element,
+ exactOwner?: Element,
+ ) {
const clipPathElements = this.extractPropertyDefinition(
obj,
'clipPath',
@@ -146,6 +150,7 @@ export class ElementsParser {
const clipPathTag = clipPathElements[0].parentElement!;
let clipPathOwner = usingElement;
while (
+ !exactOwner &&
clipPathOwner.parentElement &&
clipPathOwner.getAttribute('clip-path') !== obj.clipPath
) {
@@ -188,7 +193,14 @@ export class ElementsParser {
clipPath.calcTransformMatrix(),
);
if (clipPath.clipPath) {
- await this.resolveClipPath(clipPath, clipPathOwner);
+ await this.resolveClipPath(
+ clipPath,
+ clipPathOwner,
+ // this is tricky.
+ // it tries to differentiate from when clipPaths are inherited by outside groups
+ // or when are really clipPaths referencing other clipPaths
+ clipPathTag.getAttribute('clip-path') ? clipPathOwner : undefined,
+ );
}
const { scaleX, scaleY, angle, skewX, translateX, translateY } =
qrDecompose(gTransform);
diff --git a/src/parser/parseSVGDocument.ts b/src/parser/parseSVGDocument.ts
index 43543b585ff..b82a9458d36 100644
--- a/src/parser/parseSVGDocument.ts
+++ b/src/parser/parseSVGDocument.ts
@@ -54,7 +54,6 @@ export async function parseSVGDocument(
crossOrigin,
signal,
};
-
const elements = descendants.filter((el) => {
applyViewboxTransform(el);
return isValidSvgTag(el) && !hasInvalidAncestor(el); // http://www.w3.org/TR/SVG/struct.html#DefsElement
diff --git a/src/shapes/Group.ts b/src/shapes/Group.ts
index 8cc4b37c93e..6c0015f8f9b 100644
--- a/src/shapes/Group.ts
+++ b/src/shapes/Group.ts
@@ -34,6 +34,7 @@ import {
} from '../LayoutManager/constants';
import type { SerializedLayoutManager } from '../LayoutManager/LayoutManager';
import type { FitContentLayout } from '../LayoutManager';
+import type { DrawContext } from './Object/Object';
/**
* This class handles the specific case of creating a group using {@link Group#fromObject} and is not meant to be used in any other case.
@@ -42,7 +43,6 @@ import type { FitContentLayout } from '../LayoutManager';
* This layout manager doesn't do anything and therefore keeps the exact layout the group had when {@link Group#toObject} was called.
*/
class NoopLayoutManager extends LayoutManager {
- // eslint-disable-next-line @typescript-eslint/no-empty-function
performLayout() {}
}
@@ -493,23 +493,25 @@ export class Group
* Execute the drawing operation for an object on a specified context
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
- drawObject(ctx: CanvasRenderingContext2D) {
+ drawObject(
+ ctx: CanvasRenderingContext2D,
+ forClipping: boolean | undefined,
+ context: DrawContext,
+ ) {
this._renderBackground(ctx);
for (let i = 0; i < this._objects.length; i++) {
+ const obj = this._objects[i];
// TODO: handle rendering edge case somehow
- if (
- this.canvas?.preserveObjectStacking &&
- this._objects[i].group !== this
- ) {
+ if (this.canvas?.preserveObjectStacking && obj.group !== this) {
ctx.save();
ctx.transform(...invertTransform(this.calcTransformMatrix()));
- this._objects[i].render(ctx);
+ obj.render(ctx);
ctx.restore();
- } else if (this._objects[i].group === this) {
- this._objects[i].render(ctx);
+ } else if (obj.group === this) {
+ obj.render(ctx);
}
}
- this._drawClipPath(ctx, this.clipPath);
+ this._drawClipPath(ctx, this.clipPath, context);
}
/**
diff --git a/src/shapes/Image.ts b/src/shapes/Image.ts
index e1cc44fcd36..7c0fe0b2645 100644
--- a/src/shapes/Image.ts
+++ b/src/shapes/Image.ts
@@ -11,7 +11,7 @@ import type {
TOptions,
} from '../typedefs';
import { uid } from '../util/internals/uid';
-import { createCanvasElement } from '../util/misc/dom';
+import { createCanvasElementFor } from '../util/misc/dom';
import { findScaleToCover, findScaleToFit } from '../util/misc/findScaleTo';
import type { LoadImageOptions } from '../util/misc/objectEnlive';
import {
@@ -497,19 +497,16 @@ export class FabricImage<
this._lastScaleY = scaleY;
return;
}
- const canvasEl = createCanvasElement(),
- sourceWidth = elementToFilter.width,
- sourceHeight = elementToFilter.height;
- canvasEl.width = sourceWidth;
- canvasEl.height = sourceHeight;
+ const canvasEl = createCanvasElementFor(elementToFilter),
+ { width, height } = elementToFilter;
this._element = canvasEl;
this._lastScaleX = filter.scaleX = scaleX;
this._lastScaleY = filter.scaleY = scaleY;
getFilterBackend().applyFilters(
[filter],
elementToFilter,
- sourceWidth,
- sourceHeight,
+ width,
+ height,
this._element,
);
this._filterScalingX = canvasEl.width / this._originalElement.width;
@@ -549,9 +546,10 @@ export class FabricImage<
if (this._element === this._originalElement) {
// if the _element a reference to _originalElement
// we need to create a new element to host the filtered pixels
- const canvasEl = createCanvasElement();
- canvasEl.width = sourceWidth;
- canvasEl.height = sourceHeight;
+ const canvasEl = createCanvasElementFor({
+ width: sourceWidth,
+ height: sourceHeight,
+ });
this._element = canvasEl;
this._filteredEl = canvasEl;
} else if (this._filteredEl) {
diff --git a/src/shapes/Object/Object.ts b/src/shapes/Object/Object.ts
index 51acf19b547..5e364b3f21c 100644
--- a/src/shapes/Object/Object.ts
+++ b/src/shapes/Object/Object.ts
@@ -27,7 +27,11 @@ import type {
import { classRegistry } from '../../ClassRegistry';
import { runningAnimations } from '../../util/animation/AnimationRegistry';
import { capValue } from '../../util/misc/capValue';
-import { createCanvasElement, toDataURL } from '../../util/misc/dom';
+import {
+ createCanvasElement,
+ createCanvasElementFor,
+ toDataURL,
+} from '../../util/misc/dom';
import { invertTransform, qrDecompose } from '../../util/misc/matrix';
import { enlivenObjectEnlivables } from '../../util/misc/objectEnlive';
import {
@@ -134,6 +138,18 @@ type toDataURLOptions = ObjectToCanvasElementOptions & {
quality?: number;
};
+export type DrawContext =
+ | {
+ parentClipPaths: FabricObject[];
+ width: number;
+ height: number;
+ cacheTranslationX: number;
+ cacheTranslationY: number;
+ zoomX: number;
+ zoomY: number;
+ }
+ | Record;
+
/**
* Root object class from which all 2d shape classes inherit from
* @tutorial {@link http://fabricjs.com/fabric-intro-part-1#objects}
@@ -698,11 +714,11 @@ export class FabricObject<
this._setOpacity(ctx);
this._setShadow(ctx);
if (this.shouldCache()) {
- this.renderCache();
+ (this as TCachedFabricObject).renderCache();
(this as TCachedFabricObject).drawCacheOnCanvas(ctx);
} else {
this._removeCacheCanvas();
- this.drawObject(ctx);
+ this.drawObject(ctx, false, {});
this.dirty = false;
}
ctx.restore();
@@ -712,13 +728,23 @@ export class FabricObject<
/* no op */
}
- renderCache(options?: any) {
+ renderCache(this: TCachedFabricObject, options?: any) {
options = options || {};
if (!this._cacheCanvas || !this._cacheContext) {
this._createCacheCanvas();
}
if (this.isCacheDirty() && this._cacheContext) {
- this.drawObject(this._cacheContext, options.forClipping);
+ const { zoomX, zoomY, cacheTranslationX, cacheTranslationY } = this;
+ const { width, height } = this._cacheCanvas;
+ this.drawObject(this._cacheContext, options.forClipping, {
+ zoomX,
+ zoomY,
+ cacheTranslationX,
+ cacheTranslationY,
+ width,
+ height,
+ parentClipPaths: [],
+ });
this.dirty = false;
}
}
@@ -819,7 +845,8 @@ export class FabricObject<
*/
drawClipPathOnCache(
ctx: CanvasRenderingContext2D,
- clipPath: TCachedFabricObject,
+ clipPath: FabricObject,
+ canvasWithClipPath: HTMLCanvasElement,
) {
ctx.save();
// DEBUG: uncomment this line, comment the following
@@ -829,18 +856,9 @@ export class FabricObject<
} else {
ctx.globalCompositeOperation = 'destination-in';
}
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
//ctx.scale(1 / 2, 1 / 2);
- if (clipPath.absolutePositioned) {
- const m = invertTransform(this.calcTransformMatrix());
- ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
- }
- clipPath.transform(ctx);
- ctx.scale(1 / clipPath.zoomX, 1 / clipPath.zoomY);
- ctx.drawImage(
- clipPath._cacheCanvas,
- -clipPath.cacheTranslationX,
- -clipPath.cacheTranslationY,
- );
+ ctx.drawImage(canvasWithClipPath, 0, 0);
ctx.restore();
}
@@ -848,8 +866,13 @@ export class FabricObject<
* Execute the drawing operation for an object on a specified context
* @param {CanvasRenderingContext2D} ctx Context to render on
* @param {boolean} forClipping apply clipping styles
+ * @param {DrawContext} context additional context for rendering
*/
- drawObject(ctx: CanvasRenderingContext2D, forClipping?: boolean) {
+ drawObject(
+ ctx: CanvasRenderingContext2D,
+ forClipping: boolean | undefined,
+ context: DrawContext,
+ ) {
const originalFill = this.fill,
originalStroke = this.stroke;
if (forClipping) {
@@ -860,28 +883,55 @@ export class FabricObject<
this._renderBackground(ctx);
}
this._render(ctx);
- this._drawClipPath(ctx, this.clipPath);
+ this._drawClipPath(ctx, this.clipPath, context);
this.fill = originalFill;
this.stroke = originalStroke;
}
+ private createClipPathLayer(
+ this: TCachedFabricObject,
+ clipPath: FabricObject,
+ context: DrawContext,
+ ) {
+ const canvas = createCanvasElementFor(context as TSize);
+ const ctx = canvas.getContext('2d')!;
+ ctx.translate(context.cacheTranslationX, context.cacheTranslationY);
+ ctx.scale(context.zoomX, context.zoomY);
+ clipPath._cacheCanvas = canvas;
+ context.parentClipPaths.forEach((prevClipPath) => {
+ prevClipPath.transform(ctx);
+ });
+ context.parentClipPaths.push(clipPath);
+ if (clipPath.absolutePositioned) {
+ const m = invertTransform(this.calcTransformMatrix());
+ ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
+ }
+ clipPath.transform(ctx);
+ clipPath.drawObject(ctx, true, context);
+ return canvas;
+ }
+
/**
* Prepare clipPath state and cache and draw it on instance's cache
* @param {CanvasRenderingContext2D} ctx
* @param {FabricObject} clipPath
*/
- _drawClipPath(ctx: CanvasRenderingContext2D, clipPath?: FabricObject) {
+ _drawClipPath(
+ ctx: CanvasRenderingContext2D,
+ clipPath: FabricObject | undefined,
+ context: DrawContext,
+ ) {
if (!clipPath) {
return;
}
- // needed to setup a couple of variables
- // path canvas gets overridden with this one.
+ // needed to setup _transformDone
// TODO find a better solution?
- clipPath._set('canvas', this.canvas);
- clipPath.shouldCache();
clipPath._transformDone = true;
- clipPath.renderCache({ forClipping: true });
- this.drawClipPathOnCache(ctx, clipPath as TCachedFabricObject);
+ const canvas = (this as TCachedFabricObject).createClipPathLayer(
+ clipPath,
+ context,
+ );
+ this.drawClipPathOnCache(ctx, clipPath, canvas);
}
/**
@@ -1184,14 +1234,16 @@ export class FabricObject<
filler: TFiller,
) {
const dims = this._limitCacheSize(this._getCacheCanvasDimensions()),
- pCanvas = createCanvasElement(),
retinaScaling = this.getCanvasRetinaScaling(),
width = dims.x / this.scaleX / retinaScaling,
- height = dims.y / this.scaleY / retinaScaling;
- // in case width and height are less than 1px, we have to round up.
- // since the pattern is no-repeat, this is fine
- pCanvas.width = Math.ceil(width);
- pCanvas.height = Math.ceil(height);
+ height = dims.y / this.scaleY / retinaScaling,
+ pCanvas = createCanvasElementFor({
+ // in case width and height are less than 1px, we have to round up.
+ // since the pattern is no-repeat, this is fine
+ width: Math.ceil(width),
+ height: Math.ceil(height),
+ });
+
const pCtx = pCanvas.getContext('2d');
if (!pCtx) {
return;
diff --git a/src/shapes/Text/Text.ts b/src/shapes/Text/Text.ts
index 5f0e4049aa5..3ab492b5a22 100644
--- a/src/shapes/Text/Text.ts
+++ b/src/shapes/Text/Text.ts
@@ -18,7 +18,7 @@ import type {
} from '../../typedefs';
import { classRegistry } from '../../ClassRegistry';
import { graphemeSplit } from '../../util/lang_string';
-import { createCanvasElement } from '../../util/misc/dom';
+import { createCanvasElementFor } from '../../util/misc/dom';
import type { TextStyleArray } from '../../util/misc/textStyles';
import {
hasStyleChanged,
@@ -55,8 +55,10 @@ let measuringContext: CanvasRenderingContext2D | null;
*/
function getMeasuringContext() {
if (!measuringContext) {
- const canvas = createCanvasElement();
- canvas.width = canvas.height = 0;
+ const canvas = createCanvasElementFor({
+ width: 0,
+ height: 0,
+ });
measuringContext = canvas.getContext('2d');
}
return measuringContext;
@@ -1251,10 +1253,13 @@ export class FabricText<
* @return {CanvasPattern} a pattern to use as fill/stroke style
*/
_applyPatternGradientTransformText(filler: TFiller) {
- const pCanvas = createCanvasElement(),
- // TODO: verify compatibility with strokeUniform
- width = this.width + this.strokeWidth,
+ // TODO: verify compatibility with strokeUniform
+ const width = this.width + this.strokeWidth,
height = this.height + this.strokeWidth,
+ pCanvas = createCanvasElementFor({
+ width,
+ height,
+ }),
pCtx = pCanvas.getContext('2d')!;
pCanvas.width = width;
pCanvas.height = height;
diff --git a/src/util/misc/dom.ts b/src/util/misc/dom.ts
index cf04a88de80..f8a62e12c67 100644
--- a/src/util/misc/dom.ts
+++ b/src/util/misc/dom.ts
@@ -1,5 +1,5 @@
import { getFabricDocument } from '../../env';
-import type { ImageFormat } from '../../typedefs';
+import type { ImageFormat, TSize } from '../../typedefs';
import { FabricError } from '../internals/console';
/**
* Creates canvas element
@@ -27,11 +27,18 @@ export const createImage = (): HTMLImageElement =>
*/
export const copyCanvasElement = (
canvas: HTMLCanvasElement,
+): HTMLCanvasElement => {
+ const newCanvas = createCanvasElementFor(canvas);
+ newCanvas.getContext('2d')?.drawImage(canvas, 0, 0);
+ return newCanvas;
+};
+
+export const createCanvasElementFor = (
+ canvas: HTMLCanvasElement | ImageData | HTMLImageElement | TSize,
): HTMLCanvasElement => {
const newCanvas = createCanvasElement();
newCanvas.width = canvas.width;
newCanvas.height = canvas.height;
- newCanvas.getContext('2d')?.drawImage(canvas, 0, 0);
return newCanvas;
};
diff --git a/test/visual/assets/sharp-clip-test.svg b/test/visual/assets/sharp-clip-test.svg
new file mode 100644
index 00000000000..da2a4bebdac
--- /dev/null
+++ b/test/visual/assets/sharp-clip-test.svg
@@ -0,0 +1,12 @@
+
+
diff --git a/test/visual/assets/sharp-clip-test2.svg b/test/visual/assets/sharp-clip-test2.svg
new file mode 100644
index 00000000000..e3590d2b8e9
--- /dev/null
+++ b/test/visual/assets/sharp-clip-test2.svg
@@ -0,0 +1,18 @@
+
+
\ No newline at end of file
diff --git a/test/visual/golden/clippath-5.png b/test/visual/golden/clippath-5.png
index cf7ca9abf3c..9af7b616df1 100644
Binary files a/test/visual/golden/clippath-5.png and b/test/visual/golden/clippath-5.png differ
diff --git a/test/visual/golden/clippath-6.png b/test/visual/golden/clippath-6.png
index 4b4bd4c4cdd..f66f73e27ea 100644
Binary files a/test/visual/golden/clippath-6.png and b/test/visual/golden/clippath-6.png differ
diff --git a/test/visual/golden/clippath-7.png b/test/visual/golden/clippath-7.png
index 98fb4ff1cc0..e09d7a660e2 100644
Binary files a/test/visual/golden/clippath-7.png and b/test/visual/golden/clippath-7.png differ
diff --git a/test/visual/golden/clippath-8.png b/test/visual/golden/clippath-8.png
new file mode 100644
index 00000000000..0f073cb26e0
Binary files /dev/null and b/test/visual/golden/clippath-8.png differ
diff --git a/test/visual/golden/clippath-9.png b/test/visual/golden/clippath-9.png
index 89fef4d842c..b4d1d328540 100644
Binary files a/test/visual/golden/clippath-9.png and b/test/visual/golden/clippath-9.png differ
diff --git a/test/visual/golden/clipping1.png b/test/visual/golden/clipping1.png
index 22ad3f0f1d7..56f518b957e 100644
Binary files a/test/visual/golden/clipping1.png and b/test/visual/golden/clipping1.png differ
diff --git a/test/visual/golden/clipping10.png b/test/visual/golden/clipping10.png
index 2a893d3eef2..76bf039dcdd 100644
Binary files a/test/visual/golden/clipping10.png and b/test/visual/golden/clipping10.png differ
diff --git a/test/visual/golden/clipping4.png b/test/visual/golden/clipping4.png
index 4678760cd08..e203e7b562f 100644
Binary files a/test/visual/golden/clipping4.png and b/test/visual/golden/clipping4.png differ
diff --git a/test/visual/golden/clipping7.png b/test/visual/golden/clipping7.png
index 1a048907535..3ae5388797c 100644
Binary files a/test/visual/golden/clipping7.png and b/test/visual/golden/clipping7.png differ
diff --git a/test/visual/golden/group-layout/clip-path3.png b/test/visual/golden/group-layout/clip-path3.png
index 46bd0fae243..7eed0c41eed 100644
Binary files a/test/visual/golden/group-layout/clip-path3.png and b/test/visual/golden/group-layout/clip-path3.png differ
diff --git a/test/visual/golden/notoemoji-person.png b/test/visual/golden/notoemoji-person.png
index 44d142ac9fd..6865ea76fff 100644
Binary files a/test/visual/golden/notoemoji-person.png and b/test/visual/golden/notoemoji-person.png differ
diff --git a/test/visual/golden/seaClipPath.png b/test/visual/golden/seaClipPath.png
index 154967841a7..4a85fac96f0 100644
Binary files a/test/visual/golden/seaClipPath.png and b/test/visual/golden/seaClipPath.png differ
diff --git a/test/visual/golden/sharp-clip-test.png b/test/visual/golden/sharp-clip-test.png
new file mode 100644
index 00000000000..093eaf59e42
Binary files /dev/null and b/test/visual/golden/sharp-clip-test.png differ
diff --git a/test/visual/golden/sharp-clip-test2.png b/test/visual/golden/sharp-clip-test2.png
new file mode 100644
index 00000000000..1fd8cd71b67
Binary files /dev/null and b/test/visual/golden/sharp-clip-test2.png differ
diff --git a/test/visual/svg_import.js b/test/visual/svg_import.js
index a5e6b03b1e5..efb88d5f772 100644
--- a/test/visual/svg_import.js
+++ b/test/visual/svg_import.js
@@ -22,8 +22,10 @@
getAsset(svgName, function(err, string) {
fabric.loadSVGFromString(string).then(({ objects, options }) => {
// something is disabling objectCaching and i cannot find where it is.
- var group = fabric.util.groupSVGElements(objects, options);
- canvas.setDimensions({ width: group.width + group.left, height: group.height + group.top });
+ var nonNullObj = objects.filter(obj => !!obj);
+ var group = fabric.util.groupSVGElements(nonNullObj, options);
+ var dims = group._getTransformedDimensions()
+ canvas.setDimensions({ width: dims.x + group.left, height: dims.y + group.top });
group.includeDefaultValues = false;
canvas.includeDefaultValues = false;
canvas.add(group);
@@ -43,6 +45,8 @@
QUnit.module('Simple svg import test');
var tests = [
+ 'sharp-clip-test',
+ 'sharp-clip-test2',
'svg_stroke_1',
'svg_stroke_2',
'svg_stroke_3',
@@ -80,7 +84,7 @@
'vector-effect',
'svg-with-no-dim-rect',
'notoemoji-person',
- // 'clippath-8',
+ 'clippath-8',
'emoji-b',
'gold-logo',
'svg_missing_clippath',