Showing
7 changed files
with
560 additions
and
67 deletions
@@ -15,6 +15,7 @@ | @@ -15,6 +15,7 @@ | ||
15 | - Add more warnings on type1 fonts | 15 | - Add more warnings on type1 fonts |
16 | - Simplify PdfImage constructor | 16 | - Simplify PdfImage constructor |
17 | - Implement Image orientation | 17 | - Implement Image orientation |
18 | +- Add Exif reader | ||
18 | 19 | ||
19 | ## 1.3.23 | 20 | ## 1.3.23 |
20 | 21 |
@@ -41,6 +41,7 @@ part 'src/compatibility.dart'; | @@ -41,6 +41,7 @@ part 'src/compatibility.dart'; | ||
41 | part 'src/document.dart'; | 41 | part 'src/document.dart'; |
42 | part 'src/encryption.dart'; | 42 | part 'src/encryption.dart'; |
43 | part 'src/font_descriptor.dart'; | 43 | part 'src/font_descriptor.dart'; |
44 | +part 'src/exif.dart'; | ||
44 | part 'src/font_metrics.dart'; | 45 | part 'src/font_metrics.dart'; |
45 | part 'src/font.dart'; | 46 | part 'src/font.dart'; |
46 | part 'src/formxobject.dart'; | 47 | part 'src/formxobject.dart'; |
pdf/lib/src/exif.dart
0 → 100644
1 | +/* | ||
2 | + * Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com> | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the 'License'); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an 'AS IS' BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +part of pdf; | ||
18 | + | ||
19 | +class PdfJpegInfo { | ||
20 | + factory PdfJpegInfo(Uint8List image) { | ||
21 | + assert(image != null); | ||
22 | + | ||
23 | + final ByteData buffer = image.buffer.asByteData(); | ||
24 | + | ||
25 | + int width; | ||
26 | + int height; | ||
27 | + int offset = 0; | ||
28 | + while (offset < buffer.lengthInBytes) { | ||
29 | + while (buffer.getUint8(offset) == 0xff) { | ||
30 | + offset++; | ||
31 | + } | ||
32 | + | ||
33 | + final int mrkr = buffer.getUint8(offset); | ||
34 | + offset++; | ||
35 | + | ||
36 | + if (mrkr == 0xd8) { | ||
37 | + continue; // SOI | ||
38 | + } | ||
39 | + | ||
40 | + if (mrkr == 0xd9) { | ||
41 | + break; // EOI | ||
42 | + } | ||
43 | + | ||
44 | + if (0xd0 <= mrkr && mrkr <= 0xd7) { | ||
45 | + continue; | ||
46 | + } | ||
47 | + | ||
48 | + if (mrkr == 0x01) { | ||
49 | + continue; // TEM | ||
50 | + } | ||
51 | + | ||
52 | + final int len = buffer.getUint16(offset); | ||
53 | + offset += 2; | ||
54 | + | ||
55 | + if (mrkr == 0xc0) { | ||
56 | + height = buffer.getUint16(offset + 1); | ||
57 | + width = buffer.getUint16(offset + 3); | ||
58 | + break; | ||
59 | + } | ||
60 | + offset += len - 2; | ||
61 | + } | ||
62 | + | ||
63 | + final Map<PdfExifTag, dynamic> tags = _findExifInJpeg(buffer); | ||
64 | + | ||
65 | + return PdfJpegInfo._(width, height, tags); | ||
66 | + } | ||
67 | + | ||
68 | + PdfJpegInfo._(this.width, this.height, this.tags); | ||
69 | + | ||
70 | + final int width; | ||
71 | + | ||
72 | + final int height; | ||
73 | + | ||
74 | + final Map<PdfExifTag, dynamic> tags; | ||
75 | + | ||
76 | + /// EXIF version | ||
77 | + String get exifVersion => tags == null || tags[PdfExifTag.ExifVersion] == null | ||
78 | + ? null | ||
79 | + : utf8.decode(tags[PdfExifTag.ExifVersion]); | ||
80 | + | ||
81 | + /// Flashpix format version | ||
82 | + String get flashpixVersion => | ||
83 | + tags == null || tags[PdfExifTag.FlashpixVersion] == null | ||
84 | + ? null | ||
85 | + : utf8.decode(tags[PdfExifTag.FlashpixVersion]); | ||
86 | + | ||
87 | + PdfImageOrientation get orientation => | ||
88 | + tags == null || tags[PdfExifTag.Orientation] == null | ||
89 | + ? PdfImageOrientation.topLeft | ||
90 | + : PdfImageOrientation.values[tags[PdfExifTag.Orientation] - 1]; | ||
91 | + | ||
92 | + double get xResolution => tags == null || tags[PdfExifTag.XResolution] == null | ||
93 | + ? null | ||
94 | + : tags[PdfExifTag.XResolution][0].toDouble() / | ||
95 | + tags[PdfExifTag.XResolution][1].toDouble(); | ||
96 | + | ||
97 | + double get yResolution => tags == null || tags[PdfExifTag.YResolution] == null | ||
98 | + ? null | ||
99 | + : tags[PdfExifTag.YResolution][0].toDouble() / | ||
100 | + tags[PdfExifTag.YResolution][1].toDouble(); | ||
101 | + | ||
102 | + int get pixelXDimension => | ||
103 | + tags == null || tags[PdfExifTag.PixelXDimension] == null | ||
104 | + ? width | ||
105 | + : tags[PdfExifTag.PixelXDimension]; | ||
106 | + | ||
107 | + int get pixelYDimension => | ||
108 | + tags == null || tags[PdfExifTag.PixelYDimension] == null | ||
109 | + ? height | ||
110 | + : tags[PdfExifTag.PixelYDimension]; | ||
111 | + | ||
112 | + @override | ||
113 | + String toString() => '''width: $width height: $height | ||
114 | +exifVersion: $exifVersion flashpixVersion: $flashpixVersion | ||
115 | +xResolution: $xResolution yResolution: $yResolution | ||
116 | +pixelXDimension: $pixelXDimension pixelYDimension: $pixelYDimension | ||
117 | +orientation: $orientation'''; | ||
118 | + | ||
119 | + static Map<PdfExifTag, dynamic> _findExifInJpeg(ByteData buffer) { | ||
120 | + if ((buffer.getUint8(0) != 0xFF) || (buffer.getUint8(1) != 0xD8)) { | ||
121 | + return <PdfExifTag, dynamic>{}; // Not a valid JPEG | ||
122 | + } | ||
123 | + | ||
124 | + int offset = 2; | ||
125 | + final int length = buffer.lengthInBytes; | ||
126 | + int marker; | ||
127 | + | ||
128 | + while (offset < length) { | ||
129 | + final int lastValue = buffer.getUint8(offset); | ||
130 | + if (lastValue != 0xFF) { | ||
131 | + return <PdfExifTag, | ||
132 | + dynamic>{}; // Not a valid marker at offset $offset, found: $lastValue | ||
133 | + } | ||
134 | + | ||
135 | + marker = buffer.getUint8(offset + 1); | ||
136 | + | ||
137 | + // we could implement handling for other markers here, | ||
138 | + // but we're only looking for 0xFFE1 for EXIF data | ||
139 | + if (marker == 0xE1) { | ||
140 | + return _readEXIFData(buffer, offset + 4); | ||
141 | + } else { | ||
142 | + offset += 2 + buffer.getUint16(offset + 2); | ||
143 | + } | ||
144 | + } | ||
145 | + | ||
146 | + return <PdfExifTag, dynamic>{}; | ||
147 | + } | ||
148 | + | ||
149 | + static Map<PdfExifTag, dynamic> _readTags( | ||
150 | + ByteData file, | ||
151 | + int tiffStart, | ||
152 | + int dirStart, | ||
153 | + Endian bigEnd, | ||
154 | + ) { | ||
155 | + final int entries = file.getUint16(dirStart, bigEnd); | ||
156 | + final Map<PdfExifTag, dynamic> tags = <PdfExifTag, dynamic>{}; | ||
157 | + int entryOffset; | ||
158 | + | ||
159 | + for (int i = 0; i < entries; i++) { | ||
160 | + entryOffset = dirStart + i * 12 + 2; | ||
161 | + final int tagId = file.getUint16(entryOffset, bigEnd); | ||
162 | + final PdfExifTag tag = _exifTags[tagId]; | ||
163 | + if (tag != null) { | ||
164 | + tags[tag] = _readTagValue( | ||
165 | + file, | ||
166 | + entryOffset, | ||
167 | + tiffStart, | ||
168 | + dirStart, | ||
169 | + bigEnd, | ||
170 | + ); | ||
171 | + } | ||
172 | + } | ||
173 | + return tags; | ||
174 | + } | ||
175 | + | ||
176 | + static dynamic _readTagValue( | ||
177 | + ByteData file, | ||
178 | + int entryOffset, | ||
179 | + int tiffStart, | ||
180 | + int dirStart, | ||
181 | + Endian bigEnd, | ||
182 | + ) { | ||
183 | + final int type = file.getUint16(entryOffset + 2, bigEnd); | ||
184 | + final int numValues = file.getUint32(entryOffset + 4, bigEnd); | ||
185 | + final int valueOffset = file.getUint32(entryOffset + 8, bigEnd) + tiffStart; | ||
186 | + | ||
187 | + switch (type) { | ||
188 | + case 1: // byte, 8-bit unsigned int | ||
189 | + case 7: // undefined, 8-bit byte, value depending on field | ||
190 | + if (numValues == 1) { | ||
191 | + return file.getUint8(entryOffset + 8); | ||
192 | + } | ||
193 | + final int offset = numValues > 4 ? valueOffset : (entryOffset + 8); | ||
194 | + final Uint8List result = Uint8List(numValues); | ||
195 | + for (int i = 0; i < result.length; ++i) | ||
196 | + result[i] = file.getUint8(offset + i); | ||
197 | + return result; | ||
198 | + case 2: // ascii, 8-bit byte | ||
199 | + final int offset = numValues > 4 ? valueOffset : (entryOffset + 8); | ||
200 | + return _getStringFromDB(file, offset, numValues - 1); | ||
201 | + case 3: // short, 16 bit int | ||
202 | + if (numValues == 1) { | ||
203 | + return file.getUint16(entryOffset + 8, bigEnd); | ||
204 | + } | ||
205 | + final int offset = numValues > 2 ? valueOffset : (entryOffset + 8); | ||
206 | + final Uint16List result = Uint16List(numValues); | ||
207 | + for (int i = 0; i < result.length; ++i) | ||
208 | + result[i] = file.getUint16(offset + i * 2, bigEnd); | ||
209 | + return result; | ||
210 | + case 4: // long, 32 bit int | ||
211 | + if (numValues == 1) { | ||
212 | + return file.getUint32(entryOffset + 8, bigEnd); | ||
213 | + } | ||
214 | + final int offset = valueOffset; | ||
215 | + final Uint32List result = Uint32List(numValues); | ||
216 | + for (int i = 0; i < result.length; ++i) | ||
217 | + result[i] = file.getUint32(offset + i * 4, bigEnd); | ||
218 | + return result; | ||
219 | + case 5: // rational = two long values, first is numerator, second is denominator | ||
220 | + if (numValues == 1) { | ||
221 | + final int numerator = file.getUint32(valueOffset, bigEnd); | ||
222 | + final int denominator = file.getUint32(valueOffset + 4, bigEnd); | ||
223 | + return <int>[numerator, denominator]; | ||
224 | + } | ||
225 | + final int offset = valueOffset; | ||
226 | + final List<List<int>> result = List<List<int>>(numValues); | ||
227 | + for (int i = 0; i < result.length; ++i) { | ||
228 | + final int numerator = file.getUint32(offset + i * 8, bigEnd); | ||
229 | + final int denominator = file.getUint32(offset + i * 8 + 4, bigEnd); | ||
230 | + result[i] = <int>[numerator, denominator]; | ||
231 | + } | ||
232 | + return result; | ||
233 | + case 9: // slong, 32 bit signed int | ||
234 | + if (numValues == 1) { | ||
235 | + return file.getInt32(entryOffset + 8, bigEnd); | ||
236 | + } | ||
237 | + final int offset = valueOffset; | ||
238 | + final Int32List result = Int32List(numValues); | ||
239 | + for (int i = 0; i < result.length; ++i) | ||
240 | + result[i] = file.getInt32(offset + i * 4, bigEnd); | ||
241 | + return result; | ||
242 | + case 10: // signed rational, two slongs, first is numerator, second is denominator | ||
243 | + if (numValues == 1) { | ||
244 | + final int numerator = file.getInt32(valueOffset, bigEnd); | ||
245 | + final int denominator = file.getInt32(valueOffset + 4, bigEnd); | ||
246 | + return <int>[numerator, denominator]; | ||
247 | + } | ||
248 | + final int offset = valueOffset; | ||
249 | + final List<List<int>> result = List<List<int>>(numValues); | ||
250 | + for (int i = 0; i < result.length; ++i) { | ||
251 | + final int numerator = file.getInt32(offset + i * 8, bigEnd); | ||
252 | + final int denominator = file.getInt32(offset + i * 8 + 4, bigEnd); | ||
253 | + result[i] = <int>[numerator, denominator]; | ||
254 | + } | ||
255 | + return result; | ||
256 | + case 11: // single float, 32 bit float | ||
257 | + if (numValues == 1) { | ||
258 | + return file.getFloat32(entryOffset + 8, bigEnd); | ||
259 | + } | ||
260 | + final int offset = valueOffset; | ||
261 | + final Float32List result = Float32List(numValues); | ||
262 | + for (int i = 0; i < result.length; ++i) | ||
263 | + result[i] = file.getFloat32(offset + i * 4, bigEnd); | ||
264 | + return result; | ||
265 | + case 12: // double float, 64 bit float | ||
266 | + if (numValues == 1) { | ||
267 | + return file.getFloat64(entryOffset + 8, bigEnd); | ||
268 | + } | ||
269 | + final int offset = valueOffset; | ||
270 | + final Float64List result = Float64List(numValues); | ||
271 | + for (int i = 0; i < result.length; ++i) | ||
272 | + result[i] = file.getFloat64(offset + i * 8, bigEnd); | ||
273 | + return result; | ||
274 | + } | ||
275 | + } | ||
276 | + | ||
277 | + static String _getStringFromDB(ByteData buffer, int start, int length) { | ||
278 | + return utf8.decode( | ||
279 | + List<int>.generate(length, (int i) => buffer.getUint8(start + i)), | ||
280 | + allowMalformed: true); | ||
281 | + } | ||
282 | + | ||
283 | + static Map<PdfExifTag, dynamic> _readEXIFData(ByteData buffer, int start) { | ||
284 | + final String startingString = _getStringFromDB(buffer, start, 4); | ||
285 | + if (startingString != 'Exif') { | ||
286 | + // Not valid EXIF data! $startingString | ||
287 | + return null; | ||
288 | + } | ||
289 | + | ||
290 | + Endian bigEnd; | ||
291 | + final int tiffOffset = start + 6; | ||
292 | + | ||
293 | + // test for TIFF validity and endianness | ||
294 | + if (buffer.getUint16(tiffOffset) == 0x4949) { | ||
295 | + bigEnd = Endian.little; | ||
296 | + } else if (buffer.getUint16(tiffOffset) == 0x4D4D) { | ||
297 | + bigEnd = Endian.big; | ||
298 | + } else { | ||
299 | + // Not valid TIFF data! (no 0x4949 or 0x4D4D) | ||
300 | + return null; | ||
301 | + } | ||
302 | + | ||
303 | + if (buffer.getUint16(tiffOffset + 2, bigEnd) != 0x002A) { | ||
304 | + // Not valid TIFF data! (no 0x002A) | ||
305 | + return null; | ||
306 | + } | ||
307 | + | ||
308 | + final int firstIFDOffset = buffer.getUint32(tiffOffset + 4, bigEnd); | ||
309 | + | ||
310 | + if (firstIFDOffset < 0x00000008) { | ||
311 | + // Not valid TIFF data! (First offset less than 8) $firstIFDOffset | ||
312 | + return null; | ||
313 | + } | ||
314 | + | ||
315 | + final Map<PdfExifTag, dynamic> tags = | ||
316 | + _readTags(buffer, tiffOffset, tiffOffset + firstIFDOffset, bigEnd); | ||
317 | + | ||
318 | + if (tags.containsKey(PdfExifTag.ExifIFDPointer)) { | ||
319 | + final Map<PdfExifTag, dynamic> exifData = _readTags(buffer, tiffOffset, | ||
320 | + tiffOffset + tags[PdfExifTag.ExifIFDPointer], bigEnd); | ||
321 | + tags.addAll(exifData); | ||
322 | + } | ||
323 | + | ||
324 | + return tags; | ||
325 | + } | ||
326 | + | ||
327 | + static const Map<int, PdfExifTag> _exifTags = <int, PdfExifTag>{ | ||
328 | + 0x9000: PdfExifTag.ExifVersion, | ||
329 | + 0xA000: PdfExifTag.FlashpixVersion, | ||
330 | + 0xA001: PdfExifTag.ColorSpace, | ||
331 | + 0xA002: PdfExifTag.PixelXDimension, | ||
332 | + 0xA003: PdfExifTag.PixelYDimension, | ||
333 | + 0x9101: PdfExifTag.ComponentsConfiguration, | ||
334 | + 0x9102: PdfExifTag.CompressedBitsPerPixel, | ||
335 | + 0x927C: PdfExifTag.MakerNote, | ||
336 | + 0x9286: PdfExifTag.UserComment, | ||
337 | + 0xA004: PdfExifTag.RelatedSoundFile, | ||
338 | + 0x9003: PdfExifTag.DateTimeOriginal, | ||
339 | + 0x9004: PdfExifTag.DateTimeDigitized, | ||
340 | + 0x9290: PdfExifTag.SubsecTime, | ||
341 | + 0x9291: PdfExifTag.SubsecTimeOriginal, | ||
342 | + 0x9292: PdfExifTag.SubsecTimeDigitized, | ||
343 | + 0x829A: PdfExifTag.ExposureTime, | ||
344 | + 0x829D: PdfExifTag.FNumber, | ||
345 | + 0x8822: PdfExifTag.ExposureProgram, | ||
346 | + 0x8824: PdfExifTag.SpectralSensitivity, | ||
347 | + 0x8827: PdfExifTag.ISOSpeedRatings, | ||
348 | + 0x8828: PdfExifTag.OECF, | ||
349 | + 0x9201: PdfExifTag.ShutterSpeedValue, | ||
350 | + 0x9202: PdfExifTag.ApertureValue, | ||
351 | + 0x9203: PdfExifTag.BrightnessValue, | ||
352 | + 0x9204: PdfExifTag.ExposureBias, | ||
353 | + 0x9205: PdfExifTag.MaxApertureValue, | ||
354 | + 0x9206: PdfExifTag.SubjectDistance, | ||
355 | + 0x9207: PdfExifTag.MeteringMode, | ||
356 | + 0x9208: PdfExifTag.LightSource, | ||
357 | + 0x9209: PdfExifTag.Flash, | ||
358 | + 0x9214: PdfExifTag.SubjectArea, | ||
359 | + 0x920A: PdfExifTag.FocalLength, | ||
360 | + 0xA20B: PdfExifTag.FlashEnergy, | ||
361 | + 0xA20C: PdfExifTag.SpatialFrequencyResponse, | ||
362 | + 0xA20E: PdfExifTag.FocalPlaneXResolution, | ||
363 | + 0xA20F: PdfExifTag.FocalPlaneYResolution, | ||
364 | + 0xA210: PdfExifTag.FocalPlaneResolutionUnit, | ||
365 | + 0xA214: PdfExifTag.SubjectLocation, | ||
366 | + 0xA215: PdfExifTag.ExposureIndex, | ||
367 | + 0xA217: PdfExifTag.SensingMethod, | ||
368 | + 0xA300: PdfExifTag.FileSource, | ||
369 | + 0xA301: PdfExifTag.SceneType, | ||
370 | + 0xA302: PdfExifTag.CFAPattern, | ||
371 | + 0xA401: PdfExifTag.CustomRendered, | ||
372 | + 0xA402: PdfExifTag.ExposureMode, | ||
373 | + 0xA403: PdfExifTag.WhiteBalance, | ||
374 | + 0xA404: PdfExifTag.DigitalZoomRation, | ||
375 | + 0xA405: PdfExifTag.FocalLengthIn35mmFilm, | ||
376 | + 0xA406: PdfExifTag.SceneCaptureType, | ||
377 | + 0xA407: PdfExifTag.GainControl, | ||
378 | + 0xA408: PdfExifTag.Contrast, | ||
379 | + 0xA409: PdfExifTag.Saturation, | ||
380 | + 0xA40A: PdfExifTag.Sharpness, | ||
381 | + 0xA40B: PdfExifTag.DeviceSettingDescription, | ||
382 | + 0xA40C: PdfExifTag.SubjectDistanceRange, | ||
383 | + 0xA005: PdfExifTag.InteroperabilityIFDPointer, | ||
384 | + 0xA420: PdfExifTag.ImageUniqueID, | ||
385 | + 0x0100: PdfExifTag.ImageWidth, | ||
386 | + 0x0101: PdfExifTag.ImageHeight, | ||
387 | + 0x8769: PdfExifTag.ExifIFDPointer, | ||
388 | + 0x8825: PdfExifTag.GPSInfoIFDPointer, | ||
389 | + 0x0102: PdfExifTag.BitsPerSample, | ||
390 | + 0x0103: PdfExifTag.Compression, | ||
391 | + 0x0106: PdfExifTag.PhotometricInterpretation, | ||
392 | + 0x0112: PdfExifTag.Orientation, | ||
393 | + 0x0115: PdfExifTag.SamplesPerPixel, | ||
394 | + 0x011C: PdfExifTag.PlanarConfiguration, | ||
395 | + 0x0212: PdfExifTag.YCbCrSubSampling, | ||
396 | + 0x0213: PdfExifTag.YCbCrPositioning, | ||
397 | + 0x011A: PdfExifTag.XResolution, | ||
398 | + 0x011B: PdfExifTag.YResolution, | ||
399 | + 0x0128: PdfExifTag.ResolutionUnit, | ||
400 | + 0x0111: PdfExifTag.StripOffsets, | ||
401 | + 0x0116: PdfExifTag.RowsPerStrip, | ||
402 | + 0x0117: PdfExifTag.StripByteCounts, | ||
403 | + 0x0201: PdfExifTag.JPEGInterchangeFormat, | ||
404 | + 0x0202: PdfExifTag.JPEGInterchangeFormatLength, | ||
405 | + 0x012D: PdfExifTag.TransferFunction, | ||
406 | + 0x013E: PdfExifTag.WhitePoint, | ||
407 | + 0x013F: PdfExifTag.PrimaryChromaticities, | ||
408 | + 0x0211: PdfExifTag.YCbCrCoefficients, | ||
409 | + 0x0214: PdfExifTag.ReferenceBlackWhite, | ||
410 | + 0x0132: PdfExifTag.DateTime, | ||
411 | + 0x010E: PdfExifTag.ImageDescription, | ||
412 | + 0x010F: PdfExifTag.Make, | ||
413 | + 0x0110: PdfExifTag.Model, | ||
414 | + 0x0131: PdfExifTag.Software, | ||
415 | + 0x013B: PdfExifTag.Artist, | ||
416 | + 0x8298: PdfExifTag.Copyright, | ||
417 | + }; | ||
418 | +} | ||
419 | + | ||
420 | +enum PdfExifTag { | ||
421 | + // version tags | ||
422 | + ExifVersion, // EXIF version | ||
423 | + FlashpixVersion, // Flashpix format version | ||
424 | + | ||
425 | + // colorspace tags | ||
426 | + ColorSpace, // Color space information tag | ||
427 | + | ||
428 | + // image configuration | ||
429 | + PixelXDimension, // Valid width of meaningful image | ||
430 | + PixelYDimension, // Valid height of meaningful image | ||
431 | + ComponentsConfiguration, // Information about channels | ||
432 | + CompressedBitsPerPixel, // Compressed bits per pixel | ||
433 | + | ||
434 | + // user information | ||
435 | + MakerNote, // Any desired information written by the manufacturer | ||
436 | + UserComment, // Comments by user | ||
437 | + | ||
438 | + // related file | ||
439 | + RelatedSoundFile, // Name of related sound file | ||
440 | + | ||
441 | + // date and time | ||
442 | + DateTimeOriginal, // Date and time when the original image was generated | ||
443 | + DateTimeDigitized, // Date and time when the image was stored digitally | ||
444 | + SubsecTime, // Fractions of seconds for DateTime | ||
445 | + SubsecTimeOriginal, // Fractions of seconds for DateTimeOriginal | ||
446 | + SubsecTimeDigitized, // Fractions of seconds for DateTimeDigitized | ||
447 | + | ||
448 | + // picture-taking conditions | ||
449 | + ExposureTime, // Exposure time (in seconds) | ||
450 | + FNumber, // F number | ||
451 | + ExposureProgram, // Exposure program | ||
452 | + SpectralSensitivity, // Spectral sensitivity | ||
453 | + ISOSpeedRatings, // ISO speed rating | ||
454 | + OECF, // Optoelectric conversion factor | ||
455 | + ShutterSpeedValue, // Shutter speed | ||
456 | + ApertureValue, // Lens aperture | ||
457 | + BrightnessValue, // Value of brightness | ||
458 | + ExposureBias, // Exposure bias | ||
459 | + MaxApertureValue, // Smallest F number of lens | ||
460 | + SubjectDistance, // Distance to subject in meters | ||
461 | + MeteringMode, // Metering mode | ||
462 | + LightSource, // Kind of light source | ||
463 | + Flash, // Flash status | ||
464 | + SubjectArea, // Location and area of main subject | ||
465 | + FocalLength, // Focal length of the lens in mm | ||
466 | + FlashEnergy, // Strobe energy in BCPS | ||
467 | + SpatialFrequencyResponse, // | ||
468 | + FocalPlaneXResolution, // Number of pixels in width direction per FocalPlaneResolutionUnit | ||
469 | + FocalPlaneYResolution, // Number of pixels in height direction per FocalPlaneResolutionUnit | ||
470 | + FocalPlaneResolutionUnit, // Unit for measuring FocalPlaneXResolution and FocalPlaneYResolution | ||
471 | + SubjectLocation, // Location of subject in image | ||
472 | + ExposureIndex, // Exposure index selected on camera | ||
473 | + SensingMethod, // Image sensor type | ||
474 | + FileSource, // Image source (3 == DSC) | ||
475 | + SceneType, // Scene type (1 == directly photographed) | ||
476 | + CFAPattern, // Color filter array geometric pattern | ||
477 | + CustomRendered, // Special processing | ||
478 | + ExposureMode, // Exposure mode | ||
479 | + WhiteBalance, // 1 = auto white balance, 2 = manual | ||
480 | + DigitalZoomRation, // Digital zoom ratio | ||
481 | + FocalLengthIn35mmFilm, // Equivalent foacl length assuming 35mm film camera (in mm) | ||
482 | + SceneCaptureType, // Type of scene | ||
483 | + GainControl, // Degree of overall image gain adjustment | ||
484 | + Contrast, // Direction of contrast processing applied by camera | ||
485 | + Saturation, // Direction of saturation processing applied by camera | ||
486 | + Sharpness, // Direction of sharpness processing applied by camera | ||
487 | + DeviceSettingDescription, // | ||
488 | + SubjectDistanceRange, // Distance to subject | ||
489 | + | ||
490 | + // other tags | ||
491 | + InteroperabilityIFDPointer, | ||
492 | + ImageUniqueID, // Identifier assigned uniquely to each image | ||
493 | + | ||
494 | + // tiff Tags | ||
495 | + ImageWidth, | ||
496 | + ImageHeight, | ||
497 | + ExifIFDPointer, | ||
498 | + GPSInfoIFDPointer, | ||
499 | + BitsPerSample, | ||
500 | + Compression, | ||
501 | + PhotometricInterpretation, | ||
502 | + Orientation, | ||
503 | + SamplesPerPixel, | ||
504 | + PlanarConfiguration, | ||
505 | + YCbCrSubSampling, | ||
506 | + YCbCrPositioning, | ||
507 | + XResolution, | ||
508 | + YResolution, | ||
509 | + ResolutionUnit, | ||
510 | + StripOffsets, | ||
511 | + RowsPerStrip, | ||
512 | + StripByteCounts, | ||
513 | + JPEGInterchangeFormat, | ||
514 | + JPEGInterchangeFormatLength, | ||
515 | + TransferFunction, | ||
516 | + WhitePoint, | ||
517 | + PrimaryChromaticities, | ||
518 | + YCbCrCoefficients, | ||
519 | + ReferenceBlackWhite, | ||
520 | + DateTime, | ||
521 | + ImageDescription, | ||
522 | + Make, | ||
523 | + Model, | ||
524 | + Software, | ||
525 | + Artist, | ||
526 | + Copyright, | ||
527 | +} |
@@ -113,25 +113,25 @@ class PdfGraphics { | @@ -113,25 +113,25 @@ class PdfGraphics { | ||
113 | buf.putNumList(<double>[w, 0, 0, h, x, y]); | 113 | buf.putNumList(<double>[w, 0, 0, h, x, y]); |
114 | break; | 114 | break; |
115 | case PdfImageOrientation.topRight: | 115 | case PdfImageOrientation.topRight: |
116 | - buf.putNumList(<double>[-w, 0, 0, h, w - x, y]); | 116 | + buf.putNumList(<double>[-w, 0, 0, h, w + x, y]); |
117 | break; | 117 | break; |
118 | case PdfImageOrientation.bottomRight: | 118 | case PdfImageOrientation.bottomRight: |
119 | - buf.putNumList(<double>[-w, 0, 0, -h, w - x, h - y]); | 119 | + buf.putNumList(<double>[-w, 0, 0, -h, w + x, h + y]); |
120 | break; | 120 | break; |
121 | case PdfImageOrientation.bottomLeft: | 121 | case PdfImageOrientation.bottomLeft: |
122 | - buf.putNumList(<double>[w, 0, 0, -h, x, h - y]); | 122 | + buf.putNumList(<double>[w, 0, 0, -h, x, h + y]); |
123 | break; | 123 | break; |
124 | case PdfImageOrientation.leftTop: | 124 | case PdfImageOrientation.leftTop: |
125 | - buf.putNumList(<double>[0, -h, -w, 0, w - x, h - y]); | 125 | + buf.putNumList(<double>[0, -h, -w, 0, w + x, h + y]); |
126 | break; | 126 | break; |
127 | case PdfImageOrientation.rightTop: | 127 | case PdfImageOrientation.rightTop: |
128 | - buf.putNumList(<double>[0, h, -w, 0, w - x, y]); | 128 | + buf.putNumList(<double>[0, -h, w, 0, x, h + y]); |
129 | break; | 129 | break; |
130 | case PdfImageOrientation.rightBottom: | 130 | case PdfImageOrientation.rightBottom: |
131 | buf.putNumList(<double>[0, h, w, 0, x, y]); | 131 | buf.putNumList(<double>[0, h, w, 0, x, y]); |
132 | break; | 132 | break; |
133 | case PdfImageOrientation.leftBottom: | 133 | case PdfImageOrientation.leftBottom: |
134 | - buf.putNumList(<double>[0, -h, w, 0, x, h - y]); | 134 | + buf.putNumList(<double>[0, h, -w, 0, w + x, y]); |
135 | break; | 135 | break; |
136 | } | 136 | } |
137 | 137 |
@@ -109,56 +109,17 @@ class PdfImage extends PdfXObject { | @@ -109,56 +109,17 @@ class PdfImage extends PdfXObject { | ||
109 | PdfImageOrientation orientation, | 109 | PdfImageOrientation orientation, |
110 | }) { | 110 | }) { |
111 | assert(image != null); | 111 | assert(image != null); |
112 | - | ||
113 | - int width; | ||
114 | - int height; | ||
115 | - int offset = 0; | ||
116 | - while (offset < image.length) { | ||
117 | - while (image[offset] == 0xff) { | ||
118 | - offset++; | ||
119 | - } | ||
120 | - | ||
121 | - final int mrkr = image[offset]; | ||
122 | - offset++; | ||
123 | - | ||
124 | - if (mrkr == 0xd8) { | ||
125 | - continue; // SOI | ||
126 | - } | ||
127 | - | ||
128 | - if (mrkr == 0xd9) { | ||
129 | - break; // EOI | ||
130 | - } | ||
131 | - | ||
132 | - if (0xd0 <= mrkr && mrkr <= 0xd7) { | ||
133 | - continue; | ||
134 | - } | ||
135 | - | ||
136 | - if (mrkr == 0x01) { | ||
137 | - continue; // TEM | ||
138 | - } | ||
139 | - | ||
140 | - final int len = (image[offset] << 8) | image[offset + 1]; | ||
141 | - offset += 2; | ||
142 | - | ||
143 | - if (mrkr == 0xc0) { | ||
144 | - height = (image[offset + 1] << 8) | image[offset + 2]; | ||
145 | - width = (image[offset + 3] << 8) | image[offset + 4]; | ||
146 | - break; | ||
147 | - } | ||
148 | - offset += len - 2; | ||
149 | - } | ||
150 | - | ||
151 | - orientation ??= PdfImageOrientation.leftTop; | 112 | + final PdfJpegInfo info = PdfJpegInfo(image); |
152 | 113 | ||
153 | return PdfImage._( | 114 | return PdfImage._( |
154 | pdfDocument, | 115 | pdfDocument, |
155 | image: image, | 116 | image: image, |
156 | - width: width, | ||
157 | - height: height, | 117 | + width: info.width, |
118 | + height: info.height, | ||
158 | jpeg: true, | 119 | jpeg: true, |
159 | alpha: false, | 120 | alpha: false, |
160 | alphaChannel: false, | 121 | alphaChannel: false, |
161 | - orientation: orientation, | 122 | + orientation: orientation ?? info.orientation, |
162 | ); | 123 | ); |
163 | } | 124 | } |
164 | 125 |
@@ -42,23 +42,18 @@ void main() { | @@ -42,23 +42,18 @@ void main() { | ||
42 | )); | 42 | )); |
43 | }); | 43 | }); |
44 | 44 | ||
45 | - test('Pdf Jpeg Orientation', () { | 45 | + test('Pdf Jpeg Orientations', () { |
46 | pdf.addPage( | 46 | pdf.addPage( |
47 | Page( | 47 | Page( |
48 | - build: (Context context) => Wrap( | ||
49 | - spacing: 20, | ||
50 | - runSpacing: 20, | 48 | + build: (Context context) => GridView( |
49 | + crossAxisCount: 4, | ||
50 | + crossAxisSpacing: 10, | ||
51 | children: List<Widget>.generate( | 51 | children: List<Widget>.generate( |
52 | - PdfImageOrientation.values.length, | ||
53 | - (int index) => SizedBox( | ||
54 | - width: 230, | ||
55 | - height: 230, | ||
56 | - child: Image( | ||
57 | - PdfImage.jpeg( | ||
58 | - pdf.document, | ||
59 | - image: base64.decode(jpegImage), | ||
60 | - orientation: PdfImageOrientation.values[index], | ||
61 | - ), | 52 | + images.length, |
53 | + (int index) => Image( | ||
54 | + PdfImage.jpeg( | ||
55 | + pdf.document, | ||
56 | + image: base64.decode(images[index]), | ||
62 | ), | 57 | ), |
63 | ), | 58 | ), |
64 | ), | 59 | ), |
@@ -83,5 +78,13 @@ Future<Uint8List> download(String url) async { | @@ -83,5 +78,13 @@ Future<Uint8List> download(String url) async { | ||
83 | return Uint8List.fromList(data); | 78 | return Uint8List.fromList(data); |
84 | } | 79 | } |
85 | 80 | ||
86 | -const String jpegImage = | ||
87 | - '/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAEMuMjoyKkM6NjpLR0NPZKZsZFxcZMySmnmm8dT++u3U6eX//////////+Xp////////////////////////////2wBDAUdLS2RXZMRsbMT//+n/////////////////////////////////////////////////////////////////////wAARCAAUAAgDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAP/xAAbEAACAwEBAQAAAAAAAAAAAAABAgARIQMEQf/EABQBAQAAAAAAAAAAAAAAAAAAAAD/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCvm5joGZi1hj9iPIgIZ7Nhzl5EC3FAikC9N7ERA//Z'; | 81 | +const List<String> images = <String>[ |
82 | + '/9j/4AAQSkZJRgABAQEA3ADcAAD/4QAiRXhpZgAASUkqAAgAAAABABIBAwABAAAAAQAAAAAAAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCABQADADASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAkI/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/EABQBAQAAAAAAAAAAAAAAAAAAAAD/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwDKgAAAAAAAKqAAAAlWAAACqgAJVgAqoAAACVYAKqAAAA//2Q==', | ||
83 | + '/9j/4AAQSkZJRgABAQEA3ADcAAD/4QAiRXhpZgAASUkqAAgAAAABABIBAwABAAAAAgAAAAAAAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCABQADADASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAkI/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/EABQBAQAAAAAAAAAAAAAAAAAAAAD/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwDKgAAAAAKqAAAAlWACqgAJVgAAAqoAAACVYAKqAAAAlWAD/9k=', | ||
84 | + '/9j/4AAQSkZJRgABAQEA3ADcAAD/4QAiRXhpZgAASUkqAAgAAAABABIBAwABAAAAAwAAAAAAAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCABQADADASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAkI/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/EABQBAQAAAAAAAAAAAAAAAAAAAAD/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwDVIAAAJVgAqoAAACVYAKqAAlWAAACqgAAAJVgAAAAAAA//2Q==', | ||
85 | + '/9j/4AAQSkZJRgABAQEA3ADcAAD/4QAiRXhpZgAASUkqAAgAAAABABIBAwABAAAABAAAAAAAAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCABQADADASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAkI/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/EABQBAQAAAAAAAAAAAAAAAAAAAAD/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwDKgAKqAAAAlWACqgAAAJVgAAAqoACVYAKqAAAAlWAAAAAD/9k=', | ||
86 | + '/9j/4AAQSkZJRgABAQEA3ADcAAD/4QAiRXhpZgAASUkqAAgAAAABABIBAwABAAAABQAAAAAAAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAwAFADASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAkI/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/EABQBAQAAAAAAAAAAAAAAAAAAAAD/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwDKgAAAAAAAAAAAKqAAlWACqgAAAJVgAqoAAAAAAAD/2Q==', | ||
87 | + '/9j/4AAQSkZJRgABAQEA3ADcAAD/4QAiRXhpZgAASUkqAAgAAAABABIBAwABAAAABgAAAAAAAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAwAFADASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAkI/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/EABQBAQAAAAAAAAAAAAAAAAAAAAD/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwDKgAKqAAAAAAAAlWACqgAJVgAqoAAACVYAAAAAAAAAP//Z', | ||
88 | + '/9j/4AAQSkZJRgABAQEA3ADcAAD/4QAiRXhpZgAASUkqAAgAAAABABIBAwABAAAABwAAAAAAAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAwAFADASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAkI/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/EABQBAQAAAAAAAAAAAAAAAAAAAAD/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwDVIAAAAAAAJVgAqoAAACVYAKqAAlWAAAAAAAAAAAD/2Q==', | ||
89 | + '/9j/4AAQSkZJRgABAQEA3ADcAAD/4QAiRXhpZgAASUkqAAgAAAABABIBAwABAAAACAAAAAAAAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAwAFADASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAkI/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/EABQBAQAAAAAAAAAAAAAAAAAAAAD/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwDKgAAAAAAAAAKqAAAAlWACqgAJVgAqoAAAAAAACVYAP//Z', | ||
90 | +]; |
No preview for this file type
-
Please register or login to post a comment