/* This file is part of the LibMSWrite Library
   Copyright (C) 2001-2003 Clarence Dang <clarencedang@users.sourceforge.net>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License Version 2 as published by the Free Software Foundation.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License Version 2 for more details.

   You should have received a copy of the GNU Library General Public License
   Version 2 along with this library; see the file COPYING.LIB.  If not,
   write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.

   LibMSWrite Project Website:
   http://sourceforge.net/projects/libmswrite/
*/

/*
 * structures.cpp - Process Internal Microsoft(r) Write file structures
 * This file has ugly reading/writing code that couldn't be generated.
 * It performs some higher-level sanity checks.
 *
 */

#include "structures.h"
#include "structures_private.h"

namespace MSWrite
{
	PageLayout::PageLayout () : m_numModified (0)
	{
	}

	PageLayout::~PageLayout ()
	{
	}

	PageLayout &PageLayout::operator= (const PageLayout &rhs)
	{
		if (this == &rhs)
			return *this;

		PageLayoutGenerated::operator= (rhs);
		NeedsHeader::operator= (rhs);

		m_numModified = rhs.m_numModified;

		return *this;
	}

	bool PageLayout::readFromDevice (void)
	{
	CHECK_DEVICE;

	#ifdef DEBUG_PAGELAYOUT
		m_device->debug ("\n<<<< PageLayout::readFromDevice >>>>\n");
	#endif

		int numPageLayoutPages = m_header->getNumPageSectionProperty ();

	#ifdef DEBUG_PAGELAYOUT
		m_device->debug ("num pageLayoutPages: ", numPageLayoutPages);
	#endif

		// no PageLayout
		if (numPageLayoutPages == 0)
			return true;
		else if (numPageLayoutPages > 1)
			ErrorAndQuit (Error::InvalidFormat, "invalid #pageLayoutPages\n");

		// seek to the PageLayout in the file
		if (!m_device->seekInternal (m_header->getPageSectionProperty () * 128, SEEK_SET))
			ErrorAndQuit (Error::FileError, "could not seek to pageLayout\n");

		if (!PageLayoutGenerated::readFromDevice ())
			return false;

	#ifdef DEBUG_PAGELAYOUT
		Dump (magic102);
		Dump (magic512);

		Dump (pageHeight);
		Dump (pageWidth);
		Dump (pageNumberStart);
		Dump (topMargin);
		Dump (textHeight);
		Dump (leftMargin);
		Dump (textWidth);

		Dump (magic256);

		Dump (headerFromTop);
		Dump (footerFromTop);

		Dump (magic720);
		Dump (zero);
		Dump (magic1080);
		Dump (unknown);
		Dump (zero2);
	#endif

		#define UpdateModifiedCount(variable) if (m_##variable != variable##Default) m_numModified++
		UpdateModifiedCount (magic102);
		UpdateModifiedCount (magic512);
		UpdateModifiedCount (pageHeight);
		UpdateModifiedCount (pageWidth);
		UpdateModifiedCount (pageNumberStart);
		UpdateModifiedCount (topMargin);
		UpdateModifiedCount (textHeight);
		UpdateModifiedCount (leftMargin);
		UpdateModifiedCount (textWidth);
		UpdateModifiedCount (magic256);
		UpdateModifiedCount (headerFromTop);
		UpdateModifiedCount (footerFromTop);
		UpdateModifiedCount (magic720);
		UpdateModifiedCount (zero);
		UpdateModifiedCount (magic1080);
		//UpdateModifiedCount (unknown);	// no reliable default for unknown
		UpdateModifiedCount (zero2);
		#undef UpdateModifiedCount

		return true;
	}

	bool PageLayout::writeToDevice (void)
	{
	CHECK_DEVICE;

	#ifdef DEBUG_PAGELAYOUT
		m_device->debug ("\n>>>> PageLayout::writeToDevice <<<<\n");
	#endif

		m_header->setPageSectionProperty (m_device->tellInternal () / 128);

		// if we're just using defaults, we don't need a pageLayout
		if (!getIsModified ())
			return true;

		if (!PageLayoutGenerated::writeToDevice ())
			return false;

		return true;
	}


	Font::Font (const Byte *name, const Byte family) : m_name (NULL)
	{
		if (name) setName (name);
		FontGenerated::m_family = family;
	}

	Font::~Font ()
	{
		delete [] m_name;
	}

	Font &Font::operator= (const Font &rhs)
	{
		if (this == &rhs)
			return *this;

		FontGenerated::operator= (rhs);

		setName (rhs.getName ());

		return *this;
	}

