appshot-cli 0.1.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 (159) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +381 -0
  3. package/bin/appshot.js +5 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.d.ts.map +1 -0
  6. package/dist/cli.js +31 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/commands/build.d.ts +3 -0
  9. package/dist/commands/build.d.ts.map +1 -0
  10. package/dist/commands/build.js +200 -0
  11. package/dist/commands/build.js.map +1 -0
  12. package/dist/commands/caption.d.ts +3 -0
  13. package/dist/commands/caption.d.ts.map +1 -0
  14. package/dist/commands/caption.js +118 -0
  15. package/dist/commands/caption.js.map +1 -0
  16. package/dist/commands/check.d.ts +3 -0
  17. package/dist/commands/check.d.ts.map +1 -0
  18. package/dist/commands/check.js +121 -0
  19. package/dist/commands/check.js.map +1 -0
  20. package/dist/commands/clean.d.ts +3 -0
  21. package/dist/commands/clean.d.ts.map +1 -0
  22. package/dist/commands/clean.js +133 -0
  23. package/dist/commands/clean.js.map +1 -0
  24. package/dist/commands/init.d.ts +3 -0
  25. package/dist/commands/init.d.ts.map +1 -0
  26. package/dist/commands/init.js +84 -0
  27. package/dist/commands/init.js.map +1 -0
  28. package/dist/commands/localize.d.ts +3 -0
  29. package/dist/commands/localize.d.ts.map +1 -0
  30. package/dist/commands/localize.js +31 -0
  31. package/dist/commands/localize.js.map +1 -0
  32. package/dist/commands/presets.d.ts +3 -0
  33. package/dist/commands/presets.d.ts.map +1 -0
  34. package/dist/commands/presets.js +135 -0
  35. package/dist/commands/presets.js.map +1 -0
  36. package/dist/commands/specs.d.ts +3 -0
  37. package/dist/commands/specs.d.ts.map +1 -0
  38. package/dist/commands/specs.js +69 -0
  39. package/dist/commands/specs.js.map +1 -0
  40. package/dist/commands/validate.d.ts +3 -0
  41. package/dist/commands/validate.d.ts.map +1 -0
  42. package/dist/commands/validate.js +183 -0
  43. package/dist/commands/validate.js.map +1 -0
  44. package/dist/core/app-store-specs.d.ts +55 -0
  45. package/dist/core/app-store-specs.d.ts.map +1 -0
  46. package/dist/core/app-store-specs.js +457 -0
  47. package/dist/core/app-store-specs.js.map +1 -0
  48. package/dist/core/compose.d.ts +30 -0
  49. package/dist/core/compose.d.ts.map +1 -0
  50. package/dist/core/compose.js +420 -0
  51. package/dist/core/compose.js.map +1 -0
  52. package/dist/core/devices.d.ts +50 -0
  53. package/dist/core/devices.d.ts.map +1 -0
  54. package/dist/core/devices.js +404 -0
  55. package/dist/core/devices.js.map +1 -0
  56. package/dist/core/files.d.ts +5 -0
  57. package/dist/core/files.d.ts.map +1 -0
  58. package/dist/core/files.js +35 -0
  59. package/dist/core/files.js.map +1 -0
  60. package/dist/core/frames-analyzer.d.ts +24 -0
  61. package/dist/core/frames-analyzer.d.ts.map +1 -0
  62. package/dist/core/frames-analyzer.js +97 -0
  63. package/dist/core/frames-analyzer.js.map +1 -0
  64. package/dist/core/frames-loader.d.ts +113 -0
  65. package/dist/core/frames-loader.d.ts.map +1 -0
  66. package/dist/core/frames-loader.js +440 -0
  67. package/dist/core/frames-loader.js.map +1 -0
  68. package/dist/core/mask-generator.d.ts +10 -0
  69. package/dist/core/mask-generator.d.ts.map +1 -0
  70. package/dist/core/mask-generator.js +99 -0
  71. package/dist/core/mask-generator.js.map +1 -0
  72. package/dist/core/render.d.ts +10 -0
  73. package/dist/core/render.d.ts.map +1 -0
  74. package/dist/core/render.js +92 -0
  75. package/dist/core/render.js.map +1 -0
  76. package/dist/core/text-renderer.d.ts +18 -0
  77. package/dist/core/text-renderer.d.ts.map +1 -0
  78. package/dist/core/text-renderer.js +148 -0
  79. package/dist/core/text-renderer.js.map +1 -0
  80. package/dist/types.d.ts +57 -0
  81. package/dist/types.d.ts.map +1 -0
  82. package/dist/types.js +2 -0
  83. package/dist/types.js.map +1 -0
  84. package/dist/utils/caption-history.d.ts +35 -0
  85. package/dist/utils/caption-history.d.ts.map +1 -0
  86. package/dist/utils/caption-history.js +233 -0
  87. package/dist/utils/caption-history.js.map +1 -0
  88. package/frames/Frames.json +308 -0
  89. package/frames/MacBook Air 2020.png +0 -0
  90. package/frames/MacBook Air 2022.png +0 -0
  91. package/frames/MacBook Pro 13.png +0 -0
  92. package/frames/MacBook Pro 2021 14.png +0 -0
  93. package/frames/MacBook Pro 2021 16.png +0 -0
  94. package/frames/Watch Series 10 42.png +0 -0
  95. package/frames/Watch Series 10 42_mask.png +0 -0
  96. package/frames/Watch Series 10 46.png +0 -0
  97. package/frames/Watch Series 10 46_mask.png +0 -0
  98. package/frames/Watch Series 4 40.png +0 -0
  99. package/frames/Watch Series 4 44.png +0 -0
  100. package/frames/Watch Series 7 41.png +0 -0
  101. package/frames/Watch Series 7 45.png +0 -0
  102. package/frames/Watch Ultra 2024.png +0 -0
  103. package/frames/iMac 2021.png +0 -0
  104. package/frames/iPad 2021 Landscape.png +0 -0
  105. package/frames/iPad 2021 Portrait.png +0 -0
  106. package/frames/iPad Air 2020 Landscape.png +0 -0
  107. package/frames/iPad Air 2020 Portrait.png +0 -0
  108. package/frames/iPad Pro 2018-2021 11 Landscape.png +0 -0
  109. package/frames/iPad Pro 2018-2021 11 Portrait.png +0 -0
  110. package/frames/iPad Pro 2018-2021 Landscape.png +0 -0
  111. package/frames/iPad Pro 2018-2021 Portrait.png +0 -0
  112. package/frames/iPad Pro 2024 11 Landscape.png +0 -0
  113. package/frames/iPad Pro 2024 11 Portrait.png +0 -0
  114. package/frames/iPad Pro 2024 13 Landscape.png +0 -0
  115. package/frames/iPad Pro 2024 13 Portrait.png +0 -0
  116. package/frames/iPad mini 2021 Landscape.png +0 -0
  117. package/frames/iPad mini 2021 Portrait.png +0 -0
  118. package/frames/iPhone 11 Landscape.png +0 -0
  119. package/frames/iPhone 11 Portrait.png +0 -0
  120. package/frames/iPhone 11 Pro Max Landscape.png +0 -0
  121. package/frames/iPhone 11 Pro Max Portrait.png +0 -0
  122. package/frames/iPhone 11 Pro Portrait.png +0 -0
  123. package/frames/iPhone 12-13 Pro Landscape.png +0 -0
  124. package/frames/iPhone 12-13 Pro Max Landscape.png +0 -0
  125. package/frames/iPhone 12-13 Pro Max Landscape_mask.png +0 -0
  126. package/frames/iPhone 12-13 Pro Max Portrait.png +0 -0
  127. package/frames/iPhone 12-13 Pro Max Portrait_mask.png +0 -0
  128. package/frames/iPhone 12-13 Pro Portrait.png +0 -0
  129. package/frames/iPhone 12-13 mini Landscape.png +0 -0
  130. package/frames/iPhone 12-13 mini Portrait.png +0 -0
  131. package/frames/iPhone 16 Landscape.png +0 -0
  132. package/frames/iPhone 16 Landscape_frame_no_island.png +0 -0
  133. package/frames/iPhone 16 Landscape_mask.png +0 -0
  134. package/frames/iPhone 16 Plus Landscape.png +0 -0
  135. package/frames/iPhone 16 Plus Landscape_frame_no_island.png +0 -0
  136. package/frames/iPhone 16 Plus Landscape_mask.png +0 -0
  137. package/frames/iPhone 16 Plus Portrait.png +0 -0
  138. package/frames/iPhone 16 Plus Portrait_frame_no_island.png +0 -0
  139. package/frames/iPhone 16 Plus Portrait_mask.png +0 -0
  140. package/frames/iPhone 16 Portrait.png +0 -0
  141. package/frames/iPhone 16 Portrait_frame_no_island.png +0 -0
  142. package/frames/iPhone 16 Portrait_mask.png +0 -0
  143. package/frames/iPhone 16 Pro Landscape.png +0 -0
  144. package/frames/iPhone 16 Pro Landscape_frame_no_island.png +0 -0
  145. package/frames/iPhone 16 Pro Landscape_mask.png +0 -0
  146. package/frames/iPhone 16 Pro Max Landscape.png +0 -0
  147. package/frames/iPhone 16 Pro Max Landscape_frame_no_island.png +0 -0
  148. package/frames/iPhone 16 Pro Max Landscape_mask.png +0 -0
  149. package/frames/iPhone 16 Pro Max Portrait.png +0 -0
  150. package/frames/iPhone 16 Pro Max Portrait_frame_no_island.png +0 -0
  151. package/frames/iPhone 16 Pro Max Portrait_mask.png +0 -0
  152. package/frames/iPhone 16 Pro Portrait.png +0 -0
  153. package/frames/iPhone 16 Pro Portrait_frame_no_island.png +0 -0
  154. package/frames/iPhone 16 Pro Portrait_mask.png +0 -0
  155. package/frames/iPhone 8 Plus Landscape.png +0 -0
  156. package/frames/iPhone 8 Plus Portrait.png +0 -0
  157. 2020 SE.png +0 -0
  158. package/frames/version.txt +1 -0
  159. package/package.json +61 -0
