1 /*****************************************************************************/
2 // Copyright 2006-2019 Adobe Systems Incorporated
3 // All Rights Reserved.
4 //
5 // NOTICE: Adobe permits you to use, modify, and distribute this file in
6 // accordance with the terms of the Adobe license agreement accompanying it.
7 /*****************************************************************************/
8
9 #include "dng_iptc.h"
10
11 #include "dng_assertions.h"
12 #include "dng_auto_ptr.h"
13 #include "dng_memory_stream.h"
14 #include "dng_stream.h"
15 #include "dng_utils.h"
16
17 /*****************************************************************************/
18
dng_iptc()19 dng_iptc::dng_iptc ()
20
21 : fTitle ()
22
23 , fUrgency (-1)
24
25 , fCategory ()
26
27 , fSupplementalCategories ()
28
29 , fKeywords ()
30
31 , fInstructions ()
32
33 , fDateTimeCreated ()
34
35 , fDigitalCreationDateTime ()
36
37 , fAuthors ()
38 , fAuthorsPosition ()
39
40 , fCity ()
41 , fState ()
42 , fCountry ()
43 , fCountryCode ()
44
45 , fLocation ()
46
47 , fTransmissionReference ()
48
49 , fHeadline ()
50
51 , fCredit ()
52
53 , fSource ()
54
55 , fCopyrightNotice ()
56
57 , fDescription ()
58 , fDescriptionWriter ()
59
60 {
61
62 }
63
64 /*****************************************************************************/
65
~dng_iptc()66 dng_iptc::~dng_iptc ()
67 {
68
69 }
70
71 /*****************************************************************************/
72
IsEmpty() const73 bool dng_iptc::IsEmpty () const
74 {
75
76 if (fTitle.NotEmpty ())
77 {
78 return false;
79 }
80
81 if (fUrgency >= 0)
82 {
83 return false;
84 }
85
86 if (fCategory.NotEmpty ())
87 {
88 return false;
89 }
90
91 if (fSupplementalCategories.Count () > 0)
92 {
93 return false;
94 }
95
96 if (fKeywords.Count () > 0)
97 {
98 return false;
99 }
100
101 if (fInstructions.NotEmpty ())
102 {
103 return false;
104 }
105
106 if (fDateTimeCreated.IsValid ())
107 {
108 return false;
109 }
110
111 if (fDigitalCreationDateTime.IsValid ())
112 {
113 return false;
114 }
115
116 if (fAuthors.Count () != 0 ||
117 fAuthorsPosition.NotEmpty ())
118 {
119 return false;
120 }
121
122 if (fCity .NotEmpty () ||
123 fState .NotEmpty () ||
124 fCountry.NotEmpty ())
125 {
126 return false;
127 }
128
129 if (fCountryCode.NotEmpty ())
130 {
131 return false;
132 }
133
134 if (fLocation.NotEmpty ())
135 {
136 return false;
137 }
138
139 if (fTransmissionReference.NotEmpty ())
140 {
141 return false;
142 }
143
144 if (fHeadline.NotEmpty ())
145 {
146 return false;
147 }
148
149 if (fCredit.NotEmpty ())
150 {
151 return false;
152 }
153
154 if (fSource.NotEmpty ())
155 {
156 return false;
157 }
158
159 if (fCopyrightNotice.NotEmpty ())
160 {
161 return false;
162 }
163
164 if (fDescription .NotEmpty () ||
165 fDescriptionWriter.NotEmpty ())
166 {
167 return false;
168 }
169
170 return true;
171
172 }
173
174 /*****************************************************************************/
175
ParseString(dng_stream & stream,dng_string & s,CharSet charSet)176 void dng_iptc::ParseString (dng_stream &stream,
177 dng_string &s,
178 CharSet charSet)
179 {
180
181 uint32 length = stream.Get_uint16 ();
182
183 dng_memory_data buffer (length + 1);
184
185 char *c = buffer.Buffer_char ();
186
187 stream.Get (c, length);
188
189 c [length] = 0;
190
191 switch (charSet)
192 {
193
194 case kCharSetUTF8:
195 {
196 s.Set_UTF8 (c);
197 break;
198 }
199
200 default:
201 {
202 s.Set_SystemEncoding (c);
203 }
204
205 }
206
207 s.SetLineEndingsToNewLines ();
208
209 s.StripLowASCII ();
210
211 s.TrimTrailingBlanks ();
212
213 }
214
215 /*****************************************************************************/
216
Parse(const void * blockData,uint32 blockSize,uint64 offsetInOriginalFile)217 void dng_iptc::Parse (const void *blockData,
218 uint32 blockSize,
219 uint64 offsetInOriginalFile)
220 {
221
222 dng_stream stream (blockData,
223 blockSize,
224 offsetInOriginalFile);
225
226 stream.SetBigEndian ();
227
228 // Make a first pass though the data, trying to figure out the
229 // character set.
230
231 CharSet charSet = kCharSetUnknown;
232
233 bool isValidUTF8 = true;
234
235 bool hasEncodingMarker = false;
236
237 uint64 firstOffset = stream.Position ();
238
239 uint64 nextOffset = firstOffset;
240
241 while (nextOffset + 5 < stream.Length ())
242 {
243
244 stream.SetReadPosition (nextOffset);
245
246 uint8 firstByte = stream.Get_uint8 ();
247
248 if (firstByte != 0x1C) break;
249
250 uint8 record = stream.Get_uint8 ();
251 uint8 dataSet = stream.Get_uint8 ();
252 uint32 dataSize = stream.Get_uint16 ();
253
254 nextOffset = stream.Position () + dataSize;
255
256 if (record == 1)
257 {
258
259 switch (dataSet)
260 {
261
262 case 90:
263 {
264
265 hasEncodingMarker = true;
266
267 if (dataSize == 3)
268 {
269
270 uint32 byte1 = stream.Get_uint8 ();
271 uint32 byte2 = stream.Get_uint8 ();
272 uint32 byte3 = stream.Get_uint8 ();
273
274 if (byte1 == 27 /* Escape */ &&
275 byte2 == 0x25 &&
276 byte3 == 0x47)
277 {
278
279 charSet = kCharSetUTF8;
280
281 }
282
283 }
284
285 break;
286
287 }
288
289 default:
290 break;
291
292 }
293
294 }
295
296 else if (record == 2)
297 {
298
299 dng_memory_data buffer (dataSize + 1);
300
301 char *s = buffer.Buffer_char ();
302
303 stream.Get (s, dataSize);
304
305 s [dataSize] = 0;
306
307 isValidUTF8 = isValidUTF8 && dng_string::IsUTF8 (s);
308
309 }
310
311 }
312
313 // If we don't have an encoding marker, and the data is valid
314 // UTF-8, then assume that it is UTF-8 (rather than system encoding).
315
316 if (!hasEncodingMarker && isValidUTF8)
317 {
318
319 charSet = kCharSetUTF8;
320
321 }
322
323 // Make a second pass though the data, actually reading the data.
324
325 nextOffset = firstOffset;
326
327 while (nextOffset + 5 < stream.Length ())
328 {
329
330 stream.SetReadPosition (nextOffset);
331
332 uint8 firstByte = stream.Get_uint8 ();
333
334 if (firstByte != 0x1C) break;
335
336 uint8 record = stream.Get_uint8 ();
337 uint8 dataSet = stream.Get_uint8 ();
338 uint32 dataSize = stream.Get_uint16 ();
339
340 nextOffset = stream.Position () + dataSize;
341
342 if (record == 2)
343 {
344
345 stream.SetReadPosition (stream.Position () - 2);
346
347 switch ((DataSet) dataSet)
348 {
349
350 case kObjectNameSet:
351 {
352 ParseString (stream, fTitle, charSet);
353 break;
354 }
355
356 case kUrgencySet:
357 {
358
359 int32 size = stream.Get_uint16 ();
360
361 if (size == 1)
362 {
363
364 char c = stream.Get_int8 ();
365
366 if (c >= '0' && c <= '9')
367 {
368 fUrgency = c - '0';
369 }
370
371 }
372
373 break;
374
375 }
376
377 case kCategorySet:
378 {
379 ParseString (stream, fCategory, charSet);
380 break;
381 }
382
383 case kSupplementalCategoriesSet:
384 {
385
386 dng_string category;
387
388 ParseString (stream, category, charSet);
389
390 if (category.NotEmpty ())
391 {
392 fSupplementalCategories.Append (category);
393 }
394
395 break;
396
397 }
398
399 case kKeywordsSet:
400 {
401
402 dng_string keyword;
403
404 ParseString (stream, keyword, charSet);
405
406 if (keyword.NotEmpty ())
407 {
408 fKeywords.Append (keyword);
409 }
410
411 break;
412
413 }
414
415 case kSpecialInstructionsSet:
416 {
417 ParseString (stream, fInstructions, charSet);
418 break;
419 }
420
421 case kDateCreatedSet:
422 {
423
424 uint32 length = stream.Get_uint16 ();
425
426 if (length == 8)
427 {
428
429 char date [9];
430
431 stream.Get (date, 8);
432
433 date [8] = 0;
434
435 fDateTimeCreated.Decode_IPTC_Date (date);
436
437 }
438
439 break;
440
441 }
442
443 case kTimeCreatedSet:
444 {
445
446 uint32 length = stream.Get_uint16 ();
447
448 if (length >= 4 && length <= 11)
449 {
450
451 char time [12];
452
453 stream.Get (time, length);
454
455 time [length] = 0;
456
457 fDateTimeCreated.Decode_IPTC_Time (time);
458
459 }
460
461 break;
462
463 }
464
465 case kDigitalCreationDateSet:
466 {
467
468 uint32 length = stream.Get_uint16 ();
469
470 if (length == 8)
471 {
472
473 char date [9];
474
475 stream.Get (date, 8);
476
477 date [8] = 0;
478
479 fDigitalCreationDateTime.Decode_IPTC_Date (date);
480
481 }
482
483 break;
484
485 }
486
487 case kDigitalCreationTimeSet:
488 {
489
490 uint32 length = stream.Get_uint16 ();
491
492 if (length >= 4 && length <= 11)
493 {
494
495 char time [12];
496
497 stream.Get (time, length);
498
499 time [length] = 0;
500
501 fDigitalCreationDateTime.Decode_IPTC_Time (time);
502
503 }
504
505 break;
506
507 }
508
509 case kBylineSet:
510 {
511
512 dng_string author;
513
514 ParseString (stream, author, charSet);
515
516 if (author.NotEmpty ())
517 {
518 fAuthors.Append (author);
519 }
520
521 break;
522
523 }
524
525 case kBylineTitleSet:
526 {
527 ParseString (stream, fAuthorsPosition, charSet);
528 break;
529 }
530
531 case kCitySet:
532 {
533 ParseString (stream, fCity, charSet);
534 break;
535 }
536
537 case kProvinceStateSet:
538 {
539 ParseString (stream, fState, charSet);
540 break;
541 }
542
543 case kCountryNameSet:
544 {
545 ParseString (stream, fCountry, charSet);
546 break;
547 }
548
549 case kCountryCodeSet:
550 {
551 ParseString (stream, fCountryCode, charSet);
552 break;
553 }
554
555 case kSublocationSet:
556 {
557 ParseString (stream, fLocation, charSet);
558 break;
559 }
560
561 case kOriginalTransmissionReferenceSet:
562 {
563 ParseString (stream, fTransmissionReference, charSet);
564 break;
565 }
566
567 case kHeadlineSet:
568 {
569 ParseString (stream, fHeadline, charSet);
570 break;
571 }
572
573 case kCreditSet:
574 {
575 ParseString (stream, fCredit, charSet);
576 break;
577 }
578
579 case kSourceSet:
580 {
581 ParseString (stream, fSource, charSet);
582 break;
583 }
584
585 case kCopyrightNoticeSet:
586 {
587 ParseString (stream, fCopyrightNotice, charSet);
588 break;
589 }
590
591 case kCaptionSet:
592 {
593 ParseString (stream, fDescription, charSet);
594 break;
595 }
596
597 case kCaptionWriterSet:
598 {
599 ParseString (stream, fDescriptionWriter, charSet);
600 break;
601 }
602
603 // All other IPTC records are not part of the IPTC core
604 // and/or are not kept in sync with XMP tags, so we ignore
605 // them.
606
607 default:
608 break;
609
610 }
611
612 }
613
614 }
615
616 }
617
618 /*****************************************************************************/
619
SpoolString(dng_stream & stream,const dng_string & s,uint8 dataSet,uint32 maxChars,CharSet charSet)620 void dng_iptc::SpoolString (dng_stream &stream,
621 const dng_string &s,
622 uint8 dataSet,
623 uint32 maxChars,
624 CharSet charSet)
625 {
626
627 if (s.IsEmpty ())
628 {
629 return;
630 }
631
632 stream.Put_uint16 (0x1C02);
633 stream.Put_uint8 (dataSet);
634
635 dng_string ss (s);
636
637 ss.SetLineEndingsToReturns ();
638
639 if (charSet == kCharSetUTF8)
640 {
641
642 // UTF-8 encoding.
643
644 if (ss.Length () > maxChars)
645 {
646 ss.Truncate (maxChars);
647 }
648
649 uint32 len = ss.Length ();
650
651 stream.Put_uint16 ((uint16) len);
652
653 stream.Put (ss.Get (), len);
654
655 }
656
657 else
658 {
659
660 // System character set encoding.
661
662 dng_memory_data buffer;
663
664 uint32 len = ss.Get_SystemEncoding (buffer);
665
666 if (len > maxChars)
667 {
668
669 uint32 lower = 0;
670 uint32 upper = ss.Length () - 1;
671
672 while (upper > lower)
673 {
674
675 uint32 middle = (upper + lower + 1) >> 1;
676
677 dng_string sss (ss);
678
679 sss.Truncate (middle);
680
681 len = sss.Get_SystemEncoding (buffer);
682
683 if (len <= maxChars)
684 {
685
686 lower = middle;
687
688 }
689
690 else
691 {
692
693 upper = middle - 1;
694
695 }
696
697 }
698
699 ss.Truncate (lower);
700
701 len = ss.Get_SystemEncoding (buffer);
702
703 }
704
705 stream.Put_uint16 ((uint16) len);
706
707 stream.Put (buffer.Buffer_char (), len);
708
709 }
710
711 }
712 /*****************************************************************************/
713
Spool(dng_memory_allocator & allocator,bool padForTIFF)714 dng_memory_block * dng_iptc::Spool (dng_memory_allocator &allocator,
715 bool padForTIFF)
716 {
717
718 uint32 j;
719
720 char s [64];
721
722 dng_memory_stream stream (allocator, NULL, 2048);
723
724 stream.SetBigEndian ();
725
726 // Medata working group - now we just always write UTF-8.
727
728 CharSet charSet = kCharSetUTF8;
729
730 // UTF-8 encoding marker.
731
732 if (charSet == kCharSetUTF8)
733 {
734
735 stream.Put_uint16 (0x1C01);
736 stream.Put_uint8 (90);
737 stream.Put_uint16 (3);
738 stream.Put_uint8 (27);
739 stream.Put_uint8 (0x25);
740 stream.Put_uint8 (0x47);
741
742 }
743
744 stream.Put_uint16 (0x1C02);
745 stream.Put_uint8 (kRecordVersionSet);
746 stream.Put_uint16 (2);
747 stream.Put_uint16 (4);
748
749 SpoolString (stream,
750 fTitle,
751 kObjectNameSet,
752 64,
753 charSet);
754
755 if (fUrgency >= 0)
756 {
757
758 sprintf (s, "%1u", (unsigned) fUrgency);
759
760 stream.Put_uint16 (0x1C02);
761 stream.Put_uint8 (kUrgencySet);
762
763 stream.Put_uint16 (1);
764
765 stream.Put (s, 1);
766
767 }
768
769 SpoolString (stream,
770 fCategory,
771 kCategorySet,
772 3,
773 charSet);
774
775 for (j = 0; j < fSupplementalCategories.Count (); j++)
776 {
777
778 SpoolString (stream,
779 fSupplementalCategories [j],
780 kSupplementalCategoriesSet,
781 32,
782 charSet);
783
784 }
785
786 for (j = 0; j < fKeywords.Count (); j++)
787 {
788
789 SpoolString (stream,
790 fKeywords [j],
791 kKeywordsSet,
792 64,
793 charSet);
794
795 }
796
797 SpoolString (stream,
798 fInstructions,
799 kSpecialInstructionsSet,
800 255,
801 charSet);
802
803 if (fDateTimeCreated.IsValid ())
804 {
805
806 dng_string dateString = fDateTimeCreated.Encode_IPTC_Date ();
807
808 if (dateString.NotEmpty ())
809 {
810
811 DNG_ASSERT (dateString.Length () == 8, "Wrong length IPTC date");
812
813 stream.Put_uint16 (0x1C02);
814 stream.Put_uint8 (kDateCreatedSet);
815
816 stream.Put_uint16 (8);
817
818 stream.Put (dateString.Get (), 8);
819
820 }
821
822 dng_string timeString = fDateTimeCreated.Encode_IPTC_Time ();
823
824 if (timeString.NotEmpty ())
825 {
826
827 stream.Put_uint16 (0x1C02);
828 stream.Put_uint8 (kTimeCreatedSet);
829
830 stream.Put_uint16 ((uint16)timeString.Length ());
831
832 stream.Put (timeString.Get (), timeString.Length ());
833
834 }
835
836 }
837
838 if (fDigitalCreationDateTime.IsValid ())
839 {
840
841 dng_string dateString = fDigitalCreationDateTime.Encode_IPTC_Date ();
842
843 if (dateString.NotEmpty ())
844 {
845
846 DNG_ASSERT (dateString.Length () == 8, "Wrong length IPTC date");
847
848 stream.Put_uint16 (0x1C02);
849 stream.Put_uint8 (kDigitalCreationDateSet);
850
851 stream.Put_uint16 (8);
852
853 stream.Put (dateString.Get (), 8);
854
855 }
856
857 dng_string timeString = fDigitalCreationDateTime.Encode_IPTC_Time ();
858
859 if (timeString.NotEmpty ())
860 {
861
862 stream.Put_uint16 (0x1C02);
863 stream.Put_uint8 (kDigitalCreationTimeSet);
864
865 stream.Put_uint16 ((uint16)timeString.Length ());
866
867 stream.Put (timeString.Get (), timeString.Length ());
868
869 }
870
871 }
872
873 for (j = 0; j < fAuthors.Count (); j++)
874 {
875
876 SpoolString (stream,
877 fAuthors [j],
878 kBylineSet,
879 32,
880 charSet);
881
882 }
883
884 SpoolString (stream,
885 fAuthorsPosition,
886 kBylineTitleSet,
887 32,
888 charSet);
889
890 SpoolString (stream,
891 fCity,
892 kCitySet,
893 32,
894 charSet);
895
896 SpoolString (stream,
897 fLocation,
898 kSublocationSet,
899 32,
900 charSet);
901
902 SpoolString (stream,
903 fState,
904 kProvinceStateSet,
905 32,
906 charSet);
907
908 SpoolString (stream,
909 fCountryCode,
910 kCountryCodeSet,
911 3,
912 charSet);
913
914 SpoolString (stream,
915 fCountry,
916 kCountryNameSet,
917 64,
918 charSet);
919
920 SpoolString (stream,
921 fTransmissionReference,
922 kOriginalTransmissionReferenceSet,
923 32,
924 charSet);
925
926 SpoolString (stream,
927 fHeadline,
928 kHeadlineSet,
929 255,
930 charSet);
931
932 SpoolString (stream,
933 fCredit,
934 kCreditSet,
935 32,
936 charSet);
937
938 SpoolString (stream,
939 fSource,
940 kSourceSet,
941 32,
942 charSet);
943
944 SpoolString (stream,
945 fCopyrightNotice,
946 kCopyrightNoticeSet,
947 128,
948 charSet);
949
950 SpoolString (stream,
951 fDescription,
952 kCaptionSet,
953 2000,
954 charSet);
955
956 SpoolString (stream,
957 fDescriptionWriter,
958 kCaptionWriterSet,
959 32,
960 charSet);
961
962 if (padForTIFF)
963 {
964
965 while (stream.Length () & 3)
966 {
967 stream.Put_uint8 (0);
968 }
969
970 }
971
972 stream.Flush ();
973
974 return stream.AsMemoryBlock (allocator);
975
976 }
977
978 /*****************************************************************************/
979