// =================================================================================================
// Copyright Adobe
// Copyright 2004 Adobe
// 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. 
// =================================================================================================

#include "public/include/XMP_Environment.h"	// ! XMP_Environment.h must be the first included header.

#include "public/include/XMP_Const.h"
#include "public/include/XMP_IO.hpp"

#include "XMPFiles/source/XMPFiles_Impl.hpp"
#include "source/XMPFiles_IO.hpp"
#include "source/XIO.hpp"

#include "XMPFiles/source/FileHandlers/PostScript_Handler.hpp"

#include "XMPFiles/source/FormatSupport/XMPScanner.hpp"
#include "XMPFiles/source/FileHandlers/Scanner_Handler.hpp"

#include <algorithm>

using namespace std;
// =================================================================================================
/// \file PostScript_Handler.cpp
/// \brief File format handler for PostScript and EPS files.
///
// =================================================================================================

// =================================================================================================
// PostScript_MetaHandlerCTor
// ==========================

XMPFileHandler * PostScript_MetaHandlerCTor ( XMPFiles * parent )
{
	XMPFileHandler * newHandler = new PostScript_MetaHandler ( parent );

	return newHandler;

}	// PostScript_MetaHandlerCTor

// =================================================================================================
// PostScript_CheckFormat
// ======================

bool PostScript_CheckFormat ( XMP_FileFormat format,
	                          XMP_StringPtr  filePath,
	                          XMP_IO*    fileRef,
	                          XMPFiles *     parent )
{
	IgnoreParam(filePath); IgnoreParam(parent);
	XMP_Assert ( (format == kXMP_EPSFile) || (format == kXMP_PostScriptFile) );

	return PostScript_Support::IsValidPSFile(fileRef,format) ;

}	// PostScript_CheckFormat

// =================================================================================================
// PostScript_MetaHandler::PostScript_MetaHandler
// ==============================================

PostScript_MetaHandler::PostScript_MetaHandler ( XMPFiles * _parent ):dscFlags(0),docInfoFlags(0)
	,containsXMPHint(false),fileformat(kXMP_UnknownFile)
{
	this->parent = _parent;
	this->handlerFlags = kPostScript_HandlerFlags;
	this->stdCharForm = kXMP_Char8Bit;
	this->psHint = kPSHint_NoMarker;

}	// PostScript_MetaHandler::PostScript_MetaHandler

// =================================================================================================
// PostScript_MetaHandler::~PostScript_MetaHandler
// ===============================================

PostScript_MetaHandler::~PostScript_MetaHandler()
{
	// ! Inherit the base cleanup.

}	// PostScript_MetaHandler::~PostScript_MetaHandler

// =================================================================================================
// PostScript_MetaHandler::FindPostScriptHint
// ==========================================
//
// Search for "%ADO_ContainsXMP:" at the beginning of a line, it must be before "%%EndComments". If
// the XMP marker is found, look for the MainFirst/MainLast/NoMain options.

int PostScript_MetaHandler::FindPostScriptHint()
{
	bool     found = false;
	IOBuffer ioBuf;
	XMP_Uns8 ch;

	XMP_IO* fileRef = this->parent->ioRef;

	XMP_AbortProc abortProc  = this->parent->abortProc;
	void *        abortArg   = this->parent->abortArg;
	const bool    checkAbort = (abortProc != 0);

	// Check for the binary EPSF preview header.

	fileRef->Rewind();
	if ( ! CheckFileSpace ( fileRef, &ioBuf, 4 ) ) return false;
	XMP_Uns32 fileheader = GetUns32BE ( ioBuf.ptr );

	if ( fileheader == 0xC5D0D3C6 ) {

		if ( ! CheckFileSpace ( fileRef, &ioBuf, 30 ) ) return false;

		XMP_Uns32 psOffset = GetUns32LE ( ioBuf.ptr+4 );	// PostScript offset.
		XMP_Uns32 psLength = GetUns32LE ( ioBuf.ptr+8 );	// PostScript length.

		MoveToOffset ( fileRef, psOffset, &ioBuf );

	}

	// Look for the ContainsXMP comment.

	while ( true ) {

		if ( checkAbort && abortProc(abortArg) ) {
			XMP_Throw ( "PostScript_MetaHandler::FindPostScriptHint - User abort", kXMPErr_UserAbort );
		}

		if ( ! CheckFileSpace ( fileRef, &ioBuf, kPSContainsXMPString.length() ) ) return kPSHint_NoMarker;

		if ( CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSEndCommentString.c_str()), kPSEndCommentString.length() ) ) {

			// Found "%%EndComments", don't look any further.
			return kPSHint_NoMarker;

		} else if ( ! CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsXMPString.c_str()), kPSContainsXMPString.length() ) ) {

			// Not "%%EndComments" or "%ADO_ContainsXMP:", skip past the end of this line.
			do {
				if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return kPSHint_NoMarker;
				ch = *ioBuf.ptr;
				++ioBuf.ptr;
			} while ( ! IsNewline ( ch ) );

		} else {

			// Found "%ADO_ContainsXMP:", look for the main packet location option.

			ioBuf.ptr += kPSContainsXMPString.length();
			int xmpHint = kPSHint_NoMain;	// ! From here on, a failure means "no main", not "no marker".
			if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return kPSHint_NoMain;
			if ( ! IsSpaceOrTab ( *ioBuf.ptr ) ) return kPSHint_NoMain;

			while ( true ) {

				while ( true ) {	// Skip leading spaces and tabs.
					if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return kPSHint_NoMain;
					if ( ! IsSpaceOrTab ( *ioBuf.ptr ) ) break;
					++ioBuf.ptr;
				}
				if ( IsNewline ( *ioBuf.ptr ) ) return kPSHint_NoMain;	// Reached the end of the ContainsXMP comment.

				if ( ! CheckFileSpace ( fileRef, &ioBuf, 6 ) ) return kPSHint_NoMain;

				if ( CheckBytes ( ioBuf.ptr, Uns8Ptr("NoMain"), 6 ) ) {

					ioBuf.ptr += 6;
					xmpHint = kPSHint_NoMain;
					break;

				} else if ( CheckBytes ( ioBuf.ptr, Uns8Ptr("MainFi"), 6 ) ) {

					ioBuf.ptr += 6;
					if ( ! CheckFileSpace ( fileRef, &ioBuf, 3 ) ) return kPSHint_NoMain;
					if ( CheckBytes ( ioBuf.ptr, Uns8Ptr("rst"), 3 ) ) {
						ioBuf.ptr += 3;
						xmpHint = kPSHint_MainFirst;
					}
					break;

				} else if ( CheckBytes ( ioBuf.ptr, Uns8Ptr("MainLa"), 6 ) ) {

					ioBuf.ptr += 6;
					if ( ! CheckFileSpace ( fileRef, &ioBuf, 2 ) ) return kPSHint_NoMain;
					if ( CheckBytes ( ioBuf.ptr, Uns8Ptr("st"), 2 ) ) {
						ioBuf.ptr += 2;
						xmpHint = kPSHint_MainLast;
					}
					break;

				} else {

					while ( true ) {	// Skip until whitespace.
						if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return kPSHint_NoMain;
						if ( IsWhitespace ( *ioBuf.ptr ) ) break;
						++ioBuf.ptr;
					}

				}

			}	// Look for the main packet location option.

			// Make sure we found exactly a known option.
			if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return kPSHint_NoMain;
			if ( ! IsWhitespace ( *ioBuf.ptr ) ) return kPSHint_NoMain;
			return xmpHint;

		}	// Found "%ADO_ContainsXMP:".

	}	// Outer marker loop.

	return kPSHint_NoMarker;	// Should never reach here.

}	// PostScript_MetaHandler::FindPostScriptHint


// =================================================================================================
// PostScript_MetaHandler::FindFirstPacket
// =======================================
//
// Run the packet scanner until we find a valid packet. The first one is the main. For simplicity,
// the state of all snips is checked after each buffer is read. In theory only the last of the
// previous snips might change from partial to valid, but then we would have to special case the
// first pass when there is no previous set of snips. Since we have to get a full report to look at
// the last snip anyway, it costs virtually nothing extra to recheck all of the snips.