	bool Font::readFromDevice (void)
	{
	CHECK_DEVICE;

		if (!FontGenerated::readFromDevice ())
			return false;

	#ifdef DEBUG_FONT
		Dump (numDataBytes);
		Dump (family);
	#endif

		// is this font on the next page?
		if (m_numDataBytes == 0xFFFF)
			return false;	// Device::m_error will be clear so this is ok

		// is this the last font _ever_?
		if (m_numDataBytes == 0)
			return false;	// Device::m_error will be clear so this is ok

		// Font structure size can't exceed a page anyway
		if (m_numDataBytes > 128 - sizeof (m_numDataBytes))
			ErrorAndQuit (MSWrite::Error::InvalidFormat, "Font nameLen is too big\n");

		// calculate length of name
		int nameLen = m_numDataBytes - sizeof (m_family) - 1	/* NUL */;

		// we should really call this->setName()...
		m_name = new Byte [nameLen + 1];
		if (!m_name)
			ErrorAndQuit (Error::OutOfMemory, "could not allocate memory for fontName\n");

		// read fontName
		if (!m_device->readInternal (m_name, nameLen + 1))
			ErrorAndQuit (Error::FileError, "could not read fontName\n");

		// check name is valid
		if (m_name [nameLen] != '\0')
			ErrorAndQuit (Error::InvalidFormat, "fontName not NUL-terminated\n");

	#ifdef DEBUG_FONT
		m_device->debug ("\tfontName=", (const char *) m_name);
	#endif
		return true;
	}

	bool Font::writeToDevice (void)
	{
	CHECK_DEVICE;

		// should I be on the next page? (TODO: this check is stupid if it's the last font)
		Word offset = m_device->tellInternal () % 128;	// from start of current page
		if (offset + sizeof (m_numDataBytes) + m_numDataBytes + sizeof (m_numDataBytes)/*for next Font's 0xFFFF*/ > 128)
		{
			// signal that font is on next page
			Byte scratch [2] = {0xFF, 0xFF};
			if (!m_device->writeInternal (scratch, 2))
				ErrorAndQuit (Error::FileError, "could not write Font 0xFFFF\n");
			return false;	// Device::m_error will be clear so this is ok
		}

		// --- we don't have to write 0 to indicate last font (since we write the right number of elements) ---

		// write numDataBytes, family
		if (!FontGenerated::writeToDevice ())
			return false;

		if (!m_device->writeInternal (m_name, m_numDataBytes - sizeof (m_family)))
			ErrorAndQuit (Error::FileError, "could not write fontName\n");

		return true;
	}


	FormatCharProperty::FormatCharProperty () : m_fontTable (NULL)
	{
		// m_afterEndCharByte =
		// m_font =
	}

	FormatCharProperty::~FormatCharProperty ()
	{
	}

	FormatCharProperty &FormatCharProperty::operator= (const FormatCharProperty &rhs)
	{
		if (this == &rhs)
			return *this;

		FormatCharPropertyGenerated::operator= (rhs);

		m_afterEndCharByte = rhs.m_afterEndCharByte;
		m_fontTable = rhs.m_fontTable;
		m_font = rhs.m_font;

		return *this;
	}

	bool FormatCharProperty::operator== (FormatCharProperty &rhs)
	{
		DWord numDataBytes;

		if ((numDataBytes = getNumDataBytes ()) != rhs.getNumDataBytes ())
			return false;

		writeToArray ();
		rhs.writeToArray ();

		return memcmp (m_data + sizeof (m_numDataBytes), rhs.m_data + sizeof (m_numDataBytes), numDataBytes) == 0;
	}

	bool FormatCharProperty::readFromDevice (void)
	{
	CHECK_DEVICE;

	#ifdef DEBUG_CHAR
		m_device->debug ("\nFormatCharProperty::readFromDevice\n");
	#endif

		if (!FormatCharPropertyGenerated::readFromDevice ())
			return false;

		if (!m_fontTable)
			ErrorAndQuit (Error::InternalError, "m_fontTable not setup for FormatCharProperty::readFromDevice\n");

		// note: the converse is done in FormatInfoPage::add(), not FormatCharProperty::writeToDevice
		if (!updateFont ()) return false;

		return true;
	}

	bool FormatCharProperty::writeToDevice (void)
	{
	CHECK_DEVICE;

	#ifdef DEBUG_CHAR
		m_device->debug ("\nFormatCharProperty::writeToDevice, #bytes=", getNumDataBytes ());
	#endif

		return FormatCharPropertyGenerated::writeToDevice ();
	}