@@ -0,0 +1,420 @@
1
+ import sharp from 'sharp';
2
+ import { promises as fs } from 'fs';
3
+ import { renderGradient } from './render.js';
4
+ import { applyRoundedCorners } from './mask-generator.js';
5
+ /**
6
+ * Compose a complete App Store screenshot with gradient, caption, and framed device
7
+ */
8
+ export async function composeAppStoreScreenshot(options) {
9
+ const { screenshot, frame, frameMetadata, caption, captionConfig, gradientConfig, deviceConfig, outputWidth, outputHeight } = options;
10
+ // Determine caption position (default to 'above' for better App Store style)
11
+ const captionPosition = captionConfig.position || 'above';
12
+ const partialFrame = deviceConfig.partialFrame || false;
13
+ const frameOffset = deviceConfig.frameOffset || 25; // Default 25% cut off
14
+ // Calculate dimensions based on output
15
+ // Calculate caption height if positioned above
16
+ let captionHeight = 0;
17
+ if (captionPosition === 'above' && caption) {
18
+ // For watch devices, use top 1/3 of screen for text
19
+ const isWatch = outputWidth < 500;
20
+ if (isWatch) {
21
+ captionHeight = Math.floor(outputHeight / 3); // Use top 1/3 for watch captions
22
+ }
23
+ else {
24
+ captionHeight = captionConfig.paddingTop + captionConfig.fontsize * 2 + (captionConfig.paddingBottom || 60);
25
+ }
26
+ }
27
+ // Calculate total canvas dimensions (should be output dimensions)
28
+ const canvasWidth = outputWidth;
29
+ const canvasHeight = outputHeight;
30
+ // Create gradient background
31
+ const gradient = await renderGradient(canvasWidth, canvasHeight, gradientConfig);
32
+ // Start compositing
33
+ const composites = [];
34
+ // Add caption if positioned above
35
+ if (caption && captionPosition === 'above') {
36
+ try {
37
+ // Create simple SVG text
38
+ const isWatch = outputWidth < 500;
39
+ const fontSize = isWatch ? 36 : captionConfig.fontsize; // Smaller font for watch
40
+ let svgText;
41
+ if (isWatch) {
42
+ // Simple word wrapping for 2 lines ONLY for watch
43
+ const words = caption.split(' ');
44
+ const midPoint = Math.ceil(words.length / 2);
45
+ const line1 = words.slice(0, midPoint).join(' ');
46
+ const line2 = words.slice(midPoint).join(' ');
47
+ svgText = `<svg width="${canvasWidth}" height="${captionHeight}" xmlns="http://www.w3.org/2000/svg">
48
+ <text x="${canvasWidth / 2}" y="${captionHeight * 0.4}"
49
+ font-family="Arial, sans-serif"
50
+ font-size="${fontSize}"
51
+ fill="${captionConfig.color}"
52
+ text-anchor="middle"
53
+ font-weight="bold">${escapeXml(line1)}</text>
54
+ <text x="${canvasWidth / 2}" y="${captionHeight * 0.7}"
55
+ font-family="Arial, sans-serif"
56
+ font-size="${fontSize}"
57
+ fill="${captionConfig.color}"
58
+ text-anchor="middle"
59
+ font-weight="bold">${escapeXml(line2)}</text>
60
+ </svg>`;
61
+ }
62
+ else {
63
+ // Single line for all other devices (iPhone, iPad, etc.)
64
+ svgText = `<svg width="${canvasWidth}" height="${captionHeight}" xmlns="http://www.w3.org/2000/svg">
65
+ <text x="${canvasWidth / 2}" y="${captionHeight / 2}"
66
+ font-family="Arial, sans-serif"
67
+ font-size="${fontSize}"
68
+ fill="${captionConfig.color}"
69
+ text-anchor="middle"
70
+ dominant-baseline="middle"
71
+ font-weight="bold">${escapeXml(caption)}</text>
72
+ </svg>`;
73
+ }
74
+ const captionImage = await sharp(Buffer.from(svgText))
75
+ .png()
76
+ .toBuffer();
77
+ composites.push({
78
+ input: captionImage,
79
+ top: 0,
80
+ left: 0
81
+ });
82
+ }
83
+ catch {
84
+ // If text rendering fails, just add transparent area
85
+ console.log('[INFO] Text rendering failed, reserving space for caption');
86
+ const captionArea = await sharp({
87
+ create: {
88
+ width: canvasWidth,
89
+ height: captionHeight,
90
+ channels: 4,
91
+ background: { r: 0, g: 0, b: 0, alpha: 0 }
92
+ }
93
+ })
94
+ .png()
95
+ .toBuffer();
96
+ composites.push({
97
+ input: captionArea,
98
+ top: 0,
99
+ left: 0
100
+ });
101
+ }
102
+ }
103
+ if (frame && frameMetadata) {
104
+ // Validate that frame is actually a buffer
105
+ if (!Buffer.isBuffer(frame)) {
106
+ throw new Error(`Frame is not a valid buffer for ${frameMetadata.displayName || frameMetadata.name}`);
107
+ }
108
+ // Calculate scale factor if frame needs to be resized to fit output
109
+ const originalFrameWidth = frameMetadata.frameWidth;
110
+ const originalFrameHeight = frameMetadata.frameHeight;
111
+ // Calculate available space for the device (accounting for caption)
112
+ const availableWidth = outputWidth;
113
+ const availableHeight = Math.max(100, outputHeight - captionHeight); // Ensure minimum height
114
+ // Calculate scale to fit within available space while maintaining aspect ratio
115
+ const scaleX = availableWidth / originalFrameWidth;
116
+ const scaleY = availableHeight / originalFrameHeight;
117
+ // Different scaling for different device types
118
+ let scale;
119
+ if (frameMetadata.deviceType === 'watch') {
120
+ // For watch, make it larger since bottom will be cut off
121
+ scale = Math.min(scaleX, scaleY) * 1.3; // Use 130% scale for watch
122
+ }
123
+ else if (frameMetadata.deviceType === 'mac') {
124
+ // For Mac, make it larger to be more visible
125
+ scale = Math.min(scaleX, scaleY) * 0.95; // Use 95% scale for Mac
126
+ }
127
+ else {
128
+ scale = Math.min(scaleX, scaleY) * 0.9; // Use 90% for other devices
129
+ }
130
+ // Apply scaling to optimize canvas usage
131
+ let targetDeviceWidth = Math.min(Math.floor(originalFrameWidth * scale), outputWidth);
132
+ let targetDeviceHeight = Math.min(Math.floor(originalFrameHeight * scale), outputHeight);
133
+ // Scale screenshot to fit in frame's screen area
134
+ let resizedScreenshot;
135
+ try {
136
+ resizedScreenshot = await sharp(screenshot)
137
+ .resize(frameMetadata.screenRect.width, frameMetadata.screenRect.height, {
138
+ fit: 'fill'
139
+ })
140
+ .toBuffer();
141
+ }
142
+ catch (error) {
143
+ console.error('Failed to resize screenshot:', error);
144
+ throw error;
145
+ }
146
+ // If we have a mask, apply it to the screenshot to clip corners
147
+ let maskApplied = false;
148
+ if (frameMetadata.maskPath) {
149
+ try {
150
+ // Load the mask
151
+ const maskBuffer = await fs.readFile(frameMetadata.maskPath);
152
+ // Resize mask to match screenshot dimensions
153
+ const resizedMask = await sharp(maskBuffer)
154
+ .resize(frameMetadata.screenRect.width, frameMetadata.screenRect.height, {
155
+ fit: 'fill'
156
+ })
157
+ .toBuffer();
158
+ // Extract RGB from screenshot and alpha from mask's red channel
159
+ const screenshotRgb = await sharp(resizedScreenshot)
160
+ .removeAlpha()
161
+ .toBuffer();
162
+ const maskAlpha = await sharp(resizedMask)
163
+ .extractChannel('red') // Black=0 (transparent), White=255 (opaque)
164
+ .toBuffer();
165
+ // Join screenshot RGB with mask as alpha channel
166
+ resizedScreenshot = await sharp(screenshotRgb)
167
+ .joinChannel(maskAlpha)
168
+ .png()
169
+ .toBuffer();
170
+ maskApplied = true;
171
+ }
172
+ catch (error) {
173
+ console.warn(`Could not load mask file, will use programmatic masking: ${error}`);
174
+ // Fall through to programmatic masking
175
+ }
176
+ }
177
+ // If no mask was applied and this is an iPhone, use programmatic corner masking
178
+ if (!maskApplied && frameMetadata.deviceType === 'iphone') {
179
+ // No mask available, create a rounded corner mask for iPhone
180
+ // Different iPhone models have different corner radii
181
+ let cornerRadius;
182
+ const frameName = frameMetadata.displayName?.toLowerCase() || frameMetadata.name?.toLowerCase() || '';
183
+ if (frameName.includes('16 pro') || frameName.includes('15 pro') || frameName.includes('14 pro')) {
184
+ // Newer Pro models have larger corner radius (~12% of width)
185
+ cornerRadius = Math.floor(frameMetadata.screenRect.width * 0.12);
186
+ }
187
+ else if (frameName.includes('se') || frameName.includes('8')) {
188
+ // SE and iPhone 8 have no rounded corners on the screen
189
+ cornerRadius = 0;
190
+ }
191
+ else {
192
+ // Standard models and older Pro models (~10% of width)
193
+ cornerRadius = Math.floor(frameMetadata.screenRect.width * 0.10);
194
+ }
195
+ if (cornerRadius > 0) {
196
+ // Apply rounded corners using our custom mask generator
197
+ resizedScreenshot = await applyRoundedCorners(resizedScreenshot, frameMetadata.screenRect.width, frameMetadata.screenRect.height, cornerRadius);
198
+ }
199
+ }
200
+ // Create the device composite - screenshot UNDER frame
201
+ let deviceComposite;
202
+ try {
203
+ // First composite: screenshot on transparent background
204
+ const screenshotLayer = await sharp({
205
+ create: {
206
+ width: originalFrameWidth,
207
+ height: originalFrameHeight,
208
+ channels: 4,
209
+ background: { r: 0, g: 0, b: 0, alpha: 0 }
210
+ }
211
+ })
212
+ .composite([{
213
+ input: resizedScreenshot,
214
+ left: frameMetadata.screenRect.x,
215
+ top: frameMetadata.screenRect.y
216
+ }])
217
+ .png()
218
+ .toBuffer();
219
+ // Second composite: add frame on top using 'over' blend (frame should cover screenshot edges)
220
+ deviceComposite = await sharp(screenshotLayer)
221
+ .composite([{
222
+ input: frame,
223
+ left: 0,
224
+ top: 0,
225
+ blend: 'over'
226
+ }])
227
+ .png() // CRITICAL: Convert to PNG format, not raw pixels!
228
+ .toBuffer();
229
+ }
230
+ catch (error) {
231
+ console.error('Failed to create device composite:', error);
232
+ throw error;
233
+ }
234
+ // If partial frame, crop the bottom
235
+ if (partialFrame) {
236
+ const cropHeight = Math.floor(originalFrameHeight * (1 - frameOffset / 100));
237
+ try {
238
+ deviceComposite = await sharp(deviceComposite)
239
+ .extract({
240
+ left: 0,
241
+ top: 0,
242
+ width: originalFrameWidth,
243
+ height: cropHeight
244
+ })
245
+ .png() // Ensure PNG format
246
+ .toBuffer();
247
+ targetDeviceHeight = Math.floor(cropHeight * scale);
248
+ }
249
+ catch (error) {
250
+ console.error('Failed to crop:', error);
251
+ throw error;
252
+ }
253
+ }
254
+ // Scale the complete device if needed (now scales up or down)
255
+ if (scale !== 1) {
256
+ try {
257
+ deviceComposite = await sharp(deviceComposite)
258
+ .resize(targetDeviceWidth, targetDeviceHeight, {
259
+ fit: 'inside', // Preserve aspect ratio
260
+ withoutEnlargement: false // Allow scaling up
261
+ })
262
+ .png() // Ensure PNG format
263
+ .toBuffer();
264
+ }
265
+ catch (error) {
266
+ console.error('Failed to scale:', error);
267
+ throw error;
268
+ }
269
+ }
270
+ // Calculate position for centered device
271
+ // For watch, position lower to cut off bottom band
272
+ let deviceTop;
273
+ if (frameMetadata.deviceType === 'watch') {
274
+ // Position watch so bottom 1/4 is cut off, raised 25px total
275
+ deviceTop = canvasHeight - Math.floor(targetDeviceHeight * 0.75) - 25;
276
+ }
277
+ else {
278
+ deviceTop = captionHeight;
279
+ }
280
+ const deviceLeft = Math.floor((canvasWidth - targetDeviceWidth) / 2);
281
+ // Add the complete device to composites
282
+ composites.push({
283
+ input: deviceComposite,
284
+ top: deviceTop,
285
+ left: Math.max(0, deviceLeft)
286
+ });
287
+ }
288
+ else {
289
+ // No frame, just use the screenshot
290
+ const deviceTop = captionHeight;
291
+ const deviceLeft = Math.floor((canvasWidth - outputWidth) / 2);
292
+ composites.push({
293
+ input: screenshot,
294
+ top: deviceTop,
295
+ left: Math.max(0, deviceLeft)
296
+ });
297
+ }
298
+ // Add overlay caption if specified (legacy support)
299
+ // Note: Caption rendering requires librsvg to be installed
300
+ if (caption && captionPosition === 'overlay') {
301
+ // Skip caption rendering for now - would require librsvg
302
+ // TODO: Implement pure bitmap text rendering in future version
303
+ }
304
+ // Composite everything onto the gradient
305
+ const result = await sharp(gradient)
306
+ .composite(composites)
307
+ .png() // IMPORTANT: Ensure the output is a valid PNG
308
+ .toBuffer();
309
+ return result;
310
+ }
311
+ /**
312
+ * Create SVG for caption text
313
+ */
314
+ function _createCaptionSvg(text, config, width, height) {
315
+ const position = config.position || 'above';
316
+ // Calculate text position
317
+ let textY;
318
+ let textX;
319
+ let textAnchor;
320
+ if (position === 'above') {
321
+ // Position in the caption area
322
+ textY = config.paddingTop + config.fontsize;
323
+ }
324
+ else {
325
+ // Overlay position (legacy)
326
+ textY = config.paddingTop + config.fontsize;
327
+ }
328
+ // Handle text alignment
329
+ switch (config.align) {
330
+ case 'left':
331
+ textX = config.paddingLeft || 50;
332
+ textAnchor = 'start';
333
+ break;
334
+ case 'right':
335
+ textX = width - (config.paddingRight || 50);
336
+ textAnchor = 'end';
337
+ break;
338
+ case 'center':
339
+ default:
340
+ textX = width / 2;
341
+ textAnchor = 'middle';
342
+ break;
343
+ }
344
+ // Use a safe font stack that Sharp can render properly
345
+ // SF Pro is not available to Sharp's SVG renderer, so we use a fallback stack
346
+ const fontFamily = getFontStack(config.font);
347
+ // For watch devices, use smaller font if text is too long
348
+ const maxCharsPerLine = Math.floor(width / (config.fontsize * 0.6));
349
+ const needsWrapping = text.length > maxCharsPerLine;
350
+ if (needsWrapping) {
351
+ // Split text into multiple lines
352
+ const words = text.split(' ');
353
+ const lines = [];
354
+ let currentLine = '';
355
+ for (const word of words) {
356
+ const testLine = currentLine ? `${currentLine} ${word}` : word;
357
+ if (testLine.length <= maxCharsPerLine) {
358
+ currentLine = testLine;
359
+ }
360
+ else {
361
+ if (currentLine)
362
+ lines.push(currentLine);
363
+ currentLine = word;
364
+ }
365
+ }
366
+ if (currentLine)
367
+ lines.push(currentLine);
368
+ // Create multiple text elements for each line
369
+ const lineHeight = config.fontsize * 1.2;
370
+ const textElements = lines.map((line, index) => `<text
371
+ x="${textX}"
372
+ y="${textY + (index * lineHeight)}"
373
+ font-family="${fontFamily}"
374
+ font-size="${config.fontsize}"
375
+ fill="${config.color}"
376
+ text-anchor="${textAnchor}"
377
+ font-weight="600"
378
+ >${escapeXml(line)}</text>`).join('\n');
379
+ return `<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
380
+ ${textElements}
381
+ </svg>`;
382
+ }
383
+ // Single line text
384
+ return `<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
385
+ <text
386
+ x="${textX}"
387
+ y="${textY}"
388
+ font-family="${fontFamily}"
389
+ font-size="${config.fontsize}"
390
+ fill="${config.color}"
391
+ text-anchor="${textAnchor}"
392
+ font-weight="600"
393
+ >${escapeXml(text)}</text>
394
+ </svg>`;
395
+ }
396
+ function escapeXml(text) {
397
+ return text
398
+ .replace(/&/g, '&amp;')
399
+ .replace(/</g, '&lt;')
400
+ .replace(/>/g, '&gt;')
401
+ .replace(/"/g, '&quot;')
402
+ .replace(/'/g, '&apos;');
403
+ }
404
+ /**
405
+ * Get a safe font stack that Sharp's SVG renderer can use
406
+ */
407
+ function getFontStack(requestedFont) {
408
+ // Map common macOS fonts to web-safe alternatives
409
+ // Note: Using single quotes inside to avoid XML attribute quote conflicts
410
+ const fontMap = {
411
+ 'SF Pro': "system-ui, -apple-system, 'Helvetica Neue', Helvetica, Arial, sans-serif",
412
+ 'SF Pro Display': "system-ui, -apple-system, 'Helvetica Neue', Helvetica, Arial, sans-serif",
413
+ 'SF Pro Text': "system-ui, -apple-system, 'Helvetica Neue', Helvetica, Arial, sans-serif",
414
+ 'San Francisco': "system-ui, -apple-system, 'Helvetica Neue', Helvetica, Arial, sans-serif",
415
+ 'New York': "Georgia, 'Times New Roman', Times, serif"
416
+ };
417
+ // Return the mapped font stack or use the requested font with fallbacks
418
+ return fontMap[requestedFont] || `'${requestedFont}', system-ui, -apple-system, 'Helvetica Neue', Helvetica, Arial, sans-serif`;
419
+ }
420
+ //# sourceMappingURL=compose.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose.js","sourceRoot":"","sources":["../../src/core/compose.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,IAAI,CAAC;AAEpC,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AA2B1D;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAAC,OAAuB;IACrE,MAAM,EACJ,UAAU,EACV,KAAK,EACL,aAAa,EACb,OAAO,EACP,aAAa,EACb,cAAc,EACd,YAAY,EACZ,WAAW,EACX,YAAY,EACb,GAAG,OAAO,CAAC;IAGZ,6EAA6E;IAC7E,MAAM,eAAe,GAAG,aAAa,CAAC,QAAQ,IAAI,OAAO,CAAC;IAC1D,MAAM,YAAY,GAAG,YAAY,CAAC,YAAY,IAAI,KAAK,CAAC;IACxD,MAAM,WAAW,GAAG,YAAY,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,sBAAsB;IAE1E,uCAAuC;IAEvC,+CAA+C;IAC/C,IAAI,aAAa,GAAG,CAAC,CAAC;IACtB,IAAI,eAAe,KAAK,OAAO,IAAI,OAAO,EAAE,CAAC;QAC3C,oDAAoD;QACpD,MAAM,OAAO,GAAG,WAAW,GAAG,GAAG,CAAC;QAClC,IAAI,OAAO,EAAE,CAAC;YACZ,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC,iCAAiC;QACjF,CAAC;aAAM,CAAC;YACN,aAAa,GAAG,aAAa,CAAC,UAAU,GAAG,aAAa,CAAC,QAAQ,GAAG,CAAC,GAAG,CAAC,aAAa,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC;QAC9G,CAAC;IACH,CAAC;IAGD,kEAAkE;IAClE,MAAM,WAAW,GAAG,WAAW,CAAC;IAChC,MAAM,YAAY,GAAG,YAAY,CAAC;IAElC,6BAA6B;IAC7B,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,WAAW,EAAE,YAAY,EAAE,cAAc,CAAC,CAAC;IAEjF,oBAAoB;IACpB,MAAM,UAAU,GAA2B,EAAE,CAAC;IAE9C,kCAAkC;IAClC,IAAI,OAAO,IAAI,eAAe,KAAK,OAAO,EAAE,CAAC;QAC3C,IAAI,CAAC;YACH,yBAAyB;YACzB,MAAM,OAAO,GAAG,WAAW,GAAG,GAAG,CAAC;YAClC,MAAM,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,yBAAyB;YAEjF,IAAI,OAAe,CAAC;YAEpB,IAAI,OAAO,EAAE,CAAC;gBACZ,kDAAkD;gBAClD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;gBACjC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;gBAC7C,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACjD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAE9C,OAAO,GAAG,eAAe,WAAW,aAAa,aAAa;qBACjD,WAAW,GAAC,CAAC,QAAQ,aAAa,GAAG,GAAG;;6BAEhC,QAAQ;wBACb,aAAa,CAAC,KAAK;;qCAEN,SAAS,CAAC,KAAK,CAAC;qBAChC,WAAW,GAAC,CAAC,QAAQ,aAAa,GAAG,GAAG;;6BAEhC,QAAQ;wBACb,aAAa,CAAC,KAAK;;qCAEN,SAAS,CAAC,KAAK,CAAC;eACtC,CAAC;YACV,CAAC;iBAAM,CAAC;gBACN,yDAAyD;gBACzD,OAAO,GAAG,eAAe,WAAW,aAAa,aAAa;qBACjD,WAAW,GAAC,CAAC,QAAQ,aAAa,GAAC,CAAC;;6BAE5B,QAAQ;wBACb,aAAa,CAAC,KAAK;;;qCAGN,SAAS,CAAC,OAAO,CAAC;eACxC,CAAC;YACV,CAAC;YAED,MAAM,YAAY,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;iBACnD,GAAG,EAAE;iBACL,QAAQ,EAAE,CAAC;YAEd,UAAU,CAAC,IAAI,CAAC;gBACd,KAAK,EAAE,YAAY;gBACnB,GAAG,EAAE,CAAC;gBACN,IAAI,EAAE,CAAC;aACR,CAAC,CAAC;QAEL,CAAC;QAAC,MAAM,CAAC;YACP,qDAAqD;YACrD,OAAO,CAAC,GAAG,CAAC,2DAA2D,CAAC,CAAC;YACzE,MAAM,WAAW,GAAG,MAAM,KAAK,CAAC;gBAC9B,MAAM,EAAE;oBACN,KAAK,EAAE,WAAW;oBAClB,MAAM,EAAE,aAAa;oBACrB,QAAQ,EAAE,CAAC;oBACX,UAAU,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE;iBAC3C;aACF,CAAC;iBACC,GAAG,EAAE;iBACL,QAAQ,EAAE,CAAC;YAEd,UAAU,CAAC,IAAI,CAAC;gBACd,KAAK,EAAE,WAAW;gBAClB,GAAG,EAAE,CAAC;gBACN,IAAI,EAAE,CAAC;aACR,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,IAAI,KAAK,IAAI,aAAa,EAAE,CAAC;QAC3B,2CAA2C;QAC3C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,mCAAmC,aAAa,CAAC,WAAW,IAAI,aAAa,CAAC,IAAI,EAAE,CAAC,CAAC;QACxG,CAAC;QACD,oEAAoE;QACpE,MAAM,kBAAkB,GAAG,aAAa,CAAC,UAAU,CAAC;QACpD,MAAM,mBAAmB,GAAG,aAAa,CAAC,WAAW,CAAC;QAEtD,oEAAoE;QACpE,MAAM,cAAc,GAAG,WAAW,CAAC;QACnC,MAAM,eAAe,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,YAAY,GAAG,aAAa,CAAC,CAAC,CAAC,wBAAwB;QAE7F,+EAA+E;QAC/E,MAAM,MAAM,GAAG,cAAc,GAAG,kBAAkB,CAAC;QACnD,MAAM,MAAM,GAAG,eAAe,GAAG,mBAAmB,CAAC;QACrD,+CAA+C;QAC/C,IAAI,KAAK,CAAC;QACV,IAAI,aAAa,CAAC,UAAU,KAAK,OAAO,EAAE,CAAC;YACzC,yDAAyD;YACzD,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,2BAA2B;QACrE,CAAC;aAAM,IAAI,aAAa,CAAC,UAAU,KAAK,KAAK,EAAE,CAAC;YAC9C,6CAA6C;YAC7C,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,wBAAwB;QACnE,CAAC;aAAM,CAAC;YACN,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,4BAA4B;QACtE,CAAC;QAED,yCAAyC;QACzC,IAAI,iBAAiB,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,kBAAkB,GAAG,KAAK,CAAC,EAAE,WAAW,CAAC,CAAC;QACtF,IAAI,kBAAkB,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,mBAAmB,GAAG,KAAK,CAAC,EAAE,YAAY,CAAC,CAAC;QAEzF,iDAAiD;QACjD,IAAI,iBAAiB,CAAC;QACtB,IAAI,CAAC;YACH,iBAAiB,GAAG,MAAM,KAAK,CAAC,UAAU,CAAC;iBACxC,MAAM,CACL,aAAa,CAAC,UAAU,CAAC,KAAK,EAC9B,aAAa,CAAC,UAAU,CAAC,MAAM,EAC/B;gBACE,GAAG,EAAE,MAAM;aACZ,CACF;iBACA,QAAQ,EAAE,CAAC;QAChB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,8BAA8B,EAAE,KAAK,CAAC,CAAC;YACrD,MAAM,KAAK,CAAC;QACd,CAAC;QAED,gEAAgE;QAChE,IAAI,WAAW,GAAG,KAAK,CAAC;QAExB,IAAI,aAAa,CAAC,QAAQ,EAAE,CAAC;YAC3B,IAAI,CAAC;gBACH,gBAAgB;gBAChB,MAAM,UAAU,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;gBAE7D,6CAA6C;gBAC7C,MAAM,WAAW,GAAG,MAAM,KAAK,CAAC,UAAU,CAAC;qBACxC,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC,KAAK,EAAE,aAAa,CAAC,UAAU,CAAC,MAAM,EAAE;oBACvE,GAAG,EAAE,MAAM;iBACZ,CAAC;qBACD,QAAQ,EAAE,CAAC;gBAEd,gEAAgE;gBAChE,MAAM,aAAa,GAAG,MAAM,KAAK,CAAC,iBAAiB,CAAC;qBACjD,WAAW,EAAE;qBACb,QAAQ,EAAE,CAAC;gBAEd,MAAM,SAAS,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC;qBACvC,cAAc,CAAC,KAAK,CAAC,CAAC,4CAA4C;qBAClE,QAAQ,EAAE,CAAC;gBAEd,iDAAiD;gBACjD,iBAAiB,GAAG,MAAM,KAAK,CAAC,aAAa,CAAC;qBAC3C,WAAW,CAAC,SAAS,CAAC;qBACtB,GAAG,EAAE;qBACL,QAAQ,EAAE,CAAC;gBAEd,WAAW,GAAG,IAAI,CAAC;YACrB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,IAAI,CAAC,4DAA4D,KAAK,EAAE,CAAC,CAAC;gBAClF,uCAAuC;YACzC,CAAC;QACH,CAAC;QAED,gFAAgF;QAChF,IAAI,CAAC,WAAW,IAAI,aAAa,CAAC,UAAU,KAAK,QAAQ,EAAE,CAAC;YAC1D,6DAA6D;YAE7D,sDAAsD;YACtD,IAAI,YAAoB,CAAC;YACzB,MAAM,SAAS,GAAG,aAAa,CAAC,WAAW,EAAE,WAAW,EAAE,IAAI,aAAa,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;YAEtG,IAAI,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACjG,6DAA6D;gBAC7D,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,UAAU,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;YACnE,CAAC;iBAAM,IAAI,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC/D,wDAAwD;gBACxD,YAAY,GAAG,CAAC,CAAC;YACnB,CAAC;iBAAM,CAAC;gBACN,uDAAuD;gBACvD,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,UAAU,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;YACnE,CAAC;YAED,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;gBACrB,wDAAwD;gBACxD,iBAAiB,GAAG,MAAM,mBAAmB,CAC3C,iBAAiB,EACjB,aAAa,CAAC,UAAU,CAAC,KAAK,EAC9B,aAAa,CAAC,UAAU,CAAC,MAAM,EAC/B,YAAY,CACb,CAAC;YACJ,CAAC;QACH,CAAC;QAED,uDAAuD;QAEvD,IAAI,eAAe,CAAC;QACpB,IAAI,CAAC;YACH,wDAAwD;YACxD,MAAM,eAAe,GAAG,MAAM,KAAK,CAAC;gBAClC,MAAM,EAAE;oBACN,KAAK,EAAE,kBAAkB;oBACzB,MAAM,EAAE,mBAAmB;oBAC3B,QAAQ,EAAE,CAAC;oBACX,UAAU,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE;iBAC3C;aACF,CAAC;iBACC,SAAS,CAAC,CAAC;oBACV,KAAK,EAAE,iBAAiB;oBACxB,IAAI,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;oBAChC,GAAG,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;iBAChC,CAAC,CAAC;iBACF,GAAG,EAAE;iBACL,QAAQ,EAAE,CAAC;YAEd,8FAA8F;YAC9F,eAAe,GAAG,MAAM,KAAK,CAAC,eAAe,CAAC;iBAC3C,SAAS,CAAC,CAAC;oBACV,KAAK,EAAE,KAAK;oBACZ,IAAI,EAAE,CAAC;oBACP,GAAG,EAAE,CAAC;oBACN,KAAK,EAAE,MAAM;iBACd,CAAC,CAAC;iBACF,GAAG,EAAE,CAAE,mDAAmD;iBAC1D,QAAQ,EAAE,CAAC;QAChB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,oCAAoC,EAAE,KAAK,CAAC,CAAC;YAC3D,MAAM,KAAK,CAAC;QACd,CAAC;QAED,oCAAoC;QACpC,IAAI,YAAY,EAAE,CAAC;YACjB,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,mBAAmB,GAAG,CAAC,CAAC,GAAG,WAAW,GAAG,GAAG,CAAC,CAAC,CAAC;YAC7E,IAAI,CAAC;gBACH,eAAe,GAAG,MAAM,KAAK,CAAC,eAAe,CAAC;qBAC3C,OAAO,CAAC;oBACP,IAAI,EAAE,CAAC;oBACP,GAAG,EAAE,CAAC;oBACN,KAAK,EAAE,kBAAkB;oBACzB,MAAM,EAAE,UAAU;iBACnB,CAAC;qBACD,GAAG,EAAE,CAAE,oBAAoB;qBAC3B,QAAQ,EAAE,CAAC;gBACd,kBAAkB,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,CAAC;YACtD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,KAAK,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAAC;gBACxC,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC;QAED,8DAA8D;QAC9D,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;YAChB,IAAI,CAAC;gBACH,eAAe,GAAG,MAAM,KAAK,CAAC,eAAe,CAAC;qBAC3C,MAAM,CAAC,iBAAiB,EAAE,kBAAkB,EAAE;oBAC7C,GAAG,EAAE,QAAQ,EAAG,wBAAwB;oBACxC,kBAAkB,EAAE,KAAK,CAAE,mBAAmB;iBAC/C,CAAC;qBACD,GAAG,EAAE,CAAE,oBAAoB;qBAC3B,QAAQ,EAAE,CAAC;YAChB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,KAAK,CAAC,kBAAkB,EAAE,KAAK,CAAC,CAAC;gBACzC,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC;QAED,yCAAyC;QACzC,mDAAmD;QACnD,IAAI,SAAS,CAAC;QACd,IAAI,aAAa,CAAC,UAAU,KAAK,OAAO,EAAE,CAAC;YACzC,6DAA6D;YAC7D,SAAS,GAAG,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,kBAAkB,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACxE,CAAC;aAAM,CAAC;YACN,SAAS,GAAG,aAAa,CAAC;QAC5B,CAAC;QACD,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,WAAW,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC;QAErE,wCAAwC;QACxC,UAAU,CAAC,IAAI,CAAC;YACd,KAAK,EAAE,eAAe;YACtB,GAAG,EAAE,SAAS;YACd,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,CAAC;SAC9B,CAAC,CAAC;IACL,CAAC;SAAM,CAAC;QACN,oCAAoC;QACpC,MAAM,SAAS,GAAG,aAAa,CAAC;QAChC,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,WAAW,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;QAE/D,UAAU,CAAC,IAAI,CAAC;YACd,KAAK,EAAE,UAAU;YACjB,GAAG,EAAE,SAAS;YACd,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,CAAC;SAC9B,CAAC,CAAC;IACL,CAAC;IAED,oDAAoD;IACpD,2DAA2D;IAC3D,IAAI,OAAO,IAAI,eAAe,KAAK,SAAS,EAAE,CAAC;QAC7C,yDAAyD;QACzD,+DAA+D;IACjE,CAAC;IAED,yCAAyC;IACzC,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,QAAQ,CAAC;SACjC,SAAS,CAAC,UAAU,CAAC;SACrB,GAAG,EAAE,CAAE,8CAA8C;SACrD,QAAQ,EAAE,CAAC;IAEd,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,SAAS,iBAAiB,CACxB,IAAY,EACZ,MAAqB,EACrB,KAAa,EACb,MAAc;IAEd,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,OAAO,CAAC;IAE5C,0BAA0B;IAC1B,IAAI,KAAa,CAAC;IAClB,IAAI,KAAa,CAAC;IAClB,IAAI,UAAkB,CAAC;IAEvB,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QACzB,+BAA+B;QAC/B,KAAK,GAAG,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC,QAAQ,CAAC;IAC9C,CAAC;SAAM,CAAC;QACN,4BAA4B;QAC5B,KAAK,GAAG,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC,QAAQ,CAAC;IAC9C,CAAC;IAED,wBAAwB;IACxB,QAAQ,MAAM,CAAC,KAAK,EAAE,CAAC;QACvB,KAAK,MAAM;YACT,KAAK,GAAG,MAAM,CAAC,WAAW,IAAI,EAAE,CAAC;YACjC,UAAU,GAAG,OAAO,CAAC;YACrB,MAAM;QACR,KAAK,OAAO;YACV,KAAK,GAAG,KAAK,GAAG,CAAC,MAAM,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC;YAC5C,UAAU,GAAG,KAAK,CAAC;YACnB,MAAM;QACR,KAAK,QAAQ,CAAC;QACd;YACE,KAAK,GAAG,KAAK,GAAG,CAAC,CAAC;YAClB,UAAU,GAAG,QAAQ,CAAC;YACtB,MAAM;IACR,CAAC;IAED,uDAAuD;IACvD,8EAA8E;IAC9E,MAAM,UAAU,GAAG,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAE7C,0DAA0D;IAC1D,MAAM,eAAe,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,MAAM,CAAC,QAAQ,GAAG,GAAG,CAAC,CAAC,CAAC;IACpE,MAAM,aAAa,GAAG,IAAI,CAAC,MAAM,GAAG,eAAe,CAAC;IAEpD,IAAI,aAAa,EAAE,CAAC;QAClB,iCAAiC;QACjC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC9B,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,IAAI,WAAW,GAAG,EAAE,CAAC;QAErB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,QAAQ,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,WAAW,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;YAC/D,IAAI,QAAQ,CAAC,MAAM,IAAI,eAAe,EAAE,CAAC;gBACvC,WAAW,GAAG,QAAQ,CAAC;YACzB,CAAC;iBAAM,CAAC;gBACN,IAAI,WAAW;oBAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;gBACzC,WAAW,GAAG,IAAI,CAAC;YACrB,CAAC;QACH,CAAC;QACD,IAAI,WAAW;YAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAEzC,8CAA8C;QAC9C,MAAM,UAAU,GAAG,MAAM,CAAC,QAAQ,GAAG,GAAG,CAAC;QACzC,MAAM,YAAY,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAC7C;aACO,KAAK;aACL,KAAK,GAAG,CAAC,KAAK,GAAG,UAAU,CAAC;uBAClB,UAAU;qBACZ,MAAM,CAAC,QAAQ;gBACpB,MAAM,CAAC,KAAK;uBACL,UAAU;;SAExB,SAAS,CAAC,IAAI,CAAC,SAAS,CAC5B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEb,OAAO,eAAe,KAAK,aAAa,MAAM;QAC1C,YAAY;WACT,CAAC;IACV,CAAC;IAED,mBAAmB;IACnB,OAAO,eAAe,KAAK,aAAa,MAAM;;WAErC,KAAK;WACL,KAAK;qBACK,UAAU;mBACZ,MAAM,CAAC,QAAQ;cACpB,MAAM,CAAC,KAAK;qBACL,UAAU;;OAExB,SAAS,CAAC,IAAI,CAAC;SACb,CAAC;AACV,CAAC;AAED,SAAS,SAAS,CAAC,IAAY;IAC7B,OAAO,IAAI;SACR,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC7B,CAAC;AAED;;GAEG;AACH,SAAS,YAAY,CAAC,aAAqB;IACzC,kDAAkD;IAClD,0EAA0E;IAC1E,MAAM,OAAO,GAA2B;QACtC,QAAQ,EAAE,0EAA0E;QACpF,gBAAgB,EAAE,0EAA0E;QAC5F,aAAa,EAAE,0EAA0E;QACzF,eAAe,EAAE,0EAA0E;QAC3F,UAAU,EAAE,0CAA0C;KACvD,CAAC;IAEF,wEAAwE;IACxE,OAAO,OAAO,CAAC,aAAa,CAAC,IAAI,IAAI,aAAa,6EAA6E,CAAC;AAClI,CAAC"}
@@ -0,0 +1,50 @@
1
+ export type Orientation = 'portrait' | 'landscape';
2
+ export interface DeviceFrame {
3
+ name: string;
4
+ displayName: string;
5
+ orientation: Orientation;
6
+ frameWidth: number;
7
+ frameHeight: number;
8
+ screenRect: {
9
+ x: number;
10
+ y: number;
11
+ width: number;
12
+ height: number;
13
+ };
14
+ deviceType: 'iphone' | 'ipad' | 'mac' | 'watch';
15
+ originalName?: string;
16
+ maskPath?: string;
17
+ }
18
+ export declare let frameRegistry: DeviceFrame[];
19
+ /**
20
+ * Detect orientation from image dimensions
21
+ */
22
+ export declare function detectOrientation(width: number, height: number): Orientation;
23
+ /**
24
+ * Get image dimensions from file
25
+ */
26
+ export declare function getImageDimensions(imagePath: string): Promise<{
27
+ width: number;
28
+ height: number;
29
+ orientation: Orientation;
30
+ }>;
31
+ /**
32
+ * Find best matching frame for a screenshot
33
+ */
34
+ export declare function findBestFrame(screenshotWidth: number, screenshotHeight: number, deviceType: 'iphone' | 'ipad' | 'mac' | 'watch', preferredFrame?: string): DeviceFrame | null;
35
+ /**
36
+ * Initialize frame registry from Frames.json if available
37
+ */
38
+ export declare function initializeFrameRegistry(framesDir: string): Promise<void>;
39
+ /**
40
+ * Load frame image from disk
41
+ */
42
+ export declare function loadFrame(framePath: string, frameName: string): Promise<Buffer | null>;
43
+ /**
44
+ * Auto-detect and load appropriate frame for a screenshot
45
+ */
46
+ export declare function autoSelectFrame(screenshotPath: string, framesDir: string, deviceType: 'iphone' | 'ipad' | 'mac' | 'watch', preferredFrame?: string): Promise<{
47
+ frame: Buffer | null;
48
+ metadata: DeviceFrame | null;
49
+ }>;
50
+ //# sourceMappingURL=devices.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devices.d.ts","sourceRoot":"","sources":["../../src/core/devices.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,WAAW,CAAC;AAEnD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,WAAW,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE;QACV,CAAC,EAAE,MAAM,CAAC;QACV,CAAC,EAAE,MAAM,CAAC;QACV,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,UAAU,EAAE,QAAQ,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,CAAC;IAChD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAGD,eAAO,IAAI,aAAa,EAAE,WAAW,EA6HpC,CAAC;AAEF;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,WAAW,CAE5E;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,WAAW,CAAA;CAAE,CAAC,CAOhI;AAkED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,eAAe,EAAE,MAAM,EACvB,gBAAgB,EAAE,MAAM,EACxB,UAAU,EAAE,QAAQ,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,EAC/C,cAAc,CAAC,EAAE,MAAM,GACtB,WAAW,GAAG,IAAI,CA+GpB;AAaD;;GAEG;AACH,wBAAsB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAkC9E;AAED;;GAEG;AACH,wBAAsB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAuC5F;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,cAAc,EAAE,MAAM,EACtB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,QAAQ,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,EAC/C,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAA;CAAE,CAAC,CAoBjE"}