bool PostScript_MetaHandler::FindFirstPacket()
{
	int		snipCount;
	bool 	found	= false;
	size_t	bufPos, bufLen;

	XMP_IO* fileRef = this->parent->ioRef;
	XMP_Int64   fileLen = fileRef->Length();
	XMP_PacketInfo & packetInfo = this->packetInfo;

	XMPScanner scanner ( fileLen );
	XMPScanner::SnipInfoVector snips;

	enum { kBufferSize = 64*1024 };
	XMP_Uns8	buffer [kBufferSize];

	XMP_AbortProc abortProc  = this->parent->abortProc;
	void *        abortArg   = this->parent->abortArg;
	const bool    checkAbort = (abortProc != 0);

	bufPos = 0;
	bufLen = 0;

	fileRef->Rewind();	// Seek back to the beginning of the file.
	bool firstfound=false;

	while ( true ) 
	{

		if ( checkAbort && abortProc(abortArg) ) 
		{
			XMP_Throw ( "PostScript_MetaHandler::FindFirstPacket - User abort", kXMPErr_UserAbort );
		}

		bufPos += bufLen;
		bufLen = fileRef->Read ( buffer, kBufferSize );
		if ( bufLen == 0 ) return firstfound;	// Must be at EoF, no packets found.

		scanner.Scan ( buffer, bufPos, bufLen );
		snipCount = (int)scanner.GetSnipCount();
		scanner.Report ( snips );
		for ( int i = 0; i < snipCount; ++i ) 
		{
			if ( snips[i].fState == XMPScanner::eValidPacketSnip ) 
			{
				if (!firstfound)
				{
					if ( snips[i].fLength > 0x7FFFFFFF ) XMP_Throw ( "PostScript_MetaHandler::FindFirstPacket: Oversize packet", kXMPErr_BadXMP );
					packetInfo.offset = snips[i].fOffset;
					packetInfo.length = (XMP_Int32)snips[i].fLength;
					packetInfo.charForm  = snips[i].fCharForm;
					packetInfo.writeable = (snips[i].fAccess == 'w');
					firstPacketInfo=packetInfo;
					lastPacketInfo=packetInfo;
					firstfound=true;
				}
				else
				{					
					lastPacketInfo.offset = snips[i].fOffset;
					lastPacketInfo.length = (XMP_Int32)snips[i].fLength;
					lastPacketInfo.charForm  = snips[i].fCharForm;
					lastPacketInfo.writeable = (snips[i].fAccess == 'w');
				}
			}
		}

	}
	
	return firstfound;

}	// FindFirstPacket


// =================================================================================================
// PostScript_MetaHandler::FindLastPacket
// ======================================
//
// Run the packet scanner backwards until we find the start of a packet, or a valid packet. If we
// found a packet start, resume forward scanning to see if it is a valid packet. For simplicity, all
// of the snips are checked on each pass, for much the same reasons as in FindFirstPacket.

bool PostScript_MetaHandler::FindLastPacket()
{
	size_t	bufPos, bufLen;

	XMP_IO* fileRef = this->parent->ioRef;
	XMP_Int64   fileLen = fileRef->Length();
	XMP_PacketInfo & packetInfo = this->packetInfo;

	// ------------------------------------------------------
	// Scan the entire file to find all of the valid packets.

	XMPScanner	scanner ( fileLen );

	enum { kBufferSize = 64*1024 };
	XMP_Uns8	buffer [kBufferSize];

	XMP_AbortProc abortProc  = this->parent->abortProc;
	void *        abortArg   = this->parent->abortArg;
	const bool    checkAbort = (abortProc != 0);

	fileRef->Rewind();	// Seek back to the beginning of the file.

	for ( bufPos = 0; bufPos < (size_t)fileLen; bufPos += bufLen )
	{
		if ( checkAbort && abortProc(abortArg) ) 
		{
			XMP_Throw ( "PostScript_MetaHandler::FindLastPacket - User abort", kXMPErr_UserAbort );
		}
		bufLen = fileRef->Read ( buffer, kBufferSize );
		if ( bufLen == 0 ) XMP_Throw ( "PostScript_MetaHandler::FindLastPacket: Read failure", kXMPErr_ExternalFailure );
		scanner.Scan ( buffer, bufPos, bufLen );
	}

	// -------------------------------
	// Pick the last the valid packet.

	int snipCount = (int)scanner.GetSnipCount();

	XMPScanner::SnipInfoVector snips ( snipCount );
	scanner.Report ( snips );

	bool lastfound=false;
	for ( int i = 0; i < snipCount; ++i ) 
	{
		if ( snips[i].fState == XMPScanner::eValidPacketSnip ) 
		{
			if (!lastfound)
			{
				if ( snips[i].fLength > 0x7FFFFFFF ) XMP_Throw ( "PostScript_MetaHandler::FindLastPacket: Oversize packet", kXMPErr_BadXMP );
				packetInfo.offset = snips[i].fOffset;
				packetInfo.length = (XMP_Int32)snips[i].fLength;
				packetInfo.charForm  = snips[i].fCharForm;
				packetInfo.writeable = (snips[i].fAccess == 'w');
				firstPacketInfo=packetInfo;
				lastPacketInfo=packetInfo;
				lastfound=true;
			}
			else
			{					
				lastPacketInfo.offset = snips[i].fOffset;
				lastPacketInfo.length = (XMP_Int32)snips[i].fLength;
				lastPacketInfo.charForm  = snips[i].fCharForm;
				lastPacketInfo.writeable = (snips[i].fAccess == 'w');
				packetInfo=lastPacketInfo;
			}
		}
	}
	return lastfound;

}	// PostScript_MetaHandler::FindLastPacket

// =================================================================================================
// PostScript_MetaHandler::setTokenInfo
// ====================================
//
// Function Sets the docInfo flag for tokens(PostScript DSC comments/ Docinfo Dictionary values)
//	when parsing the file stream.Also takes note of the token offset from the start of the file
//	and the length of the token
void PostScript_MetaHandler::setTokenInfo(TokenFlag tFlag,XMP_Int64 offset,XMP_Int64 length)
{
	if (!(docInfoFlags&tFlag)&&tFlag>=kPS_ADOContainsXMP && tFlag<=kPS_EndPostScript)
	{
		size_t index=0;
		XMP_Uns64 flag=tFlag;
		while(flag>>=1) index++;
		fileTokenInfo[index].offsetStart=offset;
		fileTokenInfo[index].tokenlen=length;
		docInfoFlags|=tFlag;
	}
}

// =================================================================================================
// PostScript_MetaHandler::getTokenInfo
// ====================================
//
// Function returns the token info for the flag, which was collected in parsing the input file
//	
PostScript_MetaHandler::TokenLocation& PostScript_MetaHandler::getTokenInfo(TokenFlag tFlag)
{
	if ((docInfoFlags&tFlag)&&tFlag>=kPS_ADOContainsXMP && tFlag<=kPS_EndPostScript)
	{
		size_t index=0;
		XMP_Uns64 flag=tFlag;
		while(flag>>=1) index++;
		return fileTokenInfo[index];
	}
	return fileTokenInfo[kPS_NoData];
}

// =================================================================================================
// PostScript_MetaHandler::ExtractDSCCommentValue
// ==============================================
//
// Function extracts the DSC comment value when parsing the file.This may be later used to reconcile
//	
bool PostScript_MetaHandler::ExtractDSCCommentValue(IOBuffer &ioBuf,NativeMetadataIndex index)
{
	
	XMP_IO* fileRef = this->parent->ioRef;
	if ( ! PostScript_Support::SkipTabsAndSpaces(fileRef,ioBuf) ) return false;
	if ( !IsNewline ( *ioBuf.ptr ) )
	{
		do 
		{
			if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false;
			nativeMeta[index] += *ioBuf.ptr;
			++ioBuf.ptr;
		} while ( ! IsNewline ( *ioBuf.ptr) );
		if (!PostScript_Support::HasCodesGT127(nativeMeta[index]))
		{
			dscFlags|=nativeIndextoFlag[index];
		}
		else
			nativeMeta[index].clear();
	}
	return true;
}


// =================================================================================================
// PostScript_MetaHandler::ExtractContainsXMPHint
// ==============================================
//
// Function extracts the the value of "ADOContainsXMP:" DSC comment's value
//	
bool PostScript_MetaHandler::ExtractContainsXMPHint(IOBuffer &ioBuf,XMP_Int64 containsXMPStartpos)
{
	XMP_IO* fileRef = this->parent->ioRef;
	int xmpHint = kPSHint_NoMain;	// ! From here on, a failure means "no main", not "no marker".
	//checkfor atleast one whitespace
	if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false;
	if ( ! IsSpaceOrTab ( *ioBuf.ptr ) ) return false;
	//skip extra ones	
	if ( ! PostScript_Support::SkipTabsAndSpaces(fileRef,ioBuf) ) return false;
	if ( IsNewline ( *ioBuf.ptr ) ) return false;	// Reached the end of the ContainsXMP comment.

	if ( ! CheckFileSpace ( fileRef, &ioBuf, 6 ) ) return false ;

	if ( CheckBytes ( ioBuf.ptr, Uns8Ptr("NoMain"), 6 ) ) 
	{
		ioBuf.ptr += 6;
		if ( ! PostScript_Support::SkipTabsAndSpaces(fileRef,ioBuf) ) return false;
		if ( ! IsNewline( *ioBuf.ptr) ) return false;
		this->psHint = kPSHint_NoMain;
		setTokenInfo(kPS_ADOContainsXMP,containsXMPStartpos,ioBuf.filePos+ioBuf.ptr-ioBuf.data-containsXMPStartpos);

	} 
	else if ( CheckBytes ( ioBuf.ptr, Uns8Ptr("MainFi"), 6 ) ) 
	{
		ioBuf.ptr += 6;
		if ( ! CheckFileSpace ( fileRef, &ioBuf, 3 ) ) return false;
		if ( CheckBytes ( ioBuf.ptr, Uns8Ptr("rst"), 3 ) ) 
		{
			ioBuf.ptr += 3;
			if ( ! PostScript_Support::SkipTabsAndSpaces(fileRef,ioBuf) ) return false;
			if ( ! IsNewline( *ioBuf.ptr) ) return false;
			this->psHint = kPSHint_MainFirst;
			setTokenInfo(kPS_ADOContainsXMP,containsXMPStartpos,ioBuf.filePos+ioBuf.ptr-ioBuf.data-containsXMPStartpos);
			containsXMPHint=true;
		}
	} 
	else if ( CheckBytes ( ioBuf.ptr, Uns8Ptr("MainLa"), 6 ) ) 
	{
		ioBuf.ptr += 6;
		if ( ! CheckFileSpace ( fileRef, &ioBuf, 2 ) ) return false;
		if ( CheckBytes ( ioBuf.ptr, Uns8Ptr("st"), 2 ) ) {
			ioBuf.ptr += 2;
			if ( ! PostScript_Support::SkipTabsAndSpaces(fileRef,ioBuf) ) return false;
			if ( ! IsNewline( *ioBuf.ptr) ) return false;
			this->psHint = kPSHint_MainLast;
			setTokenInfo(kPS_ADOContainsXMP,containsXMPStartpos,ioBuf.filePos+ioBuf.ptr-ioBuf.data-containsXMPStartpos);
			containsXMPHint=true;
		}
	} 
	else 
	{
		if ( ! PostScript_Support::SkipUntilNewline(fileRef,ioBuf) ) return false;
	}
	return true;
}