	bool FormatCharProperty::updateFont (void)
	{
		Font *fontPtr = m_fontTable->getFont (getFontCode ());
		if (!fontPtr)
			ErrorAndQuit (Error::InvalidFormat, "fontCode out of range\n");

		m_font = *fontPtr;
		return true;
	}

	bool FormatCharProperty::updateFontCode (void)
	{
		DWord code = m_fontTable->addFont (&m_font);
		if (code == 0xFFFFFFFF)
			return false;
		else
		{
			setFontCode (code);
			return true;
		}
	}


	FormatParaPropertyTabulator::FormatParaPropertyTabulator ()
	{
	}

	FormatParaPropertyTabulator::~FormatParaPropertyTabulator ()
	{
	}

	FormatParaPropertyTabulator &FormatParaPropertyTabulator::operator= (const FormatParaPropertyTabulator &rhs)
	{
		if (this == &rhs)
			return *this;

		FormatParaPropertyTabulatorGenerated::operator= (rhs);

		return *this;
	}

	bool FormatParaPropertyTabulator::readFromDevice (void)
	{
	CHECK_DEVICE;

	#ifdef DEBUG_PARA_TAB
		m_device->debug ("\nFormatParaPropertyTabulator::readFromDevice\n");
	#endif

		if (!FormatParaPropertyTabulatorGenerated::readFromDevice ())
			return false;

	#ifdef DEBUG_PARA_TAB
		Dump (indent);
		Dump (type);
		Dump (zero);
	#endif

		return true;
	}

	bool FormatParaPropertyTabulator::writeToDevice (void)
	{
	CHECK_DEVICE;

	#ifdef DEBUG_PARA_TAB
		m_device->debug ("\nFormatParaPropertyTabulator::writeToDevice\n");
	#endif

		return FormatParaPropertyTabulatorGenerated::writeToDevice ();
	}


	FormatParaProperty::FormatParaProperty () : m_leftMargin (0xFFFF),
																m_rightMargin (0xFFFF),
																m_numTabulators (0),
																m_addedTooManyTabs (false)
	{
		// m_afterEndCharByte =
	}

	FormatParaProperty::~FormatParaProperty ()
	{
	}

	FormatParaProperty &FormatParaProperty::operator= (const FormatParaProperty &rhs)
	{
		if (this == &rhs)
			return *this;

		FormatParaPropertyGenerated::operator= (rhs);

		m_afterEndCharByte = rhs.m_afterEndCharByte;
		m_leftMargin = rhs.m_leftMargin, m_rightMargin = rhs.m_rightMargin;
		m_numTabulators = rhs.m_numTabulators;
		m_addedTooManyTabs = rhs.m_addedTooManyTabs;

		return *this;
	}

	bool FormatParaProperty::operator== (FormatParaProperty &rhs)
	{
		DWord numDataBytes;
		if ((numDataBytes = getNumDataBytes ()) != rhs.getNumDataBytes ())
			return false;

		writeToArray ();
		rhs.writeToArray ();

		return memcmp (m_data + sizeof (m_numDataBytes), rhs.m_data + sizeof (m_numDataBytes), numDataBytes) == 0;
	}

	bool FormatParaProperty::readFromDevice (void)
	{
	CHECK_DEVICE;

	#ifdef DEBUG_PARA
		m_device->debug ("\nFormatParaProperty::readFromDevice\n");
	#endif

	#ifdef CHECK_INTERNAL
		if (m_leftMargin == 0xFFFF && m_rightMargin == 0xFFFF)
			ErrorAndQuit (Error::InternalError, "FormatParaProperty::readFromDevice call not setup\n");
	#endif

		if (!FormatParaPropertyGenerated::readFromDevice ())
			return false;

		// Correct indentation in Header and Footer:
		//
		// For unknown reasons, Write adds the leftMargin to the leftIndent
		// and rightMargin to rightIndent in the Header and Footer only but
		// does not touch the leftIndentFirstLine
		//
		// TODO: what if it's an image in the header/footer?
		if (getIsNotNormalParagraph ())
		{
			// adjust leftIndent
			if (m_leftMargin < m_leftIndent)
				m_leftIndent -= m_leftMargin;
			else
				m_leftIndent = 0;

			// adjust rightIndent
			if (m_rightMargin < m_rightIndent)
				m_rightIndent -= m_rightMargin;
			else
				m_rightIndent = 0;
		}

		if (m_numDataBytes > 22)
			m_numTabulators = (m_numDataBytes - 22) / FormatParaPropertyTabulator::s_size;
		else
			m_numTabulators = 0;

		// for impexp
		if (getNumDataBytes () != m_numDataBytes && !m_numTabulators)
			m_device->error (Error::Warn, "m_numDataBytes != getNumDataBytes ()\n");
		UseThisMuch::signalHaveSetData (false, m_numDataBytes * 8);

		return true;
	}

