@ntlab/ntjs-assets 2.68.0 → 2.70.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/assets/js/DataTables/Extensions/FixedHeader/js/dataTables.fixedHeader.js +43 -13
  2. package/assets/js/DataTables/Extensions/FixedHeader/js/dataTables.fixedHeader.min.js +2 -2
  3. package/assets/js/DataTables/Extensions/FixedHeader/js/fixedHeader.bootstrap5.min.js +1 -1
  4. package/assets/js/DataTables/Extensions/FixedHeader/js/fixedHeader.dataTables.min.js +1 -1
  5. package/assets/js/bootstrap/css/bootstrap-grid.css +1 -1
  6. package/assets/js/bootstrap/css/bootstrap-grid.css.map +1 -1
  7. package/assets/js/bootstrap/css/bootstrap-grid.min.css +1 -1
  8. package/assets/js/bootstrap/css/bootstrap-grid.min.css.map +1 -1
  9. package/assets/js/bootstrap/css/bootstrap-grid.rtl.css +1 -1
  10. package/assets/js/bootstrap/css/bootstrap-grid.rtl.css.map +1 -1
  11. package/assets/js/bootstrap/css/bootstrap-grid.rtl.min.css +1 -1
  12. package/assets/js/bootstrap/css/bootstrap-grid.rtl.min.css.map +1 -1
  13. package/assets/js/bootstrap/css/bootstrap-reboot.css +1 -1
  14. package/assets/js/bootstrap/css/bootstrap-reboot.css.map +1 -1
  15. package/assets/js/bootstrap/css/bootstrap-reboot.min.css +1 -1
  16. package/assets/js/bootstrap/css/bootstrap-reboot.min.css.map +1 -1
  17. package/assets/js/bootstrap/css/bootstrap-reboot.rtl.css +1 -1
  18. package/assets/js/bootstrap/css/bootstrap-reboot.rtl.css.map +1 -1
  19. package/assets/js/bootstrap/css/bootstrap-reboot.rtl.min.css +1 -1
  20. package/assets/js/bootstrap/css/bootstrap-reboot.rtl.min.css.map +1 -1
  21. package/assets/js/bootstrap/css/bootstrap-utilities.css +1 -1
  22. package/assets/js/bootstrap/css/bootstrap-utilities.css.map +1 -1
  23. package/assets/js/bootstrap/css/bootstrap-utilities.min.css +1 -1
  24. package/assets/js/bootstrap/css/bootstrap-utilities.min.css.map +1 -1
  25. package/assets/js/bootstrap/css/bootstrap-utilities.rtl.css +1 -1
  26. package/assets/js/bootstrap/css/bootstrap-utilities.rtl.css.map +1 -1
  27. package/assets/js/bootstrap/css/bootstrap-utilities.rtl.min.css +1 -1
  28. package/assets/js/bootstrap/css/bootstrap-utilities.rtl.min.css.map +1 -1
  29. package/assets/js/bootstrap/css/bootstrap.css +1 -1
  30. package/assets/js/bootstrap/css/bootstrap.css.map +1 -1
  31. package/assets/js/bootstrap/css/bootstrap.min.css +1 -1
  32. package/assets/js/bootstrap/css/bootstrap.min.css.map +1 -1
  33. package/assets/js/bootstrap/css/bootstrap.rtl.css +1 -1
  34. package/assets/js/bootstrap/css/bootstrap.rtl.css.map +1 -1
  35. package/assets/js/bootstrap/css/bootstrap.rtl.min.css +1 -1
  36. package/assets/js/bootstrap/css/bootstrap.rtl.min.css.map +1 -1
  37. package/assets/js/bootstrap/js/bootstrap.bundle.js +3 -3
  38. package/assets/js/bootstrap/js/bootstrap.bundle.js.map +1 -1
  39. package/assets/js/bootstrap/js/bootstrap.bundle.min.js +2 -2
  40. package/assets/js/bootstrap/js/bootstrap.bundle.min.js.map +1 -1
  41. package/assets/js/bootstrap/js/bootstrap.esm.js +3 -3
  42. package/assets/js/bootstrap/js/bootstrap.esm.js.map +1 -1
  43. package/assets/js/bootstrap/js/bootstrap.esm.min.js +2 -2
  44. package/assets/js/bootstrap/js/bootstrap.esm.min.js.map +1 -1
  45. package/assets/js/bootstrap/js/bootstrap.js +3 -3
  46. package/assets/js/bootstrap/js/bootstrap.js.map +1 -1
  47. package/assets/js/bootstrap/js/bootstrap.min.js +2 -2
  48. package/assets/js/bootstrap/js/bootstrap.min.js.map +1 -1
  49. package/assets/js/cdn.json +6 -2
  50. package/assets/js/exif-js/exif.js +1059 -0
  51. package/assets/js/exif-js/exif.min.js +8 -0
  52. package/assets/js/metadata/metadata.js +1684 -0
  53. package/package.json +1 -1
