@remotion/studio-server 4.0.493 → 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.d.ts +1 -0
- package/dist/codemods/update-keyframes/update-keyframes.js +66 -8
- 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/helpers/video-config-numeric-expression.js +32 -1
- 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 +24 -14
- package/dist/preview-server/routes/download-remote-asset.js +2 -1
- 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
|
@@ -682,7 +682,7 @@ const addKeyframe = ({ expression, key, frame, value, schema, videoConfigValues,
|
|
|
682
682
|
},
|
|
683
683
|
};
|
|
684
684
|
};
|
|
685
|
-
const removeKeyframe = ({ expression, frame, videoConfigValues, }) => {
|
|
685
|
+
const removeKeyframe = ({ expression, frame, videoConfigValues, valueWhenLastKeyframeDeleted, }) => {
|
|
686
686
|
const existing = getInterpolationExpression(expression, videoConfigValues);
|
|
687
687
|
if (!existing) {
|
|
688
688
|
throw new Error('Cannot remove keyframe from non-interpolated expression');
|
|
@@ -694,7 +694,9 @@ const removeKeyframe = ({ expression, frame, videoConfigValues, }) => {
|
|
|
694
694
|
const nextKeyframes = existing.keyframes.filter((_keyframe, index) => index !== keyframeIndex);
|
|
695
695
|
if (nextKeyframes.length === 0) {
|
|
696
696
|
return {
|
|
697
|
-
expression:
|
|
697
|
+
expression: valueWhenLastKeyframeDeleted === null
|
|
698
|
+
? existing.keyframes[keyframeIndex].output
|
|
699
|
+
: (0, update_nested_prop_1.parseValueExpression)(valueWhenLastKeyframeDeleted),
|
|
698
700
|
introduced: noIntroducedIdentifiers,
|
|
699
701
|
};
|
|
700
702
|
}
|
|
@@ -828,6 +830,7 @@ const applyKeyframeOperation = ({ expression, key, operation, schema, videoConfi
|
|
|
828
830
|
expression,
|
|
829
831
|
frame: operation.frame,
|
|
830
832
|
videoConfigValues,
|
|
833
|
+
valueWhenLastKeyframeDeleted: operation.valueWhenLastKeyframeDeleted,
|
|
831
834
|
});
|
|
832
835
|
};
|
|
833
836
|
const getExpressionFromJsxAttribute = (attr) => {
|
|
@@ -907,6 +910,23 @@ const getInitialValueForMissingProp = ({ schema, key, newValue, }) => {
|
|
|
907
910
|
}
|
|
908
911
|
return newValue;
|
|
909
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
|
+
};
|
|
910
930
|
const getObjectExpression = (attr) => {
|
|
911
931
|
if (!attr.value || attr.value.type !== 'JSXExpressionContainer') {
|
|
912
932
|
return null;
|
|
@@ -931,7 +951,7 @@ const createMissingPropExpression = (missingPropInitialValue) => {
|
|
|
931
951
|
const getSequenceWritableProp = ({ attributes, key, missingPropInitialValue, }) => {
|
|
932
952
|
const dotIndex = key.indexOf('.');
|
|
933
953
|
if (dotIndex === -1) {
|
|
934
|
-
const { attr: topLevelAttr } = findJsxAttribute(attributes, key);
|
|
954
|
+
const { attrIndex, attr: topLevelAttr } = findJsxAttribute(attributes, key);
|
|
935
955
|
if (!topLevelAttr) {
|
|
936
956
|
if (missingPropInitialValue) {
|
|
937
957
|
return {
|
|
@@ -939,6 +959,7 @@ const getSequenceWritableProp = ({ attributes, key, missingPropInitialValue, })
|
|
|
939
959
|
setExpression: (nextExpression) => {
|
|
940
960
|
attributes.push(createJsxExpressionAttribute(key, nextExpression));
|
|
941
961
|
},
|
|
962
|
+
remove: () => undefined,
|
|
942
963
|
};
|
|
943
964
|
}
|
|
944
965
|
throw new Error(`Cannot update keyframes: "${key}" is not set`);
|
|
@@ -952,11 +973,14 @@ const getSequenceWritableProp = ({ attributes, key, missingPropInitialValue, })
|
|
|
952
973
|
setExpression: (nextExpression) => {
|
|
953
974
|
topLevelAttr.value = b.jsxExpressionContainer(nextExpression);
|
|
954
975
|
},
|
|
976
|
+
remove: () => {
|
|
977
|
+
attributes.splice(attrIndex, 1);
|
|
978
|
+
},
|
|
955
979
|
};
|
|
956
980
|
}
|
|
957
981
|
const parentKey = key.slice(0, dotIndex);
|
|
958
982
|
const childKey = key.slice(dotIndex + 1);
|
|
959
|
-
const { attr: parentAttr } = findJsxAttribute(attributes, parentKey);
|
|
983
|
+
const { attrIndex: parentAttrIndex, attr: parentAttr } = findJsxAttribute(attributes, parentKey);
|
|
960
984
|
if (!parentAttr) {
|
|
961
985
|
if (missingPropInitialValue) {
|
|
962
986
|
return {
|
|
@@ -968,6 +992,7 @@ const getSequenceWritableProp = ({ attributes, key, missingPropInitialValue, })
|
|
|
968
992
|
expression: nextExpression,
|
|
969
993
|
}));
|
|
970
994
|
},
|
|
995
|
+
remove: () => undefined,
|
|
971
996
|
};
|
|
972
997
|
}
|
|
973
998
|
throw new Error(`Cannot update keyframes: "${parentKey}" is not set`);
|
|
@@ -976,7 +1001,7 @@ const getSequenceWritableProp = ({ attributes, key, missingPropInitialValue, })
|
|
|
976
1001
|
if (!objExpr) {
|
|
977
1002
|
throw new Error(`Cannot update keyframes: "${parentKey}" is computed`);
|
|
978
1003
|
}
|
|
979
|
-
const { prop } = findObjectProperty(objExpr, childKey);
|
|
1004
|
+
const { propIndex, prop } = findObjectProperty(objExpr, childKey);
|
|
980
1005
|
if (!prop) {
|
|
981
1006
|
if (missingPropInitialValue) {
|
|
982
1007
|
return {
|
|
@@ -984,6 +1009,7 @@ const getSequenceWritableProp = ({ attributes, key, missingPropInitialValue, })
|
|
|
984
1009
|
setExpression: (nextExpression) => {
|
|
985
1010
|
objExpr.properties.push(createObjectProperty(childKey, nextExpression));
|
|
986
1011
|
},
|
|
1012
|
+
remove: () => undefined,
|
|
987
1013
|
};
|
|
988
1014
|
}
|
|
989
1015
|
throw new Error(`Cannot update keyframes: "${key}" is not set`);
|
|
@@ -993,10 +1019,16 @@ const getSequenceWritableProp = ({ attributes, key, missingPropInitialValue, })
|
|
|
993
1019
|
setExpression: (nextExpression) => {
|
|
994
1020
|
prop.value = nextExpression;
|
|
995
1021
|
},
|
|
1022
|
+
remove: () => {
|
|
1023
|
+
objExpr.properties.splice(propIndex, 1);
|
|
1024
|
+
if (objExpr.properties.length === 0) {
|
|
1025
|
+
attributes.splice(parentAttrIndex, 1);
|
|
1026
|
+
}
|
|
1027
|
+
},
|
|
996
1028
|
};
|
|
997
1029
|
};
|
|
998
1030
|
const getEffectWritableProp = ({ objExpr, key, missingPropInitialValue, }) => {
|
|
999
|
-
const { prop } = findObjectProperty(objExpr, key);
|
|
1031
|
+
const { propIndex, prop } = findObjectProperty(objExpr, key);
|
|
1000
1032
|
if (!prop) {
|
|
1001
1033
|
if (missingPropInitialValue) {
|
|
1002
1034
|
return {
|
|
@@ -1004,6 +1036,7 @@ const getEffectWritableProp = ({ objExpr, key, missingPropInitialValue, }) => {
|
|
|
1004
1036
|
setExpression: (nextExpression) => {
|
|
1005
1037
|
objExpr.properties.push(createObjectProperty(key, nextExpression));
|
|
1006
1038
|
},
|
|
1039
|
+
remove: () => undefined,
|
|
1007
1040
|
};
|
|
1008
1041
|
}
|
|
1009
1042
|
throw new Error(`Cannot update keyframes: "${key}" is not set`);
|
|
@@ -1013,6 +1046,9 @@ const getEffectWritableProp = ({ objExpr, key, missingPropInitialValue, }) => {
|
|
|
1013
1046
|
setExpression: (nextExpression) => {
|
|
1014
1047
|
prop.value = nextExpression;
|
|
1015
1048
|
},
|
|
1049
|
+
remove: () => {
|
|
1050
|
+
objExpr.properties.splice(propIndex, 1);
|
|
1051
|
+
},
|
|
1016
1052
|
};
|
|
1017
1053
|
};
|
|
1018
1054
|
const getEffectPropsObjectExpression = (call) => {
|
|
@@ -1069,7 +1105,18 @@ const updateSequenceKeyframesAst = ({ input, nodePath, updates, schema, videoCon
|
|
|
1069
1105
|
videoConfigValues: videoConfigIdentifierValues,
|
|
1070
1106
|
});
|
|
1071
1107
|
newValueStrings.push(recast.print(nextExpression).code);
|
|
1072
|
-
|
|
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
|
+
}
|
|
1073
1120
|
if (introduced.calleeName) {
|
|
1074
1121
|
requiredImports.add(introduced.calleeName);
|
|
1075
1122
|
}
|
|
@@ -1177,7 +1224,18 @@ const updateEffectKeyframesAst = ({ input, sequenceNodePath, effectIndex, update
|
|
|
1177
1224
|
videoConfigValues: videoConfigIdentifierValues,
|
|
1178
1225
|
});
|
|
1179
1226
|
newValueStrings.push(recast.print(nextExpression).code);
|
|
1180
|
-
|
|
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
|
+
}
|
|
1181
1239
|
if (introduced.calleeName) {
|
|
1182
1240
|
requiredImports.add(introduced.calleeName);
|
|
1183
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 {};
|