	bool FormatParaProperty::writeToDevice (void)
	{
	CHECK_DEVICE;

	#ifdef CHECK_INTERNAL
		if (m_leftMargin == 0xFFFF && m_rightMargin == 0xFFFF)
			ErrorAndQuit (Error::InternalError, "FormatParaProperty::writeToDevice call not setup\n");
	#endif

	#ifdef DEBUG_PARA
		m_device->debug ("\nFormatParaProperty::writeToDevice, #bytes=", getNumDataBytes ());
	#endif

		if (m_addedTooManyTabs)
			ErrorAndQuit (Error::InternalError, "cannot have more than 14 tabulators; shouldn't even have more than 12\n");

		if (m_numTabulators > 12)
			m_device->error (Error::Warn, "should not have more than 12 tabulators since you can only access 12 tabs via the GUI\n");

		// --- indents adjusted in updateIndents() called by FormatInfoPage::add() ---

		if (!FormatParaPropertyGenerated::writeToDevice ())
			return false;

		return true;
	}


	Image::Image () : m_externalImage (NULL),
							m_externalImageSize (0),
							m_externalImageUpto (0),
							m_originalWidth (0), m_originalHeight (0),
							m_displayedWidth (0), m_displayedHeight (0)
	{
	}

	Image::~Image ()
	{
		delete [] m_externalImage;
	}

	Image &Image::operator= (const Image &rhs)
	{
		if (this == &rhs)
			return *this;

		ImageGenerated::operator= (rhs);

		m_externalImageSize = rhs.m_externalImageSize;
		m_externalImageUpto = rhs.m_externalImageUpto;

		delete [] m_externalImage;
		m_externalImage = new Byte [m_externalImageSize];
		if (!m_externalImage)
			return *this;  // TODO: error check

		if (rhs.m_externalImage)
			memcpy (m_externalImage, rhs.m_externalImage, m_externalImageUpto);

		m_originalWidth = rhs.m_originalWidth;
		m_originalHeight = rhs.m_originalHeight;

		m_displayedWidth = rhs.m_displayedWidth;
		m_displayedHeight = rhs.m_displayedHeight;

		return *this;
	}

	// returns how many bytes are needed for each scanline (with byte padding)
	int Image::getBytesPerScanLine (const int width, const int bitsPerPixel, const int padBytes)
	{
		int bitWidth;
		int byteWidth;

		// figure out how many bytes it would take to represent "width" pixels, each taking "bitsPerPixel" bits
		bitWidth = width * bitsPerPixel;
		byteWidth = bitWidth / 8;
		if (bitWidth % 8)
			byteWidth++;

		// pad the number of bytes taken to the next multiple of "padBytes"
		// (if not divisible by "padBytes")
		byteWidth = (byteWidth + padBytes - 1) / padBytes * padBytes;

		return byteWidth;
	}