// =================================================================================================
// PostScript_MetaHandler::ExtractDocInfoDict
// ==============================================
//
// Function extracts the the value of DocInfo Dictionary.The keys that are looked in the dictionary
//	are Creator, CreationDate, ModDate, Author, Title, Subject and Keywords.Other keys for the 
//	Dictionary are ignored
bool PostScript_MetaHandler::ExtractDocInfoDict(IOBuffer &ioBuf)
{
	XMP_Uns8 ch;
	XMP_IO* fileRef = this->parent->ioRef;
	XMP_Int64 endofDocInfo=(ioBuf.ptr-ioBuf.data)+ioBuf.filePos;
	if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false;
	if ( IsWhitespace (*ioBuf.ptr))
	{
		//skip the whitespaces
		if ( ! ( PostScript_Support::SkipTabsAndSpaces(fileRef, ioBuf))) return false;
		//check the pdfmark
		if ( ! CheckFileSpace ( fileRef, &ioBuf, kPSContainsPdfmarkString.length() ) ) return false;
		if ( ! CheckBytes ( ioBuf.ptr,  Uns8Ptr(kPSContainsPdfmarkString.c_str()), kPSContainsPdfmarkString.length() ) ) return false;
		//reverse the direction to collect data
		while(true)
		{
			if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false;
			ch=*ioBuf.ptr;
			--ioBuf.ptr;
			if (ch=='/') break;//slash of /DOCINFO
		} 
		//skip white spaces
		while(true)
		{
			if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false;
			if (!IsWhitespace(*ioBuf.ptr)) break;
			--ioBuf.ptr;
		} 
						
		bool findingkey=false;
		std::string key, value;
		while(true)
		{
			XMP_Uns32 noOfMarks=0;
			if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false;
			if (*ioBuf.ptr==')')
			{
				--ioBuf.ptr;
				while(true)
				{
					//get the string till '('
					if (*ioBuf.ptr=='(')
					{
						if(findingkey)
						{
							reverse(key.begin(), key.end());
							reverse(value.begin(), value.end());
							RegisterKeyValue(key,value);
						}
						if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false;
						--ioBuf.ptr;
						findingkey=!findingkey;
						break;
					}
					else
					{
						if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false;
						if (findingkey)
							key+=*ioBuf.ptr;
						else
							value+=*ioBuf.ptr;
						--ioBuf.ptr;
					}
				}
			}
			else if(*ioBuf.ptr=='[')
			{
				//end of Doc Info parsing
				//return;
				break;
			}
			else
			{
				while(true)
				{
					if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false;
					if (findingkey)
						key+=*ioBuf.ptr;
					else
						value+=*ioBuf.ptr;
					--ioBuf.ptr;
					if (*ioBuf.ptr=='/')
					{
						if(findingkey)
						{
							reverse(key.begin(), key.end());
							reverse(value.begin(), value.end());
							RegisterKeyValue(key,value);
						}
						if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false;
						--ioBuf.ptr;
						findingkey=!findingkey;
						break;
					}
					else if(IsWhitespace(*ioBuf.ptr))
					{
						//something not expected in Doc Info
						break;
					}
				}
			}
			while(true)
			{
				if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false;
				if (!IsWhitespace(*ioBuf.ptr)) break;
				--ioBuf.ptr;
			} 
						
		}
						
		fileRef->Rewind();	
		FillBuffer (fileRef, endofDocInfo, &ioBuf );
		return true;
	}//white space not after DOCINFO
	return false;
}