@@ -0,0 +1,1684 @@
1
+ //
2
+ // Copyright (c) 2015 Thomas Dideriksen
3
+ //
4
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ // of this software and associated documentation files (the "Software"), to deal
6
+ // in the Software without restriction, including without limitation the rights
7
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ // copies of the Software, and to permit persons to whom the Software is
9
+ // furnished to do so, subject to the following conditions:
10
+ //
11
+ // The above copyright notice and this permission notice shall be included in
12
+ // all copies or substantial portions of the Software.
13
+ //
14
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ // THE SOFTWARE.
21
+ //
22
+
23
+ /*jshint bitwise: false*/
24
+
25
+ var MD = {};
26
+
27
+ //
28
+ // Endian indicators
29
+ //
30
+ MD.LITTLE_ENDIAN = 0;
31
+ MD.BIG_ENDIAN = 1;
32
+
33
+ //
34
+ // Jpeg markers
35
+ //
36
+ MD.JPEG_MARKER_SOI = 0xd8;
37
+ MD.JPEG_MARKER_SOS = 0xda;
38
+ MD.JPEG_MARKER_APP0 = 0xe0;
39
+ MD.JPEG_MARKER_APP1 = 0xe1;
40
+ MD.JPEG_MARKER_APP2 = 0xe2;
41
+ MD.JPEG_MARKER_APP13 = 0xed;
42
+
43
+ //
44
+ // Jpeg headers
45
+ //
46
+ MD.JPEG_HEADER_EXIF = [0x45, 0x78, 0x69, 0x66, 0x0, 0x0];
47
+ MD.JPEG_HEADER_JFIF = [0x4A, 0x46, 0x49, 0x46, 0x0];
48
+ MD.JPEG_HEADER_JFXX = [0x4A, 0x46, 0x49, 0x46, 0x0];
49
+ MD.JPEG_HEADER_ICCPROFILE = [0x49, 0x43, 0x43, 0x5F, 0x50, 0x52, 0x4F, 0x46, 0x49, 0x4C, 0x45, 0x0];
50
+ MD.JPEG_HEADER_PHOTOSHOP_30 = [0x50, 0x68, 0x6F, 0x74, 0x6F, 0x73, 0x68, 0x6F, 0x70, 0x20, 0x33, 0x2E, 0x30, 0x0];
51
+
52
+ //
53
+ // Tiff magic values
54
+ //
55
+ MD.TIFF_LITTLE_ENDIAN = 0x4949;
56
+ MD.TIFF_BIG_ENDIAN = 0x4d4d;
57
+ MD.TIFF_MAGIC = 42;
58
+
59
+ //
60
+ // Tiff data types
61
+ //
62
+ MD.TIFF_TYPE_BYTE = 1;
63
+ MD.TIFF_TYPE_ASCII = 2;
64
+ MD.TIFF_TYPE_SHORT = 3;
65
+ MD.TIFF_TYPE_LONG = 4;
66
+ MD.TIFF_TYPE_RATIONAL = 5;
67
+ MD.TIFF_TYPE_SBYTE = 6;
68
+ MD.TIFF_TYPE_UNDEFINED = 7;
69
+ MD.TIFF_TYPE_SSHORT = 8;
70
+ MD.TIFF_TYPE_SLONG = 9;
71
+ MD.TIFF_TYPE_SRATIONAL = 10;
72
+ MD.TIFF_TYPE_FLOAT = 11;
73
+ MD.TIFF_TYPE_DOUBLE = 12;
74
+ MD.TIFF_TYPE_IFD = 13;
75
+
76
+ //
77
+ // Tiff tag IDs
78
+ //
79
+ MD.TIFF_ID_EXIFIFD = 0x8769;
80
+ MD.TIFF_ID_GPSIFD = 0x8825;
81
+ MD.TIFF_ID_INTEROPERABILITYIFD = 0xA005;
82
+ MD.TIFF_ID_SUBIFDS = 0x014A;
83
+ MD.TIFF_ID_JPEGINTERCHANGEFORMAT = 0x0201;
84
+ MD.TIFF_ID_JPEGINTERCHANGEFORMATLENGTH = 0x0202;
85
+ MD.TIFF_ID_STRIPOFFSETS = 0x0111;
86
+ MD.TIFF_ID_STRIPBYTECOUNTS = 0x0117;
87
+ MD.TIFF_ID_TILEOFFSETS = 0x0144;
88
+ MD.TIFF_ID_TILEBYTECOUNTS = 0x0145;
89
+ MD.TIFF_ID_RICHTIFFIPTC = 0x83BB;
90
+
91
+ //
92
+ // Photoshop resource constants
93
+ //
94
+ MD.PHOTOSHOP_8BIM = 0x3842494D;
95
+
96
+ //
97
+ // Photoshop tag IDs
98
+ //
99
+ MD.PHOTOSHOP_ID_THUMB4 = 0x0409;
100
+ MD.PHOTOSHOP_ID_THUMB5 = 0x040C;
101
+
102
+ //
103
+ // Known tag pairs - these tag pairs indicates the position and
104
+ // size of a data payload. The codec must know about these in order
105
+ // to properly extract and re-insert this data.
106
+ //
107
+ MD.KNOWN_PAIRS = {
108
+ jpeginterchangeformat: {
109
+ positionId: MD.TIFF_ID_JPEGINTERCHANGEFORMAT,
110
+ lengthId: MD.TIFF_ID_JPEGINTERCHANGEFORMATLENGTH
111
+ },
112
+ strips: {
113
+ positionId: MD.TIFF_ID_STRIPOFFSETS,
114
+ lengthId: MD.TIFF_ID_STRIPBYTECOUNTS
115
+ },
116
+ tiles: {
117
+ positionId: MD.TIFF_ID_TILEOFFSETS,
118
+ lengthId: MD.TIFF_ID_TILEBYTECOUNTS
119
+ }
120
+ };
121
+
122
+ //
123
+ // Known sub-IFD tiff tags - these tags points to one (or multiple)
124
+ // sub-IFDs elsewhere in the file structure. The codec must know about
125
+ // these in order to properly encode/decode the full tag tree.
126
+ // This structure also includes human readable names for adressing purposes.
127
+ //
128
+ MD.KNOWN_SUBIFDS = {
129
+ exif: MD.TIFF_ID_EXIFIFD,
130
+ gps: MD.TIFF_ID_GPSIFD,
131
+ interoperability: MD.TIFF_ID_INTEROPERABILITYIFD,
132
+ subifds: MD.TIFF_ID_SUBIFDS
133
+ };
134
+
135
+ //
136
+ // Helper function for checking the validity of an expression
137
+ //
138
+ MD.check = function(expr, msg) {
139
+ 'use strict';
140
+ if (!expr) {
141
+ throw msg;
142
+ }
143
+ };
144
+
145
+ //
146
+ // Helper function for fetching binary data using XHR/AJAX
147
+ //
148
+ MD.get = function(url, success, failure) {
149
+ 'use strict';
150
+ var xhr = new XMLHttpRequest();
151
+ xhr.onreadystatechange = function() {
152
+ if (xhr.readyState == 4) {
153
+ if (xhr.status == 200) {
154
+ if (success) {
155
+ success(xhr.response);
156
+ }
157
+ } else {
158
+ if (failure) {
159
+ failure();
160
+ }
161
+ }
162
+ }
163
+ };
164
+ xhr.open('GET', url, true);
165
+ xhr.responseType = 'arraybuffer';
166
+ xhr.send();
167
+ };
168
+
169
+ //
170
+ // Base64 encodes the specified ArrayBuffer
171
+ //
172
+ MD.encodeBase64 = function(buffer) {
173
+ 'use strict';
174
+ var buffer8 = new Uint8Array(buffer);
175
+ var table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
176
+ var extraBytes = buffer8.length % 3;
177
+ var temp, result = '';
178
+ // Helper functions
179
+ var lookup = function(num) {
180
+ return table.charAt(num);
181
+ };
182
+ var encode = function(num) {
183
+ return lookup(num >> 18 & 0x3F) +
184
+ lookup(num >> 12 & 0x3F) +
185
+ lookup(num >> 6 & 0x3F) +
186
+ lookup(num & 0x3F);
187
+ };
188
+ // Iterate through buffer, 3 bytes at the time
189
+ for (var i = 0; i < (buffer8.length - extraBytes); i += 3) {
190
+ temp = (buffer8[i] << 16) +
191
+ (buffer8[i + 1] << 8) +
192
+ (buffer8[i + 2]);
193
+ result += encode(temp);
194
+ }
195
+ // Handle remaining bytes
196
+ switch (extraBytes) {
197
+ case 1:
198
+ temp = buffer8[buffer.length - 1];
199
+ result += lookup(temp >> 2);
200
+ result += lookup((temp << 4) & 0x3F);
201
+ result += '==';
202
+ break;
203
+ case 2:
204
+ temp = (buffer8[buffer8.length - 2] << 8) + (buffer8[buffer8.length - 1]);
205
+ result += lookup(temp >> 10);
206
+ result += lookup((temp >> 4) & 0x3F);
207
+ result += lookup((temp << 2) & 0x3F);
208
+ result += '=';
209
+ break;
210
+ default:
211
+ break;
212
+ }
213
+ return result;
214
+ };
215
+
216
+ //
217
+ // Create data URL from ArrayBuffer with specified mimetype (mimetype example: 'image/jpeg')
218
+ //
219
+ MD.toDataURL = function(buffer, mimetype) {
220
+ 'use strict';
221
+ return 'data:' + mimetype + ';base64,' + MD.encodeBase64(buffer);
222
+ };
223
+
224
+ //
225
+ // Binary reader class
226
+ //
227
+ MD.BinaryReader = function(buffer, endian) {
228
+ 'use strict';
229
+ this._view = new DataView(buffer);
230
+ this.endian = endian ? endian : MD.LITTLE_ENDIAN;
231
+ this.position = 0;
232
+ this.length = buffer.byteLength;
233
+ };
234
+
235
+ MD.BinaryReader.prototype = {
236
+ constructor: MD.BinaryReader,
237
+
238
+ //
239
+ // Generic reader function
240
+ //
241
+ readGeneric: function(fn, subSize, subCount, count) {
242
+ 'use strict';
243
+ var result = [];
244
+ for (var i = 0; i < count; i++) {
245
+ var item = [];
246
+ for (var j = 0; j < subCount; j++) {
247
+ var subItem = this._view[fn](this.position, (this.endian == MD.LITTLE_ENDIAN));
248
+ this.position += subSize;
249
+ item.push(subItem);
250
+ }
251
+ result.push((item.length == 1) ? item[0] : item);
252
+ }
253
+ return (result.length == 1) ? result[0] : result;
254
+ },
255
+
256
+ //
257
+ // Type-specific reader functions
258
+ //
259
+ read8u: function() { 'use strict'; return this.readGeneric('getUint8', 1, 1, 1); },
260
+ read8s: function() { 'use strict'; return this.readGeneric('getInt8', 1, 1, 1); },
261
+ read16u: function() { 'use strict'; return this.readGeneric('getUint16', 2, 1, 1); },
262
+ read16s: function() { 'use strict'; return this.readGeneric('getInt16', 2, 1, 1); },
263
+ read32u: function() { 'use strict'; return this.readGeneric('getUint32', 4, 1, 1); },
264
+ read32s: function() { 'use strict'; return this.readGeneric('getInt32', 4, 1, 1); },
265
+ read32f: function() { 'use strict'; return this.readGeneric('getFloat32', 4, 1, 1); },
266
+ read64f: function() { 'use strict'; return this.readGeneric('getFloat64', 8, 1, 1); },
267
+
268
+ //
269
+ // Read the specified number of bytes and return the result as an ArrayBuffer
270
+ //
271
+ read: function(size) {
272
+ 'use strict';
273
+ var result = this._view.buffer.slice(this.position, this.position + size);
274
+ this.position += size;
275
+ return result;
276
+ },
277
+
278
+ //
279
+ // Read the remaing bytes in the stream and return the result as an ArrayBuffer
280
+ //
281
+ readRemaining: function() {
282
+ 'use strict';
283
+ var result = this.read(this.length - this.position);
284
+ this.position = this.length;
285
+ return result;
286
+ }
287
+ };
288
+
289
+ //
290
+ // Binary writer class
291
+ //
292
+ MD.BinaryWriter = function(buffer, endian) {
293
+ 'use strict';
294
+ this.position = 0;
295
+ this.endian = endian ? endian : MD.LITTLE_ENDIAN;
296
+ this.buffer = buffer;
297
+ this._view = new DataView(buffer);
298
+ };
299
+
300
+ MD.BinaryWriter.prototype = {
301
+ constructor: MD.BinaryWriter,
302
+
303
+ //
304
+ // Generic writer function
305
+ //
306
+ writeGeneric: function(fn, subSize, subCount, count, value) {
307
+ 'use strict';
308
+ var items = (count == 1) ? [value] : value;
309
+ MD.check(items.length == count, 'Invalid tag data size');
310
+ for (var i = 0; i < items.length; i++) {
311
+ var item = (items[i] instanceof Array) ? items[i] : [items[i]];
312
+ MD.check(item.length == subCount, 'Invalid tag data size');
313
+ for (var j = 0; j < item.length; j++) {
314
+ this._view[fn](this.position, item[j], (this.endian == MD.LITTLE_ENDIAN));
315
+ this.position += subSize;
316
+ }
317
+ }
318
+ },
319
+
320
+ //
321
+ // Type specific writer functions
322
+ //
323
+ write8u: function(val) { 'use strict'; this.writeGeneric('setUint8', 1, 1, 1, val); },
324
+ write8s: function(val) { 'use strict'; this.writeGeneric('setInt8', 1, 1, 1, val); },
325
+ write16u: function(val) { 'use strict'; this.writeGeneric('setUint16', 2, 1, 1, val); },
326
+ write16s: function(val) { 'use strict'; this.writeGeneric('setInt16', 2, 1, 1, val); },
327
+ write32u: function(val) { 'use strict'; this.writeGeneric('setUint32', 4, 1, 1, val); },
328
+ write32s: function(val) { 'use strict'; this.writeGeneric('setInt32', 4, 1, 1, val); },
329
+ write32f: function(val) { 'use strict'; this.writeGeneric('setFloat32', 4, 1, 1, val); },
330
+ write64f: function(val) { 'use strict'; this.writeGeneric('setFloat64', 8, 1, 1, val); },
331
+
332
+ //
333
+ // Write ArrayBuffer
334
+ //
335
+ write: function(buf) {
336
+ 'use strict';
337
+ var u8 = new Uint8Array(this._view.buffer);
338
+ u8.set(new Uint8Array(buf), this.position);
339
+ this.position += buf.byteLength;
340
+ }
341
+ };
342
+
343
+ //
344
+ // Jpeg metadata codec class
345
+ //
346
+ MD.JpegResource = function(buffer) {
347
+ 'use strict';
348
+ this._segments = [];
349
+ this._parse(buffer);
350
+ };
351
+
352
+ MD.JpegResource.prototype = {
353
+
354
+ //
355
+ // PUBLIC METHODS
356
+ //
357
+
358
+ constructor: MD.JpegResource,
359
+
360
+ //
361
+ // Get 'Photoshop 3.0' buffer
362
+ //
363
+ get photoshopBuffer() {
364
+ 'use strict';
365
+ return this._getSegmentDataSingle(MD.JPEG_MARKER_APP13, MD.JPEG_HEADER_PHOTOSHOP_30);
366
+ },
367
+
368
+ //
369
+ // Set 'Photoshop 3.0' buffer
370
+ //
371
+ set photoshopBuffer(buffer) {
372
+ 'use strict';
373
+ this._removeSegments(MD.JPEG_MARKER_APP13, MD.JPEG_HEADER_PHOTOSHOP_30);
374
+ if (!buffer) {
375
+ return;
376
+ }
377
+ var photoshopSegment = this._createSegment(MD.JPEG_MARKER_APP13, MD.JPEG_HEADER_PHOTOSHOP_30, buffer);
378
+ var insertIdx = this._lastSegmentIndex([MD.JPEG_MARKER_APP0, MD.JPEG_MARKER_APP1]) + 1;
379
+ this._segments.splice(insertIdx, 0, photoshopSegment);
380
+ },
381
+
382
+ //
383
+ // Get EXIF buffer
384
+ //
385
+ get exifBuffer() {
386
+ 'use strict';
387
+ return this._getSegmentDataSingle(MD.JPEG_MARKER_APP1, MD.JPEG_HEADER_EXIF);
388
+ },
389
+
390
+ //
391
+ // Set EXIF buffer
392
+ //
393
+ set exifBuffer(buffer) {
394
+ 'use strict';
395
+ this._removeSegments(MD.JPEG_MARKER_APP1, MD.JPEG_HEADER_EXIF);
396
+ if (!buffer) {
397
+ return;
398
+ }
399
+ var exifSegment = this._createSegment(MD.JPEG_MARKER_APP1, MD.JPEG_HEADER_EXIF, buffer);
400
+ this._removeSegments(MD.JPEG_MARKER_APP0, MD.JPEG_HEADER_JFIF);
401
+ this._removeSegments(MD.JPEG_MARKER_APP0, MD.JPEG_HEADER_JFXX);
402
+ this._segments.unshift(exifSegment);
403
+ },
404
+
405
+ //
406
+ // Get embedded ICC profile
407
+ //
408
+ get iccProfileBuffer() {
409
+ 'use strict';
410
+ // Get ICC segments
411
+ var iccSegments = this._findSegments(MD.JPEG_MARKER_APP2, MD.JPEG_HEADER_ICCPROFILE);
412
+ if (iccSegments.length === 0) {
413
+ return undefined;
414
+ }
415
+ // Sort segments by sequence number (in ascending order)
416
+ iccSegments.sort(function(a, b) {
417
+ var ra = new MD.BinaryReader(a.data);
418
+ var rb = new MD.BinaryReader(b.data);
419
+ ra.position = rb.position = MD.JPEG_HEADER_ICCPROFILE.length;
420
+ return ra.read8u() - rb.read8u();
421
+ });
422
+ // Compute size of embedded ICC profile - also verify that all segments are present
423
+ var i, size = 0;
424
+ var iccSegment, reader;
425
+ for (i = 0; i < iccSegments.length; i++) {
426
+ iccSegment = iccSegments[i];
427
+ size += (iccSegment.data.byteLength - MD.JPEG_HEADER_ICCPROFILE.length - 2);
428
+ reader = new MD.BinaryReader(iccSegment.data);
429
+ reader.position = MD.JPEG_HEADER_ICCPROFILE.length;
430
+ var current = reader.read8u();
431
+ var total = reader.read8u();
432
+ MD.check(current == (i + 1), 'Invalid ICC sequence number');
433
+ MD.check(total == iccSegments.length, 'Invalid ICC segment count');
434
+ }
435
+ // Assemble ICC profile buffer
436
+ var buffer = new ArrayBuffer(size);
437
+ var writer = new MD.BinaryWriter(buffer);
438
+ for (i = 0; i < iccSegments.length; i++) {
439
+ iccSegment = iccSegments[i];
440
+ reader = new MD.BinaryReader(iccSegment.data);
441
+ reader.position = MD.JPEG_HEADER_ICCPROFILE.length + 2;
442
+ writer.write(reader.readRemaining());
443
+ }
444
+ // Return ICC profile (as an ArrayBuffer)
445
+ return buffer;
446
+ },
447
+
448
+ //
449
+ // Set embedded ICC profile
450
+ //
451
+ set iccProfileBuffer(buffer) {
452
+ 'use strict';
453
+ // Remove existing ICC profile (if present)
454
+ this._removeSegments(MD.JPEG_MARKER_APP2, MD.JPEG_HEADER_ICCPROFILE);
455
+ if (!buffer) {
456
+ return;
457
+ }
458
+ // Compute the number of segments needed to embed ICC profile
459
+ var maxSize = 0xffff - MD.JPEG_HEADER_ICCPROFILE.length - 4;
460
+ var segmentCount = Math.ceil(buffer.byteLength / maxSize);
461
+ MD.check(segmentCount <= 255, 'ICC profile is too large');
462
+ // Create ICC profile segments
463
+ var reader = new MD.BinaryReader(buffer);
464
+ var iccSegments = [];
465
+ var remaining = buffer.byteLength;
466
+ var currentSegment = 1;
467
+ while (remaining > 0) {
468
+ var size = Math.min(remaining, maxSize);
469
+ remaining -= size;
470
+ var iccBuffer = new ArrayBuffer(size + MD.JPEG_HEADER_ICCPROFILE.length + 2);
471
+ var writer = new MD.BinaryWriter(iccBuffer);
472
+ writer.write(new Uint8Array(MD.JPEG_HEADER_ICCPROFILE).buffer);
473
+ writer.write8u(currentSegment++);
474
+ writer.write8u(segmentCount);
475
+ writer.write(reader.read(size));
476
+ iccSegments.push({
477
+ marker: MD.JPEG_MARKER_APP2,
478
+ data: iccBuffer
479
+ });
480
+ }
481
+ // Insert ICC profile segments in segment array
482
+ var insertIdx = this._lastSegmentIndex([MD.JPEG_MARKER_APP0, MD.JPEG_MARKER_APP1]) + 1;
483
+ for (var i = 0; i < iccSegments.length; i++) {
484
+ this._segments.splice(insertIdx, 0, iccSegments[i]);
485
+ insertIdx++;
486
+ }
487
+ },
488
+
489
+ //
490
+ // Get embedded jpeg thumbnail
491
+ //
492
+ get thumbnailBuffer() {
493
+ // First, attempt to get the thumbnail from the Photoshop segment
494
+ if (this.photoshopBuffer) {
495
+ var photoshop = new MD.PhotoshopResource(this.photoshopBuffer);
496
+ var thumb = photoshop.getTag(MD.PHOTOSHOP_ID_THUMB5);
497
+ if (!thumb) {
498
+ thumb = photoshop.getTag(MD.PHOTOSHOP_ID_THUMB4);
499
+ }
500
+ if (thumb) {
501
+ var reader = new MD.BinaryReader(thumb.data, MD.BIG_ENDIAN);
502
+ var format = reader.read32u();
503
+ reader.position += 24; // Note: Skip header
504
+ // We only support jpeg compressed thumbnails for now. (0 = uncompressed, 1 = jpeg)
505
+ if (format == 1 ) {
506
+ return reader.readRemaining();
507
+ }
508
+ }
509
+ }
510
+ // Otherwise attempt to get the legacy thumbnail from IFD1
511
+ if (this.exifBuffer) {
512
+ var exif = new MD.TiffResource(this.exifBuffer);
513
+ var data = exif.getData('/ifd[1]', 'jpeginterchangeformat');
514
+ return (data && data.length == 1) ? data[0] : undefined;
515
+ }
516
+ return undefined;
517
+ },
518
+
519
+ //
520
+ // Serialize (or save) jpeg. This function returns an ArrayBuffer
521
+ // that contains valid jpeg data.
522
+ //
523
+ save: function() {
524
+ 'use strict';
525
+ // Compute size of jpeg buffer
526
+ var i, segment, size = 2;
527
+ for (i = 0; i < this._segments.length; i++) {
528
+ segment = this._segments[i];
529
+ size += 4;
530
+ size += segment.data.byteLength;
531
+ }
532
+ size -= 2; // Note: Subtract 2 since the last segment (SOS) doesn't have a 16-bit size
533
+ // Create buffer and write segment headers/data
534
+ var buffer = new ArrayBuffer(size);
535
+ var writer = new MD.BinaryWriter(buffer, MD.BIG_ENDIAN);
536
+ writer.write8u(0xff);
537
+ writer.write8u(MD.JPEG_MARKER_SOI);
538
+ for (i = 0; i < this._segments.length; i++) {
539
+ segment = this._segments[i];
540
+ writer.write8u(0xff);
541
+ writer.write8u(segment.marker);
542
+ if (segment.marker != MD.JPEG_MARKER_SOS) {
543
+ var segmentSize = 2 + segment.data.byteLength;
544
+ MD.check(segmentSize <= 0xffff, 'Segment is too large');
545
+ writer.write16u(segmentSize);
546
+ }
547
+ writer.write(segment.data);
548
+ }
549
+ // Return serialized jpeg buffer
550
+ return buffer;
551
+ },
552
+
553
+ //
554
+ // PRIVATE METHODS
555
+ //
556
+
557
+ //
558
+ // Jpeg parser, this will extract the individual jpeg segments
559
+ //
560
+ _parse: function(buffer) {
561
+ 'use strict';
562
+ var reader = new MD.BinaryReader(buffer, MD.BIG_ENDIAN);
563
+ MD.check(reader.read8u() == 0xff, 'Invalid jpeg magic value');
564
+ MD.check(reader.read8u() == MD.JPEG_MARKER_SOI, 'Invalid jpeg SOI marker');
565
+ while (true) {
566
+ MD.check(reader.read8u() == 0xff, 'Invalid jpeg marker');
567
+ var marker = reader.read8u();
568
+ if (marker != MD.JPEG_MARKER_SOS) {
569
+ var segmentSize = reader.read16u() - 2;
570
+ MD.check(segmentSize >= 0, 'Invalid jpeg segment size');
571
+ this._segments.push({
572
+ marker: marker,
573
+ data: reader.read(segmentSize)
574
+ });
575
+ } else {
576
+ this._segments.push({
577
+ marker: marker,
578
+ data: reader.readRemaining()
579
+ });
580
+ break;
581
+ }
582
+ }
583
+ },
584
+
585
+ //
586
+ // Finds the last position (index) of the specified marker type(s)
587
+ //
588
+ _lastSegmentIndex: function(markers) {
589
+ 'use strict';
590
+ var idx = 0;
591
+ for (var i = 0; i < this._segments.length; i++) {
592
+ var segment = this._segments[i];
593
+ for (var j = 0; j < markers.length; j++) {
594
+ if (segment.marker == markers[j]) {
595
+ idx = i;
596
+ }
597
+ }
598
+ }
599
+ return idx;
600
+ },
601
+
602
+ //
603
+ // Find and return segments that matches the specified marker
604
+ // and (optionally) header
605
+ //
606
+ _findSegments: function(marker, header) {
607
+ 'use strict';
608
+ var result = [];
609
+ for (var i = 0; i < this._segments.length; i++) {
610
+ var segment = this._segments[i];
611
+ if (segment.marker == marker) {
612
+ var headerMatch = true;
613
+ if (header && header.length > 0) {
614
+ var reader = new MD.BinaryReader(segment.data);
615
+ var candidate = new Uint8Array(reader.read(header.length));
616
+ for (var j = 0; j < header.length; j++) {
617
+ if (header[j] != candidate[j]) {
618
+ headerMatch = false;
619
+ break;
620
+ }
621
+ }
622
+ }
623
+ if (headerMatch) {
624
+ result.push(segment);
625
+ }
626
+ }
627
+ }
628
+ return result;
629
+ },
630
+
631
+ //
632
+ // Remove all segments with the specified marker and (optionally) header
633
+ //
634
+ _removeSegments: function(marker, header) {
635
+ 'use strict';
636
+ var toRemove = this._findSegments(marker, header);
637
+ for (var i = 0; i < toRemove.length; i++) {
638
+ var idx = this._segments.indexOf(toRemove[i]);
639
+ this._segments.splice(idx, 1);
640
+ }
641
+ },
642
+
643
+ //
644
+ // Get the data payload from a single segment with the specified marker
645
+ // and (optionally) header. If multiple matches are found this function
646
+ // will throw an exception.
647
+ //
648
+ _getSegmentDataSingle: function(marker, header) {
649
+ 'use strict';
650
+ var segments = this._findSegments(marker, header);
651
+ switch (segments.length) {
652
+ case 0: return undefined;
653
+ case 1:
654
+ var reader = new MD.BinaryReader(segments[0].data);
655
+ reader.position = header ? header.length : 0;
656
+ return reader.readRemaining();
657
+ default: throw 'Too many segments';
658
+ }
659
+ },
660
+
661
+ //
662
+ // Create a segment buffer
663
+ //
664
+ _createSegment: function(marker, header, payload) {
665
+ 'use strict';
666
+ // Compute and verify segment size
667
+ var segmentSize = payload.byteLength + header.length;
668
+ MD.check(segmentSize <= (0xffff - 2), 'Segment buffer is too large');
669
+ // Create segment
670
+ var segmentData = new ArrayBuffer(segmentSize);
671
+ var writer = new MD.BinaryWriter(segmentData);
672
+ writer.write(new Uint8Array(header).buffer);
673
+ writer.write(payload);
674
+ var segment = {
675
+ marker: marker,
676
+ data: segmentData
677
+ };
678
+ return segment;
679
+ }
680
+ };
681
+
682
+ //
683
+ // Tiff metadata codec class
684
+ //
685
+ MD.TiffResource = function(buffer) {
686
+ 'use strict';
687
+ this._tree = [];
688
+ this._nativeEndian = MD.LITTLE_ENDIAN;
689
+ this._parse(buffer);
690
+ };
691
+
692
+ MD.TiffResource.prototype = {
693
+
694
+ //
695
+ // PUBLIC METHODS
696
+ //
697
+
698
+ constructor: MD.TiffResource,
699
+
700
+ //
701
+ // Get the tag-array that matches the specified path
702
+ //
703
+ getTags: function(path) {
704
+ 'use strict';
705
+ return this._getTagsByPath(path);
706
+ },
707
+
708
+ //
709
+ // Get the tag that matches the specified path and ID
710
+ //
711
+ getTag: function(path, id) {
712
+ 'use strict';
713
+ var tags = this._getTagsByPath(path);
714
+ if (tags) {
715
+ for (var i = 0; i < tags.length; i++) {
716
+ if (tags[i].id == id) {
717
+ return tags[i];
718
+ }
719
+ }
720
+ }
721
+ return undefined;
722
+ },
723
+
724
+ //
725
+ // Remove (delete) the tag that matches the specified path and ID
726
+ //
727
+ removeTag: function(path, id) {
728
+ 'use strict';
729
+ var tags = this._getTagsByPath(path);
730
+ this._removeTag(tags, id);
731
+ },
732
+
733
+ //
734
+ // Add tag to tag-list that matches the specified path (will overwrite if the tag already exist)
735
+ //
736
+ setTag: function(path, tag) {
737
+ 'use strict';
738
+ var tags = this._getTagsByPath(path, true);
739
+ MD.check(tags, 'Failed to get or create path: ' + path);
740
+ this._removeTag(tags, tag.id);
741
+ tags.push(tag);
742
+ },
743
+
744
+ //
745
+ // Enumerate all tags in the tiff tree
746
+ //
747
+ enumerateTags: function() {
748
+ 'use strict';
749
+ var list = [];
750
+ this._enumerateRecursive(this._tree, '', function(path, ifd) {
751
+ for (var i = 0; i < ifd.tags.length; i++) {
752
+ list.push({
753
+ path: path,
754
+ tag: ifd.tags[i]
755
+ });
756
+ }
757
+ });
758
+ return list;
759
+ },
760
+
761
+ //
762
+ // Enumerate all named data-entries in the tiff tree
763
+ //
764
+ enumerateData: function() {
765
+ 'use strict';
766
+ var list = [];
767
+ this._enumerateRecursive(this._tree, '', function(path, ifd) {
768
+ for (var i in ifd.data) {
769
+ if (ifd.data.hasOwnProperty(i)) {
770
+ list.push({
771
+ path: path,
772
+ name: i,
773
+ data: ifd.data[i].data
774
+ });
775
+ }
776
+ }
777
+ });
778
+ return list;
779
+ },
780
+
781
+ //
782
+ // Get named data
783
+ //
784
+ getData: function(path, name) {
785
+ 'use strict';
786
+ MD.check(name in MD.KNOWN_PAIRS, 'Unknown data name: "' + name + '"');
787
+ var ifd = this._getIfdByPath(path, false);
788
+ if (ifd && ifd.data && (name in ifd.data)) {
789
+ return ifd.data[name].data;
790
+ }
791
+ return undefined;
792
+ },
793
+
794
+ //
795
+ // Set named data
796
+ //
797
+ setData: function(path, name, data) {
798
+ 'use strict';
799
+ MD.check(name in MD.KNOWN_PAIRS, 'Unknown data name: "' + name + '"');
800
+ // Set data
801
+ var pair = MD.KNOWN_PAIRS[name];
802
+ var ifd = this._getIfdByPath(path, true);
803
+ var dataArray = (data instanceof Array) ? data : [data];
804
+ ifd.data = ifd.data ? ifd.data : {};
805
+ ifd.data[name] = {
806
+ positionId: pair.positionId,
807
+ lengthId: pair.lengthId,
808
+ data: dataArray
809
+ };
810
+ // Create tag data
811
+ var posData = [];
812
+ var lenData = [];
813
+ for (var i = 0; i < dataArray.length; i++) {
814
+ posData.push(0);
815
+ lenData.push(0);
816
+ }
817
+ // Ensure that the corresponding tag pair is present in the IFD
818
+ this._removeTag(ifd.tags, pair.positionId);
819
+ ifd.tags.push({
820
+ id: pair.positionId,
821
+ type: MD.TIFF_TYPE_LONG,
822
+ data: (posData.length == 1) ? 0 : posData
823
+ });
824
+ this._removeTag(ifd.tags, pair.lengthId);
825
+ ifd.tags.push({
826
+ id: pair.lengthId,
827
+ type: MD.TIFF_TYPE_LONG,
828
+ data: (lenData.length == 1) ? 0 : lenData
829
+ });
830
+ },
831
+
832
+ //
833
+ // Remove named data
834
+ //
835
+ removeData: function(path, name) {
836
+ 'use strict';
837
+ // Remove named data and corresponding tag pair
838
+ MD.check(name in MD.KNOWN_PAIRS, 'Unknown data name: "' + name + '"');
839
+ var ifd = this._getIfdByPath(path, false);
840
+ var pair = MD.KNOWN_PAIRS[name];
841
+ if (ifd && ifd.data && (name in ifd.data)) {
842
+ delete ifd.data[name];
843
+ this._removeTag(ifd.tags, pair.positionId);
844
+ this._removeTag(ifd.tags, pair.lengthId);
845
+ }
846
+ },
847
+
848
+ //
849
+ // Serialize (or save) tiff structure. This function returns an ArrayBuffer
850
+ // that contains valid tiff data.
851
+ //
852
+ save: function(endian) {
853
+ 'use strict';
854
+ var sizes = this._computeSizes();
855
+ var buffer = new ArrayBuffer(sizes.layoutSize + sizes.payloadSize);
856
+
857
+ var targetEndian = endian ? endian : this._nativeEndian;
858
+ var layoutWriter = new MD.BinaryWriter(buffer, targetEndian);
859
+ var payloadWriter = new MD.BinaryWriter(buffer, targetEndian);
860
+ payloadWriter.position = sizes.layoutSize;
861
+
862
+ switch (layoutWriter.endian) {
863
+ case MD.LITTLE_ENDIAN: layoutWriter.write16u(MD.TIFF_LITTLE_ENDIAN); break;
864
+ case MD.BIG_ENDIAN: layoutWriter.write16u(MD.TIFF_BIG_ENDIAN); break;
865
+ default: throw 'Invalid endian specifier (' + layoutWriter.endian + ')';
866
+ }
867
+ layoutWriter.write16u(MD.TIFF_MAGIC);
868
+ layoutWriter.write32u(layoutWriter.position + 4);
869
+ this._saveTrunk(layoutWriter, payloadWriter, this._tree);
870
+ return buffer;
871
+ },
872
+
873
+ //
874
+ // PRIVATE METHODS
875
+ //
876
+
877
+ //
878
+ // Compute tag 'count' value based on tag type/data
879
+ //
880
+ _computeCount: function(tag) {
881
+ 'use strict';
882
+ switch (tag.type) {
883
+ case MD.TIFF_TYPE_RATIONAL:
884
+ case MD.TIFF_TYPE_SRATIONAL:
885
+ MD.check(tag.data instanceof Array, 'Invalid (S)RATIONAL data');
886
+ return (tag.data[0] instanceof Array) ? tag.data.length : 1;
887
+ case MD.TIFF_TYPE_ASCII:
888
+ MD.check(typeof tag.data === 'string', 'Invalid ASCII data');
889
+ return tag.data.length + 1; // Zero terminator
890
+ default:
891
+ return (tag.data instanceof Array) ? tag.data.length : 1;
892
+ }
893
+ },
894
+
895
+ //
896
+ // Write tag payload data
897
+ //
898
+ _writeTagData: function(writer, type, data, count) {
899
+ 'use strict';
900
+ var beforePosition = writer.position;
901
+ switch (type) {
902
+ case MD.TIFF_TYPE_UNDEFINED:
903
+ case MD.TIFF_TYPE_BYTE:
904
+ writer.writeGeneric('setUint8', 1, 1, count, data);
905
+ break;
906
+ case MD.TIFF_TYPE_ASCII:
907
+ MD.check(typeof data === 'string', 'Invalid ASCII data');
908
+ MD.check(count == data.length + 1, 'Invalid ASCII count');
909
+ for (var i = 0; i < data.length; i++) {
910
+ writer.write8u(data.charCodeAt(i));
911
+ }
912
+ writer.write8u(0); // Zero terminator
913
+ break;
914
+ case MD.TIFF_TYPE_SBYTE:
915
+ writer.writeGeneric('setInt8', 1, 1, count, data);
916
+ break;
917
+ case MD.TIFF_TYPE_SHORT:
918
+ writer.writeGeneric('setUint16', 2, 1, count, data);
919
+ break;
920
+ case MD.TIFF_TYPE_SSHORT:
921
+ writer.writeGeneric('setInt16', 2, 1, count, data);
922
+ break;
923
+ case MD.TIFF_TYPE_IFD:
924
+ case MD.TIFF_TYPE_LONG:
925
+ writer.writeGeneric('setUint32', 4, 1, count, data);
926
+ break;
927
+ case MD.TIFF_TYPE_SLONG:
928
+ writer.writeGeneric('setInt32', 4, 1, count, data);
929
+ break;
930
+ case MD.TIFF_TYPE_FLOAT:
931
+ writer.writeGeneric('setFloat32', 4, 1, count, data);
932
+ break;
933
+ case MD.TIFF_TYPE_RATIONAL:
934
+ writer.writeGeneric('setUint32', 4, 2, count, data);
935
+ break;
936
+ case MD.TIFF_TYPE_SRATIONAL:
937
+ writer.writeGeneric('setInt32', 4, 2, count, data);
938
+ break;
939
+ case MD.TIFF_TYPE_DOUBLE:
940
+ writer.writeGeneric('setFloat64', 8, 1, count, data);
941
+ break;
942
+ default:
943
+ throw 'Invalid TIFF type (' + type + ')';
944
+ }
945
+ var writtenBytes = writer.position - beforePosition;
946
+ if (writtenBytes % 2 == 1) {
947
+ writer.write8u(0); // Note: Padding
948
+ }
949
+ },
950
+
951
+ //
952
+ // Serialize sub branches
953
+ //
954
+ _saveBranches: function(layoutWriter, payloadWriter, trunk, dataOffsets) {
955
+ 'use strict';
956
+ for (var i = 0; i < trunk.length; i++) {
957
+ var ifd = trunk[i];
958
+ for (var j in ifd.branches) {
959
+ if (ifd.branches.hasOwnProperty(j)) {
960
+ var dataOffset = dataOffsets[i][j];
961
+ if (dataOffset && this._pointsToSubIfd(dataOffset.tag)) {
962
+ var subTrunks = ifd.branches[j];
963
+ MD.check(this._computeCount(dataOffset.tag) == subTrunks.length, 'Inconsistent number of sub IFDs');
964
+ var writer = new MD.BinaryWriter(layoutWriter.buffer, layoutWriter.endian);
965
+ writer.position = dataOffset.offset;
966
+ for (var k = 0; k < subTrunks.length; k++) {
967
+ writer.write32u(layoutWriter.position);
968
+ this._saveTrunk(layoutWriter, payloadWriter, subTrunks[k]);
969
+ }
970
+ }
971
+ }
972
+ }
973
+ }
974
+ },
975
+
976
+ //
977
+ // Serialize data payloads (pair IDs)
978
+ //
979
+ _saveData: function(layoutWriter, payloadWriter, trunk, dataOffsets) {
980
+ 'use strict';
981
+ for (var i = 0; i < trunk.length; i++) {
982
+ var ifd = trunk[i];
983
+ if (ifd.data) {
984
+ for (var j in ifd.data) {
985
+ if (ifd.data.hasOwnProperty(j)) {
986
+ var chunk = ifd.data[j];
987
+ var offsetLen = dataOffsets[i][chunk.lengthId];
988
+ var offsetPos = dataOffsets[i][chunk.positionId];
989
+ if (offsetLen && offsetPos) {
990
+ var k;
991
+ var writer = new MD.BinaryWriter(layoutWriter.buffer, layoutWriter.endian);
992
+ writer.position = offsetLen.offset;
993
+ for (k = 0; k < chunk.data.length; k++) {
994
+ writer.write32u(chunk.data[k].byteLength);
995
+ }
996
+ writer.position = offsetPos.offset;
997
+ for (k = 0; k < chunk.data.length; k++) {
998
+ writer.write32u(payloadWriter.position);
999
+ payloadWriter.write(chunk.data[k]);
1000
+ if (chunk.data[k].byteLength % 2 == 1) {
1001
+ payloadWriter.write8u(0); // Padding
1002
+ }
1003
+ }
1004
+ }
1005
+ }
1006
+ }
1007
+ }
1008
+ }
1009
+ },
1010
+
1011
+ //
1012
+ // Check if the specified id is part of a broken id-pair
1013
+ //
1014
+ _isBrokenPair: function(id, tagsById) {
1015
+ 'use strict';
1016
+ var valid = true;
1017
+ for (var i in MD.KNOWN_PAIRS) {
1018
+ if (MD.KNOWN_PAIRS.hasOwnProperty(i)) {
1019
+ var pair = MD.KNOWN_PAIRS[i];
1020
+ if (id == pair.positionId) {
1021
+ valid = (pair.lengthId in tagsById);
1022
+ break;
1023
+ }
1024
+ if (id == pair.lengthId) {
1025
+ valid = (pair.positionId in tagsById);
1026
+ break;
1027
+ }
1028
+ }
1029
+ }
1030
+ return !valid;
1031
+ },
1032
+
1033
+ //
1034
+ // Check if the specifed id is part of a pair where the data payload is missing
1035
+ //
1036
+ _isMissingDataPayload: function(id, data) {
1037
+ 'use strict';
1038
+ for (var i in MD.KNOWN_PAIRS) {
1039
+ if (MD.KNOWN_PAIRS.hasOwnProperty(i)) {
1040
+ var pair = MD.KNOWN_PAIRS[i];
1041
+ if (pair.positionId == id || pair.lengthId == id) {
1042
+ return !(i in data);
1043
+ }
1044
+ }
1045
+ }
1046
+ return false;
1047
+ },
1048
+
1049
+ //
1050
+ // Serialize IFD trunk
1051
+ //
1052
+ _saveTrunk: function(layoutWriter, payloadWriter, trunk) {
1053
+ 'use strict';
1054
+ var dataOffsets = {};
1055
+ // Loop through sub IFDs in trunk
1056
+ for (var i = 0; i < trunk.length; i++) {
1057
+ dataOffsets[i] = {};
1058
+ var ifd = trunk[i];
1059
+ var j, tag, tagsById = {};
1060
+ // Construct tab-by-id lookup
1061
+ for (j = 0; j < ifd.tags.length; j++) {
1062
+ tag = ifd.tags[j];
1063
+ MD.check(!(tag.id in tagsById), 'Duplicate tag ID');
1064
+ tagsById[tag.id] = tag;
1065
+ }
1066
+ // Prune tags that for some reason have become invalid
1067
+ var prunedTags = [];
1068
+ for (j in tagsById) {
1069
+ if (tagsById.hasOwnProperty(j)) {
1070
+ tag = tagsById[j];
1071
+ // Prune sub IFD pointer-tags with non-existant sub IFDs
1072
+ if (this._pointsToSubIfd(tag) && !(tag.id in ifd.branches)) {
1073
+ continue;
1074
+ }
1075
+ // Prune tags that are part of a broken id-pair
1076
+ if (this._isBrokenPair(tag.id, tagsById)) {
1077
+ continue;
1078
+ }
1079
+ // Prune id-pair tags if the corresponding data payload is missing
1080
+ if (this._isMissingDataPayload(tag.id, ifd.data)) {
1081
+ continue;
1082
+ }
1083
+ prunedTags.push(tag);
1084
+ }
1085
+ }
1086
+ // Sort tags by ID, as per the spec
1087
+ prunedTags.sort(function(a, b) {
1088
+ return (a.id - b.id);
1089
+ });
1090
+ // Serialize tags
1091
+ layoutWriter.write16u(prunedTags.length);
1092
+ for (j = 0; j < prunedTags.length; j++) {
1093
+ tag = prunedTags[j];
1094
+ var count = this._computeCount(tag);
1095
+ var size = this._getTypeSize(tag.type) * count;
1096
+ layoutWriter.write16u(tag.id);
1097
+ layoutWriter.write16u(tag.type);
1098
+ layoutWriter.write32u(count);
1099
+ dataOffsets[i][tag.id] = {
1100
+ tag: tag,
1101
+ offset: (size > 4) ? payloadWriter.position : layoutWriter.position
1102
+ };
1103
+ layoutWriter.write32u(0);
1104
+ var nextPos = layoutWriter.position;
1105
+ layoutWriter.position -= 4;
1106
+ if (size > 4) {
1107
+ layoutWriter.write32u(payloadWriter.position);
1108
+ this._writeTagData(payloadWriter, tag.type, tag.data, count);
1109
+ } else {
1110
+ this._writeTagData(layoutWriter, tag.type, tag.data, count);
1111
+ }
1112
+ layoutWriter.position = nextPos;
1113
+ }
1114
+ var isLastIfd = (i == trunk.length - 1);
1115
+ layoutWriter.write32u(isLastIfd ? 0 : layoutWriter.position + 4);
1116
+ }
1117
+ // Save branches (sub IFDs)
1118
+ this._saveBranches(layoutWriter, payloadWriter, trunk, dataOffsets);
1119
+ // Save payload data (id-pairs)
1120
+ this._saveData(layoutWriter, payloadWriter, trunk, dataOffsets);
1121
+ },
1122
+
1123
+ //
1124
+ // Compute size of tiff structure
1125
+ //
1126
+ _computeSizes: function() {
1127
+ 'use strict';
1128
+ var sizes = {
1129
+ layoutSize: 8,
1130
+ payloadSize: 0
1131
+ };
1132
+ this._computeSizesRecursive(this._tree, sizes);
1133
+ MD.check(sizes.layoutSize % 2 === 0, 'Invalid file structure size');
1134
+ MD.check(sizes.payloadSize % 2 === 0, 'Invalid file structure size');
1135
+ return sizes;
1136
+ },
1137
+
1138
+ //
1139
+ // Helper function for computing the size of the tiff structure
1140
+ //
1141
+ _computeSizesRecursive: function(trunk, sizes) {
1142
+ 'use strict';
1143
+ for (var i = 0; i < trunk.length; i++) {
1144
+ // Size contributes from the main trunk
1145
+ var ifd = trunk[i];
1146
+ var j, k, dataSize;
1147
+ sizes.layoutSize += 2;
1148
+ for (j = 0; j < ifd.tags.length; j++) {
1149
+ sizes.layoutSize += 12;
1150
+ var tag = ifd.tags[j];
1151
+ dataSize = this._computeCount(tag) * this._getTypeSize(tag.type);
1152
+ if (dataSize > 4) {
1153
+ sizes.payloadSize += dataSize + (dataSize % 2); // Note: Padding
1154
+ }
1155
+ }
1156
+ // Size contributes from data payloads (id-pairs)
1157
+ sizes.layoutSize += 4;
1158
+ if (ifd.data) {
1159
+ for (j in ifd.data) {
1160
+ if (ifd.data.hasOwnProperty(j)) {
1161
+ for (k = 0; k < ifd.data[j].data.length; k++) {
1162
+ dataSize = ifd.data[j].data[k].byteLength;
1163
+ sizes.payloadSize += dataSize + (dataSize % 2); // Note: Padding
1164
+ }
1165
+ }
1166
+ }
1167
+ }
1168
+ // Size contributions from sub IFDs
1169
+ for (j in ifd.branches) {
1170
+ if (ifd.branches.hasOwnProperty(j)) {
1171
+ var subTrunks = ifd.branches[j];
1172
+ for (k = 0; k < subTrunks.length; k++) {
1173
+ this._computeSizesRecursive(subTrunks[k], sizes);
1174
+ }
1175
+ }
1176
+ }
1177
+ }
1178
+ },
1179
+
1180
+ //
1181
+ // Parse tiff structure (main entry point)
1182
+ //
1183
+ _parse: function(buffer) {
1184
+ 'use strict';
1185
+ if (buffer) {
1186
+ // Read tiff header and set appropriate endianess
1187
+ var reader = new MD.BinaryReader(buffer);
1188
+ switch (reader.read16u()) {
1189
+ case MD.TIFF_LITTLE_ENDIAN: reader.endian = MD.LITTLE_ENDIAN; break;
1190
+ case MD.TIFF_BIG_ENDIAN: reader.endian = MD.BIG_ENDIAN; break;
1191
+ default: throw 'Invalid TIFF endian specifier';
1192
+ }
1193
+ // Store the native endianess of the tiff structure
1194
+ this._nativeEndian = reader.endian;
1195
+ // Verify the tiff magic value and parse the entire IFD tree (recursively)
1196
+ MD.check(reader.read16u() == MD.TIFF_MAGIC, 'Invalid TIFF magic number');
1197
+ reader.position = reader.read32u();
1198
+ this._tree = this._parseTree(reader);
1199
+ }
1200
+ },
1201
+
1202
+ //
1203
+ // Parse IFD array (aka. IFD trunk) at the reader position
1204
+ //
1205
+ _parseTree: function(reader) {
1206
+ 'use strict';
1207
+ // Loop through each IFD in the trunk
1208
+ var trunk = [];
1209
+ while (true) {
1210
+ // Create IFD structure
1211
+ var ifd = {
1212
+ tags: [],
1213
+ branches: {}
1214
+ };
1215
+ // Read and decode each tag in the IFD
1216
+ var tagsById = {};
1217
+ var tagCount = reader.read16u();
1218
+ for (var i = 0; i < tagCount; i++) {
1219
+ // Read raw tag data
1220
+ var id = reader.read16u();
1221
+ var type = reader.read16u();
1222
+ var count = reader.read32u();
1223
+ var nextPosition = reader.position + 4;
1224
+ var payloadSize = count * this._getTypeSize(type);
1225
+ if (payloadSize > 4) {
1226
+ reader.position = reader.read32u();
1227
+ }
1228
+ var payload = reader.read(payloadSize);
1229
+ // Decode tag data and create deserialized tag structure
1230
+ var tag = {
1231
+ id: id,
1232
+ type: type,
1233
+ data: this._parsePayload(payload, reader.endian, type, count)
1234
+ };
1235
+ tagsById[tag.id] = tag;
1236
+ ifd.tags.push(tag);
1237
+ // If the tag points to a sub-IFD, parse it and insert it as a 'branch' in the deserialized tree
1238
+ if (this._pointsToSubIfd(tag)) {
1239
+ MD.check(tag.type == MD.TIFF_TYPE_LONG || tag.type == MD.TIFF_TYPE_IFD, 'Invalid tag type for sub IFD (' + tag.type + ')');
1240
+ MD.check(!(tag.id in ifd.branches), 'Multiple sub IFDs with same parent ID (' + tag.id + ')');
1241
+ // Note: Some sub-IFD pointer tags points to an array of sub-IFD positions
1242
+ ifd.branches[tag.id] = [];
1243
+ var offsets = (tag.data instanceof Array) ? tag.data : [tag.data];
1244
+ for (var j = 0; j < offsets.length; j++) {
1245
+ reader.position = offsets[j];
1246
+ ifd.branches[tag.id].push(this._parseTree(reader));
1247
+ }
1248
+ }
1249
+ reader.position = nextPosition;
1250
+ }
1251
+ // Extract known data payloads
1252
+ var offset = reader.read32u();
1253
+ ifd.data = this._extractData(reader, tagsById);
1254
+ // Store the deserialized IFD and move on to the next IFD in the trunk (or terminate if we've reached the end)
1255
+ trunk.push(ifd);
1256
+ if (offset === 0) {
1257
+ break;
1258
+ }
1259
+ reader.position = offset;
1260
+ }
1261
+ // Return the deserialized IFD array
1262
+ return trunk;
1263
+ },
1264
+
1265
+ //
1266
+ // Extract known data payloads from the specified IFD ('tagsById')
1267
+ //
1268
+ _extractData: function(reader, tagsById) {
1269
+ 'use strict';
1270
+ // Loop through all know data payload ID pairs
1271
+ var result = {};
1272
+ for (var i in MD.KNOWN_PAIRS) {
1273
+ if (MD.KNOWN_PAIRS.hasOwnProperty(i)) {
1274
+ var pair = MD.KNOWN_PAIRS[i];
1275
+ var tagPos = tagsById[pair.positionId];
1276
+ var tagLen = tagsById[pair.lengthId];
1277
+ // Verify we don't have any broken pairs
1278
+ MD.check((tagPos && tagLen) || (!tagPos && !tagLen), 'Missing one tag in data tag-pair');
1279
+ if (tagPos && tagLen) {
1280
+ // If the pair is present, do some sanity
1281
+ MD.check(tagPos.type == MD.TIFF_TYPE_LONG, 'Invalid tag type for position tag');
1282
+ MD.check(tagLen.type == MD.TIFF_TYPE_LONG || tagLen.type == MD.TIFF_TYPE_SHORT || tagLen.type == MD.TIFF_TYPE_BYTE, 'Invalid tag type for length tag');
1283
+ var positions = (tagPos.data instanceof Array) ? tagPos.data : [tagPos.data];
1284
+ var lengths = (tagLen.data instanceof Array) ? tagLen.data : [tagLen.data];
1285
+ MD.check(positions.length == lengths.length, 'Inconsistent data pair list length');
1286
+ // Extract data payload
1287
+ var dataList = [];
1288
+ for (var j = 0; j < positions.length; j++) {
1289
+ reader.position = positions[j];
1290
+ dataList.push(reader.read(lengths[j]));
1291
+ }
1292
+ // Store data payload
1293
+ result[i] = {
1294
+ positionId: pair.positionId,
1295
+ lengthId: pair.lengthId,
1296
+ data: dataList
1297
+ };
1298
+ }
1299
+ }
1300
+ }
1301
+ // Return data payload array
1302
+ return result;
1303
+ },
1304
+
1305
+ //
1306
+ // Parse tag payload data
1307
+ //
1308
+ _parsePayload: function(payload, endian, type, count) {
1309
+ 'use strict';
1310
+ var reader = new MD.BinaryReader(payload, endian);
1311
+ switch (type) {
1312
+ case MD.TIFF_TYPE_UNDEFINED:
1313
+ case MD.TIFF_TYPE_BYTE:
1314
+ return reader.readGeneric('getUint8', 1, 1, count);
1315
+ case MD.TIFF_TYPE_ASCII:
1316
+ MD.check(count > 0, 'Invalid ASCII length');
1317
+ var ascii = reader.readGeneric('getUint8', 1, 1, count - 1);
1318
+ return String.fromCharCode.apply(null, Array.isArray(ascii) ? ascii : [ascii]);
1319
+ case MD.TIFF_TYPE_SBYTE:
1320
+ return reader.readGeneric('getInt8', 1, 1, count);
1321
+ case MD.TIFF_TYPE_SHORT:
1322
+ return reader.readGeneric('getUint16', 2, 1, count);
1323
+ case MD.TIFF_TYPE_SSHORT:
1324
+ return reader.readGeneric('getInt16', 2, 1, count);
1325
+ case MD.TIFF_TYPE_IFD:
1326
+ case MD.TIFF_TYPE_LONG:
1327
+ return reader.readGeneric('getUint32', 4, 1, count);
1328
+ case MD.TIFF_TYPE_SLONG:
1329
+ return reader.readGeneric('getInt32', 4, 1, count);
1330
+ case MD.TIFF_TYPE_FLOAT:
1331
+ return reader.readGeneric('getFloat32', 4, 1, count);
1332
+ case MD.TIFF_TYPE_RATIONAL:
1333
+ return reader.readGeneric('getUint32', 4, 2, count);
1334
+ case MD.TIFF_TYPE_SRATIONAL:
1335
+ return reader.readGeneric('getInt32', 4, 2, count);
1336
+ case MD.TIFF_TYPE_DOUBLE:
1337
+ return reader.readGeneric('getFloat64', 8, 1, count);
1338
+ default:
1339
+ throw 'Invalid TIFF type (' + type + ')';
1340
+ }
1341
+ },
1342
+
1343
+ //
1344
+ // Returns true if the specified tag points to a sub-IFD, otherwise false
1345
+ //
1346
+ _pointsToSubIfd: function(tag) {
1347
+ 'use strict';
1348
+ if (tag.type == MD.TIFF_TYPE_IFD) {
1349
+ return true;
1350
+ }
1351
+ for (var i in MD.KNOWN_SUBIFDS) {
1352
+ if (tag.id == MD.KNOWN_SUBIFDS[i]) {
1353
+ MD.check(tag.type == MD.TIFF_TYPE_LONG, 'Invalid sub IFD data type');
1354
+ return true;
1355
+ }
1356
+ }
1357
+ return false;
1358
+ },
1359
+
1360
+ //
1361
+ // Get the size (in bytes) for the given tiff type
1362
+ //
1363
+ _getTypeSize: function(type) {
1364
+ 'use strict';
1365
+ switch (type) {
1366
+ case MD.TIFF_TYPE_BYTE:
1367
+ case MD.TIFF_TYPE_ASCII:
1368
+ case MD.TIFF_TYPE_SBYTE:
1369
+ case MD.TIFF_TYPE_UNDEFINED:
1370
+ return 1;
1371
+ case MD.TIFF_TYPE_SHORT:
1372
+ case MD.TIFF_TYPE_SSHORT:
1373
+ return 2;
1374
+ case MD.TIFF_TYPE_LONG:
1375
+ case MD.TIFF_TYPE_SLONG:
1376
+ case MD.TIFF_TYPE_FLOAT:
1377
+ case MD.TIFF_TYPE_IFD:
1378
+ return 4;
1379
+ case MD.TIFF_TYPE_RATIONAL:
1380
+ case MD.TIFF_TYPE_SRATIONAL:
1381
+ case MD.TIFF_TYPE_DOUBLE:
1382
+ return 8;
1383
+ default: throw 'Invalid TIFF type (' + type + ')';
1384
+ }
1385
+ },
1386
+
1387
+ //
1388
+ // Helper function for the tag address parser
1389
+ //
1390
+ _parsePathComponent: function(component) {
1391
+ 'use strict';
1392
+ var result, match = /(\w+)\[(\d+)\]/.exec(component);
1393
+ MD.check(match, 'Invalid path component: ' + component);
1394
+ result = {
1395
+ name: match[1],
1396
+ index: parseInt(match[2])
1397
+ };
1398
+ return result;
1399
+ },
1400
+
1401
+ //
1402
+ // Get the tag-array that matches the specified path. If the 'create' flag
1403
+ // is set to true the function will create the internal structures required to
1404
+ // match the path (if they do not already exist)
1405
+ //
1406
+ _getTagsByPath: function(path, create) {
1407
+ 'use strict';
1408
+ var ifd = this._getIfdByPath(path, create);
1409
+ return ifd ? ifd.tags : undefined;
1410
+ },
1411
+
1412
+ //
1413
+ // Get the IFD that matches the specified path. If the 'create' flag
1414
+ // is set to true the function will create the internal structures required to
1415
+ // match the path (if they do not already exist)
1416
+ //
1417
+ _getIfdByPath: function(path, create) {
1418
+ 'use strict';
1419
+ // Basic sanity check and path splitting
1420
+ MD.check(path && path.startsWith('/'), 'Invalid path: ' + path);
1421
+ var components = path.split('/');
1422
+ var trunk = this._tree;
1423
+ var j, ifd = null;
1424
+ // Loop through all path components and navigate the tree accordingly
1425
+ for (var i = 1; i < components.length; i++) {
1426
+ var component = components[i].trim().toLowerCase();
1427
+ var data = this._parsePathComponent(component);
1428
+ if (i % 2 == 1) {
1429
+ // Odd compoents are IFDs
1430
+ MD.check(data.name == 'ifd', 'Invalid component (' + component + ') expected "ifd"');
1431
+ if (data.index >= trunk.length) {
1432
+ if (create) {
1433
+ for (j = trunk.length; j <= data.index; j++) {
1434
+ trunk.push({
1435
+ tags: [],
1436
+ branches: {}
1437
+ });
1438
+ }
1439
+ } else {
1440
+ return undefined;
1441
+ }
1442
+ }
1443
+ ifd = trunk[data.index];
1444
+ trunk = null;
1445
+ } else {
1446
+ // Even components are sub IFD pointers
1447
+ var id;
1448
+ if (data.name in MD.KNOWN_SUBIFDS) {
1449
+ // Check if the sub IFD is adressed via its human-readable name
1450
+ id = MD.KNOWN_SUBIFDS[data.name];
1451
+ } else {
1452
+ // ... otherwise use the numeric tag ID value
1453
+ id = parseInt(data.name);
1454
+ MD.check(!isNaN(id), 'Invalid branch in path: ' + data.name);
1455
+ var found = false;
1456
+ for (var name in MD.KNOWN_SUBIFDS) {
1457
+ if (MD.KNOWN_SUBIFDS[name] == id) {
1458
+ found = true;
1459
+ break;
1460
+ }
1461
+ }
1462
+ MD.check(found, 'Invalid sub IFD pointer ID in path: ' + id);
1463
+ }
1464
+ if (!(id in ifd.branches)) {
1465
+ if (create) {
1466
+ // Add branch
1467
+ ifd.branches[id] = [];
1468
+ // Add the corresponding pointer tag
1469
+ this._removeTag(ifd.tags, id);
1470
+ ifd.tags.push({
1471
+ id: id,
1472
+ type: MD.TIFF_TYPE_LONG,
1473
+ data: 0
1474
+ });
1475
+ } else {
1476
+ return undefined;
1477
+ }
1478
+ }
1479
+ if (data.index >= ifd.branches[id].length) {
1480
+ if (create) {
1481
+ // Make sure we have enough sub trunks in the branch to accommodate the specified index
1482
+ for (j = ifd.branches[id].length; j <= data.index; j++) {
1483
+ ifd.branches[id].push([]);
1484
+ }
1485
+ // Find the corresponding pointer tag
1486
+ var pointerTag;
1487
+ for (j = 0; j < ifd.tags.length; j++) {
1488
+ if (ifd.tags[j].id == id) {
1489
+ pointerTag = ifd.tags[j];
1490
+ break;
1491
+ }
1492
+ }
1493
+ MD.check(pointerTag, 'Could not find pointer tag');
1494
+ // Make sure that the number of offsets in the pointer tag matches number of sub trunks
1495
+ var length = ifd.branches[id].length;
1496
+ MD.check(length >= 1, 'One or more sub trunks must be present in branch');
1497
+ var offsets = [];
1498
+ for (j = 0; j < length; j++) {
1499
+ offsets.push(0);
1500
+ }
1501
+ pointerTag.data = (offsets.length == 1) ? 0 : offsets;
1502
+ } else {
1503
+ return undefined;
1504
+ }
1505
+ }
1506
+ trunk = ifd.branches[id][data.index];
1507
+ ifd = null;
1508
+ }
1509
+ }
1510
+ MD.check(ifd, 'Invalid path: ' + path + ', last component must be "ifd[N]" where N is an integer >= 0');
1511
+ return ifd;
1512
+ },
1513
+
1514
+ //
1515
+ // Helper function for (recursively) enumerating the entire tiff tree
1516
+ //
1517
+ _enumerateRecursive: function(trunk, path, callback) {
1518
+ 'use strict';
1519
+ // Loop through the IFDs in the trunk
1520
+ for (var i = 0; i < trunk.length; i++) {
1521
+ var newPath = path + '/ifd[' + i + ']';
1522
+ var j, ifd = trunk[i];
1523
+ callback(newPath, ifd);
1524
+ // Enumerate branches recursively
1525
+ for (j in ifd.branches) {
1526
+ if (ifd.branches.hasOwnProperty(j)) {
1527
+ var subTrunks = ifd.branches[j];
1528
+ var branchName = j.toString();
1529
+ for (var name in MD.KNOWN_SUBIFDS) {
1530
+ if (MD.KNOWN_SUBIFDS[name] == j) {
1531
+ branchName = name;
1532
+ break;
1533
+ }
1534
+ }
1535
+ for (var k = 0; k < subTrunks.length; k++) {
1536
+ this._enumerateRecursive(subTrunks[k], newPath + '/' + branchName + '[' + k + ']', callback);
1537
+ }
1538
+ }
1539
+ }
1540
+ }
1541
+ },
1542
+
1543
+ //
1544
+ // Remove tag with 'id' in the list of 'tags'
1545
+ //
1546
+ _removeTag: function(tags, id) {
1547
+ 'use strict';
1548
+ if (tags) {
1549
+ var idx = -1;
1550
+ for (var i = 0; i < tags.length; i++) {
1551
+ if (tags[i].id == id) {
1552
+ idx = i;
1553
+ break;
1554
+ }
1555
+ }
1556
+ if (idx >= 0) {
1557
+ tags.splice(idx, 1);
1558
+ }
1559
+ }
1560
+ }
1561
+ };
1562
+
1563
+ //
1564
+ // Photoshop resource codec class
1565
+ //
1566
+ MD.PhotoshopResource = function(buffer) {
1567
+ 'use strict';
1568
+ this._tags = [];
1569
+ this._parse(buffer);
1570
+ };
1571
+
1572
+ MD.PhotoshopResource.prototype = {
1573
+
1574
+ //
1575
+ // PUBLIC METHODS
1576
+ //
1577
+
1578
+ constructor: MD.PhotoshopResource,
1579
+
1580
+ //
1581
+ // Get tag with specified ID
1582
+ //
1583
+ getTag: function(id) {
1584
+ 'use strict';
1585
+ for (var i = 0; i < this._tags.length; i++) {
1586
+ var tag = this._tags[i];
1587
+ if (tag.id == id) {
1588
+ return tag;
1589
+ }
1590
+ }
1591
+ return undefined;
1592
+ },
1593
+
1594
+ //
1595
+ // Set tag (will overwrite if tag already exists)
1596
+ //
1597
+ setTag: function(tag) {
1598
+ 'use strict';
1599
+ throw 'PhotoshopResource::setTag is not implemented';
1600
+ // TODO: Implement
1601
+ },
1602
+
1603
+ //
1604
+ // Serialize (save) Photoshop 3.0 format
1605
+ //
1606
+ save: function() {
1607
+ 'use strict';
1608
+ var i, tag, size = 0;
1609
+ for (i = 0; i < this._tags.length; i++) {
1610
+ tag = this._tags[i];
1611
+ var nameLength = tag.name.length + 1;
1612
+ nameLength += (nameLength % 2);
1613
+ var dataLength = tag.data.byteLength;
1614
+ dataLength += (dataLength % 2);
1615
+ size += 4 + 2 + nameLength + 4 + dataLength;
1616
+ }
1617
+ var result = new ArrayBuffer(size);
1618
+ var writer = new MD.BinaryWriter(result, MD.BIG_ENDIAN);
1619
+ for (i = 0; i< this._tags.length; i++) {
1620
+ tag = this._tags[i];
1621
+ writer.write32u(MD.PHOTOSHOP_8BIM);
1622
+ writer.write16u(tag.id);
1623
+ this._writePascalString(writer, tag.name);
1624
+ writer.write32u(tag.data.byteLength);
1625
+ if (tag.data.byteLength % 2 == 1) {
1626
+ writer.write8u(0);
1627
+ }
1628
+ }
1629
+ return result;
1630
+ },
1631
+
1632
+ //
1633
+ // PRIVATE METHODS
1634
+ //
1635
+
1636
+ //
1637
+ // Write pascal string
1638
+ //
1639
+ _writePascalString: function(writer, str) {
1640
+ 'use strict';
1641
+ MD.check(str.length <= 255, 'String is too long, it can not be represented as a Pascal string');
1642
+ writer.write8u(str.length);
1643
+ for (var i = 0; i < str.length; i++) {
1644
+ writer.write8u(str.charCodeAt(i));
1645
+ }
1646
+ if (str.length + 1 % 2 == 1) {
1647
+ writer.write8u(0); // Note: Padding
1648
+ }
1649
+ },
1650
+
1651
+ //
1652
+ // Read pascal string
1653
+ //
1654
+ _readPascalString: function(reader) {
1655
+ 'use strict';
1656
+ var len = reader.read8u();
1657
+ var ascii = reader.readGeneric('getUint8', 1, 1, len);
1658
+ if ((len + 1) % 2 == 1) {
1659
+ reader.read8u(); // Note: Padding
1660
+ }
1661
+ return String.fromCharCode.apply(null, ascii);
1662
+ },
1663
+
1664
+ //
1665
+ // Parse (deserialize) Photoshop 3.0 format
1666
+ //
1667
+ _parse: function(buffer) {
1668
+ 'use strict';
1669
+ var reader = new MD.BinaryReader(buffer, MD.BIG_ENDIAN);
1670
+ while (reader.position < buffer.byteLength) {
1671
+ MD.check(reader.read32u() == MD.PHOTOSHOP_8BIM, 'Invalid 8BIM signature');
1672
+ var id = reader.read16u();
1673
+ var name = this._readPascalString(reader);
1674
+ var size = reader.read32u();
1675
+ var data = reader.read(size);
1676
+ reader.position += (size % 2);
1677
+ this._tags.push({
1678
+ id: id,
1679
+ name: name,
1680
+ data: data
1681
+ });
1682
+ }
1683
+ }
1684
+ };