	bool Image::readFromDevice (void)
	{
	CHECK_DEVICE;

	#ifdef DEBUG_IMAGE
		m_device->debug ("\n<<<< Image::readFromDevice >>>>\n");
	#endif

		if (!ImageGenerated::readFromDevice ())
			return false;

	#ifdef DEBUG_IMAGE
		Dump (mappingMode);
		Dump (MFP_width);
		Dump (MFP_height);
		Dump (MFP_unknown);

		Dump (indent);
		Dump (width);
		Dump (height);
		Dump (zero);

		Dump (numHeaderBytes);
		Dump (numDataBytes);
		Dump (horizontalScalingRel1000);
		Dump (verticalScalingRel1000);
	#endif

		if (getIsWMF ())
		{
			//
			// get image dimensions
			//

			if (m_bmh->getWidth () || m_bmh->getHeight ())
				m_device->error (Error::Warn, "m_bmh structure should be 0 for WMFs\n");

			m_originalWidth = Milli2Twip (double (m_MFP_width) / 100.0) * 4.0/3.0;
			m_originalHeight = Milli2Twip (double (m_MFP_height) / 100.0) * 4.0/3.0;

			m_displayedWidth = double (m_width);
			m_displayedHeight = double (m_height);

			if (m_horizontalScalingRel1000 != 1000)
				m_device->error (Error::Warn, "horizontal scaling should not be set for WMFs\n");
			if (m_verticalScalingRel1000 != 1000)
				m_device->error (Error::Warn, "vertical scaling should not be set for WMFs\n");


			//
			// read image
			//

			m_externalImage = new Byte [m_externalImageSize = getNumDataBytes ()];
			if (!m_externalImage)
				ErrorAndQuit (Error::OutOfMemory, "could not allocate memory for external WMF image\n");

			if (!m_device->readInternal (m_externalImage, m_externalImageSize))
				ErrorAndQuit (Error::FileError, "could not read internal WMF\n");

			// Header check
			WMFHeader wmfHeader;
			m_device->setCache (m_externalImage);
				wmfHeader.setDevice (m_device);
				if (!wmfHeader.readFromDevice ())
					return false;

				// TODO: fix this incorrect check (see wmf.cpp)
				//if (wmfHeader.getFileSize () * sizeof (Word) != m_numDataBytes)
				//	m_device->error (Error::Warn, "wmfHeader.fileSize != numDataBytes\n");
			m_device->setCache (NULL);
		}
		else	//	if (getIsBMP ())
		{
			//
			// get image dimensions
			//

			m_originalWidth = Point2Twip (DWord (m_bmh->getWidth ()));
			m_originalHeight = Point2Twip (DWord (m_bmh->getHeight ()));

			m_displayedWidth = m_originalWidth / 1.38889 * m_horizontalScalingRel1000 / 1000;
			m_displayedHeight = m_originalHeight / 1.38889 * m_verticalScalingRel1000 / 1000;

#define MSWrite_fabs(val) (((val)>=0)?(val):(-(val)))

			if (MSWrite_fabs (m_MFP_width / double (m_bmh->getWidth ()) - 2.64) > .3)
				m_device->error (Error::Warn, "m_MFP_width != m_bmh->getWidth() * 2.64\n");
			if (MSWrite_fabs (m_MFP_height / double (m_bmh->getHeight ()) - 2.64) > .3)
				m_device->error (Error::Warn, "m_MFP_height != m_bmh->getHeight() * 2.64\n");

#undef MSWrite_fabs

			if (m_width)
				m_device->error (Error::Warn, "m_width should not be set for BMPs\n");

			if (m_height)
				m_device->error (Error::Warn, "m_height should not be set for BMPs\n");


			//
			// read image
			//

			Byte *internalData = new Byte [getNumDataBytes ()];
			if (!internalData)
				ErrorAndQuit (Error::OutOfMemory, "could not allocate memory for internal BMP image\n");
			if (!m_device->readInternal (internalData, getNumDataBytes ()))
				ErrorAndQuit (Error::FileError, "could not read internal BMP\n");

			// infoHeader
			BMP_BitmapInfoHeader infoHeader;
			infoHeader.setWidth (m_bmh->getWidth ());
			infoHeader.setHeight (m_bmh->getHeight ());
			if (m_bmh->getNumPlanes () != 1)
				ErrorAndQuit (Error::InvalidFormat, "bmh.m_numPlanes != 1\n");
			infoHeader.setNumPlanes (m_bmh->getNumPlanes ());
			infoHeader.setBitsPerPixel (m_bmh->getBitsPerPixel ());
			infoHeader.setCompression (0);	// BI_RGB (uncompressed)
			infoHeader.setSizeImage (0);		// lazy
			infoHeader.setXPixelsPerMeter (0), infoHeader.setYPixelsPerMeter (0);
			infoHeader.setColoursUsed (1 << infoHeader.getBitsPerPixel ());
			infoHeader.setColoursImportant (infoHeader.getColoursUsed ());

			if (infoHeader.getColoursUsed () != 2)
				ErrorAndQuit (Error::InternalError, "colour bitmap???  Please email clarencedang@users.sourceforge.net this file\n");

			Word colourTableSize = infoHeader.getColoursUsed () * BMP_BitmapColourIndex::s_size;

			// fileHeader
			BMP_BitmapFileHeader fileHeader;
			DWord fileSize = BMP_BitmapFileHeader::s_size + BMP_BitmapInfoHeader::s_size
										+ colourTableSize
										+ (m_bmh->getHeight ()
											* getBytesPerScanLine (m_bmh->getWidth (), m_bmh->getBitsPerPixel (), 4));

			fileHeader.setTotalBytes (fileSize);
			fileHeader.setActualImageOffset (BMP_BitmapFileHeader::s_size + BMP_BitmapInfoHeader::s_size
														+ colourTableSize);

			// colourTable
			BMP_BitmapColourIndex *colourIndex = new BMP_BitmapColourIndex [infoHeader.getColoursUsed ()];
			if (!colourIndex)
				ErrorAndQuit (Error::OutOfMemory, "could not allocate memory for colourIndex[]\n");


			// black and white...
			colourIndex [0].setRed (0), colourIndex [0].setGreen (0), colourIndex [0].setBlue (0);
			colourIndex [1].setRed (0xFF), colourIndex [1].setGreen (0xFF), colourIndex [1].setBlue (0xFF);

			m_externalImage = new Byte [m_externalImageSize = fileSize];
			if (!m_externalImage)
				ErrorAndQuit (Error::OutOfMemory, "could not allocate memory for external BMP image\n");

			MemoryDevice device;
			device.setCache (m_externalImage);
			fileHeader.setDevice (&device);
			fileHeader.writeToDevice ();
			infoHeader.setDevice (&device);
			infoHeader.writeToDevice ();
			for (int i = 0; i < 2; i++)
			{
				colourIndex [i].setDevice (&device);
				colourIndex [i].writeToDevice ();
			}

			// write out each scanline
			// (BMP padded to 4 bytes vs WRI input bitmap which is actually padded to 2)
			Word scanLineWRILength = getBytesPerScanLine (infoHeader.getWidth (), infoHeader.getBitsPerPixel (), 2);
			if (scanLineWRILength != m_bmh->getWidthBytes ())
				ErrorAndQuit (Error::InvalidFormat, "scanLineWRILength != m_bmh->getWidthBytes()\n");
			Word scanLineBMPLength = getBytesPerScanLine (infoHeader.getWidth (), infoHeader.getBitsPerPixel (), 4);

		#ifdef DEBUG_IMAGE
			m_device->debug ("in: scanLineWRILength: ", scanLineWRILength);
			m_device->debug ("out: scanLineBMPLength: ", scanLineBMPLength);
		#endif

			Byte *padding = new Byte [scanLineBMPLength - scanLineWRILength];
			if (!padding)
				ErrorAndQuit (Error::OutOfMemory, "could not allocate memory for scanline\n");
			memset (padding, 0, scanLineBMPLength - scanLineWRILength);

			// the DIB is upside-down...
			for (int i = (int) infoHeader.getHeight () - 1; i >= 0; i--)
			{
				// write bitmap scanline
				device.writeInternal (internalData + i * scanLineWRILength, scanLineWRILength * sizeof (Byte));

				// write padding for scanline
				device.writeInternal (padding, (scanLineBMPLength - scanLineWRILength) * sizeof (Byte));
			}

			delete [] padding;

			device.setCache (NULL);

			delete [] colourIndex;
			delete [] internalData;
		}

		return true;
	}

