UNPKG

fabric

Version:

Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.

567 lines (487 loc) 17.3 kB
import { describe, it, expect } from 'vitest'; import { Point } from '../Point'; import { getFabricDocument } from '../env'; import { VERSION as version } from '../constants'; import { Polygon } from './Polygon'; import { Polyline } from './Polyline'; import { FabricObject } from './Object/FabricObject'; function getPoints() { return [ { x: 10, y: 12 }, { x: 20, y: 22 }, ]; } const REFERENCE_OBJECT = { version: version, type: 'Polygon', originX: 'center', originY: 'center', left: 15, top: 17, width: 10, height: 10, fill: 'rgb(0,0,0)', stroke: null, strokeWidth: 1, strokeDashArray: null, strokeLineCap: 'butt', strokeDashOffset: 0, strokeLineJoin: 'miter', strokeMiterLimit: 4, scaleX: 1, scaleY: 1, angle: 0, flipX: false, flipY: false, opacity: 1, points: getPoints(), shadow: null, visible: true, backgroundColor: '', fillRule: 'nonzero', paintFirst: 'fill', globalCompositeOperation: 'source-over', skewX: 0, skewY: 0, strokeUniform: false, } as const; const REFERENCE_EMPTY_OBJECT = { points: [], width: 0, height: 0, top: 0, left: 0, }; describe('Polygon', () => { it('constructor', () => { expect(Polygon, 'Polygon class should exist').toBeTruthy(); const polygon = new Polygon(getPoints()); expect(polygon, 'should be instance of Polygon').toBeInstanceOf(Polygon); expect(polygon, 'should be instance of Polyline').toBeInstanceOf(Polyline); expect(polygon, 'should be instance of FabricObject').toBeInstanceOf( FabricObject, ); expect(polygon.constructor, 'type should be Polygon').toHaveProperty( 'type', 'Polygon', ); expect(polygon.get('points'), 'points should match input').toEqual([ { x: 10, y: 12 }, { x: 20, y: 22 }, ]); }); it('constructor, with strokeWidth top-left and origins top-left', () => { const polygon = new Polygon(getPoints(), { strokeWidth: 2, originX: 'left', originY: 'top', }); expect(polygon.left, 'left should be 9').toBe(9); expect(polygon.top, 'top should be 11').toBe(11); }); it('constructor, with strokeWidth top-left and origins center-center', () => { const polygon = new Polygon(getPoints(), { strokeWidth: 2, originX: 'center', originY: 'center', }); expect(polygon.left, 'left should be 15').toBe(15); expect(polygon.top, 'top should be 17').toBe(17); }); it('constructor, with strokeWidth top-left and origins bottom-right', () => { const polygon = new Polygon(getPoints(), { strokeWidth: 2, originX: 'right', originY: 'bottom', }); expect(polygon.left, 'left should be 21').toBe(21); expect(polygon.top, 'top should be 23').toBe(23); }); it('polygon with exactBoundingBox false', () => { const polygon = new Polygon( [ { x: 10, y: 10 }, { x: 20, y: 10 }, { x: 20, y: 100 }, ], { // @ts-expect-error -- TODO: are types wrong for Polygon? seems like it doesn't accept exactBoundingBox property exactBoundingBox: false, strokeWidth: 60, }, ); const dimensions = polygon._getNonTransformedDimensions(); expect(dimensions.x, 'x dimension should be 70').toBe(70); expect(dimensions.y, 'y dimension should be 150').toBe(150); }); it('polygon with exactBoundingBox true', () => { const polygon = new Polygon( [ { x: 10, y: 10 }, { x: 10, y: 10 }, { x: 20, y: 10 }, { x: 20, y: 10 }, { x: 20, y: 10, }, { x: 20, y: 100 }, { x: 10, y: 10 }, ], { // @ts-expect-error -- TODO: are types wrong for Polygon? seems like it doesn't accept exactBoundingBox property exactBoundingBox: true, strokeWidth: 60, stroke: 'blue', }, ); const limitedMiter = polygon._getNonTransformedDimensions(); expect(Math.round(limitedMiter.x), 'limited miter x').toBe(74); expect(Math.round(limitedMiter.y), 'limited miter y').toBe(123); expect(polygon._getTransformedDimensions(), 'dims should match').toEqual( limitedMiter, ); polygon.set('strokeMiterLimit', 999); const miter = polygon._getNonTransformedDimensions(); expect(Math.round(miter.x), 'miter x').toBe(74); expect(Math.round(miter.y), 'miter y').toBe(662); expect(polygon._getTransformedDimensions(), 'dims should match').toEqual( miter, ); polygon.set('strokeLineJoin', 'bevel'); const bevel = polygon._getNonTransformedDimensions(); expect(Math.round(limitedMiter.x), 'bevel x').toBe(74); expect(Math.round(limitedMiter.y), 'bevel y').toBe(123); expect(polygon._getTransformedDimensions(), 'dims should match').toEqual( bevel, ); polygon.set('strokeLineJoin', 'round'); const round = polygon._getNonTransformedDimensions(); expect(Math.round(round.x), 'round x').toBe(70); expect(Math.round(round.y), 'round y').toBe(150); expect(polygon._getTransformedDimensions(), 'dims should match').toEqual( round, ); }); it.todo('polygon with exactBoundingBox true and skew', () => { const polygon = new Polygon( [ { x: 10, y: 10 }, { x: 20, y: 10 }, { x: 20, y: 100 }, ], { // @ts-expect-error -- TODO: are types wrong for Polygon? seems like it doesn't accept exactBoundingBox property exactBoundingBox: true, strokeWidth: 60, stroke: 'blue', skewX: 30, skewY: 45, }, ); const limitedMiter = polygon._getNonTransformedDimensions(); expect(Math.round(limitedMiter.x), 'limited miter x').toBe(185); expect(Math.round(limitedMiter.y), 'limited miter y').toBe(194); expect(polygon._getTransformedDimensions(), 'dims should match').toEqual( limitedMiter, ); polygon.set('strokeMiterLimit', 999); const miter = polygon._getNonTransformedDimensions(); expect(Math.round(miter.x), 'miter x').toBe(498); expect(Math.round(miter.y), 'miter y').toBe(735); expect(polygon._getTransformedDimensions(), 'dims should match').toEqual( miter, ); polygon.set('strokeLineJoin', 'bevel'); const bevel = polygon._getNonTransformedDimensions(); expect(Math.round(limitedMiter.x), 'bevel x').toBe(185); expect(Math.round(limitedMiter.y), 'bevel y').toBe(194); expect(polygon._getTransformedDimensions(), 'dims should match').toEqual( bevel, ); polygon.set('strokeLineJoin', 'round'); const round = polygon._getNonTransformedDimensions(); // WRONG value! was buggy when writing test expect(Math.round(round.x), 'round x').toBe(170); expect(Math.round(round.y), 'round y').toBe(185); expect(polygon._getTransformedDimensions(), 'dims should match').toEqual( round, ); }); it('complexity', () => { const polygon = new Polygon(getPoints()); expect(polygon.complexity, 'complexity should be a function').toBeTypeOf( 'function', ); }); it('toObject', () => { const polygon = new Polygon(getPoints()); expect(polygon.toObject, 'toObject should be a function').toBeTypeOf( 'function', ); expect( { ...polygon.toObject(), points: getPoints(), }, 'polygon object should match reference', ).toEqual(REFERENCE_OBJECT); }); it('toSVG', () => { const polygon = new Polygon(getPoints(), { fill: 'red', stroke: 'blue' }); expect(polygon.toSVG, 'toSVG should be a function').toBeTypeOf('function'); const EXPECTED_SVG = '<g transform="matrix(1 0 0 1 15 17)" >\n<polygon style="stroke: rgb(0,0,255); stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,0,0); fill-rule: nonzero; opacity: 1;" points="-5,-5 5,5 " />\n</g>\n'; expect(polygon.toSVG(), 'SVG output should match expected').toBe( EXPECTED_SVG, ); }); it('fromObject', async () => { expect(Polygon.fromObject, 'fromObject should be a function').toBeTypeOf( 'function', ); const polygon = await Polygon.fromObject(REFERENCE_OBJECT); expect(polygon, 'should be instance of Polygon').toBeInstanceOf(Polygon); expect(polygon.toObject(), 'polygon object should match reference').toEqual( REFERENCE_OBJECT, ); }); it('fromElement without points', async () => { expect(Polygon.fromElement, 'fromElement should be a function').toBeTypeOf( 'function', ); const elPolygonWithoutPoints = getFabricDocument().createElementNS( 'http://www.w3.org/2000/svg', 'polygon', ); elPolygonWithoutPoints.setAttributeNS( 'http://www.w3.org/2000/svg', 'stroke-width', String(0), ); const polygon = await Polygon.fromElement(elPolygonWithoutPoints); expect(polygon.toObject(), 'polygon object should match reference').toEqual( { ...REFERENCE_OBJECT, ...REFERENCE_EMPTY_OBJECT, strokeWidth: 0, }, ); }); it('fromElement without points but strokeWidth', async () => { const elPolygonWithoutPoints = getFabricDocument().createElementNS( 'http://www.w3.org/2000/svg', 'polygon', ); const polygon = await Polygon.fromElement(elPolygonWithoutPoints); expect(polygon.toObject(), 'polygon object should match reference').toEqual( { ...REFERENCE_OBJECT, ...REFERENCE_EMPTY_OBJECT, left: 0, top: 0, }, ); }); it('fromElement with empty points', async () => { const namespace = 'http://www.w3.org/2000/svg'; const elPolygonWithEmptyPoints = getFabricDocument().createElementNS( namespace, 'polygon', ); elPolygonWithEmptyPoints.setAttributeNS(namespace, 'points', ''); const polygon = await Polygon.fromElement(elPolygonWithEmptyPoints); expect(polygon.toObject(), 'polygon object should match reference').toEqual( { ...REFERENCE_OBJECT, ...REFERENCE_EMPTY_OBJECT, left: 0, top: 0, }, ); }); it('fromElement with points', async () => { const namespace = 'http://www.w3.org/2000/svg'; const elPolygon = getFabricDocument().createElementNS(namespace, 'polygon'); elPolygon.setAttributeNS(namespace, 'points', '10,12 20,22'); const polygon = await Polygon.fromElement(elPolygon); expect(polygon, 'should be instance of Polygon').toBeInstanceOf(Polygon); expect(polygon.toObject(), 'polygon object should match reference').toEqual( { ...REFERENCE_OBJECT, points: [ { x: 10, y: 12 }, { x: 20, y: 22 }, ], left: 15, top: 17, }, ); }); it('fromElement with points no strokewidth', async () => { const namespace = 'http://www.w3.org/2000/svg'; const elPolygon = getFabricDocument().createElementNS(namespace, 'polygon'); elPolygon.setAttributeNS(namespace, 'points', '10,12 20,22'); elPolygon.setAttributeNS(namespace, 'stroke-width', String(0)); const polygon = await Polygon.fromElement(elPolygon); expect(polygon, 'should be instance of Polygon').toBeInstanceOf(Polygon); expect(polygon.toObject(), 'polygon object should match reference').toEqual( { ...REFERENCE_OBJECT, strokeWidth: 0, points: [ { x: 10, y: 12 }, { x: 20, y: 22 }, ], left: 15, top: 17, }, ); }); it('fromElement with points and custom attributes', async () => { const namespace = 'http://www.w3.org/2000/svg'; const elPolygonWithAttrs = getFabricDocument().createElementNS( namespace, 'polygon', ); elPolygonWithAttrs.setAttributeNS( namespace, 'points', '10,10 20,20 30,30 10,10', ); elPolygonWithAttrs.setAttributeNS(namespace, 'fill', 'rgb(255,255,255)'); elPolygonWithAttrs.setAttributeNS(namespace, 'opacity', '0.34'); elPolygonWithAttrs.setAttributeNS(namespace, 'stroke-width', '3'); elPolygonWithAttrs.setAttributeNS(namespace, 'stroke', 'blue'); elPolygonWithAttrs.setAttributeNS( namespace, 'transform', 'translate(-10,-20) scale(2)', ); elPolygonWithAttrs.setAttributeNS(namespace, 'stroke-dasharray', '5, 2'); elPolygonWithAttrs.setAttributeNS(namespace, 'stroke-linecap', 'round'); elPolygonWithAttrs.setAttributeNS(namespace, 'stroke-linejoin', 'bevel'); elPolygonWithAttrs.setAttributeNS(namespace, 'stroke-miterlimit', '5'); const polygonWithAttrs = await Polygon.fromElement(elPolygonWithAttrs); const expectedPoints = [ { x: 10, y: 10 }, { x: 20, y: 20 }, { x: 30, y: 30 }, { x: 10, y: 10 }, ]; expect( polygonWithAttrs.toObject(), 'polygon object should match reference', ).toEqual({ ...REFERENCE_OBJECT, width: 20, height: 20, fill: 'rgb(255,255,255)', stroke: 'blue', strokeWidth: 3, strokeDashArray: [5, 2], strokeLineCap: 'round', strokeLineJoin: 'bevel', strokeMiterLimit: 5, opacity: 0.34, points: expectedPoints, top: 20, left: 20, }); }); it('_calcDimensions with object options', () => { const polygon = new Polygon(getPoints(), { scaleX: 2, scaleY: 3, skewX: 20, skewY: 30, strokeWidth: 20, strokeMiterLimit: 10, strokeUniform: false, strokeLineJoin: 'miter', // @ts-expect-error -- TODO: are types wrong for Polygon? seems like it doesn't accept exactBoundingBox property exactBoundingBox: true, }); const { left, top, width, height, pathOffset, strokeOffset, strokeDiff } = polygon._calcDimensions(); // Types expect(typeof left, 'left should be a number').toBe('number'); expect(typeof top, 'top should be a number').toBe('number'); expect(typeof width, 'width should be a number').toBe('number'); expect(typeof height, 'height should be a number').toBe('number'); expect(pathOffset, 'pathOffset should be a Point').toBeInstanceOf(Point); expect(strokeOffset, 'strokeOffset should be a Point').toBeInstanceOf( Point, ); expect(strokeDiff, 'strokeDiff should be a Point').toBeInstanceOf(Point); // Values expect(left, 'left should match expected value').toBe(10.485714075442775); expect(top, 'top should match expected value').toBe(14.784917784669414); expect(width, 'width should match expected value').toBe(27.707709196083425); expect(height, 'height should match expected value').toBe( 21.750672506349947, ); expect(pathOffset, 'pathOffset should match expected value').toEqual( new Point(14.999999999999998, 17.000000000000004), ); expect(strokeOffset, 'strokeOffset should match expected value').toEqual( new Point(11.966623726115365, 8.965754721680533), ); expect(strokeDiff, 'strokeDiff should match expected value').toEqual( new Point(23.933247452230738, 17.931509443361065), ); }); it('_calcDimensions with custom options', () => { const polygon = new Polygon(getPoints(), { scaleX: 2, scaleY: 3, skewX: 20, skewY: 30, strokeWidth: 20, strokeMiterLimit: 10, strokeUniform: false, strokeLineJoin: 'miter', // @ts-expect-error -- TODO: are types wrong for Polygon? seems like it doesn't accept exactBoundingBox property exactBoundingBox: true, }); const customOptions = { scaleX: 4, scaleY: 2, skewX: 0, skewY: 20, strokeWidth: 10, strokeMiterLimit: 20, strokeUniform: true, strokeLineJoin: 'miter', exactBoundingBox: true, } as const; const { left, top, width, height, pathOffset, strokeOffset, strokeDiff } = polygon._calcDimensions(customOptions); // Types expect(typeof left, 'left should be a number').toBe('number'); expect(typeof top, 'top should be a number').toBe('number'); expect(typeof width, 'width should be a number').toBe('number'); expect(typeof height, 'height should be a number').toBe('number'); expect(pathOffset, 'pathOffset should be a Point').toBeInstanceOf(Point); expect(strokeOffset, 'strokeOffset should be a Point').toBeInstanceOf( Point, ); expect(strokeDiff, 'strokeDiff should be a Point').toBeInstanceOf(Point); // Values expect(left, 'left should match expected value').toBe(9.440983005625053); expect(top, 'top should match expected value').toBe(13.60709991156367); expect(width, 'width should match expected value').toBe(11.118033988749893); expect(height, 'height should match expected value').toBe( 17.704907204858728, ); expect(pathOffset, 'pathOffset should match expected value').toEqual( new Point(6.825391045997646, 18.518912156261834), ); expect(strokeOffset, 'strokeOffset should match expected value').toEqual( new Point(1.1180339887498931, 6.097807293295057), ); expect(strokeDiff, 'strokeDiff should match expected value').toEqual( new Point(2.2360679774997863, 12.195614586590114), ); }); });