// =================================================================================================
// PostScript_MetaHandler::ParsePSFile
// ===================================
//
// Main parser for the Post script file.This is where all the DSC comments , Docinfo key value pairs 
// and other insertion related Data is looked for and stored
void PostScript_MetaHandler::ParsePSFile()
{
	bool     found = false;
	IOBuffer ioBuf;

	XMP_IO* fileRef = this->parent->ioRef;

	XMP_AbortProc abortProc  = this->parent->abortProc;
	void *        abortArg   = this->parent->abortArg;
	const bool    checkAbort = (abortProc != 0);

	//Determine the file type PS or EPS
	if ( ! PostScript_Support::IsValidPSFile(fileRef,this->fileformat) ) return ; 
	// Check for the binary EPSF preview header.

	fileRef->Rewind();
	if ( ! CheckFileSpace ( fileRef, &ioBuf, 4 ) ) return ;
	XMP_Uns32 fileheader = GetUns32BE ( ioBuf.ptr );

	if ( fileheader == 0xC5D0D3C6 ) 
	{

		if ( ! CheckFileSpace ( fileRef, &ioBuf, 30 ) ) return ;

		XMP_Uns32 psOffset = GetUns32LE ( ioBuf.ptr+4 );	// PostScript offset.
		XMP_Uns32 psLength = GetUns32LE ( ioBuf.ptr+8 );	// PostScript length.

		setTokenInfo(kPS_EndPostScript,psOffset+psLength,0);
		MoveToOffset ( fileRef, psOffset, &ioBuf );

	}

	while ( true ) 
	{
		if ( checkAbort && abortProc(abortArg) ) {
			XMP_Throw ( "PostScript_MetaHandler::FindPostScriptHint - User abort", kXMPErr_UserAbort );
		}

		if ( ! CheckFileSpace ( fileRef, &ioBuf, kPSContainsForString.length() ) ) return ;

		if ( (CheckFileSpace ( fileRef, &ioBuf, kPSEndCommentString.length() )&& 
				CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSEndCommentString.c_str()), kPSEndCommentString.length() )
				)|| *ioBuf.ptr!='%' || !(*(ioBuf.ptr+1)>32 && *(ioBuf.ptr+1)<=126 )) // implicit endcomment check
		{
			if (CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSEndCommentString.c_str()), kPSEndCommentString.length() ))
			{
				setTokenInfo(kPS_EndComments,ioBuf.filePos+ioBuf.ptr-ioBuf.data,kPSEndCommentString.length());
				ioBuf.ptr+=kPSEndCommentString.length();
			}
			else
			{
				setTokenInfo(kPS_EndComments,ioBuf.filePos+ioBuf.ptr-ioBuf.data,0);
			}
			// Found "%%EndComments", look for docInfo Dictionary
			// skip past the end of this line.
			while(true)
			{
				if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return ;
				if (! IsWhitespace (*ioBuf.ptr)) break;
				++ioBuf.ptr;
			} 
			// search for /DOCINFO
			while(true)
			{
				if ( ! CheckFileSpace ( fileRef, &ioBuf, 5 ) ) return ;
				if (CheckBytes ( ioBuf.ptr, Uns8Ptr("/DOCI"), 5 ) 
					&& CheckFileSpace ( fileRef, &ioBuf, kPSContainsDocInfoString.length() )
					&&CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsDocInfoString.c_str()), kPSContainsDocInfoString.length() ))
					
				{
					
					ioBuf.ptr+=kPSContainsDocInfoString.length();
					ExtractDocInfoDict(ioBuf);
				}// DOCINFO Not found in document
				else if(CheckBytes ( ioBuf.ptr, Uns8Ptr("%%Beg"), 5 ))
				{//possibly one of %%BeginProlog %%BeginSetup %%BeginBinary %%BeginData 
					// %%BeginDocument %%BeginPageSetup
					XMP_Int64 begStartpos=ioBuf.filePos+ioBuf.ptr-ioBuf.data;
					ioBuf.ptr+=5;
					if (!CheckFileSpace ( fileRef, &ioBuf, 6 )) return;
					if(CheckBytes ( ioBuf.ptr, Uns8Ptr("inProl"), 6 ))
					{//%%BeginProlog
						ioBuf.ptr+=6;
						if (!CheckFileSpace ( fileRef, &ioBuf, 2 ))return;
						if(CheckBytes ( ioBuf.ptr, Uns8Ptr("og"), 2 ))
						{
							ioBuf.ptr+=2;
							setTokenInfo(kPS_BeginProlog,begStartpos,13);
						}
					}
					else if (CheckBytes ( ioBuf.ptr, Uns8Ptr("inSetu"), 6 ))
					{//%%BeginSetup 
						ioBuf.ptr+=6;
						if (!CheckFileSpace ( fileRef, &ioBuf, 1 ))return;
						if(CheckBytes ( ioBuf.ptr, Uns8Ptr("p"), 1 ))
						{
							ioBuf.ptr+=1;
							setTokenInfo(kPS_BeginSetup,begStartpos,12);
						}
					}
					else if (CheckBytes ( ioBuf.ptr, Uns8Ptr("inBina"), 6 ))
					{//%%BeginBinary
						ioBuf.ptr+=6;
						if (!CheckFileSpace ( fileRef, &ioBuf, 3 ))return;
						if(CheckBytes ( ioBuf.ptr, Uns8Ptr("ry"), 3 ))
						{
							ioBuf.ptr+=3;
							//ignore till %%EndBinary
							while(true)
							{
								if (!CheckFileSpace ( fileRef, &ioBuf, 12 ))return;
								if (CheckBytes ( ioBuf.ptr, Uns8Ptr("%%EndBinary"), 11 ))
								{
									ioBuf.ptr+=11;
									if (IsWhitespace(*ioBuf.ptr))
									{
										ioBuf.ptr++;
										break;
									}
								}
								++ioBuf.ptr;
							}
						}
					}
					else if (CheckBytes ( ioBuf.ptr, Uns8Ptr("inData"), 6 ))
					{//%%BeginData
						ioBuf.ptr+=6;
						if (!CheckFileSpace ( fileRef, &ioBuf, 1 ))return;
						if(CheckBytes ( ioBuf.ptr, Uns8Ptr(":"), 1 ))
						{
							//ignore till %%EndData
							while(true)
							{
								if (!CheckFileSpace ( fileRef, &ioBuf, 10 ))return;
								if (CheckBytes ( ioBuf.ptr, Uns8Ptr("%%EndData"), 9 ))
								{
									ioBuf.ptr+=9;
									if (IsWhitespace(*ioBuf.ptr))
									{
										ioBuf.ptr++;
										break;
									}
								}
								++ioBuf.ptr;
							}
						}
					}
					else if (CheckBytes ( ioBuf.ptr, Uns8Ptr("inDocu"), 6 ))
					{// %%BeginDocument
						ioBuf.ptr+=6;
						if (!CheckFileSpace ( fileRef, &ioBuf, 5 ))return;
						if(CheckBytes ( ioBuf.ptr, Uns8Ptr("ment:"), 5 ))
						{
							ioBuf.ptr+=5;
							//ignore till %%EndDocument
							while(true)
							{
								if (!CheckFileSpace ( fileRef, &ioBuf, 14 ))return;
								if (CheckBytes ( ioBuf.ptr, Uns8Ptr("%%EndDocument"), 13 ))
								{
									ioBuf.ptr+=13;
									if (IsWhitespace(*ioBuf.ptr))
									{
										ioBuf.ptr++;
										break;
									}
								}
								++ioBuf.ptr;
							}
						}
					}
					else if (CheckBytes ( ioBuf.ptr, Uns8Ptr("inPage"), 6 ))
					{// %%BeginPageSetup
						ioBuf.ptr+=6;
						if (!CheckFileSpace ( fileRef, &ioBuf, 5 ))return;
						if(CheckBytes ( ioBuf.ptr, Uns8Ptr("Setup"), 5 ))
						{
							ioBuf.ptr+=5;
							setTokenInfo(kPS_BeginPageSetup,begStartpos,16);
						}
					}
				}
				else if(CheckBytes ( ioBuf.ptr, Uns8Ptr("%%End"), 5 ))
				{//possibly %%EndProlog %%EndSetup %%EndPageSetup %%EndPageComments
					XMP_Int64 begStartpos=ioBuf.filePos+ioBuf.ptr-ioBuf.data;
					ioBuf.ptr+=5;
					if ( ! CheckFileSpace ( fileRef, &ioBuf, 5 ) ) return ;
					if (CheckBytes ( ioBuf.ptr, Uns8Ptr("Prolo"), 5 ))
					{// %%EndProlog
						ioBuf.ptr+=5;
						if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return ;
						if (CheckBytes ( ioBuf.ptr, Uns8Ptr("g"), 1 ))
						{
							ioBuf.ptr+=1;
							setTokenInfo(kPS_EndProlog,begStartpos,11);
						}
					}
					else if (CheckBytes ( ioBuf.ptr, Uns8Ptr("Setup"), 5 ))
					{//%%EndSetup
						ioBuf.ptr+=5;
						setTokenInfo(kPS_EndSetup,begStartpos,10);
					}
					else if (CheckBytes ( ioBuf.ptr, Uns8Ptr("PageS"), 5 ))
					{//%%EndPageSetup
						ioBuf.ptr+=5;
						if ( ! CheckFileSpace ( fileRef, &ioBuf, 4 ) ) return ;
						if (CheckBytes ( ioBuf.ptr, Uns8Ptr("etup"), 4 ))
						{
							ioBuf.ptr+=4;
							setTokenInfo(kPS_EndPageSetup,begStartpos,14);
						}
					}
					else if (CheckBytes ( ioBuf.ptr, Uns8Ptr("PageC"), 5 ))
					{//%%EndPageComments
						ioBuf.ptr+=5;
						if ( ! CheckFileSpace ( fileRef, &ioBuf, 7 ) ) return ;
						if (CheckBytes ( ioBuf.ptr, Uns8Ptr("omments"), 7 ))
						{
							ioBuf.ptr+=7;
							setTokenInfo(kPS_EndPageComments,begStartpos,17);
						}
					}
				}
				else if(CheckBytes ( ioBuf.ptr, Uns8Ptr("%%Pag"), 5 ))
				{
					XMP_Int64 begStartpos=ioBuf.filePos+ioBuf.ptr-ioBuf.data;
					ioBuf.ptr+=5;
					if ( ! CheckFileSpace ( fileRef, &ioBuf, 2 ) ) return ;
					if (CheckBytes ( ioBuf.ptr, Uns8Ptr(":"), 2 ))
					{
						ioBuf.ptr+=2;
						while(!IsNewline(*ioBuf.ptr))
						{
							if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return ;
							++ioBuf.ptr;
						}
						setTokenInfo(kPS_Page,begStartpos,ioBuf.filePos+ioBuf.ptr-ioBuf.data-begStartpos);
					}

				}
				else if(CheckBytes ( ioBuf.ptr, Uns8Ptr("%%Tra"), 5 ))
				{
					XMP_Int64 begStartpos=ioBuf.filePos+ioBuf.ptr-ioBuf.data;
					ioBuf.ptr+=5;
					if ( ! CheckFileSpace ( fileRef, &ioBuf, 4 ) ) return ;
					if (CheckBytes ( ioBuf.ptr, Uns8Ptr("iler"), 4 ))
					{
						ioBuf.ptr+=4;
						if ( !CheckFileSpace( fileRef, &ioBuf, 1 ) ) return;
						while ( !IsNewline( *ioBuf.ptr ) )
						{
							if ( !CheckFileSpace( fileRef, &ioBuf, 1 ) ) return;
							++ioBuf.ptr;
						}
						setTokenInfo(kPS_Trailer,begStartpos,ioBuf.filePos+ioBuf.ptr-ioBuf.data-begStartpos);
					}
				}
				else if(CheckBytes ( ioBuf.ptr, Uns8Ptr("%%EOF"), 5 ))
				{
					ioBuf.ptr+=5;
					setTokenInfo(kPS_EOF,ioBuf.filePos+ioBuf.ptr-ioBuf.data,5);
				}
				if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return ;
				++ioBuf.ptr;
			}
			//dont have to search after this DOCINFO last thing 
			return;

		}else if (!(kPS_Creator & dscFlags) && 
			 CheckFileSpace ( fileRef, &ioBuf, kPSContainsForString.length() )&&
			CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsForString.c_str()), kPSContainsForString.length() ))
		{
			ioBuf.ptr+=kPSContainsForString.length();
			if ( ! ExtractDSCCommentValue(ioBuf,kPS_dscFor) ) return ;
		}
		else if (!(kPS_CreatorTool & dscFlags) &&
			 CheckFileSpace ( fileRef, &ioBuf, kPSContainsCreatorString.length() )&&
			 CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsCreatorString.c_str()), kPSContainsCreatorString.length() ))
		{
			ioBuf.ptr+=kPSContainsCreatorString.length();
			if ( ! ExtractDSCCommentValue(ioBuf,kPS_dscCreator) ) return ;
		}
		else if (!(kPS_CreateDate & dscFlags) &&
			 CheckFileSpace ( fileRef, &ioBuf, kPSContainsCreateDateString.length() )&&
			CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsCreateDateString.c_str()), kPSContainsCreateDateString.length() ))
		{
			
			ioBuf.ptr+=kPSContainsCreateDateString.length();
			if ( ! ExtractDSCCommentValue(ioBuf,kPS_dscCreateDate) ) return ;
		}
		else if (!(kPS_Title & dscFlags) &&
			 CheckFileSpace ( fileRef, &ioBuf, kPSContainsTitleString.length() )&&
			CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsTitleString.c_str()), kPSContainsTitleString.length() ))
		{
						
			ioBuf.ptr+=kPSContainsTitleString.length();
			if ( ! ExtractDSCCommentValue(ioBuf,kPS_dscTitle) ) return ;
		}
		else if( CheckFileSpace ( fileRef, &ioBuf, kPSContainsXMPString.length() )&&
			 (  CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsXMPString.c_str()), kPSContainsXMPString.length()    ) )) {

			// Found "%ADO_ContainsXMP:", look for the main packet location option.
			
			XMP_Int64 containsXMPStartpos=ioBuf.filePos+ioBuf.ptr-ioBuf.data;
			ioBuf.ptr += kPSContainsXMPString.length();
			ExtractContainsXMPHint(ioBuf,containsXMPStartpos);

		}	// Found "%ADO_ContainsXMP:".
		//Some other DSC comments skip past the end of this line.
		if ( ! PostScript_Support::SkipUntilNewline(fileRef,ioBuf) ) return ;

	}	// Outer marker loop.

	return ;	// Should never reach here.

}	

