/*****************************************************************************/
// Copyright 2006-2008 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/
/* $Id: //mondo/dng_sdk_1_4/dng_sdk/source/dng_iptc.cpp#1 $ */
/* $DateTime: 2012/05/30 13:28:51 $ */
/* $Change: 832332 $ */
/* $Author: tknoll $ */
/*****************************************************************************/
#include "dng_iptc.h"
#include "dng_assertions.h"
#include "dng_auto_ptr.h"
#include "dng_memory_stream.h"
#include "dng_stream.h"
#include "dng_utils.h"
/*****************************************************************************/
dng_iptc::dng_iptc ()
: fTitle ()
, fUrgency (-1)
, fCategory ()
, fSupplementalCategories ()
, fKeywords ()
, fInstructions ()
, fDateTimeCreated ()
, fDigitalCreationDateTime ()
, fAuthors ()
, fAuthorsPosition ()
, fCity ()
, fState ()
, fCountry ()
, fCountryCode ()
, fLocation ()
, fTransmissionReference ()
, fHeadline ()
, fCredit ()
, fSource ()
, fCopyrightNotice ()
, fDescription ()
, fDescriptionWriter ()
{
}
/*****************************************************************************/
dng_iptc::~dng_iptc ()
{
}
/*****************************************************************************/
bool dng_iptc::IsEmpty () const
{
if (fTitle.NotEmpty ())
{
return false;
}
if (fUrgency >= 0)
{
return false;
}
if (fCategory.NotEmpty ())
{
return false;
}
if (fSupplementalCategories.Count () > 0)
{
return false;
}
if (fKeywords.Count () > 0)
{
return false;
}
if (fInstructions.NotEmpty ())
{
return false;
}
if (fDateTimeCreated.IsValid ())
{
return false;
}
if (fDigitalCreationDateTime.IsValid ())
{
return false;
}
if (fAuthors.Count () != 0 ||
fAuthorsPosition.NotEmpty ())
{
return false;
}
if (fCity .NotEmpty () ||
fState .NotEmpty () ||
fCountry.NotEmpty ())
{
return false;
}
if (fCountryCode.NotEmpty ())
{
return false;
}
if (fLocation.NotEmpty ())
{
return false;
}
if (fTransmissionReference.NotEmpty ())
{
return false;
}
if (fHeadline.NotEmpty ())
{
return false;
}
if (fCredit.NotEmpty ())
{
return false;
}
if (fSource.NotEmpty ())
{
return false;
}
if (fCopyrightNotice.NotEmpty ())
{
return false;
}
if (fDescription .NotEmpty () ||
fDescriptionWriter.NotEmpty ())
{
return false;
}
return true;
}
/*****************************************************************************/
void dng_iptc::ParseString (dng_stream &stream,
dng_string &s,
CharSet charSet)
{
uint32 length = stream.Get_uint16 ();
dng_memory_data buffer (length + 1);
char *c = buffer.Buffer_char ();
stream.Get (c, length);
c [length] = 0;
switch (charSet)
{
case kCharSetUTF8:
{
s.Set_UTF8 (c);
break;
}
default:
{
s.Set_SystemEncoding (c);
}
}
s.SetLineEndingsToNewLines ();
s.StripLowASCII ();
s.TrimTrailingBlanks ();
}
/*****************************************************************************/
void dng_iptc::Parse (const void *blockData,
uint32 blockSize,
uint64 offsetInOriginalFile)
{
dng_stream stream (blockData,
blockSize,
offsetInOriginalFile);
stream.SetBigEndian ();
// Make a first pass though the data, trying to figure out the
// character set.
CharSet charSet = kCharSetUnknown;
bool isValidUTF8 = true;
bool hasEncodingMarker = false;
uint64 firstOffset = stream.Position ();
uint64 nextOffset = firstOffset;
while (nextOffset + 5 < stream.Length ())
{
stream.SetReadPosition (nextOffset);
uint8 firstByte = stream.Get_uint8 ();
if (firstByte != 0x1C) break;
uint8 record = stream.Get_uint8 ();
uint8 dataSet = stream.Get_uint8 ();
uint32 dataSize = stream.Get_uint16 ();
nextOffset = stream.Position () + dataSize;
if (record == 1)
{
switch (dataSet)
{
case 90:
{
hasEncodingMarker = true;
if (dataSize == 3)
{
uint32 byte1 = stream.Get_uint8 ();
uint32 byte2 = stream.Get_uint8 ();
uint32 byte3 = stream.Get_uint8 ();
if (byte1 == 27 /* Escape */ &&
byte2 == 0x25 &&
byte3 == 0x47)
{
charSet = kCharSetUTF8;
}
}
break;
}
default:
break;
}
}
else if (record == 2)
{
dng_memory_data buffer (dataSize + 1);
char *s = buffer.Buffer_char ();
stream.Get (s, dataSize);
s [dataSize] = 0;
isValidUTF8 = isValidUTF8 && dng_string::IsUTF8 (s);
}
}
// If we don't have an encoding marker, and the data is valid
// UTF-8, then assume that it is UTF-8 (rather than system encoding).
if (!hasEncodingMarker && isValidUTF8)
{
charSet = kCharSetUTF8;
}
// Make a second pass though the data, actually reading the data.
nextOffset = firstOffset;
while (nextOffset + 5 < stream.Length ())
{
stream.SetReadPosition (nextOffset);
uint8 firstByte = stream.Get_uint8 ();
if (firstByte != 0x1C) break;
uint8 record = stream.Get_uint8 ();
uint8 dataSet = stream.Get_uint8 ();
uint32 dataSize = stream.Get_uint16 ();
nextOffset = stream.Position () + dataSize;
if (record == 2)
{
stream.SetReadPosition (stream.Position () - 2);
switch ((DataSet) dataSet)
{
case kObjectNameSet:
{
ParseString (stream, fTitle, charSet);
break;
}
case kUrgencySet:
{
int32 size = stream.Get_uint16 ();
if (size == 1)
{
char c = stream.Get_int8 ();
if (c >= '0' && c <= '9')
{
fUrgency = c - '0';
}
}
break;
}
case kCategorySet:
{
ParseString (stream, fCategory, charSet);
break;
}
case kSupplementalCategoriesSet:
{
dng_string category;
ParseString (stream, category, charSet);
if (category.NotEmpty ())
{
fSupplementalCategories.Append (category);
}
break;
}
case kKeywordsSet:
{
dng_string keyword;
ParseString (stream, keyword, charSet);
if (keyword.NotEmpty ())
{
fKeywords.Append (keyword);
}
break;
}
case kSpecialInstructionsSet:
{
ParseString (stream, fInstructions, charSet);
break;
}
case kDateCreatedSet:
{
uint32 length = stream.Get_uint16 ();
if (length == 8)
{
char date [9];
stream.Get (date, 8);
date [8] = 0;
fDateTimeCreated.Decode_IPTC_Date (date);
}
break;
}
case kTimeCreatedSet:
{
uint32 length = stream.Get_uint16 ();
if (length >= 4 && length <= 11)
{
char time [12];
stream.Get (time, length);
time [length] = 0;
fDateTimeCreated.Decode_IPTC_Time (time);
}
break;
}
case kDigitalCreationDateSet:
{
uint32 length = stream.Get_uint16 ();
if (length == 8)
{
char date [9];
stream.Get (date, 8);
date [8] = 0;
fDigitalCreationDateTime.Decode_IPTC_Date (date);
}
break;
}
case kDigitalCreationTimeSet:
{
uint32 length = stream.Get_uint16 ();
if (length >= 4 && length <= 11)
{
char time [12];
stream.Get (time, length);
time [length] = 0;
fDigitalCreationDateTime.Decode_IPTC_Time (time);
}
break;
}
case kBylineSet:
{
dng_string author;
ParseString (stream, author, charSet);
if (author.NotEmpty ())
{
fAuthors.Append (author);
}
break;
}
case kBylineTitleSet:
{
ParseString (stream, fAuthorsPosition, charSet);
break;
}
case kCitySet:
{
ParseString (stream, fCity, charSet);
break;
}
case kProvinceStateSet:
{
ParseString (stream, fState, charSet);
break;
}
case kCountryNameSet:
{
ParseString (stream, fCountry, charSet);
break;
}
case kCountryCodeSet:
{
ParseString (stream, fCountryCode, charSet);
break;
}
case kSublocationSet:
{
ParseString (stream, fLocation, charSet);
break;
}
case kOriginalTransmissionReferenceSet:
{
ParseString (stream, fTransmissionReference, charSet);
break;
}
case kHeadlineSet:
{
ParseString (stream, fHeadline, charSet);
break;
}
case kCreditSet:
{
ParseString (stream, fCredit, charSet);
break;
}
case kSourceSet:
{
ParseString (stream, fSource, charSet);
break;
}
case kCopyrightNoticeSet:
{
ParseString (stream, fCopyrightNotice, charSet);
break;
}
case kCaptionSet:
{
ParseString (stream, fDescription, charSet);
break;
}
case kCaptionWriterSet:
{
ParseString (stream, fDescriptionWriter, charSet);
break;
}
// All other IPTC records are not part of the IPTC core
// and/or are not kept in sync with XMP tags, so we ignore
// them.
default:
break;
}
}
}
}
/*****************************************************************************/
void dng_iptc::SpoolString (dng_stream &stream,
const dng_string &s,
uint8 dataSet,
uint32 maxChars,
CharSet charSet)
{
if (s.IsEmpty ())
{
return;
}
stream.Put_uint16 (0x1C02);
stream.Put_uint8 (dataSet);
dng_string ss (s);
ss.SetLineEndingsToReturns ();
if (charSet == kCharSetUTF8)
{
// UTF-8 encoding.
if (ss.Length () > maxChars)
{
ss.Truncate (maxChars);
}
uint32 len = ss.Length ();
stream.Put_uint16 ((uint16) len);
stream.Put (ss.Get (), len);
}
else
{
// System character set encoding.
dng_memory_data buffer;
uint32 len = ss.Get_SystemEncoding (buffer);
if (len > maxChars)
{
uint32 lower = 0;
uint32 upper = ss.Length () - 1;
while (upper > lower)
{
uint32 middle = (upper + lower + 1) >> 1;
dng_string sss (ss);
sss.Truncate (middle);
len = sss.Get_SystemEncoding (buffer);
if (len <= maxChars)
{
lower = middle;
}
else
{
upper = middle - 1;
}
}
ss.Truncate (lower);
len = ss.Get_SystemEncoding (buffer);
}
stream.Put_uint16 ((uint16) len);
stream.Put (buffer.Buffer_char (), len);
}
}
/*****************************************************************************/
dng_memory_block * dng_iptc::Spool (dng_memory_allocator &allocator,
bool padForTIFF)
{
uint32 j;
char s [64];
dng_memory_stream stream (allocator, NULL, 2048);
stream.SetBigEndian ();
// Medata working group - now we just always write UTF-8.
CharSet charSet = kCharSetUTF8;
// UTF-8 encoding marker.
if (charSet == kCharSetUTF8)
{
stream.Put_uint16 (0x1C01);
stream.Put_uint8 (90);
stream.Put_uint16 (3);
stream.Put_uint8 (27);
stream.Put_uint8 (0x25);
stream.Put_uint8 (0x47);
}
stream.Put_uint16 (0x1C02);
stream.Put_uint8 (kRecordVersionSet);
stream.Put_uint16 (2);
stream.Put_uint16 (4);
SpoolString (stream,
fTitle,
kObjectNameSet,
64,
charSet);
if (fUrgency >= 0)
{
sprintf (s, "%1u", (unsigned) fUrgency);
stream.Put_uint16 (0x1C02);
stream.Put_uint8 (kUrgencySet);
stream.Put_uint16 (1);
stream.Put (s, 1);
}
SpoolString (stream,
fCategory,
kCategorySet,
3,
charSet);
for (j = 0; j < fSupplementalCategories.Count (); j++)
{
SpoolString (stream,
fSupplementalCategories [j],
kSupplementalCategoriesSet,
32,
charSet);
}
for (j = 0; j < fKeywords.Count (); j++)
{
SpoolString (stream,
fKeywords [j],
kKeywordsSet,
64,
charSet);
}
SpoolString (stream,
fInstructions,
kSpecialInstructionsSet,
255,
charSet);
if (fDateTimeCreated.IsValid ())
{
dng_string dateString = fDateTimeCreated.Encode_IPTC_Date ();
if (dateString.NotEmpty ())
{
DNG_ASSERT (dateString.Length () == 8, "Wrong length IPTC date");
stream.Put_uint16 (0x1C02);
stream.Put_uint8 (kDateCreatedSet);
stream.Put_uint16 (8);
stream.Put (dateString.Get (), 8);
}
dng_string timeString = fDateTimeCreated.Encode_IPTC_Time ();
if (timeString.NotEmpty ())
{
stream.Put_uint16 (0x1C02);
stream.Put_uint8 (kTimeCreatedSet);
stream.Put_uint16 ((uint16)timeString.Length ());
stream.Put (timeString.Get (), timeString.Length ());
}
}
if (fDigitalCreationDateTime.IsValid ())
{
dng_string dateString = fDigitalCreationDateTime.Encode_IPTC_Date ();
if (dateString.NotEmpty ())
{
DNG_ASSERT (dateString.Length () == 8, "Wrong length IPTC date");
stream.Put_uint16 (0x1C02);
stream.Put_uint8 (kDigitalCreationDateSet);
stream.Put_uint16 (8);
stream.Put (dateString.Get (), 8);
}
dng_string timeString = fDigitalCreationDateTime.Encode_IPTC_Time ();
if (timeString.NotEmpty ())
{
stream.Put_uint16 (0x1C02);
stream.Put_uint8 (kDigitalCreationTimeSet);
stream.Put_uint16 ((uint16)timeString.Length ());
stream.Put (timeString.Get (), timeString.Length ());
}
}
for (j = 0; j < fAuthors.Count (); j++)
{
SpoolString (stream,
fAuthors [j],
kBylineSet,
32,
charSet);
}
SpoolString (stream,
fAuthorsPosition,
kBylineTitleSet,
32,
charSet);
SpoolString (stream,
fCity,
kCitySet,
32,
charSet);
SpoolString (stream,
fLocation,
kSublocationSet,
32,
charSet);
SpoolString (stream,
fState,
kProvinceStateSet,
32,
charSet);
SpoolString (stream,
fCountryCode,
kCountryCodeSet,
3,
charSet);
SpoolString (stream,
fCountry,
kCountryNameSet,
64,
charSet);
SpoolString (stream,
fTransmissionReference,
kOriginalTransmissionReferenceSet,
32,
charSet);
SpoolString (stream,
fHeadline,
kHeadlineSet,
255,
charSet);
SpoolString (stream,
fCredit,
kCreditSet,
32,
charSet);
SpoolString (stream,
fSource,
kSourceSet,
32,
charSet);
SpoolString (stream,
fCopyrightNotice,
kCopyrightNoticeSet,
128,
charSet);
SpoolString (stream,
fDescription,
kCaptionSet,
2000,
charSet);
SpoolString (stream,
fDescriptionWriter,
kCaptionWriterSet,
32,
charSet);
if (padForTIFF)
{
while (stream.Length () & 3)
{
stream.Put_uint8 (0);
}
}
stream.Flush ();
return stream.AsMemoryBlock (allocator);
}
/*****************************************************************************/