ugcinc 3.84.6 → 3.85.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/index.d.ts +2 -1
- package/dist/index.js +6 -1
- package/dist/render/Root.d.ts +12 -0
- package/dist/render/Root.js +508 -0
- package/dist/render/components/CaptionOverlay.d.ts +21 -0
- package/dist/render/components/CaptionOverlay.js +210 -0
- package/dist/render/components/ImageElement.d.ts +26 -0
- package/dist/render/components/ImageElement.js +88 -0
- package/dist/render/components/TextElement.d.ts +30 -0
- package/dist/render/components/TextElement.js +390 -0
- package/dist/render/components/VideoElement.d.ts +30 -0
- package/dist/render/components/VideoElement.js +108 -0
- package/dist/render/components/index.d.ts +7 -0
- package/dist/render/components/index.js +14 -0
- package/dist/render/compositions/AutoCaptionComposition.d.ts +42 -0
- package/dist/render/compositions/AutoCaptionComposition.js +29 -0
- package/dist/render/compositions/DmComposition/BaseDmComposition.d.ts +112 -0
- package/dist/render/compositions/DmComposition/BaseDmComposition.js +212 -0
- package/dist/render/compositions/DmComposition/DebugOverlay.d.ts +20 -0
- package/dist/render/compositions/DmComposition/DebugOverlay.js +258 -0
- package/dist/render/compositions/DmComposition/index.d.ts +6 -0
- package/dist/render/compositions/DmComposition/index.js +10 -0
- package/dist/render/compositions/IMessageDmComposition/convertPropsToElements.d.ts +27 -0
- package/dist/render/compositions/IMessageDmComposition/convertPropsToElements.js +629 -0
- package/dist/render/compositions/IMessageDmComposition/index.d.ts +20 -0
- package/dist/render/compositions/IMessageDmComposition/index.js +485 -0
- package/dist/render/compositions/IMessageDmComposition/types.d.ts +756 -0
- package/dist/render/compositions/IMessageDmComposition/types.js +225 -0
- package/dist/render/compositions/ImageEditorComposition.d.ts +74 -0
- package/dist/render/compositions/ImageEditorComposition.js +351 -0
- package/dist/render/compositions/InstagramDmComposition/convertPropsToElements.d.ts +60 -0
- package/dist/render/compositions/InstagramDmComposition/convertPropsToElements.js +1318 -0
- package/dist/render/compositions/InstagramDmComposition/convertPublicToProps.d.ts +30 -0
- package/dist/render/compositions/InstagramDmComposition/convertPublicToProps.js +131 -0
- package/dist/render/compositions/InstagramDmComposition/index.d.ts +16 -0
- package/dist/render/compositions/InstagramDmComposition/index.js +374 -0
- package/dist/render/compositions/InstagramDmComposition/theme.d.ts +42 -0
- package/dist/render/compositions/InstagramDmComposition/theme.js +55 -0
- package/dist/render/compositions/InstagramDmComposition/types.d.ts +215 -0
- package/dist/render/compositions/InstagramDmComposition/types.js +7 -0
- package/dist/render/compositions/ScreenshotAnimation.d.ts +14 -0
- package/dist/render/compositions/ScreenshotAnimation.js +268 -0
- package/dist/render/compositions/VideoEditorComposition.d.ts +45 -0
- package/dist/render/compositions/VideoEditorComposition.js +307 -0
- package/dist/render/compositions/index.d.ts +12 -0
- package/dist/render/compositions/index.js +43 -0
- package/dist/render/compositions/messaging/components/MediaBubble.d.ts +22 -0
- package/dist/render/compositions/messaging/components/MediaBubble.js +25 -0
- package/dist/render/compositions/messaging/components/MessageBubble.d.ts +35 -0
- package/dist/render/compositions/messaging/components/MessageBubble.js +34 -0
- package/dist/render/compositions/messaging/components/ProfilePic.d.ts +23 -0
- package/dist/render/compositions/messaging/components/ProfilePic.js +37 -0
- package/dist/render/compositions/messaging/components/Reaction.d.ts +23 -0
- package/dist/render/compositions/messaging/components/Reaction.js +19 -0
- package/dist/render/compositions/messaging/components/TypingIndicator.d.ts +25 -0
- package/dist/render/compositions/messaging/components/TypingIndicator.js +66 -0
- package/dist/render/compositions/messaging/index.d.ts +14 -0
- package/dist/render/compositions/messaging/index.js +43 -0
- package/dist/render/compositions/messaging/types.d.ts +148 -0
- package/dist/render/compositions/messaging/types.js +21 -0
- package/dist/render/compositions/messaging/utils/bubbleRadius.d.ts +45 -0
- package/dist/render/compositions/messaging/utils/bubbleRadius.js +84 -0
- package/dist/render/compositions/messaging/utils/groupMessages.d.ts +41 -0
- package/dist/render/compositions/messaging/utils/groupMessages.js +110 -0
- package/dist/render/data/phone-top-nav.d.ts +1 -0
- package/dist/render/data/phone-top-nav.js +4 -0
- package/dist/render/data/screenshot.d.ts +164 -0
- package/dist/render/data/screenshot.js +63 -0
- package/dist/render/hooks/index.d.ts +54 -0
- package/dist/render/hooks/index.js +132 -0
- package/dist/render/index.d.ts +12 -0
- package/dist/render/index.js +36 -0
- package/dist/render/types/base.d.ts +148 -0
- package/dist/render/types/base.js +5 -0
- package/dist/render/types/caption.d.ts +105 -0
- package/dist/render/types/caption.js +8 -0
- package/dist/render/types/crop.d.ts +60 -0
- package/dist/render/types/crop.js +8 -0
- package/dist/render/types/deduplication.d.ts +284 -0
- package/dist/render/types/deduplication.js +240 -0
- package/dist/render/types/editor.d.ts +97 -0
- package/dist/render/types/editor.js +10 -0
- package/dist/render/types/element.d.ts +139 -0
- package/dist/render/types/element.js +19 -0
- package/dist/render/types/index.d.ts +20 -0
- package/dist/render/types/index.js +24 -0
- package/dist/render/types/instagram-dm-public.d.ts +60 -0
- package/dist/render/types/instagram-dm-public.js +8 -0
- package/dist/render/types/position.d.ts +59 -0
- package/dist/render/types/position.js +5 -0
- package/dist/render/types/screenshot.d.ts +57 -0
- package/dist/render/types/screenshot.js +34 -0
- package/dist/render/types/segment.d.ts +163 -0
- package/dist/render/types/segment.js +8 -0
- package/dist/render/types/video.d.ts +192 -0
- package/dist/render/types/video.js +14 -0
- package/dist/render/utils/captionPresets.d.ts +38 -0
- package/dist/render/utils/captionPresets.js +168 -0
- package/dist/render/utils/cropBounds.d.ts +20 -0
- package/dist/render/utils/cropBounds.js +166 -0
- package/dist/render/utils/defaults.d.ts +74 -0
- package/dist/render/utils/defaults.js +91 -0
- package/dist/render/utils/emoji.d.ts +40 -0
- package/dist/render/utils/emoji.js +105 -0
- package/dist/render/utils/fit.d.ts +35 -0
- package/dist/render/utils/fit.js +63 -0
- package/dist/render/utils/fonts.d.ts +55 -0
- package/dist/render/utils/fonts.js +191 -0
- package/dist/render/utils/index.d.ts +14 -0
- package/dist/render/utils/index.js +73 -0
- package/dist/render/utils/positionResolver.d.ts +64 -0
- package/dist/render/utils/positionResolver.js +508 -0
- package/dist/render/utils/text.d.ts +50 -0
- package/dist/render/utils/text.js +177 -0
- package/dist/render/utils/timeline.d.ts +62 -0
- package/dist/render/utils/timeline.js +172 -0
- package/dist/render.d.ts +1 -1
- package/dist/types.d.ts +136 -17
- package/dist/types.js +58 -0
- package/package.json +20 -6
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* iMessage DM Composition Types
|
|
4
|
+
*
|
|
5
|
+
* Types for the iMessage DM conversation composition.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.iMessageDmPropsSchema = void 0;
|
|
9
|
+
const zod_1 = require("zod");
|
|
10
|
+
/**
|
|
11
|
+
* Zod schema for iMessage DM composition props (for Remotion)
|
|
12
|
+
*/
|
|
13
|
+
exports.iMessageDmPropsSchema = zod_1.z.object({
|
|
14
|
+
// Canvas
|
|
15
|
+
width: zod_1.z.number().optional(),
|
|
16
|
+
height: zod_1.z.number().optional(),
|
|
17
|
+
durationInFrames: zod_1.z.number().optional(),
|
|
18
|
+
fps: zod_1.z.number().optional(),
|
|
19
|
+
// Debug
|
|
20
|
+
showDebugOverlay: zod_1.z.boolean().optional(),
|
|
21
|
+
referenceImageUrl: zod_1.z.string().optional(),
|
|
22
|
+
showReferenceImage: zod_1.z.boolean().optional(),
|
|
23
|
+
referenceOpacity: zod_1.z.number().optional(),
|
|
24
|
+
// Light mode
|
|
25
|
+
lightMode: zod_1.z.boolean().optional(),
|
|
26
|
+
// iMessage header
|
|
27
|
+
headerTop: zod_1.z.number().optional(),
|
|
28
|
+
headerBottom: zod_1.z.number().optional(),
|
|
29
|
+
headerBackgroundColor: zod_1.z.string().optional(),
|
|
30
|
+
// Header profile section
|
|
31
|
+
profilePicUrl: zod_1.z.string().optional(),
|
|
32
|
+
profilePicTop: zod_1.z.number().optional(),
|
|
33
|
+
profilePicBottom: zod_1.z.number().optional(),
|
|
34
|
+
profilePicLeft: zod_1.z.number().optional(),
|
|
35
|
+
profilePicRight: zod_1.z.number().optional(),
|
|
36
|
+
username: zod_1.z.string().optional(),
|
|
37
|
+
usernameTop: zod_1.z.number().optional(),
|
|
38
|
+
usernameBottom: zod_1.z.number().optional(),
|
|
39
|
+
usernameLeft: zod_1.z.number().optional(),
|
|
40
|
+
usernameRight: zod_1.z.number().optional(),
|
|
41
|
+
usernameFontSize: zod_1.z.number().optional(),
|
|
42
|
+
usernameLetterSpacing: zod_1.z.number().optional(),
|
|
43
|
+
usernameColor: zod_1.z.string().optional(),
|
|
44
|
+
usernameArrowOffsetX: zod_1.z.number().optional(),
|
|
45
|
+
usernameArrowOffsetY: zod_1.z.number().optional(),
|
|
46
|
+
usernameArrowWidth: zod_1.z.number().optional(),
|
|
47
|
+
usernameArrowHeight: zod_1.z.number().optional(),
|
|
48
|
+
// Back arrow
|
|
49
|
+
backArrowTop: zod_1.z.number().optional(),
|
|
50
|
+
backArrowBottom: zod_1.z.number().optional(),
|
|
51
|
+
backArrowLeft: zod_1.z.number().optional(),
|
|
52
|
+
backArrowRight: zod_1.z.number().optional(),
|
|
53
|
+
// Unread badge
|
|
54
|
+
unreadBadgeText: zod_1.z.string().optional(),
|
|
55
|
+
unreadBadgeTop: zod_1.z.number().optional(),
|
|
56
|
+
unreadBadgeBottom: zod_1.z.number().optional(),
|
|
57
|
+
unreadBadgeLeft: zod_1.z.number().optional(),
|
|
58
|
+
unreadBadgeFontSize: zod_1.z.number().optional(),
|
|
59
|
+
unreadBadgeLetterSpacing: zod_1.z.number().optional(),
|
|
60
|
+
unreadBadgeTextColor: zod_1.z.string().optional(),
|
|
61
|
+
unreadBadgeBorderRadius: zod_1.z.number().optional(),
|
|
62
|
+
unreadBadgePaddingLeft: zod_1.z.number().optional(),
|
|
63
|
+
unreadBadgePaddingRight: zod_1.z.number().optional(),
|
|
64
|
+
// Camera icon
|
|
65
|
+
cameraIconTop: zod_1.z.number().optional(),
|
|
66
|
+
cameraIconBottom: zod_1.z.number().optional(),
|
|
67
|
+
cameraIconLeft: zod_1.z.number().optional(),
|
|
68
|
+
cameraIconRight: zod_1.z.number().optional(),
|
|
69
|
+
dividerLineY: zod_1.z.number().optional(),
|
|
70
|
+
dividerLineColor: zod_1.z.string().optional(),
|
|
71
|
+
backgroundColor: zod_1.z.string().optional(),
|
|
72
|
+
// Status bar - Time
|
|
73
|
+
time: zod_1.z.string().optional(),
|
|
74
|
+
timeRight: zod_1.z.number().optional(),
|
|
75
|
+
timeBottom: zod_1.z.number().optional(),
|
|
76
|
+
timeFontSize: zod_1.z.number().optional(),
|
|
77
|
+
timeLetterSpacing: zod_1.z.number().optional(),
|
|
78
|
+
timeColor: zod_1.z.string().optional(),
|
|
79
|
+
// Status bar - No notifications
|
|
80
|
+
noNotisTop: zod_1.z.number().optional(),
|
|
81
|
+
noNotisBottom: zod_1.z.number().optional(),
|
|
82
|
+
noNotisLeft: zod_1.z.number().optional(),
|
|
83
|
+
noNotisRight: zod_1.z.number().optional(),
|
|
84
|
+
// Status bar - Cell signal
|
|
85
|
+
cellLevel: zod_1.z.number().min(0).max(4).optional(),
|
|
86
|
+
cell1Top: zod_1.z.number().optional(),
|
|
87
|
+
cell1Bottom: zod_1.z.number().optional(),
|
|
88
|
+
cell1Left: zod_1.z.number().optional(),
|
|
89
|
+
cell1Right: zod_1.z.number().optional(),
|
|
90
|
+
cell2Top: zod_1.z.number().optional(),
|
|
91
|
+
cell2Bottom: zod_1.z.number().optional(),
|
|
92
|
+
cell2Left: zod_1.z.number().optional(),
|
|
93
|
+
cell2Right: zod_1.z.number().optional(),
|
|
94
|
+
cell3Top: zod_1.z.number().optional(),
|
|
95
|
+
cell3Bottom: zod_1.z.number().optional(),
|
|
96
|
+
cell3Left: zod_1.z.number().optional(),
|
|
97
|
+
cell3Right: zod_1.z.number().optional(),
|
|
98
|
+
cell4Top: zod_1.z.number().optional(),
|
|
99
|
+
cell4Bottom: zod_1.z.number().optional(),
|
|
100
|
+
cell4Left: zod_1.z.number().optional(),
|
|
101
|
+
cell4Right: zod_1.z.number().optional(),
|
|
102
|
+
// Status bar - WiFi
|
|
103
|
+
wifiTop: zod_1.z.number().optional(),
|
|
104
|
+
wifiBottom: zod_1.z.number().optional(),
|
|
105
|
+
wifiLeft: zod_1.z.number().optional(),
|
|
106
|
+
wifiRight: zod_1.z.number().optional(),
|
|
107
|
+
// Status bar - Battery
|
|
108
|
+
batteryTop: zod_1.z.number().optional(),
|
|
109
|
+
batteryBottom: zod_1.z.number().optional(),
|
|
110
|
+
batteryLeft: zod_1.z.number().optional(),
|
|
111
|
+
batteryRight: zod_1.z.number().optional(),
|
|
112
|
+
// Home indicator
|
|
113
|
+
homeIndicatorTop: zod_1.z.number().optional(),
|
|
114
|
+
homeIndicatorBottom: zod_1.z.number().optional(),
|
|
115
|
+
homeIndicatorLeft: zod_1.z.number().optional(),
|
|
116
|
+
homeIndicatorRight: zod_1.z.number().optional(),
|
|
117
|
+
homeIndicatorColor: zod_1.z.string().optional(),
|
|
118
|
+
// iMessage footer
|
|
119
|
+
plusButtonTop: zod_1.z.number().optional(),
|
|
120
|
+
plusButtonBottom: zod_1.z.number().optional(),
|
|
121
|
+
plusButtonLeft: zod_1.z.number().optional(),
|
|
122
|
+
plusButtonRight: zod_1.z.number().optional(),
|
|
123
|
+
footerTop: zod_1.z.number().optional(),
|
|
124
|
+
footerBottom: zod_1.z.number().optional(),
|
|
125
|
+
footerLeft: zod_1.z.number().optional(),
|
|
126
|
+
footerRight: zod_1.z.number().optional(),
|
|
127
|
+
footerBgColor: zod_1.z.string().optional(),
|
|
128
|
+
footerBorderRadius: zod_1.z.number().optional(),
|
|
129
|
+
footerBorderColor: zod_1.z.string().optional(),
|
|
130
|
+
footerBorderWidth: zod_1.z.number().optional(),
|
|
131
|
+
messageTextTop: zod_1.z.number().optional(),
|
|
132
|
+
messageTextBottom: zod_1.z.number().optional(),
|
|
133
|
+
messageTextLeft: zod_1.z.number().optional(),
|
|
134
|
+
messageTextRight: zod_1.z.number().optional(),
|
|
135
|
+
messageTextFontSize: zod_1.z.number().optional(),
|
|
136
|
+
messageTextLetterSpacing: zod_1.z.number().optional(),
|
|
137
|
+
messageTextColor: zod_1.z.string().optional(),
|
|
138
|
+
messageText: zod_1.z.string().optional(),
|
|
139
|
+
// Message area bounds
|
|
140
|
+
messageAreaTop: zod_1.z.number().optional(),
|
|
141
|
+
messageAreaBottom: zod_1.z.number().optional(),
|
|
142
|
+
// Message colors
|
|
143
|
+
senderBubbleColor: zod_1.z.string().optional(),
|
|
144
|
+
recipientBubbleColor: zod_1.z.string().optional(),
|
|
145
|
+
bubbleTextColor: zod_1.z.string().optional(),
|
|
146
|
+
recipientTextColor: zod_1.z.string().optional(),
|
|
147
|
+
// Message typography
|
|
148
|
+
messageFontSize: zod_1.z.number().optional(),
|
|
149
|
+
messageLineHeight: zod_1.z.number().optional(),
|
|
150
|
+
messageLetterSpacing: zod_1.z.number().optional(),
|
|
151
|
+
// Message padding (inside bubble)
|
|
152
|
+
messagePaddingTop: zod_1.z.number().optional(),
|
|
153
|
+
messagePaddingBottom: zod_1.z.number().optional(),
|
|
154
|
+
senderPaddingLeft: zod_1.z.number().optional(),
|
|
155
|
+
senderPaddingRight: zod_1.z.number().optional(),
|
|
156
|
+
multiLineSenderPaddingLeft: zod_1.z.number().optional(),
|
|
157
|
+
recipientPaddingLeft: zod_1.z.number().optional(),
|
|
158
|
+
recipientPaddingRight: zod_1.z.number().optional(),
|
|
159
|
+
multiLineRecipientPaddingRight: zod_1.z.number().optional(),
|
|
160
|
+
// Bubble corner radii
|
|
161
|
+
bubbleRadiusNormal: zod_1.z.number().optional(),
|
|
162
|
+
bubbleRadiusGrouped: zod_1.z.number().optional(),
|
|
163
|
+
// Message spacing
|
|
164
|
+
messageGapInGroup: zod_1.z.number().optional(),
|
|
165
|
+
messageGapSameUser: zod_1.z.number().optional(),
|
|
166
|
+
messageGapDifferentUser: zod_1.z.number().optional(),
|
|
167
|
+
// Bubble positioning
|
|
168
|
+
recipientBubbleLeft: zod_1.z.number().optional(),
|
|
169
|
+
senderBubbleRight: zod_1.z.number().optional(),
|
|
170
|
+
bubbleMaxWidth: zod_1.z.number().optional(),
|
|
171
|
+
// Sender image attachment
|
|
172
|
+
senderImageLeft: zod_1.z.number().optional(),
|
|
173
|
+
senderImageHeight: zod_1.z.number().optional(),
|
|
174
|
+
senderImageGapToText: zod_1.z.number().optional(),
|
|
175
|
+
senderImageBorderRadius: zod_1.z.number().optional(),
|
|
176
|
+
// Message area header text
|
|
177
|
+
messageHeaderImessageText: zod_1.z.string().optional(),
|
|
178
|
+
messageHeaderTimestampText: zod_1.z.string().optional(),
|
|
179
|
+
messageHeaderTimestampGap: zod_1.z.number().optional(),
|
|
180
|
+
messageHeaderImessageGap: zod_1.z.number().optional(),
|
|
181
|
+
messageHeaderTextColor: zod_1.z.string().optional(),
|
|
182
|
+
messageHeaderFontSize: zod_1.z.number().optional(),
|
|
183
|
+
messageHeaderLetterSpacing: zod_1.z.number().optional(),
|
|
184
|
+
messageHeaderLeft: zod_1.z.number().optional(),
|
|
185
|
+
messageHeaderRight: zod_1.z.number().optional(),
|
|
186
|
+
// Message tails (SVG-based)
|
|
187
|
+
showMessageTails: zod_1.z.boolean().optional(),
|
|
188
|
+
// Recipient tail anchor points
|
|
189
|
+
recipientTailSideY: zod_1.z.number().optional(),
|
|
190
|
+
recipientTailCurveStartY: zod_1.z.number().optional(),
|
|
191
|
+
recipientTailTipX: zod_1.z.number().optional(),
|
|
192
|
+
recipientTailTipY: zod_1.z.number().optional(),
|
|
193
|
+
recipientTailBottomX: zod_1.z.number().optional(),
|
|
194
|
+
recipientTailBottomY: zod_1.z.number().optional(),
|
|
195
|
+
// Recipient tail curve bend
|
|
196
|
+
recipientTailTopBend: zod_1.z.number().optional(),
|
|
197
|
+
recipientTailBottomBend: zod_1.z.number().optional(),
|
|
198
|
+
// Sender tail anchor points
|
|
199
|
+
senderTailSideY: zod_1.z.number().optional(),
|
|
200
|
+
senderTailCurveStartY: zod_1.z.number().optional(),
|
|
201
|
+
senderTailTipX: zod_1.z.number().optional(),
|
|
202
|
+
senderTailTipY: zod_1.z.number().optional(),
|
|
203
|
+
senderTailBottomX: zod_1.z.number().optional(),
|
|
204
|
+
senderTailBottomY: zod_1.z.number().optional(),
|
|
205
|
+
// Sender tail curve bend
|
|
206
|
+
senderTailTopBend: zod_1.z.number().optional(),
|
|
207
|
+
senderTailBottomBend: zod_1.z.number().optional(),
|
|
208
|
+
// Read receipt
|
|
209
|
+
showReadReceipt: zod_1.z.boolean().optional(),
|
|
210
|
+
readReceiptText: zod_1.z.string().optional(),
|
|
211
|
+
readReceiptGap: zod_1.z.number().optional(),
|
|
212
|
+
readReceiptFontSize: zod_1.z.number().optional(),
|
|
213
|
+
readReceiptLetterSpacing: zod_1.z.number().optional(),
|
|
214
|
+
readReceiptColor: zod_1.z.string().optional(),
|
|
215
|
+
readReceiptLeft: zod_1.z.number().optional(),
|
|
216
|
+
readReceiptRight: zod_1.z.number().optional(),
|
|
217
|
+
// Messages
|
|
218
|
+
messages: zod_1.z.array(zod_1.z.object({
|
|
219
|
+
id: zod_1.z.string(),
|
|
220
|
+
sender: zod_1.z.enum(['user', 'recipient']),
|
|
221
|
+
text: zod_1.z.string(),
|
|
222
|
+
imageUrl: zod_1.z.string().optional(),
|
|
223
|
+
groupWithPrevious: zod_1.z.boolean().optional(),
|
|
224
|
+
})).optional(),
|
|
225
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ImageEditorComposition
|
|
3
|
+
*
|
|
4
|
+
* A composition that renders a static image from an ImageEditorConfig.
|
|
5
|
+
* This composition can be used with both client preview and server rendering
|
|
6
|
+
* for pixel-perfect consistency.
|
|
7
|
+
*
|
|
8
|
+
* NEW: Can also accept raw ImageEditorElement[] with relative positioning,
|
|
9
|
+
* which will be resolved internally for true single-source rendering.
|
|
10
|
+
*/
|
|
11
|
+
import type { ImageEditorConfig, ImageEditorElement, FitMode, DynamicCropConfig } from '../types';
|
|
12
|
+
export interface ImageEditorCompositionProps {
|
|
13
|
+
/** The editor configuration to render (legacy format with pre-resolved segments) */
|
|
14
|
+
config?: ImageEditorConfig;
|
|
15
|
+
/** Map of source URLs keyed by segment ID or input reference */
|
|
16
|
+
sources?: Record<string, string>;
|
|
17
|
+
/** Optional scale multiplier for high-DPI rendering */
|
|
18
|
+
scale?: number;
|
|
19
|
+
/** Raw elements with relative positioning (will be resolved internally) */
|
|
20
|
+
elements?: ImageEditorElement[];
|
|
21
|
+
/** Canvas width (required when using elements) */
|
|
22
|
+
width?: number;
|
|
23
|
+
/** Canvas height (required when using elements) */
|
|
24
|
+
height?: number;
|
|
25
|
+
/** Background type: 'image' for image input, 'color' for solid color */
|
|
26
|
+
backgroundType?: 'image' | 'color';
|
|
27
|
+
/** Background color (hex) when backgroundType is 'color' */
|
|
28
|
+
backgroundColor?: string;
|
|
29
|
+
/** Background fit mode (when using elements) */
|
|
30
|
+
backgroundFit?: FitMode;
|
|
31
|
+
/** Background image URL (when using elements) */
|
|
32
|
+
backgroundUrl?: string;
|
|
33
|
+
/** Image URLs keyed by inputId (when using elements) */
|
|
34
|
+
imageUrls?: Record<string, string | null>;
|
|
35
|
+
/** Text values keyed by textInputId (when using elements, for autoWidth calculation) */
|
|
36
|
+
textValues?: Record<string, string>;
|
|
37
|
+
/** Dynamic crop configuration */
|
|
38
|
+
dynamicCrop?: DynamicCropConfig;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* ImageEditorComposition renders a complete image editor configuration.
|
|
42
|
+
*
|
|
43
|
+
* Features:
|
|
44
|
+
* - Background image with fit mode
|
|
45
|
+
* - Multiple text and image elements
|
|
46
|
+
* - Z-index ordering
|
|
47
|
+
* - All text styling (fonts, colors, stroke, background, etc.)
|
|
48
|
+
* - All image styling (fit, opacity, rotation, border radius)
|
|
49
|
+
* - Automatic position resolution for relative positioning
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```tsx
|
|
53
|
+
* import { ImageEditorComposition } from 'ugcinc-render/compositions';
|
|
54
|
+
*
|
|
55
|
+
* // NEW: Pass raw elements directly (preferred)
|
|
56
|
+
* <Player
|
|
57
|
+
* component={ImageEditorComposition}
|
|
58
|
+
* inputProps={{
|
|
59
|
+
* elements,
|
|
60
|
+
* width: 1080,
|
|
61
|
+
* height: 1920,
|
|
62
|
+
* backgroundUrl,
|
|
63
|
+
* imageUrls,
|
|
64
|
+
* textValues
|
|
65
|
+
* }}
|
|
66
|
+
* durationInFrames={1}
|
|
67
|
+
* fps={1}
|
|
68
|
+
* compositionWidth={1080}
|
|
69
|
+
* compositionHeight={1920}
|
|
70
|
+
* />
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export declare function ImageEditorComposition({ config, sources, scale, elements, width, height, backgroundType, backgroundColor, backgroundFit, backgroundUrl, imageUrls, textValues, dynamicCrop, }: ImageEditorCompositionProps): import("react/jsx-runtime").JSX.Element;
|
|
74
|
+
export default ImageEditorComposition;
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ImageEditorComposition = ImageEditorComposition;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
/**
|
|
6
|
+
* ImageEditorComposition
|
|
7
|
+
*
|
|
8
|
+
* A composition that renders a static image from an ImageEditorConfig.
|
|
9
|
+
* This composition can be used with both client preview and server rendering
|
|
10
|
+
* for pixel-perfect consistency.
|
|
11
|
+
*
|
|
12
|
+
* NEW: Can also accept raw ImageEditorElement[] with relative positioning,
|
|
13
|
+
* which will be resolved internally for true single-source rendering.
|
|
14
|
+
*/
|
|
15
|
+
const react_1 = require("react");
|
|
16
|
+
const remotion_1 = require("remotion");
|
|
17
|
+
const TextElement_1 = require("../components/TextElement");
|
|
18
|
+
const ImageElement_1 = require("../components/ImageElement");
|
|
19
|
+
const positionResolver_1 = require("../utils/positionResolver");
|
|
20
|
+
const cropBounds_1 = require("../utils/cropBounds");
|
|
21
|
+
/**
|
|
22
|
+
* Get all segments from all channels, sorted by zIndex
|
|
23
|
+
*/
|
|
24
|
+
function getSortedSegments(config) {
|
|
25
|
+
const allSegments = [];
|
|
26
|
+
for (const channel of config.channels) {
|
|
27
|
+
allSegments.push(...channel.segments);
|
|
28
|
+
}
|
|
29
|
+
// Sort by zIndex (lower first, so higher zIndex renders on top)
|
|
30
|
+
return allSegments.sort((a, b) => {
|
|
31
|
+
const aZ = a.zIndex ?? 0;
|
|
32
|
+
const bZ = b.zIndex ?? 0;
|
|
33
|
+
return aZ - bZ;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Convert a resolved ImageEditorElement to a TextSegment
|
|
38
|
+
*/
|
|
39
|
+
function elementToTextSegment(elem) {
|
|
40
|
+
const segment = {
|
|
41
|
+
id: elem.id,
|
|
42
|
+
type: 'text',
|
|
43
|
+
source: '',
|
|
44
|
+
order: elem.zIndex,
|
|
45
|
+
offset: { type: 'absolute', value: 0 },
|
|
46
|
+
xOffset: elem.x,
|
|
47
|
+
yOffset: elem.y,
|
|
48
|
+
width: elem.width,
|
|
49
|
+
height: elem.height,
|
|
50
|
+
zIndex: elem.zIndex,
|
|
51
|
+
rotation: elem.rotation,
|
|
52
|
+
opacity: elem.opacity,
|
|
53
|
+
text: elem.text ?? '',
|
|
54
|
+
fontType: elem.font ?? 'tiktok',
|
|
55
|
+
fontSize: elem.fontSize ?? 40,
|
|
56
|
+
fontWeight: elem.fontWeight ?? 'normal',
|
|
57
|
+
color: elem.color ?? '#FFFFFF',
|
|
58
|
+
strokeColor: elem.strokeColor ?? '#000000',
|
|
59
|
+
strokeWidth: elem.outlineWidth ?? 0,
|
|
60
|
+
lineHeight: elem.lineHeight ?? 1.2,
|
|
61
|
+
letterSpacing: elem.letterSpacing ?? 0,
|
|
62
|
+
alignment: elem.textAlign ?? 'center',
|
|
63
|
+
verticalAlign: elem.verticalAlign ?? 'middle',
|
|
64
|
+
backgroundColor: elem.backgroundColor,
|
|
65
|
+
backgroundOpacity: elem.backgroundOpacity,
|
|
66
|
+
backgroundBorderRadius: typeof elem.backgroundBorderRadius === 'object' ? elem.backgroundBorderRadius : undefined,
|
|
67
|
+
paddingTop: elem.paddingTop,
|
|
68
|
+
paddingRight: elem.paddingRight,
|
|
69
|
+
paddingBottom: elem.paddingBottom,
|
|
70
|
+
paddingLeft: elem.paddingLeft,
|
|
71
|
+
multiLineExtraPaddingTop: elem.multiLineExtraPaddingTop,
|
|
72
|
+
multiLineExtraPaddingRight: elem.multiLineExtraPaddingRight,
|
|
73
|
+
multiLineExtraPaddingBottom: elem.multiLineExtraPaddingBottom,
|
|
74
|
+
multiLineExtraPaddingLeft: elem.multiLineExtraPaddingLeft,
|
|
75
|
+
autoWidth: elem.autoWidth,
|
|
76
|
+
boxAlign: elem.boxAlign,
|
|
77
|
+
};
|
|
78
|
+
// CONDENSED DEBUG LOG
|
|
79
|
+
console.log('[Composition] ELEMENT:', JSON.stringify({
|
|
80
|
+
id: elem.id,
|
|
81
|
+
text: (elem.text ?? '').substring(0, 20),
|
|
82
|
+
w: elem.width,
|
|
83
|
+
h: elem.height,
|
|
84
|
+
font: elem.font,
|
|
85
|
+
fontSize: elem.fontSize,
|
|
86
|
+
autoWidth: elem.autoWidth,
|
|
87
|
+
pL: elem.paddingLeft,
|
|
88
|
+
pR: elem.paddingRight,
|
|
89
|
+
}));
|
|
90
|
+
return segment;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Convert a resolved ImageEditorElement to an ImageSegment
|
|
94
|
+
*/
|
|
95
|
+
function elementToImageSegment(elem, source) {
|
|
96
|
+
return {
|
|
97
|
+
id: elem.id,
|
|
98
|
+
type: 'image',
|
|
99
|
+
source,
|
|
100
|
+
order: elem.zIndex,
|
|
101
|
+
offset: { type: 'absolute', value: 0 },
|
|
102
|
+
xOffset: elem.x,
|
|
103
|
+
yOffset: elem.y,
|
|
104
|
+
width: elem.width,
|
|
105
|
+
height: elem.height,
|
|
106
|
+
zIndex: elem.zIndex,
|
|
107
|
+
rotation: elem.rotation,
|
|
108
|
+
opacity: elem.opacity,
|
|
109
|
+
fit: elem.fit ?? 'cover',
|
|
110
|
+
borderRadius: elem.borderRadius,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* ImageEditorComposition renders a complete image editor configuration.
|
|
115
|
+
*
|
|
116
|
+
* Features:
|
|
117
|
+
* - Background image with fit mode
|
|
118
|
+
* - Multiple text and image elements
|
|
119
|
+
* - Z-index ordering
|
|
120
|
+
* - All text styling (fonts, colors, stroke, background, etc.)
|
|
121
|
+
* - All image styling (fit, opacity, rotation, border radius)
|
|
122
|
+
* - Automatic position resolution for relative positioning
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```tsx
|
|
126
|
+
* import { ImageEditorComposition } from 'ugcinc-render/compositions';
|
|
127
|
+
*
|
|
128
|
+
* // NEW: Pass raw elements directly (preferred)
|
|
129
|
+
* <Player
|
|
130
|
+
* component={ImageEditorComposition}
|
|
131
|
+
* inputProps={{
|
|
132
|
+
* elements,
|
|
133
|
+
* width: 1080,
|
|
134
|
+
* height: 1920,
|
|
135
|
+
* backgroundUrl,
|
|
136
|
+
* imageUrls,
|
|
137
|
+
* textValues
|
|
138
|
+
* }}
|
|
139
|
+
* durationInFrames={1}
|
|
140
|
+
* fps={1}
|
|
141
|
+
* compositionWidth={1080}
|
|
142
|
+
* compositionHeight={1920}
|
|
143
|
+
* />
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
function ImageEditorComposition({ config, sources = {}, scale = 1,
|
|
147
|
+
// New direct element props
|
|
148
|
+
elements, width, height, backgroundType = 'image', backgroundColor, backgroundFit = 'cover', backgroundUrl, imageUrls = {}, textValues = {}, dynamicCrop, }) {
|
|
149
|
+
// Wait for fonts to load before rendering
|
|
150
|
+
// This prevents measurement using fallback fonts
|
|
151
|
+
const [fontsLoaded, setFontsLoaded] = (0, react_1.useState)(false);
|
|
152
|
+
(0, react_1.useEffect)(() => {
|
|
153
|
+
// Delay render until fonts are loaded
|
|
154
|
+
const handle = (0, remotion_1.delayRender)('Loading fonts...');
|
|
155
|
+
const loadFonts = async () => {
|
|
156
|
+
try {
|
|
157
|
+
// Check if we're in a browser environment
|
|
158
|
+
if (typeof document !== 'undefined' && document.fonts) {
|
|
159
|
+
console.log('[Composition] Waiting for fonts to load...');
|
|
160
|
+
// Wait for all fonts to be ready (this waits for CSS @font-face fonts)
|
|
161
|
+
await document.fonts.ready;
|
|
162
|
+
// Explicitly trigger loading of all our fonts at various weights
|
|
163
|
+
// This ensures the fonts are actually fetched, not just declared
|
|
164
|
+
const fontsToLoad = [
|
|
165
|
+
// SF Pro
|
|
166
|
+
'normal 48px "SF Pro"',
|
|
167
|
+
'bold 48px "SF Pro"',
|
|
168
|
+
'100 48px "SF Pro"',
|
|
169
|
+
'200 48px "SF Pro"',
|
|
170
|
+
'300 48px "SF Pro"',
|
|
171
|
+
'400 48px "SF Pro"',
|
|
172
|
+
'500 48px "SF Pro"',
|
|
173
|
+
'600 48px "SF Pro"',
|
|
174
|
+
'700 48px "SF Pro"',
|
|
175
|
+
'800 48px "SF Pro"',
|
|
176
|
+
'900 48px "SF Pro"',
|
|
177
|
+
// TikTok Sans
|
|
178
|
+
'normal 48px "TikTok Sans"',
|
|
179
|
+
'bold 48px "TikTok Sans"',
|
|
180
|
+
'400 48px "TikTok Sans"',
|
|
181
|
+
'500 48px "TikTok Sans"',
|
|
182
|
+
'600 48px "TikTok Sans"',
|
|
183
|
+
'700 48px "TikTok Sans"',
|
|
184
|
+
'800 48px "TikTok Sans"',
|
|
185
|
+
'900 48px "TikTok Sans"',
|
|
186
|
+
// Apple Color Emoji
|
|
187
|
+
'normal 48px "Apple Color Emoji"',
|
|
188
|
+
];
|
|
189
|
+
// Load all fonts in parallel, ignore failures (some weights may not exist)
|
|
190
|
+
await Promise.all(fontsToLoad.map(font => document.fonts.load(font).catch(() => [])));
|
|
191
|
+
// Wait again after loading to ensure they're all ready
|
|
192
|
+
await document.fonts.ready;
|
|
193
|
+
// Log final status
|
|
194
|
+
console.log('[Composition] Fonts loaded:', {
|
|
195
|
+
sfPro: document.fonts.check('normal 48px "SF Pro"'),
|
|
196
|
+
tiktok: document.fonts.check('normal 48px "TikTok Sans"'),
|
|
197
|
+
emoji: document.fonts.check('normal 48px "Apple Color Emoji"'),
|
|
198
|
+
totalFonts: document.fonts.size,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
setFontsLoaded(true);
|
|
202
|
+
(0, remotion_1.continueRender)(handle);
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
console.error('[Composition] Font loading error:', err);
|
|
206
|
+
// Continue anyway to avoid hanging
|
|
207
|
+
setFontsLoaded(true);
|
|
208
|
+
(0, remotion_1.continueRender)(handle);
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
loadFonts();
|
|
212
|
+
// Cleanup in case component unmounts
|
|
213
|
+
return () => {
|
|
214
|
+
try {
|
|
215
|
+
(0, remotion_1.continueRender)(handle);
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
// Handle may have already been continued
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
}, []);
|
|
222
|
+
// Get dimensions (from new props or legacy config)
|
|
223
|
+
const canvasWidth = width ?? config?.width ?? 1080;
|
|
224
|
+
const canvasHeight = height ?? config?.height ?? 1920;
|
|
225
|
+
// Resolve positions for raw elements (if provided)
|
|
226
|
+
const resolvedElements = (0, react_1.useMemo)(() => {
|
|
227
|
+
if (!elements)
|
|
228
|
+
return null;
|
|
229
|
+
const result = (0, positionResolver_1.resolveElementPositions)(elements, textValues);
|
|
230
|
+
if (result.errors.length > 0) {
|
|
231
|
+
console.warn('Position resolution errors:', result.errors);
|
|
232
|
+
}
|
|
233
|
+
return result.elements;
|
|
234
|
+
}, [elements, textValues]);
|
|
235
|
+
// Calculate crop bounds when dynamic crop is enabled
|
|
236
|
+
const cropBounds = (0, react_1.useMemo)(() => {
|
|
237
|
+
if (!(0, cropBounds_1.isDynamicCropEnabled)(dynamicCrop) || !resolvedElements)
|
|
238
|
+
return null;
|
|
239
|
+
return (0, cropBounds_1.calculateCropBounds)(resolvedElements, dynamicCrop, canvasWidth, canvasHeight, textValues);
|
|
240
|
+
}, [resolvedElements, dynamicCrop, canvasWidth, canvasHeight, textValues]);
|
|
241
|
+
// Convert resolved elements to segments (when using new format)
|
|
242
|
+
const segmentsFromElements = (0, react_1.useMemo)(() => {
|
|
243
|
+
if (!resolvedElements)
|
|
244
|
+
return null;
|
|
245
|
+
const segments = [];
|
|
246
|
+
for (const elem of resolvedElements) {
|
|
247
|
+
if (elem.type === 'text') {
|
|
248
|
+
// Get text content (either from textValues or element itself)
|
|
249
|
+
// If no text value and no elem.text, show {{textInputId}} as placeholder
|
|
250
|
+
const text = elem.textInputId && textValues[elem.textInputId]
|
|
251
|
+
? textValues[elem.textInputId]
|
|
252
|
+
: elem.text ?? `{{${elem.textInputId ?? 'text'}}}`;
|
|
253
|
+
segments.push(elementToTextSegment({ ...elem, text }));
|
|
254
|
+
}
|
|
255
|
+
else if (elem.type === 'image') {
|
|
256
|
+
// Get image URL
|
|
257
|
+
const url = elem.inputId ? imageUrls[elem.inputId] ?? '' : '';
|
|
258
|
+
if (url) {
|
|
259
|
+
segments.push(elementToImageSegment(elem, url));
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return segments.sort((a, b) => (a.zIndex ?? 0) - (b.zIndex ?? 0));
|
|
264
|
+
}, [resolvedElements, imageUrls, textValues]);
|
|
265
|
+
const bgFit = backgroundFit ?? 'cover';
|
|
266
|
+
const bgUrl = backgroundUrl ?? sources.background;
|
|
267
|
+
// Calculate crop offset for rendering
|
|
268
|
+
const cropOffsetX = cropBounds?.x ?? 0;
|
|
269
|
+
const cropOffsetY = cropBounds?.y ?? 0;
|
|
270
|
+
// Use new format if elements provided, otherwise use legacy config
|
|
271
|
+
const contentSegments = segmentsFromElements ?? (() => {
|
|
272
|
+
if (!config)
|
|
273
|
+
return [];
|
|
274
|
+
const sorted = getSortedSegments(config);
|
|
275
|
+
return sorted.filter((s) => s.id !== 'background');
|
|
276
|
+
})();
|
|
277
|
+
// Get legacy background segment (if using config)
|
|
278
|
+
const legacyBackgroundSegment = (0, react_1.useMemo)(() => {
|
|
279
|
+
if (!config || segmentsFromElements)
|
|
280
|
+
return null;
|
|
281
|
+
const sorted = getSortedSegments(config);
|
|
282
|
+
return sorted.find((s) => s.id === 'background' && s.type === 'image');
|
|
283
|
+
}, [config, segmentsFromElements]);
|
|
284
|
+
// Get source URL for a segment (legacy format)
|
|
285
|
+
const getSource = (segment) => {
|
|
286
|
+
if (segment.source)
|
|
287
|
+
return segment.source;
|
|
288
|
+
if (sources[segment.id])
|
|
289
|
+
return sources[segment.id];
|
|
290
|
+
const segmentAny = segment;
|
|
291
|
+
if (segmentAny.inputRef && sources[segmentAny.inputRef]) {
|
|
292
|
+
return sources[segmentAny.inputRef];
|
|
293
|
+
}
|
|
294
|
+
return undefined;
|
|
295
|
+
};
|
|
296
|
+
// Determine background color for the container
|
|
297
|
+
const containerBgColor = backgroundType === 'color' && backgroundColor
|
|
298
|
+
? backgroundColor
|
|
299
|
+
: '#000000';
|
|
300
|
+
// Don't render content until fonts are loaded (prevents incorrect measurements)
|
|
301
|
+
if (!fontsLoaded) {
|
|
302
|
+
return ((0, jsx_runtime_1.jsx)(remotion_1.AbsoluteFill, { style: { backgroundColor: containerBgColor } }));
|
|
303
|
+
}
|
|
304
|
+
return ((0, jsx_runtime_1.jsx)(remotion_1.AbsoluteFill, { style: { backgroundColor: containerBgColor }, children: (0, jsx_runtime_1.jsxs)("div", { style: {
|
|
305
|
+
position: 'absolute',
|
|
306
|
+
left: -cropOffsetX * scale,
|
|
307
|
+
top: -cropOffsetY * scale,
|
|
308
|
+
width: canvasWidth * scale,
|
|
309
|
+
height: canvasHeight * scale,
|
|
310
|
+
}, children: [backgroundType === 'image' && bgUrl && segmentsFromElements && ((0, jsx_runtime_1.jsx)(remotion_1.Img, { src: bgUrl, style: {
|
|
311
|
+
position: 'absolute',
|
|
312
|
+
left: 0,
|
|
313
|
+
top: 0,
|
|
314
|
+
width: canvasWidth * scale,
|
|
315
|
+
height: canvasHeight * scale,
|
|
316
|
+
objectFit: bgFit,
|
|
317
|
+
} })), legacyBackgroundSegment && !segmentsFromElements && ((0, jsx_runtime_1.jsx)(BackgroundImage, { segment: legacyBackgroundSegment, src: getSource(legacyBackgroundSegment), width: canvasWidth, height: canvasHeight, scale: scale })), contentSegments.map((segment) => {
|
|
318
|
+
if (segment.type === 'text') {
|
|
319
|
+
return ((0, jsx_runtime_1.jsx)(TextElement_1.TextElement, { segment: segment, scale: scale }, segment.id));
|
|
320
|
+
}
|
|
321
|
+
if (segment.type === 'image') {
|
|
322
|
+
// For new format, source is already in segment
|
|
323
|
+
// For legacy format, look it up
|
|
324
|
+
const src = segment.source || getSource(segment);
|
|
325
|
+
if (!src) {
|
|
326
|
+
console.warn(`No source found for image segment: ${segment.id}`);
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
return ((0, jsx_runtime_1.jsx)(ImageElement_1.ImageElement, { segment: segment, src: src, scale: scale }, segment.id));
|
|
330
|
+
}
|
|
331
|
+
return null;
|
|
332
|
+
})] }) }));
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Background image component
|
|
336
|
+
*/
|
|
337
|
+
function BackgroundImage({ segment, src, width, height, scale, }) {
|
|
338
|
+
if (!src) {
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
const fit = segment.fit ?? 'cover';
|
|
342
|
+
return ((0, jsx_runtime_1.jsx)(remotion_1.Img, { src: src, style: {
|
|
343
|
+
position: 'absolute',
|
|
344
|
+
left: 0,
|
|
345
|
+
top: 0,
|
|
346
|
+
width: width * scale,
|
|
347
|
+
height: height * scale,
|
|
348
|
+
objectFit: fit,
|
|
349
|
+
} }));
|
|
350
|
+
}
|
|
351
|
+
exports.default = ImageEditorComposition;
|