	bool Image::writeToDevice (void)
	{
	CHECK_DEVICE;

	#ifdef DEBUG_IMAGE
		m_device->debug ("\n>>>> Image::writeToDevice <<<<\n");
	#endif

	#ifdef DEBUG_IMAGE
		Dump (mappingMode);
		//Dump (MFP_width);	// will change below
		//Dump (MFP_height);
		Dump (MFP_unknown);

		Dump (indent);
		//Dump (width);
		//Dump (height);
		Dump (zero);

		Dump (numHeaderBytes);
		//Dump (numDataBytes);
		//Dump (horizontalScalingRel1000);
		//Dump (verticalScalingRel1000);
	#endif

		//
		// write data
		//
		//
		if (getIsWMF ())
		{
			// Header check
			WMFHeader wmfHeader;
			m_device->setCache (m_externalImage);
				wmfHeader.setDevice (m_device);
				if (!wmfHeader.readFromDevice ()) return false;

				// TODO: fix this incorrect check (see wmf.cpp)
				//if (wmfHeader.getFileSize () * sizeof (Word) != m_externalImageSize)
				//	m_device->error (Error::Warn, "wmfHeader.fileSize != externalImageSize\n");
			m_device->setCache (NULL);


			//
			// set image dimensions
			//

			// entire BitmapHeader is unused with WMFs
			m_bmh->setWidth (0);
			m_bmh->setHeight (0);
			m_bmh->setWidthBytes (0);
			m_bmh->setNumPlanes (0);
			m_bmh->setBitsPerPixel (0);

			m_MFP_width = Word (Twip2Milli (m_originalWidth * 0.75) * 100.0);
			m_MFP_height = Word (Twip2Milli (m_originalHeight * 0.75) * 100.0);

			m_width = Word (m_displayedWidth);
			m_height = Word (m_displayedHeight);

			// not used by WMFs
			m_horizontalScalingRel1000 = m_verticalScalingRel1000 = 1000;


			// write header
			setNumDataBytes (m_externalImageSize);
			if (!ImageGenerated::writeToDevice ())
				return false;

			// external=internal with WMF (i.e. we really do write a WMF)
			if (!m_device->writeInternal (m_externalImage, m_externalImageSize)) return false;
		}
		else	//	if (getIsBMP ())
		{
			m_device->setCache (m_externalImage);

			BMP_BitmapFileHeader fileHeader;
			fileHeader.setDevice (m_device);
			if (!fileHeader.readFromDevice ()) return false;


			/*Word colourTableSize = (1 << m_bmh->getNumPlanes ()) * BMP_BitmapColourIndex::s_size;

			// fileHeader
			DWord fileSize = BMP_BitmapFileHeader::s_size + BMP_BitmapInfoHeader::s_size
												+ colourTableSize
												+ (m_bmh->getHeight ()
													* getBytesPerScanLine (m_bmh->getWidth (), m_bmh->getBitsPerPixel (), 4));

			fileHeader.setTotalBytes (fileSize);
			fileHeader.setActualImageOffset (BMP_BitmapFileHeader::s_size + BMP_BitmapInfoHeader::s_size
														+ colourTableSize);*/

			// infoHeader
			BMP_BitmapInfoHeader infoHeader;
			infoHeader.setDevice (m_device);
			if (!infoHeader.readFromDevice ()) return false;

			// write out each scanline
			// to .WRI (padded to 2) vs input BMP (padded to 4
			Word scanLineWRILength = getBytesPerScanLine (infoHeader.getWidth (), infoHeader.getBitsPerPixel (), 2);
			Word scanLineBMPLength = getBytesPerScanLine (infoHeader.getWidth (), infoHeader.getBitsPerPixel (), 4);

			if (infoHeader.getWidth () != Word (Twip2Point (m_originalWidth)))
				ErrorAndQuit (Error::Warn, "infoHeader width != m_originalWidth\n");
			if (infoHeader.getHeight () != Word (Twip2Point (m_originalHeight)))
				ErrorAndQuit (Error::Warn, "infoHeader.height != m_originalHeight\n");

			m_bmh->setWidth (infoHeader.getWidth ());
			m_bmh->setHeight (infoHeader.getHeight ());
			m_bmh->setWidthBytes (scanLineWRILength);
			if (infoHeader.getNumPlanes () != 1)
				ErrorAndQuit (Error::InvalidFormat, "infoHeader.getNumPlanes() != 1\n");
			m_bmh->setNumPlanes (infoHeader.getNumPlanes ());
			m_bmh->setBitsPerPixel (infoHeader.getBitsPerPixel ());
			if (infoHeader.getCompression () != 0)	// BI_RGB (uncompressed)
				ErrorAndQuit (Error::Unsupported, "compressed bitmaps unsupported\n");
			//infoHeader.setSizeImage (0);		// lazy
			//infoHeader.setXPixelsPerMeter (0), infoHeader.setYPixelsPerMeter (0);
			infoHeader.setColoursUsed (1 << infoHeader.getBitsPerPixel ());	// make life easier
			//infoHeader.setColoursImportant (infoHeader.getColoursUsed ());

			if (infoHeader.getColoursUsed () != 2)
				ErrorAndQuit (Error::Unsupported, "can't save colour BMPs, use WMFs for that purpose\n");

			// colourTable
			BMP_BitmapColourIndex *colourIndex = new BMP_BitmapColourIndex [infoHeader.getColoursUsed ()];
			if (!colourIndex)
				ErrorAndQuit (Error::OutOfMemory, "could not allocate memory for colourIndex[]\n");
			colourIndex [0].setDevice (m_device);
			if (!colourIndex [0].readFromDevice ()) return false;
			if (colourIndex [0].getRed () != 0 || colourIndex [0].getGreen () != 0 || colourIndex [0].getBlue () != 0)
				m_device->error (Error::Warn, "black not black\n");
			colourIndex [1].setDevice (m_device);
			if (!colourIndex [1].readFromDevice ()) return false;
			if (colourIndex [1].getRed () != 0xFF || colourIndex [1].getGreen () != 0xFF || colourIndex [1].getBlue () != 0xFF)
				m_device->error (Error::Warn, "white not white\n");

			// finish reading from m_externalImage
			m_device->setCache (NULL);


			//
			// set image dimensions
			//

			m_MFP_width = Word (Twip2Point (m_originalWidth) * 2.64);
			m_MFP_height = Word (Twip2Point (m_originalHeight) * 2.64);

			// BMPs don't use
			m_width = 0, m_height = 0;

			m_horizontalScalingRel1000 = Word (m_displayedWidth * 1.38889 * 1000.0 / m_originalWidth);
			m_verticalScalingRel1000 = Word (m_displayedHeight * 1.38889 * 1000.0 / m_originalHeight);


			// write header
			setNumDataBytes (infoHeader.getHeight () * scanLineBMPLength);
			if (!ImageGenerated::writeToDevice ())
				return false;

			// the DIB is upside-down...
			Byte *bmpData = m_externalImage + fileHeader.getActualImageOffset () + (infoHeader.getHeight () - 1) * scanLineBMPLength;
			for (int i = (int) infoHeader.getHeight () - 1; i >= 0; i--)
			{
				// write bitmap scanline (padded to 2)
				//if (!m_device->writeInternal (m_externalImage + fileHeader.getActualImageOffset () + i * scanLineBMPLength, scanLineWRILength))
				if (!m_device->writeInternal (bmpData, scanLineWRILength))
					return false;

				bmpData -= scanLineBMPLength;
			}

			delete [] colourIndex;
		}

		return true;
	}


