
/* This file is part of the LibMSWrite project
   Copyright (c) 2001-2003, 2007 Clarence Dang <clarencedang@users.sf.net>
   All rights reserved.

   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions
   are met:

   1. Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
   2. Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.

   THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
   IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
   IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
   NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

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

#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>

#include "libmswrite.h"

namespace MSWrite
{
	FormatInfo::FormatInfo () : m_nextChar (0),
											m_leftMargin (0xFFFF), m_rightMargin (0xFFFF),
											m_fontTable (NULL)
	{
		// m_type =
	}

	FormatInfo::~FormatInfo ()
	{
	}

	bool FormatInfo::readFromDevice (void)
	{
	CHECK_DEVICE;

	#ifdef DEBUG_FORMATINFO
		if (m_type == ParaType)
			m_device->debug ("\n<<<< libmswrite.cpp::FormatInfo::readFromDevice (ParaType) >>>>\n");
		else	// if (m_type == CharType)
			m_device->debug ("\n<<<< libmswrite.cpp::FormatInfo::readFromDevice (CharType) >>>>\n");
	#endif

		int formatInfoPageNum;
		if (m_type == ParaType)
			formatInfoPageNum = m_header->getNumPageParaInfo ();
		else	// if (m_type == CharType)
			formatInfoPageNum = m_header->getNumPageCharInfo ();


	#ifdef DEBUG_FORMATINFO
		m_device->debug ("numpages format info=", formatInfoPageNum);
	#endif

		// you have to have information pages that cover the _entire_ document (if it's not empty that is)
		if (m_header->getNumCharBytes () && formatInfoPageNum == 0)
		{
			if (m_type == ParaType)
			{
				ErrorAndQuit (Error::InvalidFormat, "no paragraph formatting information page\n");
			}
			else	// if (m_type == CharType)
			{
				ErrorAndQuit (Error::InvalidFormat, "no character formatting information page\n");
			}
		}

		// seek to start of info pages
		if (!m_device->seek (((m_type == ParaType) ?
										m_header->getPageParaInfo () : m_header->getPageCharInfo ()) * 128, SEEK_SET))
			return false;

		//
		// read in every info page
		// (this eats up all your memory...)
		//
		// The reason this is done is because infoPages (formatting information) are required
		// in between reads of text and on most devices, continually seeking back and forth
		// between the text pages and information pages is inconvenient and inefficient to say
		// the least.
		//
		// Generally speaking there is more text than formatting information some it is probably
		// cheaper (memory-wise) to cache the formatting in memory, rather than the text.
		//
		// A few more good reasons for caching formatting information:
		//
		// * we don't actually expand the formatting pages into the full FormatPointers
		//   and FormatProperty's (until begin()/next() are called) so we save quite a bit of
		//   memory
		// * some filters need to know information like the number of objects in advance
		//   so an extra parse of the formatting information (already cached :)) is required
		//
		for (int i = 0; i < formatInfoPageNum; i++)
		{
			if (!m_formatInfoPageList.addToBack ())
				ErrorAndQuit (Error::OutOfMemory, "could not allocate memory for an information page\n");

			FormatInfoPage &fip = *m_formatInfoPageList.begin (false);
			if (m_device->bad ()) return false;

			fip.setHeader (m_header);
			fip.setDevice (m_device);
			fip.setType (m_type);

			if (m_type == ParaType)
				fip.setMargins (m_leftMargin, m_rightMargin);
			else	// if (m_type == CharType)
				fip.setFontTable (m_fontTable);

			if (!fip.readFromDevice ())
				return false;
		}

		return true;
	}

	bool FormatInfo::writeToDevice (const void *defaultProperty)
	{
	CHECK_DEVICE;

	#ifdef DEBUG_FORMATINFO
		if (m_type == ParaType)
			m_device->debug ("\n<<<< libmswrite.cpp::FormatInfo::writeToDevice (ParaType) >>>>\n");
		else	// if (m_type == CharType)
			m_device->debug ("\n<<<< libmswrite.cpp::FormatInfo::writeToDevice (CharType) >>>>\n");
	#endif

		// set page number of formatting info in header
		if (m_type == ParaType)
			m_header->setPageParaInfo (m_device->tellInternal () / 128);
		else	// if (m_type == CharType)
		{
			// ms programmers were so space conservative that they didn't
			// store charInfo page
			//m_header->setPageCharInfo (m_device->tellInternal () / 128);
		}

		//
		// No formatting table?
		//
		// Mimick what Write does and make one.
		// It seems that only a fontTable is mandatory but this is conveniently
		// updated when adding to a character formatting page.
		//
		if (!m_formatInfoPageList.getNumElements ())
		{
		#ifdef DEBUG_FORMATINFO
			m_device->debug ("\tno formatting information pages, creating one\n");
		#endif

			// --- but we should only get in here if numCharBytes = 0 ---

			if (m_header->getNumCharBytes ())
			{
				if (m_type == ParaType)
					m_device->error (Error::Warn, "data but no paragraph formatting info\n");
				else	// if (m_type == CharType)
					m_device->error (Error::Warn, "data but no character formatting info\n");
			}

			long currentOffset = m_device->tellInternal ();
				if (!m_device->seekInternal (128 + m_header->getNumCharBytes (), SEEK_SET)) return false;
				if (!add (defaultProperty, true/* force */)) return false;
			if (!m_device->seekInternal (currentOffset, SEEK_SET)) return false;
		}

		List <FormatInfoPage>::Iterator it;
		for (it = m_formatInfoPageList.begin (); it != m_formatInfoPageList.end (); ++it)
		{
			FormatInfoPage &fip = *it;

			// OPT: should already have done in add but we have to handle the impexp case
			fip.setHeader (m_header);
			fip.setDevice (m_device);
			fip.setType (m_type);

			if (m_type == ParaType)
				fip.setMargins (m_leftMargin, m_rightMargin);
			else	// if (m_type == CharType)
				fip.setFontTable (m_fontTable);

			if (!fip.writeToDevice ())
				return false;
		}

		return true;
	}

	void *FormatInfo::begin (void)
	{
		m_nextChar = 0;

		m_formatInfoPageIterator = m_formatInfoPageList.begin ();

		if (m_formatInfoPageIterator == m_formatInfoPageList.end ())
			return NULL;

		void *ret = (*m_formatInfoPageIterator).begin ();

		if (ret)
		{
			if (m_type == ParaType)
				m_nextChar = ((FormatParaProperty *) ret)->getAfterEndCharByte ();
			else	// if (m_type == CharType)
				m_nextChar = ((FormatCharProperty *) ret)->getAfterEndCharByte ();
		}

		return ret;
	}

	void *FormatInfo::next (void)
	{
	#ifdef CHECK_INTERNAL
		if (!m_device)
		{
			CHECK_DEVICE_ERROR;
			return NULL;
		}
	#endif

		void *ret = NULL;
		if ((*m_formatInfoPageIterator).end ())
		{
			m_formatInfoPageIterator++;

			// out of pages?
			if (m_formatInfoPageIterator == m_formatInfoPageList.end ())
				return NULL;

			if ((*m_formatInfoPageIterator).getFirstCharByte () != m_nextChar)
				m_device->error (Error::Warn, "FormatInfoPage::firstCharByte does not flow on from nextChar\n");

			ret = (*m_formatInfoPageIterator).begin ();
		}

		if (!ret)
			ret = (*m_formatInfoPageIterator).next ();

		if (ret)
		{
			if (m_type == ParaType)
				m_nextChar = ((FormatParaProperty *) ret)->getAfterEndCharByte ();
			else	// if (m_type == CharType)
				m_nextChar = ((FormatCharProperty *) ret)->getAfterEndCharByte ();
		}

		return ret;
	}

	bool FormatInfo::end (void) /*const*/
	{
		return m_formatInfoPageIterator == m_formatInfoPageList.end ();
	}

	bool FormatInfo::add (const void *property, const bool force)
	{
	CHECK_DEVICE;

	#ifdef DEBUG_FORMATINFO
		m_device->debug (">>>> FormatInfo::add <<<<\n");
	#endif

		DWord currentChar = m_device->tellInternal () - 128;

		// so that export filter writers can be lazy...
		if (m_nextChar == currentChar && !force)
		{
		#ifdef DEBUG_FORMATINFO
			m_device->debug ("\tEmpty FormatProperty, ignoring\n");
		#endif
			return true;
		}

		bool needToAllocate = false;

		if (m_formatInfoPageList.getNumElements ())
		{
			FormatInfoPage &fip = *m_formatInfoPageList.begin (false);
			if (!fip.add (property))
			{
				// a real error
				if (m_device->bad ())
					return false;

				needToAllocate = true;
			}
		}
		else
			needToAllocate = true;

		if (needToAllocate)
		{
			#ifdef DEBUG_FORMATINFO
				m_device->debug ("\tneedToAllocate=yes, FormatInfoPage::firstCharByte=", m_nextChar);
			#endif

			if (!m_formatInfoPageList.addToBack ())
				ErrorAndQuit (Error::OutOfMemory, "could not allocate memory for new formatInfoPage\n");

			FormatInfoPage &fip = *m_formatInfoPageList.begin (false);
			fip.setDevice (m_device);
			fip.setFirstCharByte (m_nextChar);
			fip.setType (m_type);

			if (m_type == ParaType)
				fip.setMargins (m_leftMargin, m_rightMargin);
			else	// if (m_type == CharType)
			{
				assert (m_fontTable);
				fip.setFontTable (m_fontTable);
			}

			if (!fip.add (property))
				return false;
		}

		m_nextChar = currentChar;
		return true;
	}


	Generator::Generator ()
	{
	}

	Generator::~Generator ()
	{
	}

	bool Generator::processText (const Byte *string, bool willReachEndOfParagraph)
	{
		//
		// Look out for characters in the string and emit signals as appropriate:
		//
		//	1	pageNumber
		//	10	newLine
		//	13	carriageReturn
		//	12	pageBreak
		//	31	optionalHyphen
		//	?	text
		//

		const int outBufferMaxLen = 1024;
		Byte outBuffer [outBufferMaxLen];
		DWord outBufferLen = 0;

		for (; *string; string++)
		{
			// buffer full?
			if (outBufferLen >= outBufferMaxLen - 1)
			{
				// flush
				outBuffer [outBufferMaxLen - 1] = '\0';
				if (!writeText (outBuffer)) return false;
				outBufferLen = 0;
			}

			switch (*string)
			{
			// write text, generate signals for special characters, write more text...
			case 1:		// pageNumber anchor
			case 12:		// pageBreak (some silly document might have this in the middle of a paragraph!)
			case 13:
			case 10:		// newLine (some silly document _does_ have a newline in the middle of a paragraph!)
			case 31:    // optionalHyphen (aka "soft hyphen" an invisible hyphen, unless at end of line)
				// output text before this character
				if (outBufferLen)
				{
					outBuffer [outBufferLen] = 0;	// null terminate
					if (!writeText (outBuffer)) return false;
					outBufferLen = 0;
				}

				// generate signal
				switch (*string)
				{
				case 1:	if (!writePageNumber ()) return false;	break;
				case 12:	if (!writePageBreak ()) return false;	break;
				case 10:	if (!writeNewLine (willReachEndOfParagraph && string [1] == 0)) return false;	break;
				case 13:	if (!writeCarriageReturn ()) return false;	break;
				case 31: if (!writeOptionalHyphen ()) return false; break;
				}

				break;

			// normal text character
			default:
				outBuffer [outBufferLen++] = *string;
				break;
			}	// switch (*string)	{
		}	// for (; *string; string++)	{

		// flush
		if (outBufferLen)
		{
			outBuffer [outBufferLen] = 0;
			if (!writeText (outBuffer)) return false;
		}

		return true;
	}


	InternalParser::InternalParser () : m_header (NULL),
													m_sectionTable (NULL),
													m_pageLayout (NULL),
													m_pageTable (NULL),
													m_fontTable (NULL),
													m_paragraphInfo (NULL),
													m_characterInfo (NULL),
													m_image (NULL),
													m_ole (NULL)
	{
	}

	InternalParser::~InternalParser ()
	{
		delete m_ole;
		delete m_image;

		delete m_characterInfo;
		delete m_paragraphInfo;
		delete m_fontTable;
		delete m_pageTable;
		delete m_pageLayout;
		delete m_sectionTable;
		delete m_header;
	}

	// use this parser for "import" filters
	bool InternalParser::parse (void)
	{
		if (!m_device)
		{
			fprintf (stderr, "INTERNAL ERROR: InternalParser::parse() called without a device\n");
			return false;	// cannot use ErrorAndQuit() because that calls m_device->error()
		}

		if (!m_generator)
			ErrorAndQuit (Error::InternalError, "generator not passed to parser\n");

		//
		// allocate memory
		//

		m_header = new Header;
		if (!m_header)
			ErrorAndQuit (Error::OutOfMemory, "could not allocate memory for header\n");

		m_sectionTable = new SectionTable;
		if (!m_sectionTable)
			ErrorAndQuit (Error::OutOfMemory, "could not allocate memory for sectionTable\n");

		m_pageLayout = new PageLayout;
		if (!m_pageLayout)
			ErrorAndQuit (Error::OutOfMemory, "could not allocate memory for pageLayout\n");

		m_pageTable = new PageTable;
		if (!m_pageTable)
			ErrorAndQuit (Error::OutOfMemory, "could not allocate memory for pageTable\n");

		m_fontTable = new FontTable;
		if (!m_fontTable)
			ErrorAndQuit (Error::OutOfMemory, "could not allocate memory for fontTable\n");

		m_paragraphInfo = new FormatInfo;
		if (!m_paragraphInfo)
			ErrorAndQuit (Error::OutOfMemory, "could not allocate memory for paragraphInfo\n");

		m_characterInfo = new FormatInfo;
		if (!m_characterInfo)
			ErrorAndQuit (Error::OutOfMemory, "could not allocate memory for charInfo\n");

		//
		// Read everything from .WRI file
		//

		m_header->setDevice (m_device);
		if (!m_header->readFromDevice ()) return false;
		DWord numCharBytes = m_header->getNumCharBytes ();
	#ifdef DEBUG_INTERNALPARSER
		m_device->debug ("@@@ InternalParser: numCharBytes=", numCharBytes);
	#endif

		m_sectionTable->setHeader (m_header);
		m_sectionTable->setDevice (m_device);
		if (!m_sectionTable->readFromDevice ())
			return false;

		m_pageLayout->setHeader (m_header);
		m_pageLayout->setDevice (m_device);
		if (!m_pageLayout->readFromDevice ())
			return false;

		m_pageTable->setPageNumberStart (m_pageLayout->getPageNumberStart ());
		m_pageTable->setHeader (m_header);
		m_pageTable->setDevice (m_device);
		if (!m_pageTable->readFromDevice ())
			return false;

		m_fontTable->setHeader (m_header);
		m_fontTable->setDevice (m_device);
		if (!m_fontTable->readFromDevice ())
			return false;

		m_paragraphInfo->setHeader (m_header);
		m_paragraphInfo->setDevice (m_device);
		m_paragraphInfo->setType (ParaType);
		m_paragraphInfo->setMargins (m_pageLayout->getLeftMargin (), m_pageLayout->getRightMargin ());
		if (!m_paragraphInfo->readFromDevice ())
			return false;

		m_characterInfo->setHeader (m_header);
		m_characterInfo->setDevice (m_device);
		m_characterInfo->setType (CharType);
		m_characterInfo->setFontTable (m_fontTable);
		if (!m_characterInfo->readFromDevice ())
			return false;

#if 0
		DocumentInfo documentInfo;
		FormatParaProperty *paraProperty = (FormatParaProperty *) m_paragraphInfo->begin ();
		int numObjects = 0;
		while (paraProperty)
		{
			if (paraProperty->getIsObject ())
				numObjects++;
			if (paraProperty->getIsHeader ())
			{
				documentInfo.setHasHeader (true);
				if (paraProperty->getIsOnFirstPage ())
					documentInfo.setHasHeaderOnFirstPage (true);
			}
			if (paraProperty->getIsFooter ())
			{
				documentInfo.setHasFooter (true);
				if (paraProperty->getIsOnFirstPage ())
					documentInfo.setHasFooterOnFirstPage (true);
			}
			paraProperty = (FormatParaProperty *) m_paragraphInfo->next ();
		}
		documentInfo.setNumObjects (numObjects);
#endif

		// Get Ready!
		//

		enum Section { InNothing, InBody, InHeader, InFooter } inWhat = InNothing;

		// must call writeBody*() even if document doesn't have one
		bool wroteBody = false;

		PagePointer *pp = m_pageTable->begin ();
		if (m_device->bad ()) return false;

		//
		// Signal callbacks
		//
		#ifdef DEBUG_INTERNALPARSER
			m_device->debug ("@@@ InternalParser: start of document write\n");
		#endif

		if (!m_generator->writeDocumentBegin (m_header->getMagic (), m_pageLayout)) return false;
		m_generator->sigProgress (0);

		// start of text
		if (!m_device->seekInternal (1 * 128, SEEK_SET))
			return false;

		// We are probably more permissive than MSWrite: we allow having no
		// paragraph properties.  However, if there are paragraph properties,
		// we enforce having character properties (code lower down).
		FormatParaProperty *paraProp = (FormatParaProperty *) m_paragraphInfo->begin ();
		if (m_device->bad ()) return false;
		FormatCharProperty *charProp = (FormatCharProperty *) m_characterInfo->begin ();
		if (m_device->bad ()) return false;
		DWord paraStartByte = 0;

		// Not an empty document?
		if (numCharBytes)
		{
			while (paraProp)
			{
			#ifdef DEBUG_INTERNALPARSER
				m_device->debug ("@@@ InternalParser: Start of loop - section write"
					" --------------------\n");
			#endif
				//
				// Section work
				//

				enum Section inWhatNext = InNothing;
				if (paraProp->getIsFooter ())
					inWhatNext = InFooter;
				else if (paraProp->getIsHeader ())
					inWhatNext = InHeader;
				else
					inWhatNext = InBody;

				// beginning of a new section?
				if (inWhatNext != inWhat)
				{
					// end last thing we were in
					switch (inWhat)
					{
					case InFooter:	if (!m_generator->writeFooterEnd ()) return false; else break;
					case InHeader:	if (!m_generator->writeHeaderEnd ()) return false; else break;
					case InBody:	if (!m_generator->writeBodyEnd ()) return false; else break;
					default:	break;	// keep compiler happy
					}

					// start next section
					switch (inWhat = inWhatNext)
					{
					case InFooter:	if (!m_generator->writeFooterBegin ()) return false; else break;
					case InHeader:	if (!m_generator->writeHeaderBegin ()) return false; else break;
					case InBody:	if (!m_generator->writeBodyBegin ()) return false;
						// if there's not pageTable, manually signal start of page for the first and last time
						if (!pp)
							if (!m_generator->writePageNew ())
								return false;

						wroteBody = true;
					default:	break;	// keep compiler happy
					}
				}


				//
				// start paragraph
				//

				bool paraIsText = paraProp->getIsText ();
				int objectType = ObjectType::NotObject;

				DWord paraEndByte, paraAfterEndByte;
				paraAfterEndByte = paraProp->getAfterEndCharByte ();
				paraEndByte = paraAfterEndByte - 1;

			#ifdef DEBUG_INTERNALPARSER
				m_device->debug ("@@@ InternalParser: Start of paragraph write\n");
				m_device->debug ("\tisText=", (int) paraIsText);
				m_device->debug ("\tparaStartByte=", (int) paraStartByte);
				m_device->debug ("\tparaEndByte=", (int) paraEndByte);
			#endif

				if (paraIsText)
				{
					// signal paragraph
					if (!m_generator->writeParaInfoBegin (paraProp, NULL, NULL)) return false;
				}
				else
				{
					//
					// Determine whether the object is OLE or not
					// Yes, I know this isn't entirely clean but
					// name a cleaner and more efficient way of doing this...
					//

					Byte data [2];
					Word mappingMode;

					if (!m_device->readInternal (data, 2)) return false;
					ReadWord (mappingMode, data);
					if (!m_device->seekInternal (-2, SEEK_CUR)) return false;	// ungetc()x2

					switch (mappingMode)
					{
					case 0xE4:
						objectType = ObjectType::OLE;

						m_ole = new OLE;
						if (!m_ole)
							ErrorAndQuit (Error::OutOfMemory, "could not allocate memory for OLE object\n");

						m_ole->setDevice (m_device);
						if (!m_ole->readFromDevice ())
							return false;

						if (!m_generator->writeParaInfoBegin (paraProp, m_ole, NULL)) return false;
						break;
					case 0xE3:	// monochrome bitmap?
						objectType = ObjectType::BMP;

						m_image = new Image;
						if (!m_image)
							ErrorAndQuit (Error::OutOfMemory, "could not allocate memory for BMP image object\n");

						m_image->setDevice (m_device);
						if (!m_image->readFromDevice ())
							return false;

						if (!m_generator->writeParaInfoBegin (paraProp, NULL, m_image)) return false;
						break;
					default:
						objectType = ObjectType::WMF;

						m_image = new Image;
						if (!m_image)
							ErrorAndQuit (Error::OutOfMemory, "could not allocate memory for WMF image object\n");

						m_image->setDevice (m_device);
						if (!m_image->readFromDevice ())
							return false;

						if (!m_generator->writeParaInfoBegin (paraProp, NULL, m_image)) return false;
						break;
					}

					// image: BMP/WMF
					if (objectType != ObjectType::OLE)
					{
						if (m_image->getNumHeaderBytes () + m_image->getNumDataBytes ()
								!= paraAfterEndByte - paraStartByte)
						{
							m_device->error (Error::Warn, "imageHeader: numHeaderBytes + numDataBytes != paragraph length\n");

							// we had better seek to where the paragraphs expect us to be to avoid trouble
							if (!m_device->seekInternal (paraAfterEndByte + 128, SEEK_SET)) return false;
						}
					}
				}

			#ifdef DEBUG_INTERNALPARSER
				m_device->debug ("@@@ InternalParser: Start of text write filePos=",
					m_device->tell ());
			#endif

				DWord uptoByte = paraStartByte;

				// loop until we hit end of paragraph (getting one CharProperty on every iteration)
				while (uptoByte < paraAfterEndByte)
				{
				#ifdef DEBUG_INTERNALPARSER
					m_device->debug ("@@@ Writing paragraph: uptoByte=", uptoByte);
				#endif

					if (!charProp)
						ErrorAndQuit (Error::InvalidFormat, "missing character property\n");

					if (uptoByte >= charProp->getAfterEndCharByte ())
					{
						charProp = (FormatCharProperty *) m_characterInfo->next ();
						if (m_device->bad ()) return false;

						if (!charProp)
							ErrorAndQuit (Error::InvalidFormat, "missing character property (site 2)\n");
					}

				#ifdef DEBUG_INTERNALPARSER
					m_device->debug ("@@@ InternalParser: character write\n");
					m_device->debug ("@@@ Font=");
					m_device->debug ((const char *) charProp->getFont ()->getName ());
					m_device->debug (charProp->getFontSize ());
					m_device->debug ("\n");
				#endif

					if (!m_generator->writeCharInfoBegin (charProp)) return false;
					

					// ultimately aim for end of CharProperty block; if that's not possible, aim for end of paragraph
					DWord aimUltimateByte = charProp->getEndCharByte () > paraEndByte ? paraEndByte : charProp->getEndCharByte ();
				#ifdef DEBUG_INTERNALPARSER
					m_device->debug ("@@@ aimUltimateByte=", aimUltimateByte);
				#endif

					while (uptoByte <= aimUltimateByte)
					{
					#ifdef DEBUG_INTERNALPARSER
						m_device->debug ("@@@ InternalParser: writing text uptoByte=", uptoByte);
					#endif

						// flag to tell us to generate pageNewWrite/pageTable signal, after writing some normal text
						bool pageTableAck = false;

						// short-term goal (before end of CharProperty, ParaProperty or page)
						DWord aimByte = aimUltimateByte;

						// we want to efficiently send pageNewWrite signals which might be sandwhiched inside
						// a CharProperty block (note: we do NOT end and then restart a CharProperty block because of a writePageNew())
						if (pp)
						{
							if (pp->getFirstCharByte () <= aimByte)
							{
								pageTableAck = true;
								if (pp->getFirstCharByte ())
									// - 1 is because we want the PageNew signal called before the character
									aimByte = pp->getFirstCharByte () - 1;
								else
									aimByte = 0xFFFFFFFF;	// 0 - 1 :)

							#ifdef DEBUG_INTERNALPARSER
								m_device->debug ("@@@ InternalParser: writePageNew pending\n");
							#endif
							}
						}

						// write text (using buffering)
						while (uptoByte <= aimByte && aimByte != 0xFFFFFFFF)
						{
							const DWord amountToRead = aimByte - uptoByte + 1 > 1023
																? 1023 : aimByte - uptoByte + 1;

							if (paraIsText)
							{
								Byte buffer [1024];

								if (!m_device->readInternal (buffer, amountToRead)) return false;

								buffer [amountToRead] = '\0';
								if (!m_generator->processText (buffer, uptoByte + amountToRead - 1 == paraEndByte)) return false;
							}

							uptoByte += amountToRead;
						}		// while (uptoByte <= aimByte && aimByte != 0xFFFFFFFF) {

						// generate pageNewWrite/pageTable signal, if requested
						if (pageTableAck)
						{
						#ifdef DEBUG_INTERNALPARSER
							m_device->debug ("@@@ InternalParser: writePageNew\n");
						#endif

							if (!m_generator->writePageNew (pp->getPageNumber ())) return false;

							pp = m_pageTable->next ();
							if (m_device->bad ()) return false;
						}
					}		// while (uptoByte <= aimUltimateByte) {


				#ifdef DEBUG_INTERNALPARSER
					m_device->debug ("@@@ InternalParser: character end write uptoByte=",
						uptoByte);
				#endif

					// end char info
					if (charProp)
						if (!m_generator->writeCharInfoEnd (charProp, uptoByte == paraAfterEndByte))
							return false;

				}		// while (uptoByte < paraAfterEndByte) {


				//
				// ouptut object
				//

				if (!paraIsText)
				{
					if (objectType == ObjectType::OLE)
					{
						if (!m_generator->writeBinary (m_ole->getExternalObject (), m_ole->getExternalObjectSize ()))
							return false;
					}
					else	// if (objectType == ObjectType::BMP || objectType == ObjectType::WMF)
					{
						if (!m_generator->writeBinary (m_image->getExternalImage (), m_image->getExternalImageSize ()))
							return false;
					}
				}

				//
				// end paragraph
				//

				if (paraIsText)
				{
					// end paragraph
					if (!m_generator->writeParaInfoEnd (paraProp, NULL)) return false;
				}
				else
				{
					switch (objectType)
					{
					case ObjectType::OLE:
						if (!m_generator->writeParaInfoEnd (paraProp, m_ole, NULL)) return false;
						delete m_ole;
						m_ole = NULL;
						break;
					case ObjectType::BMP:
						if (!m_generator->writeParaInfoEnd (paraProp, NULL, m_image)) return false;
						delete m_image;
						m_image = NULL;
						break;
					case ObjectType::WMF:
						if (!m_generator->writeParaInfoEnd (paraProp, NULL, m_image)) return false;
						delete m_image;
						m_image = NULL;
						break;
					}
				}

				paraStartByte = paraAfterEndByte;

				// numCharBytes != 0 because we checked it before we entered the loop
				m_generator->sigProgress (paraStartByte * 100 / numCharBytes);

				// get next paragraph properties
				paraProp = (FormatParaProperty *) m_paragraphInfo->next ();
				if (m_device->bad ()) return false;
			}  // for each paragraph...
		}  // if not an empty document...

		// end last thing we were in
		switch (inWhat)
		{
		case InFooter:	if (!m_generator->writeFooterEnd ()) return false; else break;
		case InHeader:	if (!m_generator->writeHeaderEnd ()) return false; else break;
		case InBody:	if (!m_generator->writeBodyEnd ()) return false; else break;
		default:	break;	// keep compiler happy
		}

		// didn't output a body (usually due to a blank document)
		if (!wroteBody)
		{
		#ifdef DEBUG_INTERNALPARSER
			m_device->debug ("@@@ InternalParser: did not write body, writing one now\n");
		#endif

			if (!m_generator->writeBodyBegin ()) return false;
			if (!m_generator->writeBodyEnd ()) return false;
		}

		m_generator->sigProgress (100);
		if (!m_generator->writeDocumentEnd (m_header->getMagic (), m_pageLayout)) return false;

		return true;
	}


	InternalGenerator::InternalGenerator () : m_header (NULL),
															m_sectionTable (NULL),
															m_pageLayout (NULL),
															m_pageTable (NULL),
															m_fontTable (NULL),
															m_paragraphInfo (NULL),
															m_characterInfo (NULL),
															m_image (NULL),
															m_ole (NULL)
	{
	}

	InternalGenerator::~InternalGenerator ()
	{
		delete m_ole;
		delete m_image;

		delete m_characterInfo;
		delete m_paragraphInfo;
		delete m_fontTable;
		delete m_pageTable;
		delete m_pageLayout;
		delete m_sectionTable;
		delete m_header;
	}

	bool InternalGenerator::seekNextPage (void)
	{
		return m_device->seekInternal ((m_device->tellInternal () + 127) / 128 * 128, SEEK_SET);
	}

	bool InternalGenerator::writeDocumentBegin (const Word, const PageLayout *pageLayout)
	{
		if (!m_device)
		{
			fprintf (stderr, "INTERNAL ERROR: InternalGenerator::writeDocumentBegin() called without a device\n");
			return false;	// cannot use ErrorAndQuit() because that calls m_device->error()
		}

		m_header = new Header;
		if (!m_header)
			ErrorAndQuit (Error::OutOfMemory, "could not allocate memory for header\n");
		m_header->setDevice (m_device);

		m_pageLayout = new PageLayout;
		if (!m_pageLayout)
			ErrorAndQuit (Error::OutOfMemory, "could not allocate memory for pageLayout\n");
		m_pageLayout->setDevice (m_device);

		m_sectionTable = new SectionTable;
		if (!m_sectionTable)
			ErrorAndQuit (Error::OutOfMemory, "could not allocate memory for sectionTable\n");
		m_sectionTable->setDevice (m_device);

		m_pageTable = new PageTable;
		if (!m_pageTable)
			ErrorAndQuit (Error::OutOfMemory, "could not allocate memory for pageTable\n");
		m_pageTable->setDevice (m_device);

		m_fontTable = new FontTable;
		if (!m_fontTable)
			ErrorAndQuit (Error::OutOfMemory, "could not allocate memory for fontTable\n");
		m_fontTable->setDevice (m_device);

		m_paragraphInfo = new FormatInfo;
		if (!m_paragraphInfo)
			ErrorAndQuit (Error::OutOfMemory, "could not allocate memory for paragraphInfo\n");
		m_paragraphInfo->setType (ParaType);
		m_paragraphInfo->setDevice (m_device);

		m_characterInfo = new FormatInfo;
		if (!m_characterInfo)
			ErrorAndQuit (Error::OutOfMemory, "could not allocate memory for characterInfo\n");
		m_characterInfo->setType (CharType);
		m_characterInfo->setDevice (m_device);

		*m_pageLayout = *pageLayout;

		// get ready to output text
		if (!m_device->seekInternal (1 * 128, SEEK_SET)) return false;

		return true;
	}

	bool InternalGenerator::writeDocumentEnd (const Word format, const PageLayout *)
	{
	#ifdef DEBUG_INTERNALGENERATOR
		m_device->debug ("!!!! InternalGenerator: writeDocumentEnd\n");
	#endif

		m_header->setNumCharBytes (m_device->tellInternal () - 128);

	#ifdef DEBUG_INTERNALGENERATOR
		m_device->debug ("before charinfo seek next page: ", m_device->tellInternal ());
	#endif
		if (!seekNextPage ()) return false;	// from text pages
	#ifdef DEBUG_INTERNALGENERATOR
		m_device->debug ("!!!! InternalGenerator: Write characterInfo; page=", m_device->tellInternal () / 128);
	#endif
		FormatCharProperty defaultCharProperty;
		defaultCharProperty.setFontTable (m_fontTable);
			Font defaultFont ((const Byte *) "Arial");	// better than "unknown", I guess
		defaultCharProperty.setFont (&defaultFont);

		// write characterInfo pages
		m_characterInfo->setDevice (m_device);
		m_characterInfo->setHeader (m_header);
		m_characterInfo->setFontTable (m_fontTable);
		if (!m_characterInfo->writeToDevice (&defaultCharProperty)) return false;

	#ifdef DEBUG_INTERNALGENERATOR
		m_device->debug ("before parainfo seek next page: ", m_device->tellInternal ());
	#endif
		if (!seekNextPage ()) return false;
	#ifdef DEBUG_INTERNALGENERATOR
		m_device->debug ("!!!! InternalGenerator: Write paragraphInfo; page=", m_device->tellInternal () / 128);
	#endif
		FormatParaProperty defaultParaProperty;
		defaultParaProperty.setMargins (m_pageLayout->getLeftMargin (), m_pageLayout->getRightMargin ());

		// write paragraphInfo pages
		m_paragraphInfo->setDevice (m_device);
		m_paragraphInfo->setHeader (m_header);
		m_paragraphInfo->setMargins (m_pageLayout->getLeftMargin (), m_pageLayout->getRightMargin ());
		if (!m_paragraphInfo->writeToDevice (&defaultParaProperty)) return false;

	#ifdef DEBUG_INTERNALGENERATOR
		m_device->debug ("!!!! InternalGenerator: Write footnoteTable\n");
	#endif

		// there's "no such thing (tm)" as a FootnoteTable
		m_header->setPageFootnoteTable ((m_device->tellInternal () + 127) / 128);

		if (!seekNextPage ()) return false;
	#ifdef DEBUG_INTERNALGENERATOR
		m_device->debug ("!!!! InternalGenerator: Write pageLayout; page=", m_device->tellInternal () / 128);
	#endif
		m_pageLayout->setDevice (m_device);
		m_pageLayout->setHeader (m_header);
		if (!m_pageLayout->writeToDevice ()) return false;

	#ifdef DEBUG_INTERNALGENERATOR
		m_device->debug ("!!!! InternalGenerator: Write sectionTable\n");
	#endif
		if (!seekNextPage ()) return false;
		m_sectionTable->setDevice (m_device);
		m_sectionTable->setHeader (m_header);
		if (!m_sectionTable->writeToDevice (m_pageLayout->getIsModified ())) return false;

	#ifdef DEBUG_INTERNALGENERATOR
		m_device->debug ("!!!! InternalGenerator: Write pageTable\n");
	#endif
		if (!seekNextPage ()) return false;
		m_pageTable->setDevice (m_device);
		m_pageTable->setHeader (m_header);
		m_pageTable->setPageNumberStart (m_pageLayout->getPageNumberStart ());
		if (!m_pageTable->writeToDevice ()) return false;

	#ifdef DEBUG_INTERNALGENERATOR
		m_device->debug ("!!!! InternalGenerator: Write fontTable\n");
	#endif
		if (!seekNextPage ()) return false;
		m_fontTable->setDevice (m_device);
		m_fontTable->setHeader (m_header);
		if (!m_fontTable->writeToDevice ()) return false;

	#ifdef DEBUG_INTERNALGENERATOR
		m_device->debug ("!!!! InternalGenerator: Write header\n");
	#endif
		// write header
		m_header->setFormat (format);
		m_header->setNumPages ((m_device->tellInternal () + 127) / 128);
		m_header->setDevice (m_device);
		if (!m_device->seekInternal (0 * 128, SEEK_SET)) return false;
		if (!m_header->writeToDevice ()) return false;

		// pad up to 128 (it seems that the ms programmers really did expect 128-byte pages)
		// BTW, if you look in a Write file, you can normally see some garbage after the
		// fontTable that appears to be part of an earlier part of the document
		// (or even some _other_ document!) suggesting that ms really did reuse their memory
		// and that we shouldn't be all that concerned with "unknown", "reserved" and "zero"
		// fields
		if (!m_device->seekInternal (m_header->getNumPages () * 128, SEEK_SET)) return false;

		return true;
	}

	bool InternalGenerator::writeParaInfoBegin (const FormatParaProperty * /*paraProperty*/,
																const OLE *ole,
																const Image *image)
	{
	#ifdef DEBUG_INTERNALGENERATOR
		m_device->debug ("!!!! InternalGenerator: writeParaInfoBegin\n");
	#endif

		if (ole)
		{
			m_ole = new OLE;
			if (!m_ole)
				ErrorAndQuit (Error::OutOfMemory, "could not allocate memory for OLE\n");
			*m_ole = *ole;

			m_ole->setDevice (m_device);
			m_ole->setExternalObjectSize (ole->getExternalObjectSize ());
		}

		if (image)
		{
			m_image = new Image;
			if (!m_image)
				ErrorAndQuit (Error::OutOfMemory, "could not allocate memory for image\n");
			*m_image = *image;

			m_image->setDevice (m_device);
			m_image->setExternalImageSize (image->getExternalImageSize ());
		}

		return true;
	}

	bool InternalGenerator::writeParaInfoEnd (const FormatParaProperty *paraProperty,
															const OLE * /*ole*/,
															const Image * /*image*/)
	{
	#ifdef DEBUG_INTERNALGENERATOR
		m_device->debug ("!!!! InternalGenerator: writeParaInfoEnd\n");
	#endif

		if (m_ole)
		{
			if (!m_ole->writeToDevice ())
				return false;

			delete m_ole;
			m_ole = NULL;
		}
		else if (m_image)
		{
			if (!m_image->writeToDevice ())
				return false;

			delete m_image;
			m_image = NULL;
		}

		m_paragraphInfo->setMargins (m_pageLayout->getLeftMargin (), m_pageLayout->getRightMargin ());
		return m_paragraphInfo->add (paraProperty, false/* don't force */);
	}

	bool InternalGenerator::writeCharInfoBegin (const FormatCharProperty * /*charProperty*/)
	{
	#ifdef DEBUG_INTERNALGENERATOR
		m_device->debug ("!!!! InternalGenerator: writeCharInfoBegin\n");
	#endif

		return true;
	}

	bool InternalGenerator::writeCharInfoEnd (const FormatCharProperty *charProperty,
															const bool)
	{
	#ifdef DEBUG_INTERNALGENERATOR
		m_device->debug ("!!!! InternalGenerator: writeCharInfoEnd\n");
	#endif

		m_characterInfo->setFontTable (m_fontTable);
		return m_characterInfo->add (charProperty, false/* don't force */);
	}

	bool InternalGenerator::writeBinary (const Byte *buffer, const DWord length)
	{
		if (m_ole)
		{
			if (!m_ole->setExternalObject (buffer, length))
				return false;
		}
		else if (m_image)
		{
			if (!m_image->setExternalImage (buffer, length))
				return false;
		}
		else
			ErrorAndQuit (Error::InternalError, "attempt to write unknown type of binary data\n");

		return true;
	}

	bool InternalGenerator::writeText (const Byte *string)
	{
	#ifdef DEBUG_INTERNALGENERATOR
//		m_device->debug ("!!!! InternalGenerator: writeText> ", (const char *) string);
	#endif

		DWord length = DWord (strlen ((const char *) string));

		if (!m_device->writeInternal (string, length)) return false;

		return true;
	}

	bool InternalGenerator::writePageNew (const int pageNumberClaimed)
	{
	#ifdef DEBUG_INTERNALGENERATOR
		m_device->debug ("!!!! InternalGenerator: writePageNew() with pageNo ",
								pageNumberClaimed);
	#endif

		// is this a forced new page (as in, the signal was only generated because I put it in the spec?)
		if (pageNumberClaimed == 0)
			return true;	// no real pageTable

		PagePointer pp;
		pp.setPageNumber (pageNumberClaimed);
		pp.setFirstCharByte (m_device->tellInternal () - 128);

		return m_pageTable->add (&pp);
	}

}	// namespace MSWrite	{

// end of libmswrite.cpp
