David PHAM-VAN

Add Exif reader

@@ -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';
  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