@remotion/studio-server 4.0.494 → 4.0.495
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/codemods/update-keyframes/update-keyframes.js +61 -6
- package/dist/figma/figma-clipboard.d.ts +6 -0
- package/dist/figma/figma-clipboard.js +312 -0
- package/dist/figma/figma-to-svg.d.ts +116 -0
- package/dist/figma/figma-to-svg.js +1300 -0
- package/dist/helpers/resolve-composition-component.js +71 -4
- package/dist/helpers/svg-to-jsx.d.ts +2 -0
- package/dist/helpers/svg-to-jsx.js +70 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.js +1 -0
- package/dist/max-timeline-tracks.d.ts +1 -0
- package/dist/max-timeline-tracks.js +5 -1
- package/dist/preview-server/api-routes.js +2 -0
- package/dist/preview-server/dev-middleware/middleware.js +6 -4
- package/dist/preview-server/routes/convert-figma-clipboard-to-svg.d.ts +3 -0
- package/dist/preview-server/routes/convert-figma-clipboard-to-svg.js +18 -0
- package/dist/preview-server/routes/delete-keyframes.js +2 -0
- package/dist/preview-server/routes/insert-jsx-element.js +10 -1
- package/dist/preview-server/start-server.d.ts +3 -3
- package/dist/preview-server/start-server.js +3 -3
- package/dist/routes.d.ts +4 -4
- package/dist/routes.js +9 -9
- package/dist/start-studio.d.ts +4 -4
- package/dist/start-studio.js +4 -4
- package/package.json +9 -6
|
@@ -910,6 +910,23 @@ const getInitialValueForMissingProp = ({ schema, key, newValue, }) => {
|
|
|
910
910
|
}
|
|
911
911
|
return newValue;
|
|
912
912
|
};
|
|
913
|
+
const shouldRemovePropAfterKeyframeOperation = ({ expression, key, operation, schema, videoConfigValues, }) => {
|
|
914
|
+
if (operation.type !== 'remove' || !schema) {
|
|
915
|
+
return false;
|
|
916
|
+
}
|
|
917
|
+
const existing = getInterpolationExpression(expression, videoConfigValues);
|
|
918
|
+
if (!existing || existing.keyframes.length !== 1) {
|
|
919
|
+
return false;
|
|
920
|
+
}
|
|
921
|
+
const field = findFieldInSchema(schema, key);
|
|
922
|
+
if (!field || field.type === 'hidden' || field.default === undefined) {
|
|
923
|
+
return false;
|
|
924
|
+
}
|
|
925
|
+
const valueAfterRemoval = operation.valueWhenLastKeyframeDeleted === null
|
|
926
|
+
? existing.keyframes[0].value
|
|
927
|
+
: operation.valueWhenLastKeyframeDeleted;
|
|
928
|
+
return JSON.stringify(valueAfterRemoval) === JSON.stringify(field.default);
|
|
929
|
+
};
|
|
913
930
|
const getObjectExpression = (attr) => {
|
|
914
931
|
if (!attr.value || attr.value.type !== 'JSXExpressionContainer') {
|
|
915
932
|
return null;
|
|
@@ -934,7 +951,7 @@ const createMissingPropExpression = (missingPropInitialValue) => {
|
|
|
934
951
|
const getSequenceWritableProp = ({ attributes, key, missingPropInitialValue, }) => {
|
|
935
952
|
const dotIndex = key.indexOf('.');
|
|
936
953
|
if (dotIndex === -1) {
|
|
937
|
-
const { attr: topLevelAttr } = findJsxAttribute(attributes, key);
|
|
954
|
+
const { attrIndex, attr: topLevelAttr } = findJsxAttribute(attributes, key);
|
|
938
955
|
if (!topLevelAttr) {
|
|
939
956
|
if (missingPropInitialValue) {
|
|
940
957
|
return {
|
|
@@ -942,6 +959,7 @@ const getSequenceWritableProp = ({ attributes, key, missingPropInitialValue, })
|
|
|
942
959
|
setExpression: (nextExpression) => {
|
|
943
960
|
attributes.push(createJsxExpressionAttribute(key, nextExpression));
|
|
944
961
|
},
|
|
962
|
+
remove: () => undefined,
|
|
945
963
|
};
|
|
946
964
|
}
|
|
947
965
|
throw new Error(`Cannot update keyframes: "${key}" is not set`);
|
|
@@ -955,11 +973,14 @@ const getSequenceWritableProp = ({ attributes, key, missingPropInitialValue, })
|
|
|
955
973
|
setExpression: (nextExpression) => {
|
|
956
974
|
topLevelAttr.value = b.jsxExpressionContainer(nextExpression);
|
|
957
975
|
},
|
|
976
|
+
remove: () => {
|
|
977
|
+
attributes.splice(attrIndex, 1);
|
|
978
|
+
},
|
|
958
979
|
};
|
|
959
980
|
}
|
|
960
981
|
const parentKey = key.slice(0, dotIndex);
|
|
961
982
|
const childKey = key.slice(dotIndex + 1);
|
|
962
|
-
const { attr: parentAttr } = findJsxAttribute(attributes, parentKey);
|
|
983
|
+
const { attrIndex: parentAttrIndex, attr: parentAttr } = findJsxAttribute(attributes, parentKey);
|
|
963
984
|
if (!parentAttr) {
|
|
964
985
|
if (missingPropInitialValue) {
|
|
965
986
|
return {
|
|
@@ -971,6 +992,7 @@ const getSequenceWritableProp = ({ attributes, key, missingPropInitialValue, })
|
|
|
971
992
|
expression: nextExpression,
|
|
972
993
|
}));
|
|
973
994
|
},
|
|
995
|
+
remove: () => undefined,
|
|
974
996
|
};
|
|
975
997
|
}
|
|
976
998
|
throw new Error(`Cannot update keyframes: "${parentKey}" is not set`);
|
|
@@ -979,7 +1001,7 @@ const getSequenceWritableProp = ({ attributes, key, missingPropInitialValue, })
|
|
|
979
1001
|
if (!objExpr) {
|
|
980
1002
|
throw new Error(`Cannot update keyframes: "${parentKey}" is computed`);
|
|
981
1003
|
}
|
|
982
|
-
const { prop } = findObjectProperty(objExpr, childKey);
|
|
1004
|
+
const { propIndex, prop } = findObjectProperty(objExpr, childKey);
|
|
983
1005
|
if (!prop) {
|
|
984
1006
|
if (missingPropInitialValue) {
|
|
985
1007
|
return {
|
|
@@ -987,6 +1009,7 @@ const getSequenceWritableProp = ({ attributes, key, missingPropInitialValue, })
|
|
|
987
1009
|
setExpression: (nextExpression) => {
|
|
988
1010
|
objExpr.properties.push(createObjectProperty(childKey, nextExpression));
|
|
989
1011
|
},
|
|
1012
|
+
remove: () => undefined,
|
|
990
1013
|
};
|
|
991
1014
|
}
|
|
992
1015
|
throw new Error(`Cannot update keyframes: "${key}" is not set`);
|
|
@@ -996,10 +1019,16 @@ const getSequenceWritableProp = ({ attributes, key, missingPropInitialValue, })
|
|
|
996
1019
|
setExpression: (nextExpression) => {
|
|
997
1020
|
prop.value = nextExpression;
|
|
998
1021
|
},
|
|
1022
|
+
remove: () => {
|
|
1023
|
+
objExpr.properties.splice(propIndex, 1);
|
|
1024
|
+
if (objExpr.properties.length === 0) {
|
|
1025
|
+
attributes.splice(parentAttrIndex, 1);
|
|
1026
|
+
}
|
|
1027
|
+
},
|
|
999
1028
|
};
|
|
1000
1029
|
};
|
|
1001
1030
|
const getEffectWritableProp = ({ objExpr, key, missingPropInitialValue, }) => {
|
|
1002
|
-
const { prop } = findObjectProperty(objExpr, key);
|
|
1031
|
+
const { propIndex, prop } = findObjectProperty(objExpr, key);
|
|
1003
1032
|
if (!prop) {
|
|
1004
1033
|
if (missingPropInitialValue) {
|
|
1005
1034
|
return {
|
|
@@ -1007,6 +1036,7 @@ const getEffectWritableProp = ({ objExpr, key, missingPropInitialValue, }) => {
|
|
|
1007
1036
|
setExpression: (nextExpression) => {
|
|
1008
1037
|
objExpr.properties.push(createObjectProperty(key, nextExpression));
|
|
1009
1038
|
},
|
|
1039
|
+
remove: () => undefined,
|
|
1010
1040
|
};
|
|
1011
1041
|
}
|
|
1012
1042
|
throw new Error(`Cannot update keyframes: "${key}" is not set`);
|
|
@@ -1016,6 +1046,9 @@ const getEffectWritableProp = ({ objExpr, key, missingPropInitialValue, }) => {
|
|
|
1016
1046
|
setExpression: (nextExpression) => {
|
|
1017
1047
|
prop.value = nextExpression;
|
|
1018
1048
|
},
|
|
1049
|
+
remove: () => {
|
|
1050
|
+
objExpr.properties.splice(propIndex, 1);
|
|
1051
|
+
},
|
|
1019
1052
|
};
|
|
1020
1053
|
};
|
|
1021
1054
|
const getEffectPropsObjectExpression = (call) => {
|
|
@@ -1072,7 +1105,18 @@ const updateSequenceKeyframesAst = ({ input, nodePath, updates, schema, videoCon
|
|
|
1072
1105
|
videoConfigValues: videoConfigIdentifierValues,
|
|
1073
1106
|
});
|
|
1074
1107
|
newValueStrings.push(recast.print(nextExpression).code);
|
|
1075
|
-
|
|
1108
|
+
if (shouldRemovePropAfterKeyframeOperation({
|
|
1109
|
+
expression: prop.expression,
|
|
1110
|
+
key: update.key,
|
|
1111
|
+
operation: update.operation,
|
|
1112
|
+
schema: schema !== null && schema !== void 0 ? schema : null,
|
|
1113
|
+
videoConfigValues: videoConfigIdentifierValues,
|
|
1114
|
+
})) {
|
|
1115
|
+
prop.remove();
|
|
1116
|
+
}
|
|
1117
|
+
else {
|
|
1118
|
+
prop.setExpression(nextExpression);
|
|
1119
|
+
}
|
|
1076
1120
|
if (introduced.calleeName) {
|
|
1077
1121
|
requiredImports.add(introduced.calleeName);
|
|
1078
1122
|
}
|
|
@@ -1180,7 +1224,18 @@ const updateEffectKeyframesAst = ({ input, sequenceNodePath, effectIndex, update
|
|
|
1180
1224
|
videoConfigValues: videoConfigIdentifierValues,
|
|
1181
1225
|
});
|
|
1182
1226
|
newValueStrings.push(recast.print(nextExpression).code);
|
|
1183
|
-
|
|
1227
|
+
if (shouldRemovePropAfterKeyframeOperation({
|
|
1228
|
+
expression: prop.expression,
|
|
1229
|
+
key: update.key,
|
|
1230
|
+
operation: update.operation,
|
|
1231
|
+
schema: schema !== null && schema !== void 0 ? schema : null,
|
|
1232
|
+
videoConfigValues: videoConfigIdentifierValues,
|
|
1233
|
+
})) {
|
|
1234
|
+
prop.remove();
|
|
1235
|
+
}
|
|
1236
|
+
else {
|
|
1237
|
+
prop.setExpression(nextExpression);
|
|
1238
|
+
}
|
|
1184
1239
|
if (introduced.calleeName) {
|
|
1185
1240
|
requiredImports.add(introduced.calleeName);
|
|
1186
1241
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const getFigmaClipboardPasteSupportError: (zstdDecompressSync: unknown) => "Figma paste is only available with Node.js 22.15 or newer" | null;
|
|
2
|
+
export declare const convertFigmaClipboardToSvg: (html: string) => {
|
|
3
|
+
height: number;
|
|
4
|
+
svg: string;
|
|
5
|
+
width: number;
|
|
6
|
+
};
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.convertFigmaClipboardToSvg = exports.getFigmaClipboardPasteSupportError = void 0;
|
|
37
|
+
const node_buffer_1 = require("node:buffer");
|
|
38
|
+
const zlib = __importStar(require("node:zlib"));
|
|
39
|
+
const kiwi_schema_1 = require("kiwi-schema");
|
|
40
|
+
const figma_to_svg_1 = require("./figma-to-svg");
|
|
41
|
+
const figmaMetaStart = '<!--(figmeta)';
|
|
42
|
+
const figmaMetaEnd = '(/figmeta)-->';
|
|
43
|
+
const figmaDataStart = '<!--(figma)';
|
|
44
|
+
const figmaDataEnd = '(/figma)-->';
|
|
45
|
+
const figKiwiPrelude = 'fig-kiwi';
|
|
46
|
+
const maxClipboardHtmlBytes = 25 * 1024 * 1024;
|
|
47
|
+
const maxMetaBytes = 1024 * 1024;
|
|
48
|
+
const maxArchiveBytes = 16 * 1024 * 1024;
|
|
49
|
+
const maxCompressedSchemaBytes = 5 * 1024 * 1024;
|
|
50
|
+
const maxCompressedMessageBytes = 10 * 1024 * 1024;
|
|
51
|
+
const maxSchemaBytes = 10 * 1024 * 1024;
|
|
52
|
+
const maxMessageBytes = 40 * 1024 * 1024;
|
|
53
|
+
const maxSchemaDefinitions = 2000;
|
|
54
|
+
const maxSchemaFields = 50000;
|
|
55
|
+
const maxNodeChanges = 20000;
|
|
56
|
+
const maxBlobs = 20000;
|
|
57
|
+
const nodeVersionRequirement = 'Figma paste is only available with Node.js 22.15 or newer';
|
|
58
|
+
const nativeZstdDecompressSync = zlib.zstdDecompressSync;
|
|
59
|
+
const fail = (message) => {
|
|
60
|
+
throw new Error(`Cannot import Figma selection: ${message}`);
|
|
61
|
+
};
|
|
62
|
+
const withFriendlyFailure = (operation, fallback) => {
|
|
63
|
+
try {
|
|
64
|
+
return operation();
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (error instanceof Error &&
|
|
68
|
+
error.message.startsWith('Cannot import Figma selection:')) {
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
return fail(fallback);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const getFigmaClipboardPasteSupportError = (zstdDecompressSync) => {
|
|
75
|
+
return typeof zstdDecompressSync === 'function'
|
|
76
|
+
? null
|
|
77
|
+
: nodeVersionRequirement;
|
|
78
|
+
};
|
|
79
|
+
exports.getFigmaClipboardPasteSupportError = getFigmaClipboardPasteSupportError;
|
|
80
|
+
const extractMarker = ({ end, html, label, start, }) => {
|
|
81
|
+
const startIndex = html.indexOf(start);
|
|
82
|
+
if (startIndex === -1) {
|
|
83
|
+
fail(`clipboard is missing its ${label}`);
|
|
84
|
+
}
|
|
85
|
+
const valueStart = startIndex + start.length;
|
|
86
|
+
const endIndex = html.indexOf(end, valueStart);
|
|
87
|
+
if (endIndex === -1) {
|
|
88
|
+
fail(`clipboard has an incomplete ${label}`);
|
|
89
|
+
}
|
|
90
|
+
return html.slice(valueStart, endIndex);
|
|
91
|
+
};
|
|
92
|
+
const decodeBase64 = ({ label, limit, value, }) => {
|
|
93
|
+
const compact = value.replace(/[\t\n\r ]/g, '');
|
|
94
|
+
if (compact.length === 0 ||
|
|
95
|
+
compact.length % 4 !== 0 ||
|
|
96
|
+
!/^[A-Za-z0-9+/]*={0,2}$/.test(compact)) {
|
|
97
|
+
fail(`${label} is not valid Base64`);
|
|
98
|
+
}
|
|
99
|
+
if (compact.length > Math.ceil(limit / 3) * 4) {
|
|
100
|
+
fail(`${label} is too large`);
|
|
101
|
+
}
|
|
102
|
+
const decoded = node_buffer_1.Buffer.from(compact, 'base64');
|
|
103
|
+
if (decoded.byteLength > limit) {
|
|
104
|
+
fail(`${label} is too large`);
|
|
105
|
+
}
|
|
106
|
+
return new Uint8Array(decoded.buffer, decoded.byteOffset, decoded.byteLength);
|
|
107
|
+
};
|
|
108
|
+
const parseMeta = (base64) => {
|
|
109
|
+
const bytes = decodeBase64({
|
|
110
|
+
label: 'Figma metadata',
|
|
111
|
+
limit: maxMetaBytes,
|
|
112
|
+
value: base64,
|
|
113
|
+
});
|
|
114
|
+
let parsed;
|
|
115
|
+
try {
|
|
116
|
+
parsed = JSON.parse(new TextDecoder().decode(bytes));
|
|
117
|
+
}
|
|
118
|
+
catch (_a) {
|
|
119
|
+
fail('clipboard metadata is invalid');
|
|
120
|
+
}
|
|
121
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
122
|
+
fail('clipboard metadata is invalid');
|
|
123
|
+
}
|
|
124
|
+
const meta = parsed;
|
|
125
|
+
if (meta.dataType !== 'scene') {
|
|
126
|
+
fail('clipboard does not contain a Figma scene');
|
|
127
|
+
}
|
|
128
|
+
const { selectedNodeData } = meta;
|
|
129
|
+
if (typeof selectedNodeData !== 'string') {
|
|
130
|
+
fail('clipboard does not identify its selected node');
|
|
131
|
+
}
|
|
132
|
+
const selectedNodeId = selectedNodeData.split('|')[0];
|
|
133
|
+
if (!/^\d+:\d+$/.test(selectedNodeId)) {
|
|
134
|
+
fail('clipboard has an invalid selected node ID');
|
|
135
|
+
}
|
|
136
|
+
return selectedNodeId;
|
|
137
|
+
};
|
|
138
|
+
const parseArchive = (base64) => {
|
|
139
|
+
const bytes = decodeBase64({
|
|
140
|
+
label: 'Figma scene',
|
|
141
|
+
limit: maxArchiveBytes,
|
|
142
|
+
value: base64,
|
|
143
|
+
});
|
|
144
|
+
if (bytes.length < figKiwiPrelude.length + 4) {
|
|
145
|
+
fail('clipboard scene is truncated');
|
|
146
|
+
}
|
|
147
|
+
const prelude = new TextDecoder().decode(bytes.subarray(0, figKiwiPrelude.length));
|
|
148
|
+
if (prelude !== figKiwiPrelude) {
|
|
149
|
+
fail('clipboard scene has an unknown format');
|
|
150
|
+
}
|
|
151
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
152
|
+
const version = view.getUint32(figKiwiPrelude.length, true);
|
|
153
|
+
if (version === 0) {
|
|
154
|
+
fail('clipboard scene has an invalid archive version');
|
|
155
|
+
}
|
|
156
|
+
let offset = figKiwiPrelude.length + 4;
|
|
157
|
+
const chunks = [];
|
|
158
|
+
while (offset < bytes.length) {
|
|
159
|
+
if (offset + 4 > bytes.length) {
|
|
160
|
+
fail('clipboard scene has a truncated chunk header');
|
|
161
|
+
}
|
|
162
|
+
const size = view.getUint32(offset, true);
|
|
163
|
+
offset += 4;
|
|
164
|
+
if (size === 0 || offset + size > bytes.length) {
|
|
165
|
+
fail('clipboard scene has an invalid chunk');
|
|
166
|
+
}
|
|
167
|
+
chunks.push(bytes.slice(offset, offset + size));
|
|
168
|
+
offset += size;
|
|
169
|
+
}
|
|
170
|
+
if (chunks.length !== 2) {
|
|
171
|
+
fail('clipboard scene uses an unsupported archive layout');
|
|
172
|
+
}
|
|
173
|
+
if (chunks[0].length > maxCompressedSchemaBytes) {
|
|
174
|
+
fail('clipboard schema is too large');
|
|
175
|
+
}
|
|
176
|
+
if (chunks[1].length > maxCompressedMessageBytes) {
|
|
177
|
+
fail('clipboard scene is too large');
|
|
178
|
+
}
|
|
179
|
+
return { message: chunks[1], schema: chunks[0] };
|
|
180
|
+
};
|
|
181
|
+
const inflateSchema = (compressed) => {
|
|
182
|
+
const inflated = zlib.inflateRawSync(compressed, {
|
|
183
|
+
maxOutputLength: maxSchemaBytes,
|
|
184
|
+
});
|
|
185
|
+
return new Uint8Array(inflated.buffer, inflated.byteOffset, inflated.byteLength);
|
|
186
|
+
};
|
|
187
|
+
const readLimitedLittleEndian = ({ bytes, limit, length, offset, }) => {
|
|
188
|
+
let result = 0;
|
|
189
|
+
for (let index = length - 1; index >= 0; index--) {
|
|
190
|
+
const nextByte = bytes[offset + index];
|
|
191
|
+
if (result > Math.floor((limit - nextByte) / 256)) {
|
|
192
|
+
fail('clipboard scene expands beyond the size limit');
|
|
193
|
+
}
|
|
194
|
+
result = result * 256 + nextByte;
|
|
195
|
+
}
|
|
196
|
+
return result;
|
|
197
|
+
};
|
|
198
|
+
const getZstandardContentSize = (compressed) => {
|
|
199
|
+
if (compressed.length < 6 ||
|
|
200
|
+
compressed[0] !== 0x28 ||
|
|
201
|
+
compressed[1] !== 0xb5 ||
|
|
202
|
+
compressed[2] !== 0x2f ||
|
|
203
|
+
compressed[3] !== 0xfd) {
|
|
204
|
+
fail('clipboard scene uses an unsupported compression format');
|
|
205
|
+
}
|
|
206
|
+
const descriptor = compressed[4];
|
|
207
|
+
if ((descriptor & 8) !== 0) {
|
|
208
|
+
fail('clipboard scene has an invalid Zstandard frame');
|
|
209
|
+
}
|
|
210
|
+
const singleSegment = (descriptor >> 5) & 1;
|
|
211
|
+
const dictionaryFlag = descriptor & 3;
|
|
212
|
+
const contentSizeFlag = descriptor >> 6;
|
|
213
|
+
const dictionaryBytes = dictionaryFlag === 3 ? 4 : dictionaryFlag;
|
|
214
|
+
const contentSizeBytes = contentSizeFlag
|
|
215
|
+
? 1 << contentSizeFlag
|
|
216
|
+
: singleSegment;
|
|
217
|
+
if (contentSizeBytes === 0) {
|
|
218
|
+
fail('clipboard scene does not declare its expanded size');
|
|
219
|
+
}
|
|
220
|
+
const contentSizeOffset = 6 - singleSegment + dictionaryBytes;
|
|
221
|
+
if (contentSizeOffset + contentSizeBytes > compressed.length) {
|
|
222
|
+
fail('clipboard scene has a truncated Zstandard header');
|
|
223
|
+
}
|
|
224
|
+
let contentSize = readLimitedLittleEndian({
|
|
225
|
+
bytes: compressed,
|
|
226
|
+
limit: maxMessageBytes,
|
|
227
|
+
length: contentSizeBytes,
|
|
228
|
+
offset: contentSizeOffset,
|
|
229
|
+
});
|
|
230
|
+
if (contentSizeFlag === 1) {
|
|
231
|
+
contentSize += 256;
|
|
232
|
+
}
|
|
233
|
+
if (contentSize <= 0 || contentSize > maxMessageBytes) {
|
|
234
|
+
fail('clipboard scene expands beyond the size limit');
|
|
235
|
+
}
|
|
236
|
+
return contentSize;
|
|
237
|
+
};
|
|
238
|
+
const decompressMessage = ({ compressed, zstdDecompressSync, }) => {
|
|
239
|
+
const expectedBytes = getZstandardContentSize(compressed);
|
|
240
|
+
const decompressed = zstdDecompressSync(compressed, {
|
|
241
|
+
maxOutputLength: expectedBytes,
|
|
242
|
+
});
|
|
243
|
+
if (decompressed.byteLength !== expectedBytes) {
|
|
244
|
+
fail('clipboard scene did not match its declared size');
|
|
245
|
+
}
|
|
246
|
+
return new Uint8Array(decompressed.buffer, decompressed.byteOffset, decompressed.byteLength);
|
|
247
|
+
};
|
|
248
|
+
const decodeMessage = ({ messageBytes, schemaBytes, }) => {
|
|
249
|
+
var _a, _b;
|
|
250
|
+
var _c, _d;
|
|
251
|
+
const schema = (0, kiwi_schema_1.decodeBinarySchema)(schemaBytes);
|
|
252
|
+
if (schema.package !== null &&
|
|
253
|
+
(typeof schema.package !== 'string' ||
|
|
254
|
+
!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(schema.package))) {
|
|
255
|
+
fail('clipboard schema has an invalid package name');
|
|
256
|
+
}
|
|
257
|
+
if (schema.definitions.length > maxSchemaDefinitions) {
|
|
258
|
+
fail('clipboard schema has too many definitions');
|
|
259
|
+
}
|
|
260
|
+
const fieldCount = schema.definitions.reduce((total, definition) => total + definition.fields.length, 0);
|
|
261
|
+
if (fieldCount > maxSchemaFields) {
|
|
262
|
+
fail('clipboard schema has too many fields');
|
|
263
|
+
}
|
|
264
|
+
const compiled = (0, kiwi_schema_1.compileSchema)(schema);
|
|
265
|
+
const decoded = compiled.decodeMessage(messageBytes);
|
|
266
|
+
if (typeof decoded !== 'object' ||
|
|
267
|
+
decoded === null ||
|
|
268
|
+
Array.isArray(decoded)) {
|
|
269
|
+
fail('clipboard scene decoded to an invalid message');
|
|
270
|
+
}
|
|
271
|
+
const message = decoded;
|
|
272
|
+
if (((_c = (_a = message.nodeChanges) === null || _a === void 0 ? void 0 : _a.length) !== null && _c !== void 0 ? _c : 0) > maxNodeChanges ||
|
|
273
|
+
((_d = (_b = message.blobs) === null || _b === void 0 ? void 0 : _b.length) !== null && _d !== void 0 ? _d : 0) > maxBlobs) {
|
|
274
|
+
fail('clipboard scene has too many objects');
|
|
275
|
+
}
|
|
276
|
+
return message;
|
|
277
|
+
};
|
|
278
|
+
const convertFigmaClipboardToSvg = (html) => {
|
|
279
|
+
if (typeof html !== 'string' || html.length === 0) {
|
|
280
|
+
fail('clipboard HTML is empty');
|
|
281
|
+
}
|
|
282
|
+
if (node_buffer_1.Buffer.byteLength(html, 'utf8') > maxClipboardHtmlBytes) {
|
|
283
|
+
fail('clipboard HTML is too large');
|
|
284
|
+
}
|
|
285
|
+
const supportError = (0, exports.getFigmaClipboardPasteSupportError)(nativeZstdDecompressSync);
|
|
286
|
+
if (supportError !== null) {
|
|
287
|
+
fail(supportError);
|
|
288
|
+
}
|
|
289
|
+
const zstdDecompressSync = nativeZstdDecompressSync;
|
|
290
|
+
const metaBase64 = extractMarker({
|
|
291
|
+
end: figmaMetaEnd,
|
|
292
|
+
html,
|
|
293
|
+
label: 'figmeta section',
|
|
294
|
+
start: figmaMetaStart,
|
|
295
|
+
});
|
|
296
|
+
const figmaBase64 = extractMarker({
|
|
297
|
+
end: figmaDataEnd,
|
|
298
|
+
html,
|
|
299
|
+
label: 'figma section',
|
|
300
|
+
start: figmaDataStart,
|
|
301
|
+
});
|
|
302
|
+
const selectedNodeId = parseMeta(metaBase64);
|
|
303
|
+
const archive = parseArchive(figmaBase64);
|
|
304
|
+
const schemaBytes = withFriendlyFailure(() => inflateSchema(archive.schema), 'clipboard schema could not be decompressed');
|
|
305
|
+
const messageBytes = withFriendlyFailure(() => decompressMessage({
|
|
306
|
+
compressed: archive.message,
|
|
307
|
+
zstdDecompressSync,
|
|
308
|
+
}), 'clipboard scene could not be decompressed');
|
|
309
|
+
const message = withFriendlyFailure(() => decodeMessage({ messageBytes, schemaBytes }), 'clipboard scene could not be decoded');
|
|
310
|
+
return (0, figma_to_svg_1.renderFigmaMessageToSvg)({ message, selectedNodeId });
|
|
311
|
+
};
|
|
312
|
+
exports.convertFigmaClipboardToSvg = convertFigmaClipboardToSvg;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
type FigmaVector = {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
};
|
|
5
|
+
type FigmaGuid = {
|
|
6
|
+
sessionID?: number;
|
|
7
|
+
localID?: number;
|
|
8
|
+
};
|
|
9
|
+
type FigmaColor = {
|
|
10
|
+
r?: number;
|
|
11
|
+
g?: number;
|
|
12
|
+
b?: number;
|
|
13
|
+
a?: number;
|
|
14
|
+
};
|
|
15
|
+
type FigmaPaint = {
|
|
16
|
+
type?: string;
|
|
17
|
+
color?: FigmaColor;
|
|
18
|
+
opacity?: number;
|
|
19
|
+
visible?: boolean;
|
|
20
|
+
blendMode?: string;
|
|
21
|
+
};
|
|
22
|
+
type FigmaTransform = {
|
|
23
|
+
m00?: number;
|
|
24
|
+
m01?: number;
|
|
25
|
+
m02?: number;
|
|
26
|
+
m10?: number;
|
|
27
|
+
m11?: number;
|
|
28
|
+
m12?: number;
|
|
29
|
+
};
|
|
30
|
+
type FigmaArcData = {
|
|
31
|
+
startingAngle?: number;
|
|
32
|
+
endingAngle?: number;
|
|
33
|
+
innerRadius?: number;
|
|
34
|
+
};
|
|
35
|
+
export type FigmaNode = {
|
|
36
|
+
guid?: FigmaGuid;
|
|
37
|
+
phase?: string;
|
|
38
|
+
parentIndex?: {
|
|
39
|
+
guid?: FigmaGuid;
|
|
40
|
+
position?: string;
|
|
41
|
+
};
|
|
42
|
+
type?: string;
|
|
43
|
+
name?: string;
|
|
44
|
+
visible?: boolean;
|
|
45
|
+
opacity?: number;
|
|
46
|
+
blendMode?: string;
|
|
47
|
+
size?: FigmaVector;
|
|
48
|
+
transform?: FigmaTransform;
|
|
49
|
+
mask?: boolean;
|
|
50
|
+
maskType?: string;
|
|
51
|
+
maskIsOutline?: boolean;
|
|
52
|
+
fillPaints?: FigmaPaint[];
|
|
53
|
+
backgroundPaints?: FigmaPaint[];
|
|
54
|
+
strokePaints?: FigmaPaint[];
|
|
55
|
+
strokeWeight?: number;
|
|
56
|
+
borderTopWeight?: number;
|
|
57
|
+
borderBottomWeight?: number;
|
|
58
|
+
borderLeftWeight?: number;
|
|
59
|
+
borderRightWeight?: number;
|
|
60
|
+
borderStrokeWeightsIndependent?: boolean;
|
|
61
|
+
strokeAlign?: string;
|
|
62
|
+
strokeCap?: string;
|
|
63
|
+
strokeJoin?: string;
|
|
64
|
+
dashPattern?: number[];
|
|
65
|
+
miterLimit?: number;
|
|
66
|
+
cornerRadius?: number;
|
|
67
|
+
cornerSmoothing?: number;
|
|
68
|
+
rectangleTopLeftCornerRadius?: number;
|
|
69
|
+
rectangleTopRightCornerRadius?: number;
|
|
70
|
+
rectangleBottomLeftCornerRadius?: number;
|
|
71
|
+
rectangleBottomRightCornerRadius?: number;
|
|
72
|
+
rectangleCornerRadiiIndependent?: boolean;
|
|
73
|
+
frameMaskDisabled?: boolean;
|
|
74
|
+
resizeToFit?: boolean;
|
|
75
|
+
effects?: Array<{
|
|
76
|
+
visible?: boolean;
|
|
77
|
+
}>;
|
|
78
|
+
vectorData?: {
|
|
79
|
+
vectorNetworkBlob?: number;
|
|
80
|
+
normalizedSize?: FigmaVector;
|
|
81
|
+
};
|
|
82
|
+
arcData?: FigmaArcData;
|
|
83
|
+
};
|
|
84
|
+
export type FigmaMessage = {
|
|
85
|
+
type?: string;
|
|
86
|
+
nodeChanges?: FigmaNode[];
|
|
87
|
+
blobs?: Array<{
|
|
88
|
+
bytes?: Uint8Array;
|
|
89
|
+
}>;
|
|
90
|
+
};
|
|
91
|
+
type VectorVertex = FigmaVector;
|
|
92
|
+
type VectorSegment = {
|
|
93
|
+
start: number;
|
|
94
|
+
end: number;
|
|
95
|
+
tangentStart: FigmaVector;
|
|
96
|
+
tangentEnd: FigmaVector;
|
|
97
|
+
};
|
|
98
|
+
type VectorRegion = {
|
|
99
|
+
windingRule: 'evenodd' | 'nonzero';
|
|
100
|
+
loops: number[][];
|
|
101
|
+
};
|
|
102
|
+
type VectorNetwork = {
|
|
103
|
+
vertices: VectorVertex[];
|
|
104
|
+
segments: VectorSegment[];
|
|
105
|
+
regions: VectorRegion[];
|
|
106
|
+
};
|
|
107
|
+
export declare const decodeFigmaVectorNetwork: (data: Uint8Array<ArrayBufferLike>) => VectorNetwork;
|
|
108
|
+
export declare const renderFigmaMessageToSvg: ({ message, selectedNodeId, }: {
|
|
109
|
+
message: FigmaMessage;
|
|
110
|
+
selectedNodeId: string;
|
|
111
|
+
}) => {
|
|
112
|
+
height: number;
|
|
113
|
+
svg: string;
|
|
114
|
+
width: number;
|
|
115
|
+
};
|
|
116
|
+
export {};
|