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