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