Skip to content

Commit 7c05fa7

Browse files
committed
test
Update typedefs.ts better test pass ctx update CHANGELOG.md
1 parent 37f2298 commit 7c05fa7

File tree

9 files changed

+158
-18
lines changed

9 files changed

+158
-18
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
- fix(CanvasEvents): regression of `getPointer` usages + BREAKING: drop event data [#9186](https://github.com/fabricjs/fabric.js/pull/9186)
121121
- feat(Object): BREAKING rm \_setOriginToCenter and \_resetOrigin unuseful methods [#9179](https://github.com/fabricjs/fabric.js/pull/9179)
122122
- fix(ActiveSelection): reset positioning when cleared [#9088](https://github.com/fabricjs/fabric.js/pull/9088)
123+
- feat(Node): pdf/svg output [#9185](https://github.com/fabricjs/fabric.js/pull/9185)
123124
- ci(): generate docs [#9169](https://github.com/fabricjs/fabric.js/pull/9169)
124125
- fix(utils) Fixes the code for the anchor point in point controls for polygons [#9178](https://github.com/fabricjs/fabric.js/pull/9178)
125126
- CD(): website submodule [#9165](https://github.com/fabricjs/fabric.js/pull/9165)
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import { Page, TestInfo, expect, test } from '@playwright/test';
2+
import { Canvas } from 'canvas';
3+
import * as fabric from 'fabric/node';
4+
import { createWriteStream, writeFileSync } from 'fs';
5+
import { ensureDirSync } from 'fs-extra';
6+
import path from 'path';
7+
8+
import '../../setup/setupCoverage';
9+
10+
test.describe('Exporting node canvas', () => {
11+
const size = {
12+
width: 460,
13+
height: 450,
14+
};
15+
16+
const createCanvas = () => {
17+
const canvas = new fabric.StaticCanvas(null, {
18+
width: 1000,
19+
height: 1000,
20+
enableRetinaScaling: false,
21+
});
22+
const rect = new fabric.Rect({ width: 50, height: 50, fill: 'red' });
23+
const path = fabric.util.getSmoothPathFromPoints([
24+
new fabric.Point(50, 50),
25+
new fabric.Point(100, 100),
26+
new fabric.Point(50, 200),
27+
new fabric.Point(400, 150),
28+
new fabric.Point(500, 500),
29+
]);
30+
const text = new fabric.Text(new Array(9).fill('fabric.js').join(' '), {
31+
fill: 'blue',
32+
fontSize: 24,
33+
path: new fabric.Path(path),
34+
});
35+
canvas.add(rect, text);
36+
return canvas;
37+
};
38+
39+
const createCtxForExport = (type: 'pdf' | 'svg') => {
40+
const ctx = new Canvas(0, 0, type).getContext('2d');
41+
ctx.textDrawingMode = 'glyph';
42+
return ctx;
43+
};
44+
45+
test.describe('SVG', () => {
46+
const attachSVG = async (result: Canvas, testInfo: TestInfo) => {
47+
ensureDirSync(testInfo.outputDir);
48+
const pathTo = path.resolve(testInfo.outputDir, 'output.svg');
49+
writeFileSync(pathTo, result.toBuffer());
50+
await testInfo.attach('output', {
51+
path: pathTo,
52+
});
53+
};
54+
55+
const testSVG = async (page: Page, result: Canvas) => {
56+
await page.goto(
57+
`data:image/svg+xml,${encodeURIComponent(result.toBuffer().toString())
58+
.replace(/'/g, '%27')
59+
.replace(/"/g, '%22')}`,
60+
{ waitUntil: 'load' }
61+
);
62+
expect(
63+
await page.screenshot({
64+
clip: { x: 0, y: 0, width: 460, height: 450 },
65+
})
66+
).toMatchSnapshot();
67+
};
68+
69+
test('canvas', async ({ page }, testInfo) => {
70+
const ctx = createCtxForExport('svg');
71+
const result = createCanvas().toCanvasElement(1, size, ctx);
72+
expect(result).toMatchObject(size);
73+
await attachSVG(result, testInfo);
74+
await testSVG(page, result);
75+
});
76+
77+
test('object', async ({ page }, testInfo) => {
78+
const ctx = createCtxForExport('svg');
79+
const result = createCanvas()
80+
.item(1)
81+
.toCanvasElement({ ...size, ctx });
82+
expect(result).toMatchObject(size);
83+
await attachSVG(result, testInfo);
84+
await testSVG(page, result);
85+
});
86+
});
87+
88+
test.describe('PDF', () => {
89+
const attachPDF = async (result: Canvas, testInfo: TestInfo) => {
90+
ensureDirSync(testInfo.outputDir);
91+
const pathTo = path.resolve(testInfo.outputDir, 'output.pdf');
92+
const out = createWriteStream(pathTo);
93+
await new Promise((resolve) => {
94+
out.on('finish', resolve);
95+
result.createPDFStream().pipe(out);
96+
});
97+
await testInfo.attach('output', {
98+
path: pathTo,
99+
contentType: 'application/pdf',
100+
});
101+
};
102+
103+
const testPDF = async (page: Page, result: Canvas) => {
104+
await page.goto(
105+
`data:application/pdf;base64,${Buffer.from(result.toBuffer()).toString(
106+
'base64'
107+
)}`,
108+
{ waitUntil: 'load' }
109+
);
110+
await page.waitForTimeout(5000);
111+
expect(
112+
await page.screenshot({
113+
clip: { x: 80, y: 25, width: 500, height: 500 },
114+
})
115+
).toMatchSnapshot();
116+
};
117+
118+
test('canvas', async ({ page }, testInfo) => {
119+
const ctx = createCtxForExport('pdf');
120+
const result = createCanvas().toCanvasElement(1, size, ctx);
121+
expect(result).toMatchObject(size);
122+
await attachPDF(result, testInfo);
123+
await testPDF(page, result);
124+
});
125+
126+
test('object', async ({ page }, testInfo) => {
127+
const ctx = createCtxForExport('pdf');
128+
const result = createCanvas()
129+
.item(1)
130+
.toCanvasElement({ ...size, ctx });
131+
expect(result).toMatchObject(size);
132+
await attachPDF(result, testInfo);
133+
await testPDF(page, result);
134+
});
135+
});
136+
});
32 KB
Loading
31.9 KB
Loading
24.3 KB
Loading
24.2 KB
Loading

src/canvas/StaticCanvas.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1411,11 +1411,12 @@ export class StaticCanvas<
14111411
* @param {Number} [options.width] Cropping width.
14121412
* @param {Number} [options.height] Cropping height.
14131413
* @param {(object: fabric.Object) => boolean} [options.filter] Function to filter objects.
1414+
* @param {CanvasRenderingContext2D} [ctx] Supports passing a pdf/svg ctx to in node, see https://github.com/Automattic/node-canvas#createcanvas.
14141415
*/
14151416
toCanvasElement(
14161417
multiplier = 1,
1417-
{ width, height, left, top, filter }: Partial<TToCanvasElementOptions> = {},
1418-
canvasEl = createCanvasElement()
1418+
{ width, height, left, top, filter }: TToCanvasElementOptions = {},
1419+
ctx = createCanvasElement().getContext('2d')!
14191420
): HTMLCanvasElement {
14201421
const scaledWidth = (width || this.width) * multiplier,
14211422
scaledHeight = (height || this.height) * multiplier,
@@ -1431,20 +1432,20 @@ export class StaticCanvas<
14311432
objectsToRender = filter
14321433
? this._objects.filter((obj) => filter(obj))
14331434
: this._objects;
1434-
canvasEl.width = scaledWidth;
1435-
canvasEl.height = scaledHeight;
1435+
ctx.canvas.width = scaledWidth;
1436+
ctx.canvas.height = scaledHeight;
14361437
this.enableRetinaScaling = false;
14371438
this.viewportTransform = newVp;
14381439
this.width = scaledWidth;
14391440
this.height = scaledHeight;
14401441
this.calcViewportBoundaries();
1441-
this.renderCanvas(canvasEl.getContext('2d')!, objectsToRender);
1442+
this.renderCanvas(ctx, objectsToRender);
14421443
this.viewportTransform = vp;
14431444
this.width = originalWidth;
14441445
this.height = originalHeight;
14451446
this.calcViewportBoundaries();
14461447
this.enableRetinaScaling = originalRetina;
1447-
return canvasEl;
1448+
return ctx.canvas;
14481449
}
14491450

14501451
/**

src/shapes/Object/Object.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1370,13 +1370,13 @@ export class FabricObject<
13701370
viewportTransform,
13711371
format,
13721372
multiplier,
1373-
canvasElement,
13741373
canvasProvider = () =>
13751374
new StaticCanvas(undefined, {
13761375
enableRetinaScaling: false,
13771376
renderOnAddRemove: false,
13781377
skipOffscreen: false,
13791378
}),
1379+
ctx,
13801380
...options
13811381
}: ObjectToCanvasElementOptions = {}) {
13821382
const origParams = saveObjectTransform(this),
@@ -1439,7 +1439,7 @@ export class FabricObject<
14391439
const canvasEl = canvas.toCanvasElement(
14401440
(multiplier || 1) * retinaScaling,
14411441
options,
1442-
canvasElement
1442+
ctx
14431443
);
14441444
this.set('canvas', originalCanvas);
14451445
this.shadow = originalShadow;

src/typedefs.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,11 @@ export type TRectBounds = [min: XY, max: XY];
100100

101101
export type TToCanvasElementOptions<
102102
T extends BaseFabricObject = BaseFabricObject
103-
> = TBBox & {
104-
filter: (object: T) => boolean;
105-
};
103+
> = Partial<
104+
TBBox & {
105+
filter: (object: T) => boolean;
106+
}
107+
>;
106108

107109
type ToDataUrlExtraOptions = { quality: number };
108110

@@ -126,14 +128,17 @@ type ObjectToCanvasElementExtraOptions = ToCanvasElementExtraOptions & {
126128
*/
127129
viewportTransform: boolean;
128130
/**
129-
* The element being to used to draw the object onto.
131+
* The context to draw the object onto.
132+
* Supports passing a pdf/svg ctx in node.
133+
* @see https://github.com/Automattic/node-canvas#createcanvas
130134
*/
131-
canvasElement: HTMLCanvasElement;
132135

133136
/**
134137
* Create the fabric canvas instance that will generate the output
135138
*/
136139
canvasProvider: () => StaticCanvas;
140+
141+
ctx: CanvasRenderingContext2D;
137142
};
138143

139144
export type ObjectToCanvasElementOptions = Partial<
@@ -144,11 +149,8 @@ export type ObjectToDataUrlOptions = ObjectToCanvasElementOptions &
144149
Partial<ToDataUrlExtraOptions>;
145150

146151
export type TDataUrlOptions<T extends BaseFabricObject = BaseFabricObject> =
147-
Partial<
148-
TToCanvasElementOptions<T> &
149-
ToCanvasElementExtraOptions &
150-
ToDataUrlExtraOptions
151-
>;
152+
TToCanvasElementOptions<T> &
153+
Partial<ToCanvasElementExtraOptions & ToDataUrlExtraOptions>;
152154

153155
export type Abortable = {
154156
/**

0 commit comments

Comments
 (0)