ag-psd 15.0.4 → 15.0.5

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.
@@ -0,0 +1,305 @@
1
+ import { LayerEffectsInfo, BevelStyle, LayerEffectShadow } from './psd';
2
+ import { toBlendMode, fromBlendMode } from './helpers';
3
+ import {
4
+ PsdReader, checkSignature, readSignature, skipBytes, readUint16, readUint8,
5
+ readUint32, readFixedPoint32, readColor
6
+ } from './psdReader';
7
+ import {
8
+ PsdWriter, writeSignature, writeUint16, writeZeros, writeFixedPoint32,
9
+ writeUint8, writeUint32, writeColor
10
+ } from './psdWriter';
11
+
12
+ const bevelStyles: BevelStyle[] = [
13
+ undefined as any, 'outer bevel', 'inner bevel', 'emboss', 'pillow emboss', 'stroke emboss'
14
+ ];
15
+
16
+ function readBlendMode(reader: PsdReader) {
17
+ checkSignature(reader, '8BIM');
18
+ return toBlendMode[readSignature(reader)] || 'normal';
19
+ }
20
+
21
+ function writeBlendMode(writer: PsdWriter, mode: string | undefined) {
22
+ writeSignature(writer, '8BIM');
23
+ writeSignature(writer, fromBlendMode[mode!] || 'norm');
24
+ }
25
+
26
+ function readFixedPoint8(reader: PsdReader) {
27
+ return readUint8(reader) / 0xff;
28
+ }
29
+
30
+ function writeFixedPoint8(writer: PsdWriter, value: number) {
31
+ writeUint8(writer, Math.round(value * 0xff) | 0);
32
+ }
33
+
34
+ export function readEffects(reader: PsdReader) {
35
+ const version = readUint16(reader);
36
+ if (version !== 0) throw new Error(`Invalid effects layer version: ${version}`);
37
+
38
+ const effectsCount = readUint16(reader);
39
+ const effects: LayerEffectsInfo = <any>{};
40
+
41
+ for (let i = 0; i < effectsCount; i++) {
42
+ checkSignature(reader, '8BIM');
43
+ const type = readSignature(reader);
44
+
45
+ switch (type) {
46
+ case 'cmnS': { // common state (see See Effects layer, common state info)
47
+ const size = readUint32(reader);
48
+ const version = readUint32(reader);
49
+ const visible = !!readUint8(reader);
50
+ skipBytes(reader, 2);
51
+
52
+ if (size !== 7 || version !== 0 || !visible) throw new Error(`Invalid effects common state`);
53
+ break;
54
+ }
55
+ case 'dsdw': // drop shadow (see See Effects layer, drop shadow and inner shadow info)
56
+ case 'isdw': { // inner shadow (see See Effects layer, drop shadow and inner shadow info)
57
+ const blockSize = readUint32(reader);
58
+ const version = readUint32(reader);
59
+
60
+ if (blockSize !== 41 && blockSize !== 51) throw new Error(`Invalid shadow size: ${blockSize}`);
61
+ if (version !== 0 && version !== 2) throw new Error(`Invalid shadow version: ${version}`);
62
+
63
+ const size = readFixedPoint32(reader);
64
+ readFixedPoint32(reader); // intensity
65
+ const angle = readFixedPoint32(reader);
66
+ const distance = readFixedPoint32(reader);
67
+ const color = readColor(reader);
68
+ const blendMode = readBlendMode(reader);
69
+ const enabled = !!readUint8(reader);
70
+ const useGlobalLight = !!readUint8(reader);
71
+ const opacity = readFixedPoint8(reader);
72
+ if (blockSize >= 51) readColor(reader); // native color
73
+ const shadowInfo: LayerEffectShadow = {
74
+ size: { units: 'Pixels', value: size },
75
+ distance: { units: 'Pixels', value: distance },
76
+ angle, color, blendMode, enabled, useGlobalLight, opacity
77
+ };
78
+
79
+ if (type === 'dsdw') {
80
+ effects.dropShadow = [shadowInfo];
81
+ } else {
82
+ effects.innerShadow = [shadowInfo];
83
+ }
84
+ break;
85
+ }
86
+ case 'oglw': { // outer glow (see See Effects layer, outer glow info)
87
+ const blockSize = readUint32(reader);
88
+ const version = readUint32(reader);
89
+
90
+ if (blockSize !== 32 && blockSize !== 42) throw new Error(`Invalid outer glow size: ${blockSize}`);
91
+ if (version !== 0 && version !== 2) throw new Error(`Invalid outer glow version: ${version}`);
92
+
93
+ const size = readFixedPoint32(reader);
94
+ readFixedPoint32(reader); // intensity
95
+ const color = readColor(reader);
96
+ const blendMode = readBlendMode(reader);
97
+ const enabled = !!readUint8(reader);
98
+ const opacity = readFixedPoint8(reader);
99
+ if (blockSize >= 42) readColor(reader); // native color
100
+
101
+ effects.outerGlow = {
102
+ size: { units: 'Pixels', value: size },
103
+ color, blendMode, enabled, opacity
104
+ };
105
+ break;
106
+ }
107
+ case 'iglw': { // inner glow (see See Effects layer, inner glow info)
108
+ const blockSize = readUint32(reader);
109
+ const version = readUint32(reader);
110
+
111
+ if (blockSize !== 32 && blockSize !== 43) throw new Error(`Invalid inner glow size: ${blockSize}`);
112
+ if (version !== 0 && version !== 2) throw new Error(`Invalid inner glow version: ${version}`);
113
+
114
+ const size = readFixedPoint32(reader);
115
+ readFixedPoint32(reader); // intensity
116
+ const color = readColor(reader);
117
+ const blendMode = readBlendMode(reader);
118
+ const enabled = !!readUint8(reader);
119
+ const opacity = readFixedPoint8(reader);
120
+
121
+ if (blockSize >= 43) {
122
+ readUint8(reader); // inverted
123
+ readColor(reader); // native color
124
+ }
125
+
126
+ effects.innerGlow = {
127
+ size: { units: 'Pixels', value: size },
128
+ color, blendMode, enabled, opacity
129
+ };
130
+ break;
131
+ }
132
+ case 'bevl': { // bevel (see See Effects layer, bevel info)
133
+ const blockSize = readUint32(reader);
134
+ const version = readUint32(reader);
135
+
136
+ if (blockSize !== 58 && blockSize !== 78) throw new Error(`Invalid bevel size: ${blockSize}`);
137
+ if (version !== 0 && version !== 2) throw new Error(`Invalid bevel version: ${version}`);
138
+
139
+ const angle = readFixedPoint32(reader);
140
+ const strength = readFixedPoint32(reader);
141
+ const size = readFixedPoint32(reader);
142
+ const highlightBlendMode = readBlendMode(reader);
143
+ const shadowBlendMode = readBlendMode(reader);
144
+ const highlightColor = readColor(reader);
145
+ const shadowColor = readColor(reader);
146
+ const style = bevelStyles[readUint8(reader)] || 'inner bevel';
147
+ const highlightOpacity = readFixedPoint8(reader);
148
+ const shadowOpacity = readFixedPoint8(reader);
149
+ const enabled = !!readUint8(reader);
150
+ const useGlobalLight = !!readUint8(reader);
151
+ const direction = readUint8(reader) ? 'down' : 'up';
152
+
153
+ if (blockSize >= 78) {
154
+ readColor(reader); // real highlight color
155
+ readColor(reader); // real shadow color
156
+ }
157
+
158
+ effects.bevel = {
159
+ size: { units: 'Pixels', value: size },
160
+ angle, strength, highlightBlendMode, shadowBlendMode, highlightColor, shadowColor,
161
+ style, highlightOpacity, shadowOpacity, enabled, useGlobalLight, direction,
162
+ };
163
+ break;
164
+ }
165
+ case 'sofi': { // solid fill (Photoshop 7.0) (see See Effects layer, solid fill (added in Photoshop 7.0))
166
+ const size = readUint32(reader);
167
+ const version = readUint32(reader);
168
+
169
+ if (size !== 34) throw new Error(`Invalid effects solid fill info size: ${size}`);
170
+ if (version !== 2) throw new Error(`Invalid effects solid fill info version: ${version}`);
171
+
172
+ const blendMode = readBlendMode(reader);
173
+ const color = readColor(reader);
174
+ const opacity = readFixedPoint8(reader);
175
+ const enabled = !!readUint8(reader);
176
+ readColor(reader); // native color
177
+
178
+ effects.solidFill = [{ blendMode, color, opacity, enabled }];
179
+ break;
180
+ }
181
+ default:
182
+ throw new Error(`Invalid effect type: '${type}'`);
183
+ }
184
+ }
185
+
186
+ return effects;
187
+ }
188
+
189
+ function writeShadowInfo(writer: PsdWriter, shadow: LayerEffectShadow) {
190
+ writeUint32(writer, 51);
191
+ writeUint32(writer, 2);
192
+ writeFixedPoint32(writer, shadow.size && shadow.size.value || 0);
193
+ writeFixedPoint32(writer, 0); // intensity
194
+ writeFixedPoint32(writer, shadow.angle || 0);
195
+ writeFixedPoint32(writer, shadow.distance && shadow.distance.value || 0);
196
+ writeColor(writer, shadow.color);
197
+ writeBlendMode(writer, shadow.blendMode);
198
+ writeUint8(writer, shadow.enabled ? 1 : 0);
199
+ writeUint8(writer, shadow.useGlobalLight ? 1 : 0);
200
+ writeFixedPoint8(writer, shadow.opacity ?? 1);
201
+ writeColor(writer, shadow.color); // native color
202
+ }
203
+
204
+ export function writeEffects(writer: PsdWriter, effects: LayerEffectsInfo) {
205
+ const dropShadow = effects.dropShadow?.[0];
206
+ const innerShadow = effects.innerShadow?.[0];
207
+ const outerGlow = effects.outerGlow;
208
+ const innerGlow = effects.innerGlow;
209
+ const bevel = effects.bevel;
210
+ const solidFill = effects.solidFill?.[0];
211
+
212
+ let count = 1;
213
+ if (dropShadow) count++;
214
+ if (innerShadow) count++;
215
+ if (outerGlow) count++;
216
+ if (innerGlow) count++;
217
+ if (bevel) count++;
218
+ if (solidFill) count++;
219
+
220
+ writeUint16(writer, 0);
221
+ writeUint16(writer, count);
222
+
223
+ writeSignature(writer, '8BIM');
224
+ writeSignature(writer, 'cmnS');
225
+ writeUint32(writer, 7); // size
226
+ writeUint32(writer, 0); // version
227
+ writeUint8(writer, 1); // visible
228
+ writeZeros(writer, 2);
229
+
230
+ if (dropShadow) {
231
+ writeSignature(writer, '8BIM');
232
+ writeSignature(writer, 'dsdw');
233
+ writeShadowInfo(writer, dropShadow);
234
+ }
235
+
236
+ if (innerShadow) {
237
+ writeSignature(writer, '8BIM');
238
+ writeSignature(writer, 'isdw');
239
+ writeShadowInfo(writer, innerShadow);
240
+ }
241
+
242
+ if (outerGlow) {
243
+ writeSignature(writer, '8BIM');
244
+ writeSignature(writer, 'oglw');
245
+ writeUint32(writer, 42);
246
+ writeUint32(writer, 2);
247
+ writeFixedPoint32(writer, outerGlow.size?.value || 0);
248
+ writeFixedPoint32(writer, 0); // intensity
249
+ writeColor(writer, outerGlow.color);
250
+ writeBlendMode(writer, outerGlow.blendMode);
251
+ writeUint8(writer, outerGlow.enabled ? 1 : 0);
252
+ writeFixedPoint8(writer, outerGlow.opacity || 0);
253
+ writeColor(writer, outerGlow.color);
254
+ }
255
+
256
+ if (innerGlow) {
257
+ writeSignature(writer, '8BIM');
258
+ writeSignature(writer, 'iglw');
259
+ writeUint32(writer, 43);
260
+ writeUint32(writer, 2);
261
+ writeFixedPoint32(writer, innerGlow.size?.value || 0);
262
+ writeFixedPoint32(writer, 0); // intensity
263
+ writeColor(writer, innerGlow.color);
264
+ writeBlendMode(writer, innerGlow.blendMode);
265
+ writeUint8(writer, innerGlow.enabled ? 1 : 0);
266
+ writeFixedPoint8(writer, innerGlow.opacity || 0);
267
+ writeUint8(writer, 0); // inverted
268
+ writeColor(writer, innerGlow.color);
269
+ }
270
+
271
+ if (bevel) {
272
+ writeSignature(writer, '8BIM');
273
+ writeSignature(writer, 'bevl');
274
+ writeUint32(writer, 78);
275
+ writeUint32(writer, 2);
276
+ writeFixedPoint32(writer, bevel.angle || 0);
277
+ writeFixedPoint32(writer, bevel.strength || 0);
278
+ writeFixedPoint32(writer, bevel.size?.value || 0);
279
+ writeBlendMode(writer, bevel.highlightBlendMode);
280
+ writeBlendMode(writer, bevel.shadowBlendMode);
281
+ writeColor(writer, bevel.highlightColor);
282
+ writeColor(writer, bevel.shadowColor);
283
+ const style = bevelStyles.indexOf(bevel.style!);
284
+ writeUint8(writer, style <= 0 ? 1 : style);
285
+ writeFixedPoint8(writer, bevel.highlightOpacity || 0);
286
+ writeFixedPoint8(writer, bevel.shadowOpacity || 0);
287
+ writeUint8(writer, bevel.enabled ? 1 : 0);
288
+ writeUint8(writer, bevel.useGlobalLight ? 1 : 0);
289
+ writeUint8(writer, bevel.direction === 'down' ? 1 : 0);
290
+ writeColor(writer, bevel.highlightColor);
291
+ writeColor(writer, bevel.shadowColor);
292
+ }
293
+
294
+ if (solidFill) {
295
+ writeSignature(writer, '8BIM');
296
+ writeSignature(writer, 'sofi');
297
+ writeUint32(writer, 34);
298
+ writeUint32(writer, 2);
299
+ writeBlendMode(writer, solidFill.blendMode);
300
+ writeColor(writer, solidFill.color);
301
+ writeFixedPoint8(writer, solidFill.opacity || 0);
302
+ writeUint8(writer, solidFill.enabled ? 1 : 0);
303
+ writeColor(writer, solidFill.color);
304
+ }
305
+ }
@@ -0,0 +1,359 @@
1
+ function isWhitespace(char: number) {
2
+ // ' ', '\n', '\r', '\t'
3
+ return char === 32 || char === 10 || char === 13 || char === 9;
4
+ }
5
+
6
+ function isNumber(char: number) {
7
+ // 0123456789.-
8
+ return (char >= 48 && char <= 57) || char === 46 || char === 45;
9
+ }
10
+
11
+ export function parseEngineData(data: number[] | Uint8Array) {
12
+ let index = 0;
13
+
14
+ function skipWhitespace() {
15
+ while (index < data.length && isWhitespace(data[index])) {
16
+ index++;
17
+ }
18
+ }
19
+
20
+ function getTextByte() {
21
+ let byte = data[index];
22
+ index++;
23
+
24
+ if (byte === 92) { // \
25
+ byte = data[index];
26
+ index++;
27
+ }
28
+
29
+ return byte;
30
+ }
31
+
32
+ function getText() {
33
+ let result = '';
34
+
35
+ if (data[index] === 41) { // )
36
+ index++;
37
+ return result;
38
+ }
39
+
40
+ // Strings start with utf-16 BOM
41
+ if (data[index] !== 0xFE || data[index + 1] !== 0xFF) {
42
+ throw new Error('Invalid utf-16 BOM');
43
+ }
44
+
45
+ index += 2;
46
+
47
+ // ), ( and \ characters are escaped in ascii manner, remove the escapes before interpreting
48
+ // the bytes as utf-16
49
+ while (index < data.length && data[index] !== 41) { // )
50
+ const high = getTextByte();
51
+ const low = getTextByte();
52
+ const char = (high << 8) | low;
53
+ result += String.fromCharCode(char);
54
+ }
55
+
56
+ index++;
57
+ return result;
58
+ }
59
+
60
+ let root: any = null;
61
+ const stack: any[] = [];
62
+
63
+ function pushContainer(value: any) {
64
+ if (!stack.length) {
65
+ stack.push(value);
66
+ root = value;
67
+ } else {
68
+ pushValue(value);
69
+ stack.push(value);
70
+ }
71
+ }
72
+
73
+ function pushValue(value: any) {
74
+ if (!stack.length) throw new Error('Invalid data');
75
+
76
+ const top = stack[stack.length - 1];
77
+
78
+ if (typeof top === 'string') {
79
+ stack[stack.length - 2][top] = value;
80
+ pop();
81
+ } else if (Array.isArray(top)) {
82
+ top.push(value);
83
+ } else {
84
+ throw new Error('Invalid data');
85
+ }
86
+ }
87
+
88
+ function pushProperty(name: string) {
89
+ if (!stack.length) pushContainer({});
90
+
91
+ const top = stack[stack.length - 1];
92
+
93
+ if (top && typeof top === 'string') {
94
+ if (name === 'nil') {
95
+ pushValue(null);
96
+ } else {
97
+ pushValue(`/${name}`);
98
+ }
99
+ } else if (top && typeof top === 'object') {
100
+ stack.push(name);
101
+ } else {
102
+ throw new Error('Invalid data');
103
+ }
104
+ }
105
+
106
+ function pop() {
107
+ if (!stack.length) throw new Error('Invalid data');
108
+ stack.pop();
109
+ }
110
+
111
+ skipWhitespace();
112
+
113
+ while (index < data.length) {
114
+ const i = index;
115
+ const char = data[i];
116
+
117
+ if (char === 60 && data[i + 1] === 60) { // <<
118
+ index += 2;
119
+ pushContainer({});
120
+ } else if (char === 62 && data[i + 1] === 62) { // >>
121
+ index += 2;
122
+ pop();
123
+ } else if (char === 47) { // /
124
+ index += 1;
125
+ const start = index;
126
+
127
+ while (index < data.length && !isWhitespace(data[index])) {
128
+ index++;
129
+ }
130
+
131
+ let name = '';
132
+
133
+ for (let i = start; i < index; i++) {
134
+ name += String.fromCharCode(data[i]);
135
+ }
136
+
137
+ pushProperty(name);
138
+ } else if (char === 40) { // (
139
+ index += 1;
140
+ pushValue(getText());
141
+ } else if (char === 91) { // [
142
+ index += 1;
143
+ pushContainer([]);
144
+ } else if (char === 93) { // ]
145
+ index += 1;
146
+ pop();
147
+ } else if (char === 110 && data[i + 1] === 117 && data[i + 2] === 108 && data[i + 3] === 108) { // null
148
+ index += 4;
149
+ pushValue(null);
150
+ } else if (char === 116 && data[i + 1] === 114 && data[i + 2] === 117 && data[i + 3] === 101) { // true
151
+ index += 4;
152
+ pushValue(true);
153
+ } else if (char === 102 && data[i + 1] === 97 && data[i + 2] === 108 && data[i + 3] === 115 && data[i + 4] === 101) { // false
154
+ index += 5;
155
+ pushValue(false);
156
+ } else if (isNumber(char)) {
157
+ let value = '';
158
+
159
+ while (index < data.length && isNumber(data[index])) {
160
+ value += String.fromCharCode(data[index]);
161
+ index++;
162
+ }
163
+
164
+ pushValue(parseFloat(value));
165
+ } else {
166
+ index += 1;
167
+ console.log(`Invalid token ${String.fromCharCode(char)} at ${index}`);
168
+ // ` near ${String.fromCharCode.apply(null, data.slice(index - 10, index + 20) as any)}` +
169
+ // `data [${Array.from(data.slice(index - 10, index + 20)).join(', ')}]`
170
+ }
171
+
172
+ skipWhitespace();
173
+ }
174
+
175
+ return root;
176
+ }
177
+
178
+ const floatKeys = [
179
+ 'Axis', 'XY', 'Zone', 'WordSpacing', 'FirstLineIndent', 'GlyphSpacing', 'StartIndent', 'EndIndent', 'SpaceBefore',
180
+ 'SpaceAfter', 'LetterSpacing', 'Values', 'GridSize', 'GridLeading', 'PointBase', 'BoxBounds', 'TransformPoint0', 'TransformPoint1',
181
+ 'TransformPoint2', 'FontSize', 'Leading', 'HorizontalScale', 'VerticalScale', 'BaselineShift', 'Tsume',
182
+ 'OutlineWidth', 'AutoLeading',
183
+ ];
184
+
185
+ const intArrays = ['RunLengthArray'];
186
+
187
+ // TODO: handle /nil
188
+ export function serializeEngineData(data: any, condensed = false) {
189
+ let buffer = new Uint8Array(1024);
190
+ let offset = 0;
191
+ let indent = 0;
192
+
193
+ function write(value: number) {
194
+ if (offset >= buffer.length) {
195
+ const newBuffer = new Uint8Array(buffer.length * 2);
196
+ newBuffer.set(buffer);
197
+ buffer = newBuffer;
198
+ }
199
+
200
+ buffer[offset] = value;
201
+ offset++;
202
+ }
203
+
204
+ function writeString(value: string) {
205
+ for (let i = 0; i < value.length; i++) {
206
+ write(value.charCodeAt(i));
207
+ }
208
+ }
209
+
210
+ function writeIndent() {
211
+ if (condensed) {
212
+ writeString(' ');
213
+ } else {
214
+ for (let i = 0; i < indent; i++) {
215
+ writeString('\t');
216
+ }
217
+ }
218
+ }
219
+
220
+ function writeProperty(key: string, value: any) {
221
+ writeIndent();
222
+ writeString(`/${key}`);
223
+ writeValue(value, key, true);
224
+ if (!condensed) writeString('\n');
225
+ }
226
+
227
+ function serializeInt(value: number) {
228
+ return value.toString();
229
+ }
230
+
231
+ function serializeFloat(value: number) {
232
+ return value.toFixed(5)
233
+ .replace(/(\d)0+$/g, '$1')
234
+ .replace(/^0+\.([1-9])/g, '.$1')
235
+ .replace(/^-0+\.0(\d)/g, '-.0$1');
236
+ }
237
+
238
+ function serializeNumber(value: number, key?: string) {
239
+ const isFloat = (key && floatKeys.indexOf(key) !== -1) || (value | 0) !== value;
240
+ return isFloat ? serializeFloat(value) : serializeInt(value);
241
+ }
242
+
243
+ function getKeys(value: any) {
244
+ const keys = Object.keys(value);
245
+
246
+ if (keys.indexOf('98') !== -1)
247
+ keys.unshift(...keys.splice(keys.indexOf('99'), 1));
248
+
249
+ if (keys.indexOf('99') !== -1)
250
+ keys.unshift(...keys.splice(keys.indexOf('99'), 1));
251
+
252
+ return keys;
253
+ }
254
+
255
+ function writeStringByte(value: number) {
256
+ if (value === 40 || value === 41 || value === 92) { // ( ) \
257
+ write(92); // \
258
+ }
259
+
260
+ write(value);
261
+ }
262
+
263
+ function writeValue(value: any, key?: string, inProperty = false) {
264
+ function writePrefix() {
265
+ if (inProperty) {
266
+ writeString(' ');
267
+ } else {
268
+ writeIndent();
269
+ }
270
+ }
271
+
272
+ if (value === null) {
273
+ writePrefix();
274
+ writeString(condensed ? '/nil' : 'null');
275
+ } else if (typeof value === 'number') {
276
+ writePrefix();
277
+ writeString(serializeNumber(value, key));
278
+ } else if (typeof value === 'boolean') {
279
+ writePrefix();
280
+ writeString(value ? 'true' : 'false');
281
+ } else if (typeof value === 'string') {
282
+ writePrefix();
283
+
284
+ if ((key === '99' || key === '98') && value.charAt(0) === '/') {
285
+ writeString(value);
286
+ } else {
287
+ writeString('(');
288
+ write(0xfe);
289
+ write(0xff);
290
+
291
+ for (let i = 0; i < value.length; i++) {
292
+ const code = value.charCodeAt(i);
293
+ writeStringByte((code >> 8) & 0xff);
294
+ writeStringByte(code & 0xff);
295
+ }
296
+
297
+ writeString(')');
298
+ }
299
+ } else if (Array.isArray(value)) {
300
+ writePrefix();
301
+
302
+ if (value.every(x => typeof x === 'number')) {
303
+ writeString('[');
304
+
305
+ const intArray = intArrays.indexOf(key!) !== -1;
306
+
307
+ for (const x of value) {
308
+ writeString(' ');
309
+ writeString(intArray ? serializeNumber(x) : serializeFloat(x));
310
+ }
311
+
312
+ writeString(' ]');
313
+ } else {
314
+ writeString('[');
315
+ if (!condensed) writeString('\n');
316
+
317
+ for (const x of value) {
318
+ writeValue(x, key);
319
+ if (!condensed) writeString('\n');
320
+ }
321
+
322
+ writeIndent();
323
+ writeString(']');
324
+ }
325
+ } else if (typeof value === 'object') {
326
+ if (inProperty && !condensed) writeString('\n');
327
+
328
+ writeIndent();
329
+ writeString('<<');
330
+
331
+ if (!condensed) writeString('\n');
332
+
333
+ indent++;
334
+
335
+ for (const key of getKeys(value)) {
336
+ writeProperty(key, value[key]);
337
+ }
338
+
339
+ indent--;
340
+ writeIndent();
341
+ writeString('>>');
342
+ }
343
+
344
+ return undefined;
345
+ }
346
+
347
+ if (condensed) {
348
+ if (typeof data === 'object') {
349
+ for (const key of getKeys(data)) {
350
+ writeProperty(key, data[key]);
351
+ }
352
+ }
353
+ } else {
354
+ writeString('\n\n');
355
+ writeValue(data);
356
+ }
357
+
358
+ return buffer.slice(0, offset);
359
+ }