@umituz/web-canvas 1.0.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/dist/CanvasConfig-pzoE0RHD.d.mts +310 -0
- package/dist/CanvasConfig-pzoE0RHD.d.ts +310 -0
- package/dist/application/index.d.mts +78 -0
- package/dist/application/index.d.ts +78 -0
- package/dist/application/index.js +1083 -0
- package/dist/application/index.mjs +11 -0
- package/dist/chunk-BN7JTXL7.mjs +346 -0
- package/dist/chunk-MC6PNV2F.mjs +34 -0
- package/dist/chunk-SF6WND7M.mjs +547 -0
- package/dist/chunk-TZGTTYTZ.mjs +0 -0
- package/dist/chunk-WMKPCJFI.mjs +661 -0
- package/dist/chunk-Z6IB4BQV.mjs +31 -0
- package/dist/domain/config/index.d.mts +87 -0
- package/dist/domain/config/index.d.ts +87 -0
- package/dist/domain/config/index.js +92 -0
- package/dist/domain/config/index.mjs +15 -0
- package/dist/domain/index.d.mts +79 -0
- package/dist/domain/index.d.ts +79 -0
- package/dist/domain/index.js +137 -0
- package/dist/domain/index.mjs +55 -0
- package/dist/index.d.mts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1633 -0
- package/dist/index.mjs +38 -0
- package/dist/infrastructure/index.d.mts +174 -0
- package/dist/infrastructure/index.d.ts +174 -0
- package/dist/infrastructure/index.js +714 -0
- package/dist/infrastructure/index.mjs +13 -0
- package/dist/presentation/index.d.mts +108 -0
- package/dist/presentation/index.d.ts +108 -0
- package/dist/presentation/index.js +1617 -0
- package/dist/presentation/index.mjs +14 -0
- package/package.json +86 -0
- package/src/application/index.ts +7 -0
- package/src/application/services/ReportService.ts +200 -0
- package/src/application/services/ScreenshotStudioService.ts +243 -0
- package/src/domain/config/CanvasConfig.ts +36 -0
- package/src/domain/config/DeviceConfig.ts +47 -0
- package/src/domain/config/ScreenshotStudioConfig.ts +82 -0
- package/src/domain/config/index.ts +8 -0
- package/src/domain/index.ts +7 -0
- package/src/domain/types/canvas.types.ts +85 -0
- package/src/domain/types/index.ts +8 -0
- package/src/domain/types/report.types.ts +83 -0
- package/src/domain/types/screenshot.types.ts +238 -0
- package/src/index.ts +102 -0
- package/src/infrastructure/index.ts +9 -0
- package/src/infrastructure/renderers/CanvasRenderer.ts +287 -0
- package/src/infrastructure/renderers/PatternRenderer.ts +160 -0
- package/src/infrastructure/renderers/ScreenshotRenderer.ts +331 -0
- package/src/infrastructure/services/DeviceConfigService.ts +144 -0
- package/src/presentation/hooks/useCanvasExport.ts +162 -0
- package/src/presentation/hooks/useReportExport.ts +161 -0
- package/src/presentation/hooks/useScreenshotStudio.ts +480 -0
- package/src/presentation/index.ts +8 -0
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canvas Renderer
|
|
3
|
+
* Core canvas rendering operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
CanvasDimensions,
|
|
8
|
+
CanvasContextConfig,
|
|
9
|
+
CanvasExportOptions,
|
|
10
|
+
CanvasResult,
|
|
11
|
+
} from '../../domain/types/canvas.types';
|
|
12
|
+
import { DEFAULT_CANVAS_CONFIG } from '../../domain/config/CanvasConfig';
|
|
13
|
+
|
|
14
|
+
export class CanvasRenderer {
|
|
15
|
+
private config: typeof DEFAULT_CANVAS_CONFIG;
|
|
16
|
+
|
|
17
|
+
constructor(config = DEFAULT_CANVAS_CONFIG) {
|
|
18
|
+
this.config = { ...DEFAULT_CANVAS_CONFIG, ...config };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Create a new canvas element
|
|
23
|
+
*/
|
|
24
|
+
public createCanvas(dimensions: CanvasDimensions): HTMLCanvasElement {
|
|
25
|
+
const canvas = document.createElement('canvas');
|
|
26
|
+
canvas.width = dimensions.width;
|
|
27
|
+
canvas.height = dimensions.height;
|
|
28
|
+
return canvas;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get canvas 2D context
|
|
33
|
+
*/
|
|
34
|
+
public getContext(
|
|
35
|
+
canvas: HTMLCanvasElement
|
|
36
|
+
): CanvasRenderingContext2D | null {
|
|
37
|
+
return canvas.getContext('2d');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Apply context configuration
|
|
42
|
+
*/
|
|
43
|
+
public configureContext(
|
|
44
|
+
ctx: CanvasRenderingContext2D,
|
|
45
|
+
config: CanvasContextConfig
|
|
46
|
+
): void {
|
|
47
|
+
if (config.fillStyle !== undefined) {
|
|
48
|
+
ctx.fillStyle = config.fillStyle;
|
|
49
|
+
}
|
|
50
|
+
if (config.strokeStyle !== undefined) {
|
|
51
|
+
ctx.strokeStyle = config.strokeStyle;
|
|
52
|
+
}
|
|
53
|
+
if (config.lineWidth !== undefined) {
|
|
54
|
+
ctx.lineWidth = config.lineWidth;
|
|
55
|
+
}
|
|
56
|
+
if (config.lineCap !== undefined) {
|
|
57
|
+
ctx.lineCap = config.lineCap;
|
|
58
|
+
}
|
|
59
|
+
if (config.lineJoin !== undefined) {
|
|
60
|
+
ctx.lineJoin = config.lineJoin;
|
|
61
|
+
}
|
|
62
|
+
if (config.globalAlpha !== undefined) {
|
|
63
|
+
ctx.globalAlpha = config.globalAlpha;
|
|
64
|
+
}
|
|
65
|
+
if (config.font !== undefined) {
|
|
66
|
+
ctx.font = config.font;
|
|
67
|
+
}
|
|
68
|
+
if (config.textAlign !== undefined) {
|
|
69
|
+
ctx.textAlign = config.textAlign;
|
|
70
|
+
}
|
|
71
|
+
if (config.textBaseline !== undefined) {
|
|
72
|
+
ctx.textBaseline = config.textBaseline;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Clear canvas
|
|
78
|
+
*/
|
|
79
|
+
public clearCanvas(
|
|
80
|
+
ctx: CanvasRenderingContext2D,
|
|
81
|
+
dimensions: CanvasDimensions
|
|
82
|
+
): void {
|
|
83
|
+
ctx.clearRect(0, 0, dimensions.width, dimensions.height);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Fill rectangle
|
|
88
|
+
*/
|
|
89
|
+
public fillRect(
|
|
90
|
+
ctx: CanvasRenderingContext2D,
|
|
91
|
+
x: number,
|
|
92
|
+
y: number,
|
|
93
|
+
width: number,
|
|
94
|
+
height: number,
|
|
95
|
+
color?: string
|
|
96
|
+
): void {
|
|
97
|
+
if (color) {
|
|
98
|
+
ctx.fillStyle = color;
|
|
99
|
+
}
|
|
100
|
+
ctx.fillRect(x, y, width, height);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Stroke rectangle
|
|
105
|
+
*/
|
|
106
|
+
public strokeRect(
|
|
107
|
+
ctx: CanvasRenderingContext2D,
|
|
108
|
+
x: number,
|
|
109
|
+
y: number,
|
|
110
|
+
width: number,
|
|
111
|
+
height: number,
|
|
112
|
+
color?: string,
|
|
113
|
+
lineWidth?: number
|
|
114
|
+
): void {
|
|
115
|
+
if (color) {
|
|
116
|
+
ctx.strokeStyle = color;
|
|
117
|
+
}
|
|
118
|
+
if (lineWidth !== undefined) {
|
|
119
|
+
ctx.lineWidth = lineWidth;
|
|
120
|
+
}
|
|
121
|
+
ctx.strokeRect(x, y, width, height);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Draw text
|
|
126
|
+
*/
|
|
127
|
+
public drawText(
|
|
128
|
+
ctx: CanvasRenderingContext2D,
|
|
129
|
+
text: string,
|
|
130
|
+
x: number,
|
|
131
|
+
y: number,
|
|
132
|
+
config?: CanvasContextConfig
|
|
133
|
+
): void {
|
|
134
|
+
if (config) {
|
|
135
|
+
this.configureContext(ctx, config);
|
|
136
|
+
}
|
|
137
|
+
ctx.fillText(text, x, y);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Draw image
|
|
142
|
+
*/
|
|
143
|
+
public async drawImage(
|
|
144
|
+
ctx: CanvasRenderingContext2D,
|
|
145
|
+
imageSource: string | HTMLImageElement,
|
|
146
|
+
x: number,
|
|
147
|
+
y: number,
|
|
148
|
+
width?: number,
|
|
149
|
+
height?: number
|
|
150
|
+
): Promise<void> {
|
|
151
|
+
let img: HTMLImageElement;
|
|
152
|
+
|
|
153
|
+
if (typeof imageSource === 'string') {
|
|
154
|
+
img = await this.loadImage(imageSource);
|
|
155
|
+
} else {
|
|
156
|
+
img = imageSource;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (width && height) {
|
|
160
|
+
ctx.drawImage(img, x, y, width, height);
|
|
161
|
+
} else {
|
|
162
|
+
ctx.drawImage(img, x, y);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Load image from URL
|
|
168
|
+
*/
|
|
169
|
+
private loadImage(url: string): Promise<HTMLImageElement> {
|
|
170
|
+
return new Promise((resolve, reject) => {
|
|
171
|
+
const img = new Image();
|
|
172
|
+
img.crossOrigin = this.config.useCORS ? 'anonymous' : (undefined as any);
|
|
173
|
+
img.onload = () => resolve(img);
|
|
174
|
+
img.onerror = () => reject(new Error(`Failed to load image: ${url}`));
|
|
175
|
+
img.src = url;
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Create gradient
|
|
181
|
+
*/
|
|
182
|
+
public createLinearGradient(
|
|
183
|
+
ctx: CanvasRenderingContext2D,
|
|
184
|
+
x1: number,
|
|
185
|
+
y1: number,
|
|
186
|
+
x2: number,
|
|
187
|
+
y2: number,
|
|
188
|
+
colorStops: Array<{ offset: number; color: string }>
|
|
189
|
+
): CanvasGradient {
|
|
190
|
+
const gradient = ctx.createLinearGradient(x1, y1, x2, y2);
|
|
191
|
+
colorStops.forEach(({ offset, color }) => {
|
|
192
|
+
gradient.addColorStop(offset, color);
|
|
193
|
+
});
|
|
194
|
+
return gradient;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Export canvas to blob
|
|
199
|
+
*/
|
|
200
|
+
public async exportToBlob(
|
|
201
|
+
canvas: HTMLCanvasElement,
|
|
202
|
+
options: CanvasExportOptions = {}
|
|
203
|
+
): Promise<CanvasResult> {
|
|
204
|
+
const format = options.format || this.config.defaultFormat;
|
|
205
|
+
const quality = options.quality || this.config.defaultQuality;
|
|
206
|
+
const scale = options.scale || this.config.scale;
|
|
207
|
+
|
|
208
|
+
// Scale canvas if needed
|
|
209
|
+
const scaledCanvas = (scale || 1) !== 1 ? this.scaleCanvas(canvas, scale || 1) : canvas;
|
|
210
|
+
|
|
211
|
+
const mimeType = `image/${format || 'png'}`;
|
|
212
|
+
const blob = await new Promise<Blob | null>((resolve) => {
|
|
213
|
+
scaledCanvas.toBlob(
|
|
214
|
+
(result) => resolve(result),
|
|
215
|
+
mimeType,
|
|
216
|
+
quality
|
|
217
|
+
);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
if (!blob) {
|
|
221
|
+
throw new Error('Failed to export canvas to blob');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const url = URL.createObjectURL(blob);
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
blob,
|
|
228
|
+
url,
|
|
229
|
+
dimensions: {
|
|
230
|
+
width: scaledCanvas.width,
|
|
231
|
+
height: scaledCanvas.height,
|
|
232
|
+
},
|
|
233
|
+
format: format || 'png',
|
|
234
|
+
size: blob.size,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Export canvas to data URL
|
|
240
|
+
*/
|
|
241
|
+
public exportToDataURL(
|
|
242
|
+
canvas: HTMLCanvasElement,
|
|
243
|
+
format?: string,
|
|
244
|
+
quality?: number
|
|
245
|
+
): string {
|
|
246
|
+
const mimeType = format || `image/${this.config.defaultFormat}`;
|
|
247
|
+
const q = quality || this.config.defaultQuality;
|
|
248
|
+
return canvas.toDataURL(mimeType, q);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Scale canvas
|
|
253
|
+
*/
|
|
254
|
+
private scaleCanvas(
|
|
255
|
+
canvas: HTMLCanvasElement,
|
|
256
|
+
scale: number
|
|
257
|
+
): HTMLCanvasElement {
|
|
258
|
+
const scaled = document.createElement('canvas');
|
|
259
|
+
scaled.width = canvas.width * scale;
|
|
260
|
+
scaled.height = canvas.height * scale;
|
|
261
|
+
|
|
262
|
+
const ctx = scaled.getContext('2d');
|
|
263
|
+
if (ctx) {
|
|
264
|
+
ctx.imageSmoothingEnabled = true;
|
|
265
|
+
ctx.imageSmoothingQuality = 'high';
|
|
266
|
+
ctx.drawImage(canvas, 0, 0, scaled.width, scaled.height);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return scaled;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Clone canvas
|
|
274
|
+
*/
|
|
275
|
+
public cloneCanvas(canvas: HTMLCanvasElement): HTMLCanvasElement {
|
|
276
|
+
const clone = document.createElement('canvas');
|
|
277
|
+
clone.width = canvas.width;
|
|
278
|
+
clone.height = canvas.height;
|
|
279
|
+
|
|
280
|
+
const ctx = clone.getContext('2d');
|
|
281
|
+
if (ctx) {
|
|
282
|
+
ctx.drawImage(canvas, 0, 0);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return clone;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pattern Renderer
|
|
3
|
+
* Background pattern rendering operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { CanvasDimensions } from '../../domain/types/canvas.types';
|
|
7
|
+
import type { BackgroundPattern } from '../../domain/types/screenshot.types';
|
|
8
|
+
|
|
9
|
+
export class PatternRenderer {
|
|
10
|
+
/**
|
|
11
|
+
* Render pattern on canvas
|
|
12
|
+
*/
|
|
13
|
+
public renderPattern(
|
|
14
|
+
ctx: CanvasRenderingContext2D,
|
|
15
|
+
pattern: BackgroundPattern,
|
|
16
|
+
dimensions: CanvasDimensions,
|
|
17
|
+
color: string = '#000000'
|
|
18
|
+
): void {
|
|
19
|
+
ctx.save();
|
|
20
|
+
ctx.globalAlpha = 0.1;
|
|
21
|
+
|
|
22
|
+
switch (pattern) {
|
|
23
|
+
case 'dots':
|
|
24
|
+
this.renderDotPattern(ctx, dimensions, color);
|
|
25
|
+
break;
|
|
26
|
+
case 'grid':
|
|
27
|
+
this.renderGridPattern(ctx, dimensions, color);
|
|
28
|
+
break;
|
|
29
|
+
case 'lines':
|
|
30
|
+
this.renderLinePattern(ctx, dimensions, color);
|
|
31
|
+
break;
|
|
32
|
+
case 'waves':
|
|
33
|
+
this.renderWavePattern(ctx, dimensions, color);
|
|
34
|
+
break;
|
|
35
|
+
case 'checkers':
|
|
36
|
+
this.renderCheckerPattern(ctx, dimensions, color);
|
|
37
|
+
break;
|
|
38
|
+
case 'none':
|
|
39
|
+
default:
|
|
40
|
+
// Do nothing
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
ctx.restore();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Render dot pattern
|
|
49
|
+
*/
|
|
50
|
+
private renderDotPattern(
|
|
51
|
+
ctx: CanvasRenderingContext2D,
|
|
52
|
+
dimensions: CanvasDimensions,
|
|
53
|
+
color: string
|
|
54
|
+
): void {
|
|
55
|
+
ctx.fillStyle = color;
|
|
56
|
+
const spacing = 30;
|
|
57
|
+
|
|
58
|
+
for (let x = spacing; x < dimensions.width; x += spacing) {
|
|
59
|
+
for (let y = spacing; y < dimensions.height; y += spacing) {
|
|
60
|
+
ctx.beginPath();
|
|
61
|
+
ctx.arc(x, y, 2, 0, Math.PI * 2);
|
|
62
|
+
ctx.fill();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Render grid pattern
|
|
69
|
+
*/
|
|
70
|
+
private renderGridPattern(
|
|
71
|
+
ctx: CanvasRenderingContext2D,
|
|
72
|
+
dimensions: CanvasDimensions,
|
|
73
|
+
color: string
|
|
74
|
+
): void {
|
|
75
|
+
ctx.strokeStyle = color;
|
|
76
|
+
ctx.lineWidth = 1;
|
|
77
|
+
const spacing = 30;
|
|
78
|
+
|
|
79
|
+
for (let x = 0; x <= dimensions.width; x += spacing) {
|
|
80
|
+
ctx.beginPath();
|
|
81
|
+
ctx.moveTo(x, 0);
|
|
82
|
+
ctx.lineTo(x, dimensions.height);
|
|
83
|
+
ctx.stroke();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (let y = 0; y <= dimensions.height; y += spacing) {
|
|
87
|
+
ctx.beginPath();
|
|
88
|
+
ctx.moveTo(0, y);
|
|
89
|
+
ctx.lineTo(dimensions.width, y);
|
|
90
|
+
ctx.stroke();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Render line pattern
|
|
96
|
+
*/
|
|
97
|
+
private renderLinePattern(
|
|
98
|
+
ctx: CanvasRenderingContext2D,
|
|
99
|
+
dimensions: CanvasDimensions,
|
|
100
|
+
color: string
|
|
101
|
+
): void {
|
|
102
|
+
ctx.strokeStyle = color;
|
|
103
|
+
ctx.lineWidth = 2;
|
|
104
|
+
const spacing = 20;
|
|
105
|
+
|
|
106
|
+
for (let y = spacing; y < dimensions.height; y += spacing) {
|
|
107
|
+
ctx.beginPath();
|
|
108
|
+
ctx.moveTo(0, y);
|
|
109
|
+
ctx.lineTo(dimensions.width, y);
|
|
110
|
+
ctx.stroke();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Render wave pattern
|
|
116
|
+
*/
|
|
117
|
+
private renderWavePattern(
|
|
118
|
+
ctx: CanvasRenderingContext2D,
|
|
119
|
+
dimensions: CanvasDimensions,
|
|
120
|
+
color: string
|
|
121
|
+
): void {
|
|
122
|
+
ctx.strokeStyle = color;
|
|
123
|
+
ctx.lineWidth = 2;
|
|
124
|
+
const amplitude = 10;
|
|
125
|
+
const frequency = 0.1;
|
|
126
|
+
const spacing = 40;
|
|
127
|
+
|
|
128
|
+
for (let y = spacing; y < dimensions.height; y += spacing) {
|
|
129
|
+
ctx.beginPath();
|
|
130
|
+
for (let x = 0; x <= dimensions.width; x += 5) {
|
|
131
|
+
const waveY = y + Math.sin(x * frequency) * amplitude;
|
|
132
|
+
if (x === 0) {
|
|
133
|
+
ctx.moveTo(x, waveY);
|
|
134
|
+
} else {
|
|
135
|
+
ctx.lineTo(x, waveY);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
ctx.stroke();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Render checker pattern
|
|
144
|
+
*/
|
|
145
|
+
private renderCheckerPattern(
|
|
146
|
+
ctx: CanvasRenderingContext2D,
|
|
147
|
+
dimensions: CanvasDimensions,
|
|
148
|
+
color: string
|
|
149
|
+
): void {
|
|
150
|
+
ctx.fillStyle = color;
|
|
151
|
+
const size = 20;
|
|
152
|
+
|
|
153
|
+
for (let x = 0; x < dimensions.width; x += size * 2) {
|
|
154
|
+
for (let y = 0; y < dimensions.height; y += size * 2) {
|
|
155
|
+
ctx.fillRect(x, y, size, size);
|
|
156
|
+
ctx.fillRect(x + size, y + size, size, size);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|