// =================================================================================================
// PostScript_MetaHandler::ReadXMPPacket
// =====================================
//
// Helper method read the raw xmp into a string from a file
void PostScript_MetaHandler::ReadXMPPacket (std::string & xmpPacket )
{
	if ( packetInfo.length == 0 ) XMP_Throw ( "ReadXMPPacket - No XMP packet", kXMPErr_BadXMP );

	xmpPacket.erase();
	xmpPacket.reserve ( packetInfo.length );
	xmpPacket.append ( packetInfo.length, ' ' );

	XMP_StringPtr packetStr = XMP_StringPtr ( xmpPacket.c_str() );	// Don't set until after reserving the space!

	this->parent->ioRef->Seek ( packetInfo.offset, kXMP_SeekFromStart );
	this->parent->ioRef->ReadAll ( (char*)packetStr, packetInfo.length );

}	// ReadXMPPacket


// =================================================================================================
// PostScript_MetaHandler::RegisterKeyValue
// =========================================
//
// Helper method registers the Key value pairs for the DocInfo dictionary and sets the Appriopriate 
// DocInfo flags
void PostScript_MetaHandler::RegisterKeyValue(std::string& key, std::string& value)
{
	size_t vallen=value.length();
	if (key.length()==0||vallen==0)
	{
		key.clear();
		value.clear();
		return;
	}
	for (size_t index=0;index<vallen;index++)
	{
		if ((unsigned char)value[index]>127)
		{
			key.clear();
			value.clear();
			return;
		}
	}
	switch (key[0])
	{	
		case 'A': // probably Author
			{
				if (!key.compare("Author"))
				{
					nativeMeta[kPS_docInfoAuthor]=value;
					docInfoFlags|=kPS_Creator;
				}
			break;
			}
		case 'C': //probably Creator or CreationDate
			{
				if (!key.compare("Creator"))
				{
					nativeMeta[kPS_docInfoCreator]=value;
					docInfoFlags|=kPS_CreatorTool;
				}
				else if (!key.compare("CreationDate"))
				{
					nativeMeta[kPS_docInfoCreateDate]=value;
					docInfoFlags|=kPS_CreateDate;
				}
				break;
			}
		case 'T': // probably Title
			{				
				if (!key.compare("Title"))
				{
					nativeMeta[kPS_docInfoTitle]=value;
					docInfoFlags|=kPS_Title;
				}
				break;
			}
		case 'S':// probably Subject
			{
				if (!key.compare("Subject"))
				{
					nativeMeta[kPS_docInfoSubject]=value;
					docInfoFlags|=kPS_Description;
				}
				break;
			}
		case 'K':// probably Keywords
			{
				if (!key.compare("Keywords"))
				{
					nativeMeta[kPS_docInfoKeywords]=value;
					docInfoFlags|=kPS_Subject;
				}
				break;
			}
		case 'M': // probably ModDate 
			{
				if (!key.compare("ModDate"))
				{
					nativeMeta[kPS_docInfoModDate]=value;
					docInfoFlags|=kPS_ModifyDate;
				}
				break;
			}
		default: //ignore everything else
			{
				;
			}
	}
	key.clear();
	value.clear();
}


// =================================================================================================
// PostScript_MetaHandler::ReconcileXMP
// =========================================
//
// Helper method that facilitates the read time reconcilliation of native metadata

void PostScript_MetaHandler::ReconcileXMP( const std::string &xmpStr, std::string *outStr ) 
{
	SXMPMeta xmp;
	xmp.ParseFromBuffer( xmpStr.c_str(), (XMP_Uns32)xmpStr.length() );
	// Adding creator Toll if any 
	if (!xmp.DoesPropertyExist ( kXMP_NS_XMP,"CreatorTool" ))
	{
		if(docInfoFlags&kPS_CreatorTool)
		{
			xmp.SetProperty( kXMP_NS_XMP, "CreatorTool", nativeMeta[kPS_docInfoCreator] );
		}
		else if (dscFlags&kPS_CreatorTool)
		{
			xmp.SetProperty( kXMP_NS_XMP, "CreatorTool", nativeMeta[kPS_dscCreator] );
		}
	}
	if (!xmp.DoesPropertyExist ( kXMP_NS_XMP,"CreateDate" ))
	{
		if(docInfoFlags&kPS_CreateDate && nativeMeta[kPS_docInfoCreateDate].length()>0)
		{
			std::string xmpdate=PostScript_Support::ConvertToDate(nativeMeta[kPS_docInfoCreateDate].c_str());
			if (xmpdate.length()>0)
			{
				xmp.SetProperty( kXMP_NS_XMP, "CreateDate", xmpdate );
			}
		}
		else if (dscFlags&kPS_CreateDate&& nativeMeta[kPS_dscCreateDate].length()>0)
		{
			std::string xmpdate=PostScript_Support::ConvertToDate(nativeMeta[kPS_dscCreateDate].c_str());
			xmp.SetProperty( kXMP_NS_XMP, "CreateDate", xmpdate );
		}
	}
	if (!xmp.DoesPropertyExist ( kXMP_NS_XMP,"ModifyDate" ))
	{
		if(docInfoFlags&kPS_ModifyDate && nativeMeta[kPS_docInfoModDate].length()>0)
		{
			std::string xmpdate=PostScript_Support::ConvertToDate(nativeMeta[kPS_docInfoModDate].c_str());
			if (xmpdate.length()>0)
			{
				xmp.SetProperty( kXMP_NS_XMP, "ModifyDate", xmpdate );
			}
		}
	}
	if (!xmp.DoesPropertyExist ( kXMP_NS_DC,"creator" ))
	{
		if(docInfoFlags&kPS_Creator)
		{
			xmp.AppendArrayItem ( kXMP_NS_DC, "creator", kXMP_PropArrayIsOrdered,
										   nativeMeta[kPS_docInfoAuthor] );
		}
		else if ( dscFlags&kPS_Creator)
		{
			xmp.AppendArrayItem ( kXMP_NS_DC, "creator", kXMP_PropArrayIsOrdered,
										   nativeMeta[kPS_dscFor] );
		}
	}
	if (!xmp.DoesPropertyExist ( kXMP_NS_DC,"title" ))
	{
		if(docInfoFlags&kPS_Title)
		{
			xmp.SetLocalizedText( kXMP_NS_DC, "title",NULL,"x-default", nativeMeta[kPS_docInfoTitle] );
		}
		else if ( dscFlags&kPS_Title)
		{
			xmp.SetLocalizedText( kXMP_NS_DC, "title",NULL,"x-default", nativeMeta[kPS_dscTitle] );
		}
	}
	if (!xmp.DoesPropertyExist ( kXMP_NS_DC,"description" ))
	{
		if(docInfoFlags&kPS_Description)
		{
			xmp.SetLocalizedText( kXMP_NS_DC, "description",NULL,"x-default", nativeMeta[kPS_docInfoSubject] );
		}
	}
	if (!xmp.DoesPropertyExist ( kXMP_NS_DC,"subject" ))
	{
		if(docInfoFlags&kPS_Subject)
		{
			xmp.AppendArrayItem( kXMP_NS_DC, "subject", kXMP_PropArrayIsUnordered,
								nativeMeta[kPS_docInfoKeywords], kXMP_NoOptions );
		}
	}

	if (packetInfo.length>0)
	{
		try
		{
			xmp.SerializeToBuffer( outStr, kXMP_ExactPacketLength|kXMP_UseCompactFormat,packetInfo.length);
		}
		catch(...)
		{
			xmp.SerializeToBuffer( outStr, kXMP_UseCompactFormat,0);
		}
	}
	else
	{
		xmp.SerializeToBuffer( outStr, kXMP_UseCompactFormat,0);
	}
}


