You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
986 lines
16 KiB
986 lines
16 KiB
/*****************************************************************************/
|
|
// 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);
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|