@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,714 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/infrastructure/index.ts
|
|
21
|
+
var infrastructure_exports = {};
|
|
22
|
+
__export(infrastructure_exports, {
|
|
23
|
+
CanvasRenderer: () => CanvasRenderer,
|
|
24
|
+
DeviceConfigService: () => DeviceConfigService,
|
|
25
|
+
PatternRenderer: () => PatternRenderer,
|
|
26
|
+
ScreenshotRenderer: () => ScreenshotRenderer
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(infrastructure_exports);
|
|
29
|
+
|
|
30
|
+
// src/domain/config/DeviceConfig.ts
|
|
31
|
+
var DEVICE_DIMENSIONS = {
|
|
32
|
+
"iphone-15-pro": { width: 1290, height: 2796 },
|
|
33
|
+
"iphone-15": { width: 1179, height: 2556 },
|
|
34
|
+
"iphone-se": { width: 1080, height: 2340 },
|
|
35
|
+
"ipad-pro": { width: 2048, height: 2732 },
|
|
36
|
+
"ipad": { width: 1620, height: 2160 },
|
|
37
|
+
"android-phone": { width: 1440, height: 3120 },
|
|
38
|
+
"android-tablet": { width: 2e3, height: 2200 },
|
|
39
|
+
"desktop": { width: 1920, height: 1080 }
|
|
40
|
+
};
|
|
41
|
+
var DEFAULT_DEVICE_CONFIG = {
|
|
42
|
+
devices: Object.keys(DEVICE_DIMENSIONS),
|
|
43
|
+
defaultDevice: "iphone-15-pro",
|
|
44
|
+
showFrame: true,
|
|
45
|
+
frameColor: "#1f2937",
|
|
46
|
+
frameThickness: 8
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// src/infrastructure/services/DeviceConfigService.ts
|
|
50
|
+
var DeviceConfigService = class _DeviceConfigService {
|
|
51
|
+
constructor() {
|
|
52
|
+
this.devices = /* @__PURE__ */ new Map();
|
|
53
|
+
this.initializeDevices();
|
|
54
|
+
}
|
|
55
|
+
static getInstance() {
|
|
56
|
+
if (!_DeviceConfigService.instance) {
|
|
57
|
+
_DeviceConfigService.instance = new _DeviceConfigService();
|
|
58
|
+
}
|
|
59
|
+
return _DeviceConfigService.instance;
|
|
60
|
+
}
|
|
61
|
+
initializeDevices() {
|
|
62
|
+
const deviceConfigs = [
|
|
63
|
+
{
|
|
64
|
+
name: "iPhone 15 Pro",
|
|
65
|
+
dimensions: { width: 1290, height: 2796, scale: 3 },
|
|
66
|
+
isPhone: true,
|
|
67
|
+
hasNotch: true,
|
|
68
|
+
cornerRadius: 20,
|
|
69
|
+
frameThickness: 8
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "iPhone 15",
|
|
73
|
+
dimensions: { width: 1179, height: 2556, scale: 3 },
|
|
74
|
+
isPhone: true,
|
|
75
|
+
hasNotch: true,
|
|
76
|
+
cornerRadius: 20,
|
|
77
|
+
frameThickness: 8
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: "iPhone SE",
|
|
81
|
+
dimensions: { width: 1080, height: 2340, scale: 3 },
|
|
82
|
+
isPhone: true,
|
|
83
|
+
hasNotch: false,
|
|
84
|
+
cornerRadius: 16,
|
|
85
|
+
frameThickness: 6
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: "iPad Pro",
|
|
89
|
+
dimensions: { width: 2048, height: 2732, scale: 2 },
|
|
90
|
+
isTablet: true,
|
|
91
|
+
hasNotch: false,
|
|
92
|
+
cornerRadius: 24,
|
|
93
|
+
frameThickness: 10
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: "iPad",
|
|
97
|
+
dimensions: { width: 1620, height: 2160, scale: 2 },
|
|
98
|
+
isTablet: true,
|
|
99
|
+
hasNotch: false,
|
|
100
|
+
cornerRadius: 20,
|
|
101
|
+
frameThickness: 10
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: "Android Phone",
|
|
105
|
+
dimensions: { width: 1440, height: 3120, scale: 3 },
|
|
106
|
+
isPhone: true,
|
|
107
|
+
hasNotch: false,
|
|
108
|
+
cornerRadius: 24,
|
|
109
|
+
frameThickness: 8
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: "Android Tablet",
|
|
113
|
+
dimensions: { width: 2e3, height: 2200, scale: 2 },
|
|
114
|
+
isTablet: true,
|
|
115
|
+
hasNotch: false,
|
|
116
|
+
cornerRadius: 24,
|
|
117
|
+
frameThickness: 10
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: "Desktop",
|
|
121
|
+
dimensions: { width: 1920, height: 1080, scale: 1 },
|
|
122
|
+
isPhone: false,
|
|
123
|
+
isTablet: false,
|
|
124
|
+
hasNotch: false,
|
|
125
|
+
cornerRadius: 0,
|
|
126
|
+
frameThickness: 0
|
|
127
|
+
}
|
|
128
|
+
];
|
|
129
|
+
Object.entries(DEVICE_DIMENSIONS).forEach(([type, dimensions]) => {
|
|
130
|
+
const deviceType = type;
|
|
131
|
+
const config = deviceConfigs.find((_, index) => {
|
|
132
|
+
const types = Object.keys(DEVICE_DIMENSIONS);
|
|
133
|
+
return types[index] === type;
|
|
134
|
+
});
|
|
135
|
+
if (config) {
|
|
136
|
+
this.devices.set(deviceType, {
|
|
137
|
+
type: deviceType,
|
|
138
|
+
...config,
|
|
139
|
+
dimensions
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
getDeviceConfig(deviceType) {
|
|
145
|
+
return this.devices.get(deviceType);
|
|
146
|
+
}
|
|
147
|
+
getAllDevices() {
|
|
148
|
+
return Array.from(this.devices.values());
|
|
149
|
+
}
|
|
150
|
+
getPhones() {
|
|
151
|
+
return this.getAllDevices().filter((d) => d.isPhone);
|
|
152
|
+
}
|
|
153
|
+
getTablets() {
|
|
154
|
+
return this.getAllDevices().filter((d) => d.isTablet);
|
|
155
|
+
}
|
|
156
|
+
isPhone(deviceType) {
|
|
157
|
+
const config = this.getDeviceConfig(deviceType);
|
|
158
|
+
return config?.isPhone || false;
|
|
159
|
+
}
|
|
160
|
+
isTablet(deviceType) {
|
|
161
|
+
const config = this.getDeviceConfig(deviceType);
|
|
162
|
+
return config?.isTablet || false;
|
|
163
|
+
}
|
|
164
|
+
hasNotch(deviceType) {
|
|
165
|
+
const config = this.getDeviceConfig(deviceType);
|
|
166
|
+
return config?.hasNotch || false;
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// src/domain/config/CanvasConfig.ts
|
|
171
|
+
var DEFAULT_CANVAS_CONFIG = {
|
|
172
|
+
defaultFormat: "png",
|
|
173
|
+
defaultQuality: 0.9,
|
|
174
|
+
maxDimension: 4096,
|
|
175
|
+
scale: 2,
|
|
176
|
+
backgroundColor: "#ffffff",
|
|
177
|
+
useCORS: true
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// src/infrastructure/renderers/CanvasRenderer.ts
|
|
181
|
+
var CanvasRenderer = class {
|
|
182
|
+
constructor(config = DEFAULT_CANVAS_CONFIG) {
|
|
183
|
+
this.config = { ...DEFAULT_CANVAS_CONFIG, ...config };
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Create a new canvas element
|
|
187
|
+
*/
|
|
188
|
+
createCanvas(dimensions) {
|
|
189
|
+
const canvas = document.createElement("canvas");
|
|
190
|
+
canvas.width = dimensions.width;
|
|
191
|
+
canvas.height = dimensions.height;
|
|
192
|
+
return canvas;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Get canvas 2D context
|
|
196
|
+
*/
|
|
197
|
+
getContext(canvas) {
|
|
198
|
+
return canvas.getContext("2d");
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Apply context configuration
|
|
202
|
+
*/
|
|
203
|
+
configureContext(ctx, config) {
|
|
204
|
+
if (config.fillStyle !== void 0) {
|
|
205
|
+
ctx.fillStyle = config.fillStyle;
|
|
206
|
+
}
|
|
207
|
+
if (config.strokeStyle !== void 0) {
|
|
208
|
+
ctx.strokeStyle = config.strokeStyle;
|
|
209
|
+
}
|
|
210
|
+
if (config.lineWidth !== void 0) {
|
|
211
|
+
ctx.lineWidth = config.lineWidth;
|
|
212
|
+
}
|
|
213
|
+
if (config.lineCap !== void 0) {
|
|
214
|
+
ctx.lineCap = config.lineCap;
|
|
215
|
+
}
|
|
216
|
+
if (config.lineJoin !== void 0) {
|
|
217
|
+
ctx.lineJoin = config.lineJoin;
|
|
218
|
+
}
|
|
219
|
+
if (config.globalAlpha !== void 0) {
|
|
220
|
+
ctx.globalAlpha = config.globalAlpha;
|
|
221
|
+
}
|
|
222
|
+
if (config.font !== void 0) {
|
|
223
|
+
ctx.font = config.font;
|
|
224
|
+
}
|
|
225
|
+
if (config.textAlign !== void 0) {
|
|
226
|
+
ctx.textAlign = config.textAlign;
|
|
227
|
+
}
|
|
228
|
+
if (config.textBaseline !== void 0) {
|
|
229
|
+
ctx.textBaseline = config.textBaseline;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Clear canvas
|
|
234
|
+
*/
|
|
235
|
+
clearCanvas(ctx, dimensions) {
|
|
236
|
+
ctx.clearRect(0, 0, dimensions.width, dimensions.height);
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Fill rectangle
|
|
240
|
+
*/
|
|
241
|
+
fillRect(ctx, x, y, width, height, color) {
|
|
242
|
+
if (color) {
|
|
243
|
+
ctx.fillStyle = color;
|
|
244
|
+
}
|
|
245
|
+
ctx.fillRect(x, y, width, height);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Stroke rectangle
|
|
249
|
+
*/
|
|
250
|
+
strokeRect(ctx, x, y, width, height, color, lineWidth) {
|
|
251
|
+
if (color) {
|
|
252
|
+
ctx.strokeStyle = color;
|
|
253
|
+
}
|
|
254
|
+
if (lineWidth !== void 0) {
|
|
255
|
+
ctx.lineWidth = lineWidth;
|
|
256
|
+
}
|
|
257
|
+
ctx.strokeRect(x, y, width, height);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Draw text
|
|
261
|
+
*/
|
|
262
|
+
drawText(ctx, text, x, y, config) {
|
|
263
|
+
if (config) {
|
|
264
|
+
this.configureContext(ctx, config);
|
|
265
|
+
}
|
|
266
|
+
ctx.fillText(text, x, y);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Draw image
|
|
270
|
+
*/
|
|
271
|
+
async drawImage(ctx, imageSource, x, y, width, height) {
|
|
272
|
+
let img;
|
|
273
|
+
if (typeof imageSource === "string") {
|
|
274
|
+
img = await this.loadImage(imageSource);
|
|
275
|
+
} else {
|
|
276
|
+
img = imageSource;
|
|
277
|
+
}
|
|
278
|
+
if (width && height) {
|
|
279
|
+
ctx.drawImage(img, x, y, width, height);
|
|
280
|
+
} else {
|
|
281
|
+
ctx.drawImage(img, x, y);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Load image from URL
|
|
286
|
+
*/
|
|
287
|
+
loadImage(url) {
|
|
288
|
+
return new Promise((resolve, reject) => {
|
|
289
|
+
const img = new Image();
|
|
290
|
+
img.crossOrigin = this.config.useCORS ? "anonymous" : void 0;
|
|
291
|
+
img.onload = () => resolve(img);
|
|
292
|
+
img.onerror = () => reject(new Error(`Failed to load image: ${url}`));
|
|
293
|
+
img.src = url;
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Create gradient
|
|
298
|
+
*/
|
|
299
|
+
createLinearGradient(ctx, x1, y1, x2, y2, colorStops) {
|
|
300
|
+
const gradient = ctx.createLinearGradient(x1, y1, x2, y2);
|
|
301
|
+
colorStops.forEach(({ offset, color }) => {
|
|
302
|
+
gradient.addColorStop(offset, color);
|
|
303
|
+
});
|
|
304
|
+
return gradient;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Export canvas to blob
|
|
308
|
+
*/
|
|
309
|
+
async exportToBlob(canvas, options = {}) {
|
|
310
|
+
const format = options.format || this.config.defaultFormat;
|
|
311
|
+
const quality = options.quality || this.config.defaultQuality;
|
|
312
|
+
const scale = options.scale || this.config.scale;
|
|
313
|
+
const scaledCanvas = (scale || 1) !== 1 ? this.scaleCanvas(canvas, scale || 1) : canvas;
|
|
314
|
+
const mimeType = `image/${format || "png"}`;
|
|
315
|
+
const blob = await new Promise((resolve) => {
|
|
316
|
+
scaledCanvas.toBlob(
|
|
317
|
+
(result) => resolve(result),
|
|
318
|
+
mimeType,
|
|
319
|
+
quality
|
|
320
|
+
);
|
|
321
|
+
});
|
|
322
|
+
if (!blob) {
|
|
323
|
+
throw new Error("Failed to export canvas to blob");
|
|
324
|
+
}
|
|
325
|
+
const url = URL.createObjectURL(blob);
|
|
326
|
+
return {
|
|
327
|
+
blob,
|
|
328
|
+
url,
|
|
329
|
+
dimensions: {
|
|
330
|
+
width: scaledCanvas.width,
|
|
331
|
+
height: scaledCanvas.height
|
|
332
|
+
},
|
|
333
|
+
format: format || "png",
|
|
334
|
+
size: blob.size
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Export canvas to data URL
|
|
339
|
+
*/
|
|
340
|
+
exportToDataURL(canvas, format, quality) {
|
|
341
|
+
const mimeType = format || `image/${this.config.defaultFormat}`;
|
|
342
|
+
const q = quality || this.config.defaultQuality;
|
|
343
|
+
return canvas.toDataURL(mimeType, q);
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Scale canvas
|
|
347
|
+
*/
|
|
348
|
+
scaleCanvas(canvas, scale) {
|
|
349
|
+
const scaled = document.createElement("canvas");
|
|
350
|
+
scaled.width = canvas.width * scale;
|
|
351
|
+
scaled.height = canvas.height * scale;
|
|
352
|
+
const ctx = scaled.getContext("2d");
|
|
353
|
+
if (ctx) {
|
|
354
|
+
ctx.imageSmoothingEnabled = true;
|
|
355
|
+
ctx.imageSmoothingQuality = "high";
|
|
356
|
+
ctx.drawImage(canvas, 0, 0, scaled.width, scaled.height);
|
|
357
|
+
}
|
|
358
|
+
return scaled;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Clone canvas
|
|
362
|
+
*/
|
|
363
|
+
cloneCanvas(canvas) {
|
|
364
|
+
const clone = document.createElement("canvas");
|
|
365
|
+
clone.width = canvas.width;
|
|
366
|
+
clone.height = canvas.height;
|
|
367
|
+
const ctx = clone.getContext("2d");
|
|
368
|
+
if (ctx) {
|
|
369
|
+
ctx.drawImage(canvas, 0, 0);
|
|
370
|
+
}
|
|
371
|
+
return clone;
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
// src/infrastructure/renderers/PatternRenderer.ts
|
|
376
|
+
var PatternRenderer = class {
|
|
377
|
+
/**
|
|
378
|
+
* Render pattern on canvas
|
|
379
|
+
*/
|
|
380
|
+
renderPattern(ctx, pattern, dimensions, color = "#000000") {
|
|
381
|
+
ctx.save();
|
|
382
|
+
ctx.globalAlpha = 0.1;
|
|
383
|
+
switch (pattern) {
|
|
384
|
+
case "dots":
|
|
385
|
+
this.renderDotPattern(ctx, dimensions, color);
|
|
386
|
+
break;
|
|
387
|
+
case "grid":
|
|
388
|
+
this.renderGridPattern(ctx, dimensions, color);
|
|
389
|
+
break;
|
|
390
|
+
case "lines":
|
|
391
|
+
this.renderLinePattern(ctx, dimensions, color);
|
|
392
|
+
break;
|
|
393
|
+
case "waves":
|
|
394
|
+
this.renderWavePattern(ctx, dimensions, color);
|
|
395
|
+
break;
|
|
396
|
+
case "checkers":
|
|
397
|
+
this.renderCheckerPattern(ctx, dimensions, color);
|
|
398
|
+
break;
|
|
399
|
+
case "none":
|
|
400
|
+
default:
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
ctx.restore();
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Render dot pattern
|
|
407
|
+
*/
|
|
408
|
+
renderDotPattern(ctx, dimensions, color) {
|
|
409
|
+
ctx.fillStyle = color;
|
|
410
|
+
const spacing = 30;
|
|
411
|
+
for (let x = spacing; x < dimensions.width; x += spacing) {
|
|
412
|
+
for (let y = spacing; y < dimensions.height; y += spacing) {
|
|
413
|
+
ctx.beginPath();
|
|
414
|
+
ctx.arc(x, y, 2, 0, Math.PI * 2);
|
|
415
|
+
ctx.fill();
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Render grid pattern
|
|
421
|
+
*/
|
|
422
|
+
renderGridPattern(ctx, dimensions, color) {
|
|
423
|
+
ctx.strokeStyle = color;
|
|
424
|
+
ctx.lineWidth = 1;
|
|
425
|
+
const spacing = 30;
|
|
426
|
+
for (let x = 0; x <= dimensions.width; x += spacing) {
|
|
427
|
+
ctx.beginPath();
|
|
428
|
+
ctx.moveTo(x, 0);
|
|
429
|
+
ctx.lineTo(x, dimensions.height);
|
|
430
|
+
ctx.stroke();
|
|
431
|
+
}
|
|
432
|
+
for (let y = 0; y <= dimensions.height; y += spacing) {
|
|
433
|
+
ctx.beginPath();
|
|
434
|
+
ctx.moveTo(0, y);
|
|
435
|
+
ctx.lineTo(dimensions.width, y);
|
|
436
|
+
ctx.stroke();
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Render line pattern
|
|
441
|
+
*/
|
|
442
|
+
renderLinePattern(ctx, dimensions, color) {
|
|
443
|
+
ctx.strokeStyle = color;
|
|
444
|
+
ctx.lineWidth = 2;
|
|
445
|
+
const spacing = 20;
|
|
446
|
+
for (let y = spacing; y < dimensions.height; y += spacing) {
|
|
447
|
+
ctx.beginPath();
|
|
448
|
+
ctx.moveTo(0, y);
|
|
449
|
+
ctx.lineTo(dimensions.width, y);
|
|
450
|
+
ctx.stroke();
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Render wave pattern
|
|
455
|
+
*/
|
|
456
|
+
renderWavePattern(ctx, dimensions, color) {
|
|
457
|
+
ctx.strokeStyle = color;
|
|
458
|
+
ctx.lineWidth = 2;
|
|
459
|
+
const amplitude = 10;
|
|
460
|
+
const frequency = 0.1;
|
|
461
|
+
const spacing = 40;
|
|
462
|
+
for (let y = spacing; y < dimensions.height; y += spacing) {
|
|
463
|
+
ctx.beginPath();
|
|
464
|
+
for (let x = 0; x <= dimensions.width; x += 5) {
|
|
465
|
+
const waveY = y + Math.sin(x * frequency) * amplitude;
|
|
466
|
+
if (x === 0) {
|
|
467
|
+
ctx.moveTo(x, waveY);
|
|
468
|
+
} else {
|
|
469
|
+
ctx.lineTo(x, waveY);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
ctx.stroke();
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Render checker pattern
|
|
477
|
+
*/
|
|
478
|
+
renderCheckerPattern(ctx, dimensions, color) {
|
|
479
|
+
ctx.fillStyle = color;
|
|
480
|
+
const size = 20;
|
|
481
|
+
for (let x = 0; x < dimensions.width; x += size * 2) {
|
|
482
|
+
for (let y = 0; y < dimensions.height; y += size * 2) {
|
|
483
|
+
ctx.fillRect(x, y, size, size);
|
|
484
|
+
ctx.fillRect(x + size, y + size, size, size);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
// src/infrastructure/renderers/ScreenshotRenderer.ts
|
|
491
|
+
var ScreenshotRenderer = class {
|
|
492
|
+
constructor() {
|
|
493
|
+
this.canvasRenderer = new CanvasRenderer();
|
|
494
|
+
this.patternRenderer = new PatternRenderer();
|
|
495
|
+
this.deviceConfigService = DeviceConfigService.getInstance();
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Render device frame
|
|
499
|
+
*/
|
|
500
|
+
renderDeviceFrame(ctx, dimensions, deviceType, frameStyle, frameColor) {
|
|
501
|
+
const deviceConfig = this.deviceConfigService.getDeviceConfig(deviceType);
|
|
502
|
+
if (!deviceConfig) return;
|
|
503
|
+
if (frameStyle === "none") return;
|
|
504
|
+
const isPhone = this.deviceConfigService.isPhone(deviceType);
|
|
505
|
+
const isTablet = this.deviceConfigService.isTablet(deviceType);
|
|
506
|
+
if (!isPhone && !isTablet) return;
|
|
507
|
+
const frameThickness = deviceConfig.frameThickness || 8;
|
|
508
|
+
if (frameStyle === "realistic") {
|
|
509
|
+
this.renderRealisticFrame(ctx, dimensions, frameColor, frameThickness);
|
|
510
|
+
if (isPhone && this.deviceConfigService.hasNotch(deviceType)) {
|
|
511
|
+
this.renderNotch(ctx, dimensions);
|
|
512
|
+
}
|
|
513
|
+
} else if (frameStyle === "minimal") {
|
|
514
|
+
this.renderMinimalFrame(ctx, dimensions, frameColor, frameThickness);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Render realistic device frame
|
|
519
|
+
*/
|
|
520
|
+
renderRealisticFrame(ctx, dimensions, color, thickness) {
|
|
521
|
+
ctx.strokeStyle = color;
|
|
522
|
+
ctx.lineWidth = thickness;
|
|
523
|
+
ctx.strokeRect(
|
|
524
|
+
thickness / 2,
|
|
525
|
+
thickness / 2,
|
|
526
|
+
dimensions.width - thickness,
|
|
527
|
+
dimensions.height - thickness
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Render minimal device frame
|
|
532
|
+
*/
|
|
533
|
+
renderMinimalFrame(ctx, dimensions, color, thickness) {
|
|
534
|
+
ctx.strokeStyle = color;
|
|
535
|
+
ctx.lineWidth = thickness;
|
|
536
|
+
ctx.strokeRect(0, 0, dimensions.width, dimensions.height);
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Render device notch
|
|
540
|
+
*/
|
|
541
|
+
renderNotch(ctx, dimensions) {
|
|
542
|
+
ctx.fillStyle = "#1f2937";
|
|
543
|
+
ctx.beginPath();
|
|
544
|
+
ctx.arc(dimensions.width / 2, 20, 10, 0, Math.PI * 2);
|
|
545
|
+
ctx.fill();
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Render background
|
|
549
|
+
*/
|
|
550
|
+
renderBackground(ctx, design, dimensions) {
|
|
551
|
+
if (design.backgroundGradientEnd) {
|
|
552
|
+
const gradient = this.canvasRenderer.createLinearGradient(
|
|
553
|
+
ctx,
|
|
554
|
+
0,
|
|
555
|
+
0,
|
|
556
|
+
dimensions.width,
|
|
557
|
+
dimensions.height,
|
|
558
|
+
[
|
|
559
|
+
{ offset: 0, color: design.backgroundColor },
|
|
560
|
+
{ offset: 1, color: design.backgroundGradientEnd }
|
|
561
|
+
]
|
|
562
|
+
);
|
|
563
|
+
ctx.fillStyle = gradient;
|
|
564
|
+
} else {
|
|
565
|
+
ctx.fillStyle = design.backgroundColor;
|
|
566
|
+
}
|
|
567
|
+
ctx.fillRect(0, 0, dimensions.width, dimensions.height);
|
|
568
|
+
if (design.backgroundPattern && design.backgroundPattern !== "none") {
|
|
569
|
+
this.patternRenderer.renderPattern(
|
|
570
|
+
ctx,
|
|
571
|
+
design.backgroundPattern,
|
|
572
|
+
dimensions
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Render text content
|
|
578
|
+
*/
|
|
579
|
+
renderTextContent(ctx, content, design, dimensions) {
|
|
580
|
+
const titleConfig = {
|
|
581
|
+
fillStyle: design.textColor,
|
|
582
|
+
font: `bold ${design.titleFontSize}px ${design.fontFamily}`,
|
|
583
|
+
textAlign: design.textAlign
|
|
584
|
+
};
|
|
585
|
+
const subtitleConfig = {
|
|
586
|
+
fillStyle: design.textColor,
|
|
587
|
+
font: `${design.subtitleFontSize}px ${design.fontFamily}`,
|
|
588
|
+
textAlign: design.textAlign
|
|
589
|
+
};
|
|
590
|
+
const titleY = design.layout === "text-bottom" ? dimensions.height - 150 : 100 + (design.offsetY || 0);
|
|
591
|
+
this.canvasRenderer.drawText(
|
|
592
|
+
ctx,
|
|
593
|
+
this.formatText(content.title, design.textCase),
|
|
594
|
+
dimensions.width / 2,
|
|
595
|
+
titleY,
|
|
596
|
+
titleConfig
|
|
597
|
+
);
|
|
598
|
+
const subtitleY = titleY + design.titleFontSize + (design.gap || 20);
|
|
599
|
+
this.canvasRenderer.drawText(
|
|
600
|
+
ctx,
|
|
601
|
+
this.formatText(content.subtitle, design.textCase),
|
|
602
|
+
dimensions.width / 2,
|
|
603
|
+
subtitleY,
|
|
604
|
+
subtitleConfig
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Format text case
|
|
609
|
+
*/
|
|
610
|
+
formatText(text, textCase = "none") {
|
|
611
|
+
switch (textCase) {
|
|
612
|
+
case "uppercase":
|
|
613
|
+
return text.toUpperCase();
|
|
614
|
+
case "lowercase":
|
|
615
|
+
return text.toLowerCase();
|
|
616
|
+
default:
|
|
617
|
+
return text;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Render screenshot image
|
|
622
|
+
*/
|
|
623
|
+
async renderScreenshotImage(ctx, imageUrl, design, dimensions) {
|
|
624
|
+
if (!imageUrl) return;
|
|
625
|
+
const deviceConfig = this.deviceConfigService.getDeviceConfig(
|
|
626
|
+
design.deviceType
|
|
627
|
+
);
|
|
628
|
+
if (!deviceConfig) return;
|
|
629
|
+
const frameThickness = deviceConfig.frameThickness || 8;
|
|
630
|
+
const padding = 60 + frameThickness;
|
|
631
|
+
const screenshotWidth = dimensions.width - padding * 2;
|
|
632
|
+
const screenshotHeight = dimensions.height - padding * 2;
|
|
633
|
+
try {
|
|
634
|
+
await this.canvasRenderer.drawImage(
|
|
635
|
+
ctx,
|
|
636
|
+
imageUrl,
|
|
637
|
+
padding,
|
|
638
|
+
padding,
|
|
639
|
+
screenshotWidth,
|
|
640
|
+
screenshotHeight
|
|
641
|
+
);
|
|
642
|
+
} catch (error) {
|
|
643
|
+
console.error("Failed to render screenshot image:", error);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Render complete screenshot
|
|
648
|
+
*/
|
|
649
|
+
async renderDesignedScreenshot(content, design) {
|
|
650
|
+
const deviceConfig = this.deviceConfigService.getDeviceConfig(design.deviceType);
|
|
651
|
+
if (!deviceConfig) {
|
|
652
|
+
throw new Error(`Device config not found: ${design.deviceType}`);
|
|
653
|
+
}
|
|
654
|
+
const dimensions = deviceConfig.dimensions;
|
|
655
|
+
const canvas = this.canvasRenderer.createCanvas(dimensions);
|
|
656
|
+
const ctx = canvas.getContext("2d");
|
|
657
|
+
if (!ctx) {
|
|
658
|
+
throw new Error("Failed to get canvas context");
|
|
659
|
+
}
|
|
660
|
+
this.renderBackground(ctx, design, dimensions);
|
|
661
|
+
if (content.screenshot) {
|
|
662
|
+
await this.renderScreenshotImage(ctx, content.screenshot, design, dimensions);
|
|
663
|
+
}
|
|
664
|
+
this.renderTextContent(ctx, content, design, dimensions);
|
|
665
|
+
this.renderDeviceFrame(
|
|
666
|
+
ctx,
|
|
667
|
+
dimensions,
|
|
668
|
+
design.deviceType,
|
|
669
|
+
design.frameStyle,
|
|
670
|
+
design.deviceColor
|
|
671
|
+
);
|
|
672
|
+
return canvas;
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Render placeholder content
|
|
676
|
+
*/
|
|
677
|
+
renderPlaceholderContent(ctx, dimensions, deviceType) {
|
|
678
|
+
this.canvasRenderer.fillRect(ctx, 0, 0, dimensions.width, dimensions.height, "#ffffff");
|
|
679
|
+
this.canvasRenderer.fillRect(ctx, 0, 0, dimensions.width, 60, "#f3f4f6");
|
|
680
|
+
this.canvasRenderer.fillRect(
|
|
681
|
+
ctx,
|
|
682
|
+
0,
|
|
683
|
+
dimensions.height - 80,
|
|
684
|
+
dimensions.width,
|
|
685
|
+
80,
|
|
686
|
+
"#f3f4f6"
|
|
687
|
+
);
|
|
688
|
+
ctx.fillStyle = "#e5e7eb";
|
|
689
|
+
for (let i = 0; i < 5; i++) {
|
|
690
|
+
const y = 80 + i * 120;
|
|
691
|
+
const height = Math.min(100, dimensions.height - y - 100);
|
|
692
|
+
if (y < dimensions.height - 100) {
|
|
693
|
+
this.canvasRenderer.fillRect(ctx, 20, y, dimensions.width - 40, height);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
const deviceConfig = this.deviceConfigService.getDeviceConfig(deviceType);
|
|
697
|
+
if (deviceConfig) {
|
|
698
|
+
this.renderDeviceFrame(
|
|
699
|
+
ctx,
|
|
700
|
+
dimensions,
|
|
701
|
+
deviceType,
|
|
702
|
+
"realistic",
|
|
703
|
+
"#1f2937"
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
709
|
+
0 && (module.exports = {
|
|
710
|
+
CanvasRenderer,
|
|
711
|
+
DeviceConfigService,
|
|
712
|
+
PatternRenderer,
|
|
713
|
+
ScreenshotRenderer
|
|
714
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CanvasRenderer,
|
|
3
|
+
DeviceConfigService,
|
|
4
|
+
PatternRenderer,
|
|
5
|
+
ScreenshotRenderer
|
|
6
|
+
} from "../chunk-WMKPCJFI.mjs";
|
|
7
|
+
import "../chunk-MC6PNV2F.mjs";
|
|
8
|
+
export {
|
|
9
|
+
CanvasRenderer,
|
|
10
|
+
DeviceConfigService,
|
|
11
|
+
PatternRenderer,
|
|
12
|
+
ScreenshotRenderer
|
|
13
|
+
};
|