// =================================================================================================
// PostScript_MetaHandler::CacheFileData
// =====================================
void PostScript_MetaHandler::CacheFileData()
{
	this->containsXMP = false;
	this->psHint = kPSHint_NoMarker;
	ParsePSFile();

	if ( this->psHint == kPSHint_MainFirst ) {
		this->containsXMP = FindFirstPacket();
	} else if ( this->psHint == kPSHint_MainLast ) {
		this->containsXMP = FindLastPacket();
	}else
	{
		//find first packet in case of NoMain or absence of hint
		//When inserting new packet should be inserted in front 
		//any other existing packet
		FindFirstPacket();
	}
	if ( this->containsXMP )
	{
		ReadXMPPacket ( xmpPacket );
	}
}	// PostScript_MetaHandler::CacheFileData

// =================================================================================================
// PostScript_MetaHandler::ProcessXMP
// ==================================
void PostScript_MetaHandler::ProcessXMP()
{
	
	XMP_Assert ( ! this->processedXMP );
	this->processedXMP = true;	// Make sure we only come through here once.

	std::string xmptempStr=xmpPacket;

	//Read time reconciliation with native metadata
	try
	{
		ReconcileXMP(xmptempStr, &xmpPacket);
	}
	catch(...)
	{
	}
	if ( ! this->xmpPacket.empty() ) 
	{
		XMP_StringPtr packetStr = this->xmpPacket.c_str();
		XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size();
		this->xmpObj.ParseFromBuffer ( packetStr, packetLen );
	}
	if (xmpPacket.length()>0)
	{
		this->containsXMP = true;	// Assume we had something for the XMP.
	}
}


// =================================================================================================
// PostScript_MetaHandler::modifyHeader
// =====================================
//
// Method modifies the header (if one is present) for the postscript offset, tiff offset etc. 
//	when an XMP update resulted in increase in the file size(non-inplace updates)
void PostScript_MetaHandler::modifyHeader(XMP_IO* fileRef,XMP_Int64 extrabytes,XMP_Int64 offset )
{
	//change the header
	IOBuffer temp;
	fileRef->Rewind();
	if ( ! CheckFileSpace ( fileRef, &temp, 4 ) ) return ;
	XMP_Uns32 fileheader = GetUns32BE ( temp.ptr );

	if ( fileheader == 0xC5D0D3C6 ) 
	{
		XMP_Uns8 buffLE[4];
		if ( ! CheckFileSpace ( fileRef, &temp, 32 ) ) return ;
		XMP_Uns32 psLength = GetUns32LE ( temp.ptr+8 );	// PostScript length.
		if (psLength>0)
		{
			psLength+=(XMP_Uns32)extrabytes;
			PutUns32LE ( psLength, buffLE);
			fileRef->Seek ( 8, kXMP_SeekFromStart );
			fileRef->Write(buffLE,4);
		}
		XMP_Uns32 wmfOffset = GetUns32LE ( temp.ptr+12 );	// WMF offset.
		if (wmfOffset>0 && wmfOffset>offset)
		{
			wmfOffset+=(XMP_Uns32)extrabytes;
			PutUns32LE ( wmfOffset, buffLE);
			fileRef->Seek ( 12, kXMP_SeekFromStart );
			fileRef->Write(buffLE,4);
		}
						
		XMP_Uns32 tiffOffset = GetUns32LE ( temp.ptr+20 );	// Tiff offset.
		if (tiffOffset>0 && tiffOffset>offset)
		{
			tiffOffset+=(XMP_Uns32)extrabytes;
			PutUns32LE ( tiffOffset, buffLE);
			fileRef->Seek ( 20, kXMP_SeekFromStart );
			fileRef->Write(buffLE,4);
		}
		//set the checksum to 0xFFFFFFFF
		XMP_Uns16 checksum=0xFFFF;
		PutUns16LE ( checksum, buffLE);
		fileRef->Seek ( 28, kXMP_SeekFromStart );
		fileRef->Write(buffLE,2);
	}
}

// =================================================================================================
// PostScript_MetaHandler::DetermineUpdateMethod
// =============================================
//
// The policy followed to update a Postscript file is
//	a) if the update can fit into the existing xpacket size, go for inplace update.
//	b) If sub file decode filter is used to embed the metadata expand the metadata 
//		and the move the rest contents(after the xpacket) by some calc offset.
//  c) If some other method is used to embed the xpacket readstring or readline
//		insert a new metadata xpacket before the existing xpacket.
//	The preference to use these methods is in the same order a , b and then c
//  DetermineUpdateMethod helps to decide which method be used for the given
//	input file
//
UpdateMethod PostScript_MetaHandler::DetermineUpdateMethod(std::string & outStr)
{
	SXMPMeta xmp;
	std::string &    xmpPacket  = this->xmpPacket;
	XMP_PacketInfo & packetInfo = this->packetInfo;
	xmp.ParseFromBuffer( xmpPacket.c_str(), (XMP_Uns32)xmpPacket.length() );
	if (packetInfo.length>0)
	{
		try
		{
			//First try to fit the modified XMP data into existing XMP packet length
			//prefers Inplace
			xmp.SerializeToBuffer( &outStr, kXMP_ExactPacketLength|kXMP_UseCompactFormat,packetInfo.length);
		}
		catch(...)
		{
			// if it doesnt fit :(
			xmp.SerializeToBuffer( &outStr, kXMP_UseCompactFormat,0);
		}
	}
	else
	{
		// this will be the case for Injecting new metadata
		xmp.SerializeToBuffer( &outStr, kXMP_UseCompactFormat,0);
	}
	if ( this->containsXMPHint && (outStr.size() == (size_t)packetInfo.length) )
	{
		return kPS_Inplace;
	}
	else if (this->containsXMPHint && PostScript_Support::IsSFDFilterUsed(this->parent->ioRef,packetInfo.offset))
	{
		return kPS_ExpandSFDFilter;
	}
	else
	{
		return kPS_InjectNew;
	}

}

// =================================================================================================
// PostScript_MetaHandler::InplaceUpdate
// =====================================
//
// Method does the inplace update of the metadata
void PostScript_MetaHandler::InplaceUpdate (std::string &outStr,XMP_IO* &tempRef ,bool doSafeUpdate)
{
	
	XMP_IO*      fileRef		= this->parent->ioRef;
	XMP_Int64	 pos			= 0;
	XMP_ProgressTracker* progressTracker = this->parent->progressTracker;
	//Inplace update of metadata
	if (!doSafeUpdate)
	{
		if ( progressTracker != 0 )  progressTracker->AddTotalWork ((float) outStr.length() );
		fileRef->Seek(packetInfo.offset,kXMP_SeekFromStart);
		fileRef->Write((void *)outStr.c_str(), static_cast<XMP_Uns32>(outStr.length()));
	}
	else
	{
		if ( ! tempRef ) tempRef=fileRef->DeriveTemp();
		pos=fileRef->Length();
		if ( progressTracker != 0 )  progressTracker->AddTotalWork ((float) pos );
		//copy data till xmp Packet
		fileRef->Seek(0,kXMP_SeekFromStart);
		XIO::Copy ( fileRef, tempRef, packetInfo.offset, this->parent->abortProc, this->parent->abortArg );

		//insert the new XMP packet
		fileRef->Seek(packetInfo.offset+packetInfo.length,kXMP_SeekFromStart);
		tempRef->Write((void *)outStr.c_str(), static_cast<XMP_Uns32>(outStr.length()));

		//copy the rest of data
		XIO::Copy ( fileRef, tempRef,pos-packetInfo.offset-packetInfo.length, this->parent->abortProc, this->parent->abortArg );
	}
}


