@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.
- package/assets/js/DataTables/Extensions/FixedHeader/js/dataTables.fixedHeader.js +43 -13
- package/assets/js/DataTables/Extensions/FixedHeader/js/dataTables.fixedHeader.min.js +2 -2
- package/assets/js/DataTables/Extensions/FixedHeader/js/fixedHeader.bootstrap5.min.js +1 -1
- package/assets/js/DataTables/Extensions/FixedHeader/js/fixedHeader.dataTables.min.js +1 -1
- package/assets/js/bootstrap/css/bootstrap-grid.css +1 -1
- package/assets/js/bootstrap/css/bootstrap-grid.css.map +1 -1
- package/assets/js/bootstrap/css/bootstrap-grid.min.css +1 -1
- package/assets/js/bootstrap/css/bootstrap-grid.min.css.map +1 -1
- package/assets/js/bootstrap/css/bootstrap-grid.rtl.css +1 -1
- package/assets/js/bootstrap/css/bootstrap-grid.rtl.css.map +1 -1
- package/assets/js/bootstrap/css/bootstrap-grid.rtl.min.css +1 -1
- package/assets/js/bootstrap/css/bootstrap-grid.rtl.min.css.map +1 -1
- package/assets/js/bootstrap/css/bootstrap-reboot.css +1 -1
- package/assets/js/bootstrap/css/bootstrap-reboot.css.map +1 -1
- package/assets/js/bootstrap/css/bootstrap-reboot.min.css +1 -1
- package/assets/js/bootstrap/css/bootstrap-reboot.min.css.map +1 -1
- package/assets/js/bootstrap/css/bootstrap-reboot.rtl.css +1 -1
- package/assets/js/bootstrap/css/bootstrap-reboot.rtl.css.map +1 -1
- package/assets/js/bootstrap/css/bootstrap-reboot.rtl.min.css +1 -1
- package/assets/js/bootstrap/css/bootstrap-reboot.rtl.min.css.map +1 -1
- package/assets/js/bootstrap/css/bootstrap-utilities.css +1 -1
- package/assets/js/bootstrap/css/bootstrap-utilities.css.map +1 -1
- package/assets/js/bootstrap/css/bootstrap-utilities.min.css +1 -1
- package/assets/js/bootstrap/css/bootstrap-utilities.min.css.map +1 -1
- package/assets/js/bootstrap/css/bootstrap-utilities.rtl.css +1 -1
- package/assets/js/bootstrap/css/bootstrap-utilities.rtl.css.map +1 -1
- package/assets/js/bootstrap/css/bootstrap-utilities.rtl.min.css +1 -1
- package/assets/js/bootstrap/css/bootstrap-utilities.rtl.min.css.map +1 -1
- package/assets/js/bootstrap/css/bootstrap.css +1 -1
- package/assets/js/bootstrap/css/bootstrap.css.map +1 -1
- package/assets/js/bootstrap/css/bootstrap.min.css +1 -1
- package/assets/js/bootstrap/css/bootstrap.min.css.map +1 -1
- package/assets/js/bootstrap/css/bootstrap.rtl.css +1 -1
- package/assets/js/bootstrap/css/bootstrap.rtl.css.map +1 -1
- package/assets/js/bootstrap/css/bootstrap.rtl.min.css +1 -1
- package/assets/js/bootstrap/css/bootstrap.rtl.min.css.map +1 -1
- package/assets/js/bootstrap/js/bootstrap.bundle.js +3 -3
- package/assets/js/bootstrap/js/bootstrap.bundle.js.map +1 -1
- package/assets/js/bootstrap/js/bootstrap.bundle.min.js +2 -2
- package/assets/js/bootstrap/js/bootstrap.bundle.min.js.map +1 -1
- package/assets/js/bootstrap/js/bootstrap.esm.js +3 -3
- package/assets/js/bootstrap/js/bootstrap.esm.js.map +1 -1
- package/assets/js/bootstrap/js/bootstrap.esm.min.js +2 -2
- package/assets/js/bootstrap/js/bootstrap.esm.min.js.map +1 -1
- package/assets/js/bootstrap/js/bootstrap.js +3 -3
- package/assets/js/bootstrap/js/bootstrap.js.map +1 -1
- package/assets/js/bootstrap/js/bootstrap.min.js +2 -2
- package/assets/js/bootstrap/js/bootstrap.min.js.map +1 -1
- package/assets/js/cdn.json +6 -2
- package/assets/js/exif-js/exif.js +1059 -0
- package/assets/js/exif-js/exif.min.js +8 -0
- package/assets/js/metadata/metadata.js +1684 -0
- 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
|
+
};
|