	OLE::OLE () : m_externalObject (NULL),
						m_externalObjectSize (0), m_externalObjectUpto (0)
	{
	}

	OLE::~OLE ()
	{
		delete [] m_externalObject;
	}

	OLE &OLE::operator= (const OLE &rhs)
	{
		if (this == &rhs)
			return *this;

		OLEGenerated::operator= (rhs);

		m_externalObjectSize = rhs.m_externalObjectSize;
		m_externalObjectUpto = rhs.m_externalObjectUpto;

		delete [] m_externalObject;
		m_externalObject = new Byte [m_externalObjectSize];
		if (!m_externalObject)
			return *this;  // TODO: error check

		if (rhs.m_externalObject)
			memcpy (m_externalObject, rhs.m_externalObject, m_externalObjectUpto);

		return *this;
	}

	bool OLE::readFromDevice (void)
	{
	CHECK_DEVICE;

	#ifdef DEBUG_OBJECT
		m_device->debug ("\n<<<< OLE::readFromDevice >>>>\n");
	#endif

		if (!OLEGenerated::readFromDevice ())
			return false;

	#ifdef DEBUG_OBJECT
		Dump (zero);

		switch (m_objectType)
		{
		case OLEType::Static:
			m_device->debug ("\tobjectType: 1 - static\n");
			break;
		case OLEType::Embedded:
			m_device->debug ("\tobjectType: 2 - embedded\n");
			break;
		case OLEType::Link:
			m_device->debug ("\tobjectType: 3 - link\n");
			break;
		}

		Dump (indent);
		Dump (width);
		Dump (height);
		Dump (zero2);
		Dump (numDataBytes);
		Dump (zero3);
		Dump (objectName);
		Dump (zero4);
		Dump (numHeaderBytes);
		Dump (zero5);
		Dump (widthScaledRel1000);
		Dump (heightScaledRel1000);
	#endif

		// OPT: TODO: this is dumb, we read it only to give it back to the parser who tells the generator to write it
		m_externalObject = new Byte [m_externalObjectSize = getNumDataBytes ()];
		if (!m_externalObject)
			ErrorAndQuit (Error::OutOfMemory, "could not allocate memory for external OLE object\n");

		if (!m_device->readInternal (m_externalObject, m_externalObjectSize))
			return false;

		return true;
	}

	bool OLE::writeToDevice (void)
	{
	CHECK_DEVICE;

	#ifdef DEBUG_OBJECT
		m_device->debug ("\n>>>> OLE::writeToDevice <<<<\n");
	#endif

	#ifdef DEBUG_OBJECT
		Dump (zero);

		switch (m_objectType)
		{
		case OLEType::Static:
			m_device->debug ("\tobjectType: 1 - static\n");
			break;
		case OLEType::Embedded:
			m_device->debug ("\tobjectType: 2 - embedded\n");
			break;
		case OLEType::Link:
			m_device->debug ("\tobjectType: 3 - link\n");
			break;
		}

		Dump (indent);
		Dump (width);
		Dump (height);
		Dump (zero2);
		Dump (numDataBytes);
		Dump (zero3);
		Dump (objectName);
		Dump (zero4);
		Dump (numHeaderBytes);
		Dump (zero5);
		Dump (widthScaledRel1000);
		Dump (heightScaledRel1000);
	#endif

		// write header
		if (!OLEGenerated::writeToDevice ())
			return false;

		// write data
		if (!m_device->writeInternal (m_externalObject, m_externalObjectSize))
			return false;

		return true;
	}

}	// namespace MSWrite	{

// end of structures.cpp
