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