@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.
Files changed (55) hide show
  1. package/dist/CanvasConfig-pzoE0RHD.d.mts +310 -0
  2. package/dist/CanvasConfig-pzoE0RHD.d.ts +310 -0
  3. package/dist/application/index.d.mts +78 -0
  4. package/dist/application/index.d.ts +78 -0
  5. package/dist/application/index.js +1083 -0
  6. package/dist/application/index.mjs +11 -0
  7. package/dist/chunk-BN7JTXL7.mjs +346 -0
  8. package/dist/chunk-MC6PNV2F.mjs +34 -0
  9. package/dist/chunk-SF6WND7M.mjs +547 -0
  10. package/dist/chunk-TZGTTYTZ.mjs +0 -0
  11. package/dist/chunk-WMKPCJFI.mjs +661 -0
  12. package/dist/chunk-Z6IB4BQV.mjs +31 -0
  13. package/dist/domain/config/index.d.mts +87 -0
  14. package/dist/domain/config/index.d.ts +87 -0
  15. package/dist/domain/config/index.js +92 -0
  16. package/dist/domain/config/index.mjs +15 -0
  17. package/dist/domain/index.d.mts +79 -0
  18. package/dist/domain/index.d.ts +79 -0
  19. package/dist/domain/index.js +137 -0
  20. package/dist/domain/index.mjs +55 -0
  21. package/dist/index.d.mts +6 -0
  22. package/dist/index.d.ts +6 -0
  23. package/dist/index.js +1633 -0
  24. package/dist/index.mjs +38 -0
  25. package/dist/infrastructure/index.d.mts +174 -0
  26. package/dist/infrastructure/index.d.ts +174 -0
  27. package/dist/infrastructure/index.js +714 -0
  28. package/dist/infrastructure/index.mjs +13 -0
  29. package/dist/presentation/index.d.mts +108 -0
  30. package/dist/presentation/index.d.ts +108 -0
  31. package/dist/presentation/index.js +1617 -0
  32. package/dist/presentation/index.mjs +14 -0
  33. package/package.json +86 -0
  34. package/src/application/index.ts +7 -0
  35. package/src/application/services/ReportService.ts +200 -0
  36. package/src/application/services/ScreenshotStudioService.ts +243 -0
  37. package/src/domain/config/CanvasConfig.ts +36 -0
  38. package/src/domain/config/DeviceConfig.ts +47 -0
  39. package/src/domain/config/ScreenshotStudioConfig.ts +82 -0
  40. package/src/domain/config/index.ts +8 -0
  41. package/src/domain/index.ts +7 -0
  42. package/src/domain/types/canvas.types.ts +85 -0
  43. package/src/domain/types/index.ts +8 -0
  44. package/src/domain/types/report.types.ts +83 -0
  45. package/src/domain/types/screenshot.types.ts +238 -0
  46. package/src/index.ts +102 -0
  47. package/src/infrastructure/index.ts +9 -0
  48. package/src/infrastructure/renderers/CanvasRenderer.ts +287 -0
  49. package/src/infrastructure/renderers/PatternRenderer.ts +160 -0
  50. package/src/infrastructure/renderers/ScreenshotRenderer.ts +331 -0
  51. package/src/infrastructure/services/DeviceConfigService.ts +144 -0
  52. package/src/presentation/hooks/useCanvasExport.ts +162 -0
  53. package/src/presentation/hooks/useReportExport.ts +161 -0
  54. package/src/presentation/hooks/useScreenshotStudio.ts +480 -0
  55. 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
+ };