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',