// =================================================================================================
// PostScript_MetaHandler::ExpandingSFDFilterUpdate
// ================================================
//
// Method updates the metadata by expanding it
void PostScript_MetaHandler::ExpandingSFDFilterUpdate (std::string &outStr,XMP_IO* &tempRef ,bool doSafeUpdate)
{
	//If SubFileDecode Filter is present expanding the
	//existing metadata is easy 

	XMP_IO*      fileRef		= this->parent->ioRef;
	XMP_Int64	 pos			= 0;
	XMP_Int64 extrapacketlength=outStr.length()-packetInfo.length;
	XMP_ProgressTracker* progressTracker = this->parent->progressTracker;	
	if ( progressTracker != 0 )  progressTracker->AddTotalWork ((float) (extrapacketlength + fileRef->Length() -packetInfo.offset+14) );
	if (!doSafeUpdate)
	{
		size_t bufSize=extrapacketlength/(kIOBufferSize) +1*(extrapacketlength!=(kIOBufferSize));
		std::vector<IOBuffer> tempfilebuffer1(bufSize);
		IOBuffer temp;
		XMP_Int64 readpoint=packetInfo.offset+packetInfo.length,writepoint=packetInfo.offset;
		fileRef->Seek ( readpoint, kXMP_SeekFromStart );
					
		for(size_t x=0;x<bufSize;x++)
		{
			tempfilebuffer1[x].len=fileRef->Read(tempfilebuffer1[x].data,kIOBufferSize,false);
			readpoint+=tempfilebuffer1[x].len;
		}

		fileRef->Seek ( writepoint, kXMP_SeekFromStart );
		fileRef->Write( (void *)outStr.c_str(), static_cast<XMP_Uns32>(outStr.length()));
		writepoint+=static_cast<XMP_Uns32>(outStr.length());
		size_t y=0;
		bool continueread=(tempfilebuffer1[bufSize-1].len==kIOBufferSize);
		size_t loop=bufSize;
		while(loop)
		{
			if(continueread)
			{
				fileRef->Seek ( readpoint, kXMP_SeekFromStart );
				temp.len=fileRef->Read(temp.data,kIOBufferSize,false);
				readpoint+=temp.len;
			}
			fileRef->Seek ( writepoint, kXMP_SeekFromStart );
			fileRef->Write(tempfilebuffer1[y].data,(XMP_Uns32)tempfilebuffer1[y].len);
			writepoint+=tempfilebuffer1[y].len;
			if (continueread)
				tempfilebuffer1[y]=temp;
			else
				--loop;
			if (temp.len<kIOBufferSize)continueread=false;
			y=(y+1)%bufSize;
		}

		modifyHeader(fileRef,extrapacketlength,packetInfo.offset );
	}
	else
	{
		if ( progressTracker != 0 )  progressTracker->AddTotalWork ((float) (packetInfo.offset) );
		if ( ! tempRef ) tempRef=fileRef->DeriveTemp();
		//copy data till xmp Packet
		fileRef->Seek(0,kXMP_SeekFromStart);
		XIO::Copy ( fileRef, tempRef, packetInfo.offset, this->parent->abortProc, this->parent->abortArg );

		//insert the new XMP packet
		fileRef->Seek(packetInfo.offset+packetInfo.length,kXMP_SeekFromStart);
		tempRef->Write((void *)outStr.c_str(), static_cast<XMP_Uns32>(outStr.length()));

		//copy the rest of data
		pos=fileRef->Length();
		XIO::Copy ( fileRef, tempRef,pos-packetInfo.offset-packetInfo.length,  this->parent->abortProc, this->parent->abortArg );
		modifyHeader(tempRef,extrapacketlength,packetInfo.offset );
	}
}

// =================================================================================================
// PostScript_MetaHandler::DetermineInsertionOffsets
// =============================================
//
// Method determines the offsets at which the new xpacket and other postscript code be inserted
void PostScript_MetaHandler::DetermineInsertionOffsets(XMP_Int64& ADOhintOffset,XMP_Int64& InjectData1Offset,
							XMP_Int64& InjectData3Offset)
{
	//find the position to place ADOContainsXMP hint
	if(psHint!=kPSHint_MainFirst && (fileformat==kXMP_EPSFile||kXMPFiles_UnknownLength==packetInfo.offset))
	{
		TokenLocation& tokenLoc= getTokenInfo(kPS_ADOContainsXMP);
		if(tokenLoc.offsetStart==-1)
		{
			TokenLocation& tokenLoc1= getTokenInfo(kPS_EndComments);
			if(tokenLoc1.offsetStart==-1)
			{
				//should never reach here
				throw XMP_Error(kXMPErr_BadFileFormat,"%%EndComment Missing");
			}
			ADOhintOffset=tokenLoc1.offsetStart;
		}
		else
		{
			ADOhintOffset= tokenLoc.offsetStart;
		}
	}
	else if (psHint!=kPSHint_MainLast &&fileformat==kXMP_PostScriptFile)
	{
		TokenLocation& tokenLoc= getTokenInfo(kPS_ADOContainsXMP);
		if(tokenLoc.offsetStart==-1)
		{
			TokenLocation& tokenLoc1= getTokenInfo(kPS_EndComments);
			if(tokenLoc1.offsetStart==-1)
			{
				//should never reach here
				throw XMP_Error(kXMPErr_BadFileFormat,"%%EndComment Missing");
			}
			ADOhintOffset=tokenLoc1.offsetStart;
		}
		else
		{
			ADOhintOffset= tokenLoc.offsetStart;
		}
	}
	//Find the location to insert kEPS_Injectdata1
	XMP_Uns64 xpacketLoc;
	if ( (fileformat == kXMP_PostScriptFile) && (kXMPFiles_UnknownLength != packetInfo.offset) )
	{
		xpacketLoc = (XMP_Uns64)lastPacketInfo.offset;
		TokenLocation& endPagsetuploc = getTokenInfo(kPS_EndPageSetup);
		if ( (endPagsetuploc.offsetStart > -1) && (xpacketLoc < (XMP_Uns64)endPagsetuploc.offsetStart) )
		{
			InjectData1Offset=endPagsetuploc.offsetStart;
		}
		else
		{
			TokenLocation& trailerloc= getTokenInfo(kPS_Trailer);
			if ( (trailerloc.offsetStart > -1) && (xpacketLoc < (XMP_Uns64)trailerloc.offsetStart) )
			{
				InjectData1Offset=trailerloc.offsetStart;
			}
			else
			{
				TokenLocation& eofloc= getTokenInfo(kPS_EOF);
				if ( (eofloc.offsetStart > -1) && (xpacketLoc < (XMP_Uns64)eofloc.offsetStart) )
				{
					InjectData1Offset=eofloc.offsetStart;
				}
				else
				{
					TokenLocation& endPostScriptloc= getTokenInfo(kPS_EndPostScript);
					if ( (endPostScriptloc.offsetStart > -1) && (xpacketLoc < (XMP_Uns64)endPostScriptloc.offsetStart) )
					{
						InjectData1Offset=endPostScriptloc.offsetStart;
					}
				}
			}
		}
	}
	else
	{
		xpacketLoc = (XMP_Uns64)firstPacketInfo.offset;
		TokenLocation& endPagsetuploc = getTokenInfo(kPS_EndPageSetup);
		if ( (endPagsetuploc.offsetStart > -1) && (xpacketLoc > (XMP_Uns64)endPagsetuploc.offsetStart) )
		{
			InjectData1Offset=endPagsetuploc.offsetStart;
		}
		else 
		{
			TokenLocation& beginPagsetuploc= getTokenInfo(kPS_BeginPageSetup);
			if ( (beginPagsetuploc.offsetStart > -1) &&
				 (xpacketLoc > (XMP_Uns64)(beginPagsetuploc.offsetStart + beginPagsetuploc.tokenlen)) )
			{
				InjectData1Offset=beginPagsetuploc.offsetStart+beginPagsetuploc.tokenlen;
			}
			else
			{
				TokenLocation& endPageCommentsloc= getTokenInfo(kPS_EndPageComments);
				if ( (endPageCommentsloc.offsetStart > -1) &&
					 (xpacketLoc > (XMP_Uns64)(endPageCommentsloc.offsetStart + endPageCommentsloc.tokenlen)) )
				{
					InjectData1Offset=endPageCommentsloc.offsetStart+endPageCommentsloc.tokenlen;
				}
				else
				{
					TokenLocation& pageLoc= getTokenInfo(kPS_Page);
					if ( (pageLoc.offsetStart > -1) &&
						 (xpacketLoc > (XMP_Uns64)(pageLoc.offsetStart + pageLoc.tokenlen)) )
					{
						InjectData1Offset=pageLoc.offsetStart+pageLoc.tokenlen;
					}
					else
					{
						TokenLocation& endSetupLoc= getTokenInfo(kPS_EndSetup);
						if ( (endSetupLoc.offsetStart > -1) && (xpacketLoc > (XMP_Uns64)endSetupLoc.offsetStart) )
						{
							InjectData1Offset=endSetupLoc.offsetStart;
						}
						else
						{
							TokenLocation& beginSetupLoc= getTokenInfo(kPS_BeginSetup);
							if ( (beginSetupLoc.offsetStart > -1) &&
								 (xpacketLoc > (XMP_Uns64)(beginSetupLoc.offsetStart + beginSetupLoc.tokenlen)) )
							{
								InjectData1Offset=beginSetupLoc.offsetStart+beginSetupLoc.tokenlen;
							}
							else
							{
								TokenLocation& endPrologLoc= getTokenInfo(kPS_EndProlog);
								if ( (endPrologLoc.offsetStart > -1) &&
									 (xpacketLoc > (XMP_Uns64)(endPrologLoc.offsetStart + endPrologLoc.tokenlen)) )
								{
									InjectData1Offset=endPrologLoc.offsetStart+endPrologLoc.tokenlen;
								}
								else
								{
									TokenLocation& endCommentsLoc= getTokenInfo(kPS_EndComments);
									if ( (endCommentsLoc.offsetStart > -1) &&
										 (xpacketLoc > (XMP_Uns64)(endCommentsLoc.offsetStart + endCommentsLoc.tokenlen)) )
									{
										InjectData1Offset=endCommentsLoc.offsetStart+endCommentsLoc.tokenlen;
									}
									else
									{
										//should never reach here
										throw XMP_Error(kXMPErr_BadFileFormat,"%%EndComment Missing");
									}
								}

							}
						}
					}
				}
			}
		}
	}
	
					
	//Find the location to insert kEPS_Injectdata3
	TokenLocation& trailerloc= getTokenInfo(kPS_Trailer);
	if(trailerloc.offsetStart>-1 )
	{
		InjectData3Offset=trailerloc.offsetStart+trailerloc.tokenlen;
	}
	else
	{
		TokenLocation& eofloc= getTokenInfo(kPS_EOF);
		if(eofloc.offsetStart>-1 )
		{
			InjectData3Offset=eofloc.offsetStart;
		}
		else
		{
			TokenLocation& endPostScriptloc= getTokenInfo(kPS_EndPostScript);
			if(endPostScriptloc.offsetStart>-1 )
			{
				InjectData3Offset=endPostScriptloc.offsetStart;
			}
		}
	}
}

// =================================================================================================
// PostScript_MetaHandler::InsertNewUpdate
// =======================================
//
// Method inserts a new Xpacket in the postscript file.This will be called in two cases
//	a) If there is no xpacket in the PS file
//	b) If the existing xpacket is embedded using readstring or readline method
void PostScript_MetaHandler::InsertNewUpdate (std::string &outStr,XMP_IO* &tempRef,bool doSafeUpdate )
{
	// In this case it is better to have safe update 
	// as non-safe update implementation is going to be complex 
	// and more time consuming 
	// ignoring doSafeUpdate for this update method

	//No SubFileDecode Filter 
	// Need to insert new Metadata before existing metadata
	// with SubFileDecode Filter
	
	XMP_IO*      fileRef		= this->parent->ioRef;	
	if ( ! tempRef ) tempRef=fileRef->DeriveTemp();
	//inject metadata at the right place
	XMP_Int64 ADOhintOffset=-1,InjectData1Offset=-1,InjectData3Offset=-1;
	DetermineInsertionOffsets(ADOhintOffset,InjectData1Offset,InjectData3Offset);
	XMP_Int64 tempInjectData1Offset=InjectData1Offset;
	fileRef->Rewind();
	XMP_ProgressTracker* progressTracker = this->parent->progressTracker;	
	if ( progressTracker != 0 )  
	{
		progressTracker->AddTotalWork ((float) ( fileRef->Length() +outStr.length() + 14) );
		if (fileformat==kXMP_EPSFile)
		{
			progressTracker->AddTotalWork ((float) ( kEPS_Injectdata1.length() + kEPS_Injectdata2.length() + kEPS_Injectdata3.length()) );
		}
		else
		{
			progressTracker->AddTotalWork ((float) ( kPS_Injectdata1.length() + kPS_Injectdata2.length()) );
		}
	}
	XMP_Int64 totalReadLength=0;
	//copy contents from orignal file to Temp File
	if(ADOhintOffset!=-1)
	{
		XIO::Copy(fileRef,tempRef,ADOhintOffset,this->parent->abortProc,this->parent->abortArg);
		totalReadLength+=ADOhintOffset;
		if (fileformat==kXMP_EPSFile || kXMPFiles_UnknownLength==packetInfo.offset)
		{
			if ( progressTracker != 0 )  progressTracker->AddTotalWork ((float) ( kPS_XMPHintMainFirst.length()) );
			tempRef->Write(kPS_XMPHintMainFirst.c_str(),(XMP_Uns32)kPS_XMPHintMainFirst.length());
		}
		else
		{
			if ( progressTracker != 0 )  progressTracker->AddTotalWork ((float) ( kPS_XMPHintMainLast.length()) );
			tempRef->Write(kPS_XMPHintMainLast.c_str(),(XMP_Uns32)kPS_XMPHintMainLast.length());
		}
	}
	InjectData1Offset-=totalReadLength;
	XIO::Copy(fileRef,tempRef,InjectData1Offset,this->parent->abortProc,this->parent->abortArg);
	totalReadLength+=InjectData1Offset;
	if (fileformat==kXMP_EPSFile)
	{
		tempRef->Write(kEPS_Injectdata1.c_str(),(XMP_Uns32)kEPS_Injectdata1.length());
		tempRef->Write((void *)outStr.c_str(), static_cast<XMP_Uns32>(outStr.length()));
		tempRef->Write(kEPS_Injectdata2.c_str(),(XMP_Uns32)kEPS_Injectdata2.length());
	}
	else
	{
		tempRef->Write(kPS_Injectdata1.c_str(),(XMP_Uns32)kPS_Injectdata1.length());
		tempRef->Write((void *)outStr.c_str(), static_cast<XMP_Uns32>(outStr.length()));
		tempRef->Write(kPS_Injectdata2.c_str(),(XMP_Uns32)kPS_Injectdata2.length());
	}
	if (InjectData3Offset!=-1)
	{
		InjectData3Offset-=totalReadLength;
		XIO::Copy(fileRef,tempRef,InjectData3Offset,this->parent->abortProc,this->parent->abortArg);
		totalReadLength+=InjectData3Offset;
		if (fileformat==kXMP_EPSFile)
		{
			tempRef->Write(kEPS_Injectdata3.c_str(),(XMP_Uns32)kEPS_Injectdata3.length());
		}
		XMP_Int64 remlength=fileRef->Length()-totalReadLength;
		XIO::Copy(fileRef,tempRef,remlength,this->parent->abortProc,this->parent->abortArg);
		totalReadLength+=remlength;
	}
	else
	{
		XMP_Int64 remlength=fileRef->Length()-totalReadLength;
		XIO::Copy(fileRef,tempRef,remlength,this->parent->abortProc,this->parent->abortArg);
		totalReadLength+=remlength;
		if (fileformat==kXMP_EPSFile)
		{
			tempRef->Write(kEPS_Injectdata3.c_str(),(XMP_Uns32)kEPS_Injectdata3.length());
		}
	}
	XMP_Int64 extraBytes;
	if (fileformat==kXMP_EPSFile )
	{
		extraBytes=((ADOhintOffset!=-1)?kPS_XMPHintMainFirst.length():0)+kEPS_Injectdata3.length()+kEPS_Injectdata2.length()+
			kEPS_Injectdata1.length()+outStr.length();
	}
	else
	{
		extraBytes=((ADOhintOffset!=-1)?(kXMPFiles_UnknownLength!=packetInfo.offset?kPS_XMPHintMainLast.length():kPS_XMPHintMainFirst.length()):0)+kPS_Injectdata2.length()+kPS_Injectdata1.length()+outStr.length();
	}
	modifyHeader(tempRef,extraBytes,tempInjectData1Offset );
}

// =================================================================================================
// PostScript_MetaHandler::UpdateFile
// ==================================
//
// Virtual Method implementation to update XMP metadata in a PS file 
void PostScript_MetaHandler::UpdateFile ( bool doSafeUpdate )
{
	IgnoreParam ( doSafeUpdate );
	if ( ! this->needsUpdate ) return;

	XMP_IO *	tempRef			= 0;
	XMP_IO*      fileRef		= this->parent->ioRef;
	std::string &    xmpPacket  = this->xmpPacket;
	std::string outStr;
	
	if (!fileRef )
	{
		XMP_Throw ( "Invalid File Refernce Cannot update XMP", kXMPErr_BadOptions );
	}
	bool localProgressTracking = false;
	XMP_ProgressTracker* progressTracker = this->parent->progressTracker;	
	if ( progressTracker != 0 ) 
	{
		if ( ! progressTracker->WorkInProgress() ) 
		{
			localProgressTracking = true;
			progressTracker->BeginWork ();
		}
	}
	try
	{	switch(DetermineUpdateMethod(outStr))
		{
			case kPS_Inplace:
			{
				InplaceUpdate ( outStr, tempRef, doSafeUpdate );
				break;
			}
			case kPS_ExpandSFDFilter:
			{
				ExpandingSFDFilterUpdate ( outStr, tempRef, doSafeUpdate );
				break;
			}
			case kPS_InjectNew:
			{
				InsertNewUpdate ( outStr, tempRef, doSafeUpdate );
				break;
			}
			case kPS_None:
			default:
			{
				XMP_Throw ( "XMP Write Failed ", kXMPErr_BadOptions );
			}
		}
	}
	catch(...)
	{
		if( tempRef ) fileRef->DeleteTemp();
		throw;
	}
	// rename the modified temp file and then delete the temp file
	if ( tempRef ) fileRef->AbsorbTemp();
	if ( localProgressTracking ) progressTracker->WorkComplete();
	this->needsUpdate = false;

}	// PostScript_MetaHandler::UpdateFile


// =================================================================================================
// PostScript_MetaHandler::UpdateFile
// ==================================
//
//  Method to write the file with updated XMP metadata to the passed temp file reference
void PostScript_MetaHandler::WriteTempFile ( XMP_IO* tempRef )
{
	XMP_IO* origRef = this->parent->ioRef;

	XMP_AbortProc abortProc  = this->parent->abortProc;
	void *        abortArg   = this->parent->abortArg;

	XMP_Int64 fileLen = origRef->Length();
	
	XMP_ProgressTracker* progressTracker = this->parent->progressTracker;
	if ( progressTracker != 0 )  progressTracker->BeginWork ((float) fileLen );
	origRef->Rewind ( );
	tempRef->Truncate ( 0 );
	XIO::Copy ( origRef, tempRef, fileLen, abortProc, abortArg );

	try 
	{
		this->parent->ioRef = tempRef;	// ! Make UpdateFile update the temp.
		this->UpdateFile ( false );
		this->parent->ioRef = origRef;
	} 
	catch ( ... ) 
	{
		this->parent->ioRef = origRef;
		throw;
	}
	
	if ( progressTracker != 0 ) progressTracker->WorkComplete();
}
// =================================================================================================
