/* This file is part of the KDE project
   Copyright (C) 2002-2003 Clarence Dang <dang@kde.org>

   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.
*/

#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <tqbuffer.h>
#include <tqcstring.h>
#include <tqfile.h>
#include <tqfont.h>
#include <tqimage.h>
#include <tqtextcodec.h>
#include <tqvaluelist.h>
#include <tqvaluestack.h>

#include <kdebug.h>
#include <kgenericfactory.h>

#include <KoFilterChain.h>
#include <kowmfpaint.h>

#include <KWEFStructures.h>
#include <KWEFBaseWorker.h>
#include <KWEFKWordLeader.h>

#include "libmswrite.h"

#include "mswriteexport.h"


class MSWriteExportFactory : KGenericFactory <MSWriteExport, KoFilter>
{
public:
	MSWriteExportFactory () : KGenericFactory <MSWriteExport, KoFilter> ("kwordmswriteexport")
	{
	}

protected:
	virtual void setupTranslations (void)
	{
		TDEGlobal::locale()->insertCatalogue ("kofficefilters");
	}
};

K_EXPORT_COMPONENT_FACTORY (libmswriteexport, MSWriteExportFactory ())


class WRIDevice : public MSWrite::Device
{
private:
	FILE *m_outfp;
	long m_outfp_pos, m_outfp_eof;

public:
	WRIDevice () : m_outfp (NULL), m_outfp_pos (0), m_outfp_eof (0)
	{
	}

	virtual ~WRIDevice ()
	{
		closeFile ();
	}

	bool openFile (const char *fileName)
	{
		m_outfp = fopen (fileName, "wb");
		if (!m_outfp)
		{
			error (MSWrite::Error::FileError, "could not open file for writing\n");
			return false;
		}

		return true;
	}

	bool closeFile (void)
	{
		if (m_outfp)
		{
			if (fclose (m_outfp))
			{
				error (MSWrite::Error::FileError, "could not close output file\n");
				return false;
			}

			m_outfp = NULL;
		}

		return true;
	}

	bool read (MSWrite::Byte *, const MSWrite::DWord)
	{
		error (MSWrite::Error::InternalError, "reading from an output file?\n");
		return false;
	}

	bool write (const MSWrite::Byte *buf, const MSWrite::DWord numBytes)
	{
		size_t cwrite = fwrite (buf, 1, numBytes, m_outfp);
		if (cwrite != numBytes)
		{
			error (MSWrite::Error::FileError, "could not write to output file\n");
			return false;
		}

		// keep track of where we are up to in the output file and where EOF is
		m_outfp_pos += numBytes;
		if (m_outfp_pos > m_outfp_eof)
			m_outfp_eof = m_outfp_pos;

		return true;
	}

	bool seek (const long offset, const int whence)
	{
		long absloc;
		switch (whence)
		{
		case SEEK_SET:
			absloc = offset;
			break;
		case SEEK_CUR:
			absloc = m_outfp_pos + offset;
			break;
		case SEEK_END:
			absloc = m_outfp_eof + offset;
			break;
		default:
			error (MSWrite::Error::InternalError, "invalid whence passed to WRIDevice::seek\n");
			return false;
		}

		if (absloc > m_outfp_eof)
		{
			kdDebug (30509) << "Want to seek to " << absloc
					  				<< " but EOF is at " << m_outfp_eof
									<< "; so writing " << absloc - m_outfp_eof
									<< " zeros" << endl;

			if (fseek (m_outfp, m_outfp_eof, SEEK_SET))
			{
				error (MSWrite::Error::FileError,
							"could not seek to EOF in output file\n");
				return false;
			}

			MSWrite::Byte *zero = new MSWrite::Byte [absloc - m_outfp_eof];
			if (!zero)
			{
				error (MSWrite::Error::OutOfMemory,
							"could not allocate memory for zeros\n");
				return false;
			}
			memset (zero, 0, absloc - m_outfp_eof);
			if (!write (zero, absloc - m_outfp_eof)) return false;
			delete [] zero;

			m_outfp_eof = absloc;
			m_outfp_pos = absloc;
			return true;
		}
		else
		{
			if (fseek (m_outfp, offset, whence) == 0)
			{
				m_outfp_pos = absloc;
				return true;
			}
			else
			{
				error (MSWrite::Error::FileError, "could not seek output file\n");
				return false;
			}
		}
	}

	long tell (void)
	{
		return ftell (m_outfp);
	}

	void debug (const char *s)
	{
		kdDebug (30509) << s;
	}
	void debug (const int i)
	{
		kdDebug (30509) << i;
	}
	void error (const int errorCode, const char *message,
					const char * /*file*/ = "", const int /*lineno*/ = 0,
					MSWrite::DWord /*token*/ = MSWrite::Device::NoToken)
	{
		if (errorCode == MSWrite::Error::Warn)
			kdWarning (30509) << message;
		else
		{
			m_error = errorCode;
			kdError (30509) << message;
		}
	}
};


class KWordMSWriteWorker : public KWEFBaseWorker
{
private:
	WRIDevice *m_device;
	MSWrite::InternalGenerator *m_generator;

	MSWrite::PageLayout m_pageLayout;
	MSWrite::Word m_pageHeight, m_pageWidth,
						m_topMargin, m_leftMargin, m_bottomMargin, m_rightMargin;
	MSWrite::Word m_pageNumberStart;

	// for charset conversion
	TQTextCodec *m_codec;
	TQTextEncoder *m_encoder;

	TQValueList <HeaderData> m_headerData;
	TQValueList <FooterData> m_footerData;

	int m_headerType, m_footerType;
	bool m_hasHeader, m_isHeaderOnFirstPage;
	bool m_hasFooter, m_isFooterOnFirstPage;

	enum inWhatPossiblities
	{
		Nothing,
		Header,
		Footer,
		Body
	} m_inWhat;

public:
	KWordMSWriteWorker () : m_device (NULL), m_generator (NULL),
									m_pageHeight (0xFFFF), m_pageWidth (0xFFFF),
									m_topMargin (0xFFFF), m_leftMargin (0xFFFF),
									m_bottomMargin (0xFFFF), m_rightMargin (0xFFFF),
									m_encoder (NULL),
									m_hasHeader (false), m_hasFooter (false),
									m_inWhat (Nothing)
	{
		// just select windows-1252 until the "Select Encoding" dialog works
		m_codec = TQTextCodec::codecForName ("CP 1252");

		if (m_codec)
			m_encoder = m_codec->makeEncoder();
		else
			kdWarning (30509) << "Cannot convert to Win Charset!" << endl;

		m_device = new WRIDevice;
		if (!m_device)
		{
			kdError (30509) << "Could not allocate memory for Device" << endl;
			return;
		}

		m_generator = new MSWrite::InternalGenerator;
		if (!m_generator)
		{
			m_device->error (MSWrite::Error::OutOfMemory, "could not allocate memory for InternalGenerator\n");
			return;
		}

		m_generator->setDevice (m_device);
	}

	virtual ~KWordMSWriteWorker ()
	{
		delete m_generator;
		delete m_device;
		delete m_encoder;
	}

	int getError (void) const
	{
		return m_device->bad ();
	}

	bool doOpenFile (const TQString &outFileName, const TQString &)
	{
		// constructor failed?
		if (!m_device || !m_generator)
			return false;

		if (!m_device->openFile (TQFile::encodeName (outFileName))) return false;

		return true;
	}

	bool doCloseFile (void)
	{
		if (!m_device->closeFile ()) return false;
		return true;
	}

	bool doOpenDocument (void)
	{
		kdDebug (30509) << "doOpenDocument ()" << endl;

		// We can't open the document here because we don't yet have
		// PageLayout * as doFullPaperFormat() and doFullPaperBorders()
		// haven't been called yet.
		//
		// doTrulyOpenDocument truly opens the document and is called by
		// doOpenBody()

		return true;
	}

	bool doTrulyOpenDocument (void)
	{
		// TODO: Write's UI doesn't allow the user to change Height or Width so
		//       setting it here might not be a good idea...
		m_pageLayout.setPageHeight (m_pageHeight);
		m_pageLayout.setPageWidth (m_pageWidth);
		m_pageLayout.setPageNumberStart (m_pageNumberStart);
		m_pageLayout.setTopMargin (m_topMargin);
		m_pageLayout.setLeftMargin (m_leftMargin);
		m_pageLayout.setTextHeight (m_pageHeight - m_topMargin - m_bottomMargin);
		m_pageLayout.setTextWidth (m_pageWidth - m_leftMargin - m_rightMargin);

		// TODO: libexport
		// headerFromTop
		// footerFromTop

		if (!m_generator->writeDocumentBegin (MSWrite::Format::Write_3_0,
															&m_pageLayout)) return false;

		return true;
	}

	bool doCloseDocument (void)
	{
		kdDebug (30509) << "doCloseDocument ()" << endl;

		if (!m_generator->writeDocumentEnd (MSWrite::Format::Write_3_0,
														&m_pageLayout)) return false;

		return true;
	}

	bool doFullPaperFormat (const int format,
						 			const double width, const double height,
									const int orientation)
	{
		kdDebug (30509) << "doFullPaperFormat ("
								<< format << ", "
								<< width << ", "
								<< height << ", "
								<< orientation << ")" << endl;

		// TODO: does "format" or "orientation" matter?

		m_pageHeight = MSWrite::Word (Point2Twip (height));
		m_pageWidth = MSWrite::Word (Point2Twip (width));

		return true;
	}

	bool doFullPaperBorders (const double top, const double left,
						 				const double bottom, const double right)
	{
		kdDebug (30509) << "doFullPaperBorders ("
								<< top << ", "
								<< left << ", "
								<< bottom << ", "
								<< right << ")" << endl;

		m_topMargin = MSWrite::Word (Point2Twip (top));
		m_leftMargin = MSWrite::Word (Point2Twip (left));
		m_bottomMargin = MSWrite::Word (Point2Twip (bottom));
		m_rightMargin = MSWrite::Word (Point2Twip (right));

		return true;
	}

	bool doVariableSettings (const VariableSettingsData &varSettings)
	{
		m_pageNumberStart = MSWrite::Word (varSettings.startingPageNumber);

		kdDebug (30509) << "doVariableSettings pageNumberStart="
								<< m_pageNumberStart << endl;
		return true;
	}

	// In Write the header/footer must be the same for every page except that
	// you can choose to not display it on the first page
	//
	// /*This filter aims to be as lossless as possible so if we can't
	// accommodate the types of headers/footers found in KWord, we at least
	// print out the paragraphs in the body*/
	//
	//    Not anymore. Dumping headers & footers in the body didn't
	//    turn out to be that useful, nor was it expected by users.
	//
	bool doPageInfo (int headerType, int footerType)
	{
		kdDebug (30509) << "doPageInfo (headerType=" << headerType
								<< ", footerType=" << footerType
								<< ")" << endl;

		m_headerType = headerType;
		switch (headerType)
		{
		case 0:	// same on all pages
		case 3:	// different on even and odd pages
			m_isHeaderOnFirstPage = true;
			break;
		case 1:	// different on first, even and odd pages
		case 2:	// different on first and other pages
			m_isHeaderOnFirstPage = false;
			break;
		default:
			kdWarning (30509) << "Unknown headerType: " << headerType << endl;
			m_isHeaderOnFirstPage = false;	// just a guess
			break;
		}

		m_footerType = footerType;
		switch (footerType)
		{
		case 0:	// same on all pages
		case 3:	// different on even and odd pages
			m_isFooterOnFirstPage = true;
			break;
		case 1:	// different on first, even and odd pages
		case 2:	// different on first and other pages
			m_isFooterOnFirstPage = false;
			break;
		default:
			kdWarning (30590) << "Unknown footerType: " << footerType << endl;
			m_isFooterOnFirstPage = false;	// just a guess
			break;
		}

		return true;
	}

	bool isParaListEmpty (const TQValueList <ParaData> &para)
	{
		if (para.count () == 1)
		{
			if (para.first ().text.isEmpty ())
				return true;
		}

		return false;
	}

	bool doHeader (const HeaderData &header)
	{
		kdDebug (30509) << "doHeader (header.page=" << header.page << ")" << endl;

		if (isParaListEmpty (header.para))
		{
			kdDebug (30509) << "\tEmpty, ignoring" << endl;
			return true;
		}

	#if 0
		switch (m_headerType)
		{
		case 0:	// same on all pages
			if (header.page != HeaderData::PAGE_ALL)
				return true;
			break;
		case 3:	// different on even and odd pages
			if (header.page != HeaderData::PAGE_ODD &&
					header.page != HeaderData::PAGE_EVEN)
				return true;
			break;
		case 1:	// different on first, even and odd pages
			// accept everything
			break;
		case 2:	// different on first and other pages
			if (header.page != HeaderData::PAGE_FIRST &&
					header.page != HeaderData::PAGE_ODD)
				return true;
			break;
		}
	#endif

		m_hasHeader = true;
		m_headerData.push_back (header);
		return true;
	}

	bool doFooter (const FooterData &footer)
	{
		kdDebug (30509) << "doFooter (footer.page=" << footer.page << ")" << endl;

		if (isParaListEmpty (footer.para))
		{
			kdDebug (30509) << "\tEmpty, ignoring" << endl;
			return true;
		}

	#if 0
		switch (m_footerType)
		{
		case 0:	// same on all pages
			if (footer.page != FooterData::PAGE_ALL)
				return true;
			break;
		case 3:	// different on even and odd pages
			if (footer.page != FooterData::PAGE_ODD &&
					footer.page != FooterData::PAGE_EVEN)
				return true;
			break;
		case 1:	// different on first, even and odd pages
			// accept everything
			break;
		case 2:	// different on first and other pages
			if (footer.page != FooterData::PAGE_FIRST &&
					footer.page != FooterData::PAGE_ODD)
				return true;
			break;
		}
	#endif

		m_hasFooter = true;
		m_footerData.push_back (footer);
		return true;
	}

	bool doOpenBody (void)
	{
		kdDebug (30509) << "doOpenBody ()" << endl;

		//
		// Document Start
		//
		if (!doTrulyOpenDocument ()) return false;


		//
		// Footers followed by Headers (in this order)
		//

		bool wroteFooter = false;
		m_inWhat = Footer;

		for (TQValueList <FooterData>::Iterator it = m_footerData.begin ();
				it != m_footerData.end ();
				it++)
		{
			if ((*it).page != FooterData::PAGE_FIRST)
			{
				if (!wroteFooter)
				{
					if (!m_generator->writeFooterBegin ()) return false;
					wroteFooter = true;
				}

				if (!doFullParagraphList ((*it).para)) return false;
				it = --m_footerData.erase (it);
			}
		}
		if (wroteFooter)
			if (!m_generator->writeFooterEnd ()) return false;

		bool wroteHeader = false;
		m_inWhat = Header;

		for (TQValueList <HeaderData>::Iterator it = m_headerData.begin ();
			it != m_headerData.end ();
			it++)
		{
			if ((*it).page != HeaderData::PAGE_FIRST)
			{
				if (!wroteHeader)
				{
					if (!m_generator->writeHeaderBegin ()) return false;
					wroteHeader = true;
				}

				if (!doFullParagraphList ((*it).para)) return false;
				it = --m_headerData.erase (it);
			}
		}
		if (wroteHeader)
			if (!m_generator->writeHeaderEnd ()) return false;


		//
		// Body Start
		//

		m_inWhat = Body;
		if (!m_generator->writeBodyBegin ()) return false;
		// KWord doesn't have a PageTable but we must emit the pageNew
		// signal at least once
		if (!m_generator->writePageNew ()) return false;

#if 0
		// dump remaining header paragraphs at the start of the body
		for (TQValueList <HeaderData>::Iterator it = m_headerData.begin ();
				it != m_headerData.end ();
				it++)
		{
			kdDebug (30509) << "BODY START ADDING HEADER: " << (*it).page << endl;
			if (!doFullParagraphList ((*it).para)) return false;
			it = --m_headerData.erase (it);
		}

		// dump remaining footer paragraphs too
		for (TQValueList <FooterData>::Iterator it = m_footerData.begin ();
				it != m_footerData.end ();
				it++)
		{
			kdDebug (30509) << "BODY START ADDING FOOTER: " << (*it).page << endl;
			if (!doFullParagraphList ((*it).para)) return false;
			it = --m_footerData.erase (it);
		}
#endif

		return true;
	}

	bool doCloseBody (void)
	{
		kdDebug (30509) << "doCloseBody ()" << endl;

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

		return true;
	}

	// device that can either read from or write to a TQBuffer
	// (but not both at the same time, please :))
	class TQBufferDevice : public MSWrite::Device
	{
	private:
		TQBuffer *m_buffer;

	public:
		TQBufferDevice (TQBuffer *buffer)
		{
			m_buffer = buffer;
		}

		bool read (MSWrite::Byte *buf, const MSWrite::DWord numBytes)
		{
			if (m_buffer->readBlock ((char *) buf, (TQ_ULONG) numBytes) != TQ_LONG (numBytes))
			{
				error (MSWrite::Error::FileError, "could not read from TQBuffer (not really a FileError)\n");
				return false;
			}
			else
				return true;
		}

		bool write (const MSWrite::Byte *buf, const MSWrite::DWord numBytes)
		{
			if (m_buffer->writeBlock ((char *) buf, (TQ_ULONG) numBytes) != TQ_LONG (numBytes))
			{
				error (MSWrite::Error::FileError, "could not write to TQBuffer (not really a FileError)\n");
				return false;
			}
			else
				return true;
		}

		// normally we must write zeros if we seek past EOF
		// but we know that won't happen :)
		bool seek (const long offset, const int whence)
		{
			long absoffset;
			switch (whence)
			{
			case SEEK_SET:
				absoffset = offset;
				break;
			case SEEK_CUR:
				absoffset = m_buffer->at () + offset;
				break;
			case SEEK_END:
				absoffset = m_buffer->size () + offset;
				break;
			default:
				error (MSWrite::Error::InternalError, "unknown seek\n");
				return false;
			}

			if (absoffset > long (m_buffer->size ()))
			{
				error (MSWrite::Error::InternalError, "seek past EOF unimplemented\n");
				return false;
			}

			if (!m_buffer->at (absoffset))
			{
				error (MSWrite::Error::FileError, "TQBuffer could not seek (not really a FileError)\n");
				return false;
			}

			return true;
		}

		long tell (void)
		{
			return long (m_buffer->at ());
		}

		void debug (const char *s)
		{
			kdDebug (30509) << s;
		}
		void debug (const int i)
		{
			kdDebug (30509) << i;
		}

		void error (const int errorCode, const char *message,
						const char * /*file*/ = "", const int /*lineno*/ = 0,
						MSWrite::DWord /*token*/ = MSWrite::Device::NoToken)
		{
			if (errorCode == MSWrite::Error::Warn)
				kdWarning (30509) << message;
			else
			{
				m_error = errorCode;
				kdError (30509) << message;
			}
		}
	};

	class WMFRecord : public MSWrite::NeedsDevice
	{
	public:
		static const int s_size = 6;

	protected:
		MSWrite::Byte m_data [s_size];

		MSWrite::DWord m_size;	// record size in Words including everything in this struct
		MSWrite::Word m_function;

		MSWrite::Short m_args [100];	// "ought to be enough for anybody"
		int m_argUpto;

	public:
		WMFRecord () : m_argUpto (0)
		{
		}

		WMFRecord (const MSWrite::DWord size, const MSWrite::Word function, MSWrite::Device *device)
						: MSWrite::NeedsDevice (device),
							m_size (size), m_function (function),
							m_argUpto (0)
		{
		}

		MSWrite::DWord getSize (void) const	{	return m_size;	}
		void setSize (const MSWrite::DWord size)	{	m_size = size;	}

		MSWrite::Word getFunction (void) const	{	return m_function;	}
		void setFunction (const MSWrite::Word function)	{	m_function = function;	}

		void add (const MSWrite::Short arg)
		{
			m_args [m_argUpto++] = arg;
		}

		bool readFromDevice (void)
		{
			if (!m_device->readInternal (m_data, 6)) return false;
			MSWrite::ReadDWord (m_size, m_data + 0);
			MSWrite::ReadWord (m_function, m_data + 4);
			printf ("Size (Words): %i  Size (Bytes): %i  Function: %04X  (func=%02X,numArgs=%i)\n",
						m_size, m_size * sizeof (MSWrite::Word), m_function, m_function & 255, m_function >> 8);
	#if 1
			if (m_function == 0 && m_size == 3)	// last record
				return false;
	#endif

			switch (m_function)
			{
			case 0x0103:
				printf ("\tSetMapMode\n");
				break;
			case 0x020c:
				printf ("\tSetWindowExt\n");
				break;
			case 0x020b:
				printf ("\tSetWindowOrg\n");
				break;
			case 0x0b41:
				printf ("\tDibStretchBlt\n");
				break;
			default:
				printf ("\tUnknown function\n");
			}

			long offset = m_device->tellInternal ();

			for (int i = 0; i < ((m_function == 0x0b41) ? 10 : (m_function >> 8)); i++)
			{
				MSWrite::Byte data [2];
				if (!m_device->readInternal (data, 2)) return false;
				MSWrite::ReadShort (m_args [i], data);
				printf ("\tArg (rev) #%i=%i\n", i, m_args [i]);
			}

	#if 0
			// arguments are reversed normally
			int u = 0;
			for (int i = (m_function >> 8) - 1; i >= 0; i--, u++)
				printf ("\tArg #%i=%u\n", u, m_args [i]);
	#endif

		#if 1
			if (m_function == 0xb41)
			{
				// just curious but what's the infoHeader like?
				MSWrite::BMP_BitmapInfoHeader bih;
				bih.setDevice (m_device);
				if (!bih.readFromDevice ()) return false;
			}
		#endif

			m_size *= sizeof (MSWrite::Word);	// in Bytes now
			m_size -= 6;	// skip past prefix
			printf (">>> At: %li  Next: %li\n", m_device->tellInternal (), offset + m_size);
			if (!m_device->seekInternal (offset + m_size, SEEK_SET)) return false;
			return true;
		}

		bool writeToDevice (void)
		{
			MSWrite::WriteDWord (m_size, m_data + 0);
			MSWrite::WriteWord (m_function, m_data + 4);
			if (!m_device->writeInternal (m_data, 6)) return false;

			for (int i = 0; i < ((m_function == 0x0B41) ? 10/*not 11*/ : (m_function >> 8)); i++)
			{
				MSWrite::Byte data [2];
				MSWrite::WriteShort (m_args [i], data);
				if (!m_device->writeInternal (data, 2)) return false;
			}

			return true;
		}
	};

	// converts a DIB to a Standard WMF
	bool BMP2WMF (MSWrite::Device &readDevice, MSWrite::Device &writeDevice)
	{
		// read BMP's FileHeader
		MSWrite::BMP_BitmapFileHeader bfh;
		bfh.setDevice (&readDevice);
		if (!bfh.readFromDevice ()) return false;

		// WMF bitmap doesn't contain FileHeader
		MSWrite::DWord totalBytes = bfh.getTotalBytes () - MSWrite::BMP_BitmapFileHeader::s_size;

		// read BMP's InfoHeader to get some info...
		MSWrite::BMP_BitmapInfoHeader bih;
		bih.setDevice (&readDevice);
		if (!bih.readFromDevice ()) return false;

		// get some info about the image
		MSWrite::Long width = bih.getWidth ();
		MSWrite::Long height = bih.getHeight ();


		// Note: not from LibMSWrite's wmf.cpp
		kdDebug (30509) << "\t\tBIH: width(pt)=" << width
								<< " height(pt)=" << height
								<< " BPP=" << bih.getBitsPerPixel ()
								<< endl;
		kdDebug (30509) << "\t\tBIH: xPixelsPerMeter=" << bih.getXPixelsPerMeter ()
								<< " yPixelsPerMeter=" << bih.getYPixelsPerMeter ()
								<< endl;


		// write WMF Header
		MSWrite::WMFHeader wmfHeader;
		wmfHeader.setDevice (&writeDevice);

		MSWrite::DWord maxRecordSizeBytes
			= (totalBytes
				+ 10 * sizeof (MSWrite::Word)/*parameters for DibStretchBlt*/
				+ WMFRecord::s_size);
		wmfHeader.setFileSize ((MSWrite::WMFHeader::s_size
										+ WMFRecord::s_size + 1 * sizeof (MSWrite::Word)/*SetMapMode*/
										+ WMFRecord::s_size + 2 * sizeof (MSWrite::Word)/*SetWindowExt*/
										+ WMFRecord::s_size + 2 * sizeof (MSWrite::Word)/*SetWindowOrg*/
										+ maxRecordSizeBytes/*DibStretchBlt*/
										+ WMFRecord::s_size/*Sentinel*/)
											/ sizeof (MSWrite::Word));
		wmfHeader.setMaxRecordSize (maxRecordSizeBytes / sizeof (MSWrite::Word));
		if (!wmfHeader.writeToDevice ()) return false;

		WMFRecord wmfRecordSetMapMode (4/*(Words)*/, 0x0103, &writeDevice);
		wmfRecordSetMapMode.add (8/*MM_ANISOTROPIC*/);
		if (!wmfRecordSetMapMode.writeToDevice ()) return false;

		WMFRecord wmfRecordSetWindowExt (5/*(Words)*/, 0x020C, &writeDevice);
		wmfRecordSetWindowExt.add (-height);
		wmfRecordSetWindowExt.add (width);
		if (!wmfRecordSetWindowExt.writeToDevice ()) return false;

		WMFRecord wmfRecordSetWindowOrg (5/*(Words)*/, 0x020B, &writeDevice);
		wmfRecordSetWindowOrg.add (0);
		wmfRecordSetWindowOrg.add (0);
		if (!wmfRecordSetWindowOrg.writeToDevice ()) return false;

		WMFRecord wmfRecordBMP (maxRecordSizeBytes / sizeof (MSWrite::Word),
										0x0B41/*DibStretchBlt*/,
										&writeDevice);
		wmfRecordBMP.add (32);	// ?
		wmfRecordBMP.add (204);	// ?
		wmfRecordBMP.add (height);	// src height
		wmfRecordBMP.add (width);	// src width
		wmfRecordBMP.add (0);	// src y
		wmfRecordBMP.add (0);	// src x
		wmfRecordBMP.add (-height);	// dest height
		wmfRecordBMP.add (width);	// dest width
		wmfRecordBMP.add (0);	// dest y
		wmfRecordBMP.add (0);	// dest x
		if (!wmfRecordBMP.writeToDevice ()) return false;

		// write BMP InfoHeader back to the device
		bih.setDevice (&writeDevice);
		if (!bih.writeToDevice ()) return false;

		long left = totalBytes - MSWrite::BMP_BitmapInfoHeader::s_size;
		while (left)
		{
			MSWrite::Byte data [1024];
			long amountToRead = left > 1024 ? 1024 : left;
			if (!readDevice.readInternal (data, amountToRead)) return false;
			if (!writeDevice.writeInternal (data, amountToRead)) return false;

			left -= amountToRead;
		}

		WMFRecord wmfRecordSentinel (3/*(Words)*/, 0x0000, &writeDevice);
		if (!wmfRecordSentinel.writeToDevice ()) return false;

	#if 1
		// bug with Word97?
		MSWrite::Byte zero = 0;
		if (!writeDevice.writeInternal (&zero, sizeof (MSWrite::Byte))) return false;
	#endif

		return true;
	}

	// all windows measurements depend on there being 72 dots/points per inch
	static double getDimen72DPI (const int measurement, const int dotsPerMeter)
	{
		kdDebug (30509) << "\t\tgetDimen72DPI (measurement=" << measurement
								<< ",dotsPerMeter=" << dotsPerMeter << ")" << endl;

		// Can't get resolution?
		// Assume that we are already 72dpi
		if (dotsPerMeter <= 0)
			return double (measurement);

		// 2834.65 = 100 / 2.54 * 72
		return double (measurement) * 2834.65 / double (dotsPerMeter);
	}

	//
	// Note: if we suffer from a conversion error in this function (and can't
	// export the image), we _still_ return true, not false because there is
	// nothing worse than a filter that aborts due to its own incompetence [1]
	// (don't flame the author please, just blame the function :)).  But if we
	// do suffer from a file-like error, we abort right away because something
	// bad (memory corruption, internal error...) is happening!
	//
	// Why then _do_ we abort on text errors when images tell a thousand
	// words?  Because this saying is wrong and text is probably more
	// important to the user.
	//
	// [1] yes, "worse than an itch you can't scratch"
	//
	bool processImage (const FrameAnchor &frameAnchor,
								const MSWrite::FormatParaProperty *paraPropIn,
								const MSWrite::FormatCharProperty *charPropIn,
								const bool ignoreIndent)
	{
		kdDebug (30509) << "--------------------------" << endl
								<< "processImage()" << endl;


		// Write supports images in 3 formats:
		//
		// 1. Monochrome BMP (not very useful)
		// 2. OLE (hard to work with and only supported in ver >= 3.1)
		// 3. Standard WMF
		//
		// So we convert all images to WMF for convenience.
		// We don't even bother saving Monochrome BMPs "as is" because
		// there's no point (just save it in WMF to make life easier)
		//
		// But a Standard WMF is basically a BMP with some headers/GDI calls
		// so the conversion process is like this:
		//
		// start->WMF->finish
		// start->BMP->WMF->finish
		// start->???->BMP->WMF->finish
		//

		double imageActualWidth = -1, imageActualHeight = -1;
		MSWrite::DWord imageSize = 0;

		TQString imageType;
		int pos = frameAnchor.picture.koStoreName.findRev ('.');
		if (pos != -1) imageType = frameAnchor.picture.koStoreName.mid (pos).lower ();
		kdDebug (30509) << "\timageType: " << imageType << endl;

		TQByteArray imageData;
		kdDebug (30509) << "\tReading image: " << frameAnchor.picture.koStoreName << endl;
		if (!loadSubFile (frameAnchor.picture.koStoreName, imageData))
			ErrorAndQuit (MSWrite::Error::FileError, "could not open image from store\n");

		// FSM
		for (;;)
		{
			if (imageType == ".wmf")
			{
				imageSize = imageData.size ();
				if (imageActualWidth == -1 && imageActualHeight == -1)
				{
					// load WMF
					KoWmfPaint wmf;
					if (!wmf.load (imageData))
					{
						kdError (30509) << "Could not open WMF - Invalid Format!" << endl;
						return true;
					}

					// get raw dimensions
					TQRect dimen = wmf.boundingRect ();
					int width = abs (dimen.width ());
					int height = abs (dimen.height ());
					kdDebug (30509) << "\tRaw WMF dimensions: " << width << "x" << height << endl;

					if (wmf.isPlaceable ())
					{
						kdDebug (30509) << "\tConverting Placeable WMF" << endl;

						// convert twip measurements that aren't in 72dpi
						int defaultDpi = wmf.defaultDpi ();
						if (defaultDpi <= 0)
						{
							kdWarning (30509) << "Invalid defaultDPI: " << defaultDpi << endl;
							defaultDpi = 1440;
						}
						imageActualWidth = width * 1440 / defaultDpi;
						imageActualHeight = height * 1440 / defaultDpi;

						// Remove Aldus Placeable WMF Header
						for (int i = 0; i < int (imageSize) - 22; i++)
							imageData [i] = imageData [i + 22];

						imageData.resize (imageSize - 22);
						imageSize -= 22;
					}
					else if (wmf.isEnhanced ())
					{
						kdError (30509) << "Enhanced WMF unsupported by TQWmf, internal error!" << endl;

						return true;
					}
					// Standard WMF
					else
					{
						kdDebug (30509) << "\tStandard WMF - no conversion required" << endl;

						// assume width & height were in 72dpi points
						imageActualWidth = Point2Twip (width);
						imageActualHeight = Point2Twip (height);
					}
				}

				kdDebug (30509) << "\tNow WMF: width=" << imageActualWidth
										<< " height=" << imageActualHeight
										<< " size=" << imageSize
										<< endl;

				// we're done!
				break;
			}
			// TODO: DDB?
			else if (imageType == ".bmp")
			{
				TQImage image (imageData);
				if (image.isNull ())
				{
					kdError (30509) << "TQImage IsNull: Line=" << __LINE__ << endl;
					return true;
				}

				if (imageActualWidth == -1 && imageActualHeight == -1)
				{
					imageActualWidth = Point2Twip (getDimen72DPI (image.width (), image.dotsPerMeterX ()));
					imageActualHeight = Point2Twip (getDimen72DPI (image.height (), image.dotsPerMeterY ()));
				}

				kdDebug (30509) << "\tNow BMP: width=" << imageActualWidth
										<< " height=" << imageActualHeight
										<< " size=" << imageSize
										<< endl;

				TQByteArray imageWMF;
					// input device
					TQBuffer inBuffer (imageData);
					inBuffer.open (IO_ReadOnly);
					TQBufferDevice inDevice (&inBuffer);

					// output device
					TQBuffer outBuffer (imageWMF);
					outBuffer.open (IO_WriteOnly);
					TQBufferDevice outDevice (&outBuffer);

					// BMP --> WMF
					if (!BMP2WMF (inDevice, outDevice))
					{
						kdError (30509) << "BMP to WMF conversion error" << endl;
						return true;
					}

					outBuffer.close ();
					inBuffer.close ();
				imageData = imageWMF.copy ();

				imageType = ".wmf";
			}
			else
			{
				if (imageActualWidth == -1 && imageActualHeight == -1)
				{
					TQImage image (imageData);
					if (image.isNull())
					{
						kdError (30509) << "TQImage isNull: Line=" << __LINE__ << endl;
						return true;
					}

					imageActualWidth = Point2Twip (getDimen72DPI (image.width (), image.dotsPerMeterX ()));
					imageActualHeight = Point2Twip (getDimen72DPI (image.height (), image.dotsPerMeterY ()));
				}

				kdDebug (30509) << "\tForeign format: width=" << imageActualWidth
										<< " height=" << imageActualHeight
										<< " size=" << imageSize
										<< endl;

				TQByteArray imageBMP;
					// input device
					TQBuffer inBuffer (imageData);
					inBuffer.open (IO_ReadOnly);

					// read foreign image
					TQImageIO imageIO (&inBuffer, NULL);
					if (!imageIO.read ())
					{
						kdError (30509) << "Could not read foreign format" << endl;
						return true;
					}

					// output device
					TQBuffer outBuffer (imageBMP);
					outBuffer.open (IO_WriteOnly);

					// write BMP
					imageIO.setIODevice (&outBuffer);
					imageIO.setFormat ("BMP");
					if (!imageIO.write ())
					{
						kdError (30509) << "Could not convert to BMP" << endl;
						return true;
					}

					outBuffer.close ();
					inBuffer.close ();
				imageData = imageBMP.copy ();

				imageType = ".bmp";
			}
		}


		kdDebug (30509) << "\tActual dimensions: width=" << imageActualWidth
								<< " height=" << imageActualHeight << endl;

		kdDebug (30509) << "\tKOffice position: left=" << frameAnchor.frame.left
								<< " right=" << frameAnchor.frame.right
								<< " top=" << frameAnchor.frame.top
								<< " bottom=" << frameAnchor.frame.bottom
								<< endl;

		kdDebug (30509) << "\tIndent=" << MSWrite::Word (Point2Twip (frameAnchor.frame.left)) - m_leftMargin << endl;
		if (ignoreIndent)
			kdDebug (30509) << "\t\tIgnoring indent - already exported at least one image in a KWord paragraph" << endl;

		double displayedWidth = Point2Twip (frameAnchor.frame.right - frameAnchor.frame.left + 1);
		double displayedHeight = Point2Twip (frameAnchor.frame.bottom - frameAnchor.frame.top + 1);

		kdDebug (30509) << "\tdisplayedWidth=" << displayedWidth
								<< " displayedHeight=" << displayedHeight
								<< endl;


		//
		// Start writing out the image now
		//
		// Note: here, we can start returning false again because the errors
		// won't be conversion-related
		//

		MSWrite::Image image;
		image.setIsWMF (true);
		if (!ignoreIndent)
		{
			if (paraPropIn->getAlignment () != MSWrite::Alignment::Centre)
				image.setIndent (MSWrite::Word (Point2Twip (frameAnchor.frame.left)) - m_leftMargin);
			else
			{
				// TODO: what is the image offset relative to (it's not always rel. to the left margin)?
				kdDebug (30509) << "\tCentred paragraph, cannot position image" << endl;
			}
		}

		image.setOriginalWidth (imageActualWidth);
		image.setOriginalHeight (imageActualHeight);
		image.setDisplayedWidth (displayedWidth);
		image.setDisplayedHeight (displayedHeight);
		image.setExternalImageSize (imageSize);

		MSWrite::FormatParaProperty paraProp; paraProp = *paraPropIn;
		paraProp.setIsObject (true);
		paraProp.setLeftIndent (0);	// not necessary but...
		if (!m_generator->writeParaInfoBegin (&paraProp, NULL, &image))
			return false;

		// yes, images have character formatting as well
		// (character formatting _must_ cover entire document)
		// (but I think it's ignored)
		MSWrite::FormatCharProperty charProp; charProp = *charPropIn;
		if (!m_generator->writeCharInfoBegin (&charProp))
			return false;

		// actually write image
		if (!m_generator->writeBinary ((const MSWrite::Byte *) (const char *) imageData.data (), imageSize)) return false;

		// 2nd argument endOfParagraph is ignored by InternalGenerator so
		// there's no need to specify it
		if (!m_generator->writeCharInfoEnd (&charProp, true))
			return false;
;
		if (!m_generator->writeParaInfoEnd (&paraProp, NULL, &image))
			return false;


		kdDebug (30509) << "processImage() successful!" << endl
								<< "==========================" << endl
																			<< endl
																			<< endl;
		return true;
	}

	bool processTable (const Table &table)
	{
		// just dump the table out for now (no layout)
		for (TQValueList <TableCell>::ConstIterator it = table.cellList.begin ();
				it != table.cellList.end ();
				it++)
		{
			if (!doFullParagraphList (*(*it).paraList)) return false;
		}

		return true;
	}

	bool processCounter (const CounterData &counter)
	{
		//kdDebug (30509) << "processCounter(counter.text=" << counter.text << ")" << endl;

		if (!counter.text.isEmpty ())
		{
			// isn't this wonderful? :)
			if (!processText (counter.text)) return false;
			if (!processText (" ")) return false;
		}

		return true;
	}

	#ifndef NDEBUG
		//#define KMF_DEBUG_FONT
	#endif
	void processFormatData (MSWrite::FormatCharProperty &charProp,
						 			const TextFormatting &f)
	{
		if (!f.fontName.isEmpty ())
		{
			// create new Font with Name
			MSWrite::Font font ((const MSWrite::Byte *) (const char *) f.fontName.utf8 ());
		#ifdef KMF_DEBUG_FONT
			kdDebug (30509) << "FontName " << f.fontName << endl;
		#endif

			// get Font Family
			TQFont TQTFontInfo (f.fontName);
			switch (TQTFontInfo.styleHint ())
			{
			case TQFont::Serif:
			#ifdef KMF_DEBUG_FONT
				kdDebug (30509) << "FontFamily Serif" << endl;
			#endif
				font.setFamily (MSWrite::Font::Roman);
				break;
			case TQFont::SansSerif:
			#ifdef KMF_DEBUG_FONT
				kdDebug (30509) << "FontFamily SansSerif" << endl;
			#endif
				font.setFamily (MSWrite::Font::Swiss);
				break;
			case TQFont::Courier:
			#ifdef KMF_DEBUG_FONT
				kdDebug (30509) << "FontFamily Courier" << endl;
			#endif
				font.setFamily (MSWrite::Font::Modern);
				break;
			case TQFont::OldEnglish:
			#ifdef KMF_DEBUG_FONT
				kdDebug (30509) << "FontFamily OldEnglish" << endl;
			#endif
				font.setFamily (MSWrite::Font::Decorative);
				break;
			default:
			#ifdef KMF_DEBUG_FONT
				kdDebug (30509) << "FontFamily DontKnow" << endl;
			#endif
				// it's either DontCare or MSWrite::Font::Script
				font.setFamily (MSWrite::Font::DontCare);
				break;
			}

			charProp.setFont (&font);
		}
		if (f.fontSize > 0) charProp.setFontSize (f.fontSize);

		charProp.setIsItalic (f.italic);
		charProp.setIsUnderlined (f.underline);	// TODO: underlineWord
		charProp.setIsBold (f.weight > (50/*normal*/ + 75/*bold*/) / 2);

		switch (f.verticalAlignment)
		{
		case 0:	// normal
			charProp.setIsNormalPosition ();
			break;
		case 1:	// subscript
			charProp.setIsSubscript ();
			break;
		case 2:	// superscript
			charProp.setIsSuperscript ();
			break;
		}

		// TODO: fontAttribute;
	}

	static MSWrite::Word getClosestLineSpacing (const double points)
	{
		const double twips = Point2Twip (points);

	#if 1
		if (twips < double ((MSWrite::LineSpacing::Single + MSWrite::LineSpacing::OneAndAHalf) / 2))
			return MSWrite::LineSpacing::Single;
		else if (twips < double ((MSWrite::LineSpacing::OneAndAHalf + MSWrite::LineSpacing::Double) / 2))
			return MSWrite::LineSpacing::OneAndAHalf;
		else
			return MSWrite::LineSpacing::Double;
	#else	// or do we want a non-"standard" linespacing?
		return MSWrite::Word (twips);
	#endif
	}

	bool doFullParagraphList (const TQValueList <ParaData> &paraList)
	{
		for (TQValueList <ParaData>::ConstIterator it = paraList.begin ();
				it != paraList.end ();
				it ++)
		{
			if (!doFullParagraph (*it)) return false;
		}

		return true;
	}

	bool doFullParagraph (const ParaData &paraData)
	{
		return doFullParagraph (paraData.text,
							 			paraData.layout,
										paraData.formattingList);
	}

	bool doFullParagraph (const TQString &paraText,
						 			const LayoutData &layout,
									const ValueListFormatData &paraFormatDataList)
	{
		MSWrite::FormatParaProperty paraProp;

		if (m_inWhat == Body)
			paraProp.setIsNormalParagraph (true);
		else
		{
			if (m_inWhat == Header)
			{
				paraProp.setIsHeader (true);
				paraProp.setIsOnFirstPage (m_isHeaderOnFirstPage);
			}
			else if (m_inWhat == Footer)
			{
				paraProp.setIsFooter (true);
				paraProp.setIsOnFirstPage (m_isFooterOnFirstPage);
			}
		}

		paraProp.setIsText (true);

		// Alignment
		if (!layout.alignment.isEmpty ())
		{
			if (layout.alignment == "left")
				// quite useless since MSWrite::Alignment::Left is the default anyway
				paraProp.setAlignment (MSWrite::Alignment::Left);
			else if (layout.alignment == "right")
				paraProp.setAlignment (MSWrite::Alignment::Right);
			else if (layout.alignment == "center")
				paraProp.setAlignment (MSWrite::Alignment::Center);
			else if (layout.alignment == "justify")
				paraProp.setAlignment (MSWrite::Alignment::Justify);
			else
				kdWarning (30509) << "Unknown Alignment: " << layout.alignment << endl;
		}

		// Indentation
		if (layout.indentFirst) paraProp.setLeftIndentFirstLine (MSWrite::Short (Point2Twip (layout.indentFirst)));
		if (layout.indentLeft >= 0) paraProp.setLeftIndent (MSWrite::Word (Point2Twip (layout.indentLeft)));
		if (layout.indentRight >= 0) paraProp.setRightIndent (MSWrite::Word (Point2Twip (layout.indentRight)));
	#if 0
		kdDebug (30509) << "Indent: " << Point2Twip (layout.indentFirst) << " "
												<< Point2Twip (layout.indentLeft) << " "
												<< Point2Twip (layout.indentRight) << endl;
	#endif

		// Line Spacing
		MSWrite::Word lineSpacing = MSWrite::LineSpacing::Normal;
		switch (layout.lineSpacingType)
		{
		case LayoutData::LS_SINGLE:
			lineSpacing = MSWrite::LineSpacing::Normal;
			break;
		case LayoutData::LS_ONEANDHALF:
			lineSpacing = MSWrite::LineSpacing::OneAndAHalf;
			break;
		case LayoutData::LS_DOUBLE:
			lineSpacing = MSWrite::LineSpacing::Double;
			break;
		case LayoutData::LS_CUSTOM:
		case LayoutData::LS_FIXED:
		case LayoutData::LS_ATLEAST:
			lineSpacing = getClosestLineSpacing (layout.lineSpacing);
			break;
		case LayoutData::LS_MULTIPLE:
			break;
		default:
			kdWarning (30509) << "unknown lineSpacingType \'" << layout.lineSpacingType << "\'" << endl;
		}
		paraProp.setLineSpacing (lineSpacing);

		// Tabs are a Document Property, not a Paragraph Property, in Write, yet are stored for each paragraph.
		// It seems that Write applies the 1st paragraph's Tabulator settings to the _entire_ document
		// Word97 and KWord, however, will treat them like a Paragraph Property
		int numTabs = 0;
		for (TabulatorList::ConstIterator tabIt = layout.tabulatorList.begin ();
				tabIt != layout.tabulatorList.end ();
				tabIt++)
		{
			MSWrite::FormatParaPropertyTabulator tab;

			// Write's UI only supports 12 as opposed to the 14 supposedly
			// supported in the file so let's play it safe and quit when
			// we reach 12
			// Actually, KWord's UI also only supports 12 so this should never be true
			if (numTabs >= 12)
			{
				kdWarning (30509) << "Write does not support more 12 tabulators, not writing out all tabulators" << endl;
				break;
			}

			// Write only supports Decimal and Left tabs
			// TODO: KOffice 1.3 alignchar (modify libexport)
			if ((*tabIt).m_type == 3 /* && (*tabIt).m_alignchar == '.' */)
				tab.setIsDecimal ();
			else
				tab.setIsNormal ();

			tab.setIndent (MSWrite::Word (Point2Twip ((*tabIt).m_ptpos)));

			// int m_filling;
			// double m_width;
			if ((*tabIt).m_filling != TabulatorData::TF_NONE)
				kdWarning (30509) <<  "Write does not support Tabulator Filling" << endl;

			paraProp.addTabulator (&tab);
			numTabs++;
		}

		// TODO: double      marginTop;      // space before the paragraph  (a negative value means invalid)
		// TODO: double      marginBottom;   // space after the paragraph (a negative value means invalid)

		// TODO: TQString     styleName;
		// TODO: TQString     styleFollowing;

		if (!m_generator->writeParaInfoBegin (&paraProp)) return false;

		// get this paragraph's "default formatting"
		MSWrite::FormatCharProperty charPropDefault;
		processFormatData (charPropDefault, layout.formatData.text);

		MSWrite::DWord uptoByte = 0;	// relative to start of KWord paragraph
		MSWrite::DWord numBytes = paraText.length ();	// relative to start of KWord paragraph

		bool startOfWRIParagraph = true;
		bool exportedAtLeastOneImage = false;	// ...from the KWord paragraph

		// empty paragraph
		if (numBytes == 0)
		{
			//kdDebug (30509) << "Outputting empty paragraph!" << endl;

			// write default character property start
			if (!m_generator->writeCharInfoBegin (&charPropDefault)) return false;

			// page break at start of paragraph?
			if (layout.pageBreakBefore)
				if (!m_generator->writePageBreak ()) return false;

			// counter data
			processCounter (layout.counter);

			// end of line
			if (!m_generator->writeCarriageReturn ()) return false;
			if (!m_generator->writeNewLine (true/*end of paragraph*/)) return false;

			// page break at end of paragraph?
			if (layout.pageBreakAfter)
				if (!m_generator->writePageBreak ()) return false;

			// write default character property end
			if (!m_generator->writeCharInfoEnd (&charPropDefault, true)) return false;
		}
		else
		{
			for (ValueListFormatData::ConstIterator formatIt = paraFormatDataList.begin ();
					formatIt != paraFormatDataList.end ();
					formatIt++)
			{
				bool textSegment = true;

				// apply local <FORMAT> tag on top of "default formatting"
				MSWrite::FormatCharProperty charProp; charProp = charPropDefault;
				processFormatData (charProp, (*formatIt).text);

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

				if (uptoByte == 0)
				{
					// page break at start of paragraph?
					if (layout.pageBreakBefore)
						if (!m_generator->writePageBreak ()) return false;

					// counter data
					processCounter (layout.counter);
				}

				// yes, this is slightly premature but it doesn't matter
				// ... just be careful when using uptoByte
				uptoByte += (*formatIt).len;

				switch ((*formatIt).id)
				{
				case 0:	// none?
					ErrorAndQuit (MSWrite::Error::InternalError, "Format ID = 0\n");
				case 1:	// text
					if (!processText (paraText.mid ((*formatIt).pos, (*formatIt).len)))
											/*uptoByte == numBytes))*/
							return false;

					startOfWRIParagraph = false;
					break;
				case 2:	// picture (deprecated)
					m_device->error (MSWrite::Error::Warn, "Picture (deprecated) unsupported\n");
					break;
				case 3:	// tabulator (deprecated)
					m_device->error (MSWrite::Error::Warn, "Tabulator (deprecated) unsupported\n");
					break;
				case 4:	// variable
				{
					bool justPrintText = true;

					// Page Number / Number of Pages
					if ((*formatIt).variable.m_type == 4 &&
							(*formatIt).variable.isPageNumber () &&
							m_inWhat != Body/*Write replaces it with '*' in the body*/)
					{
						if (!m_generator->writeCharInfoEnd (&charProp)) return false;
						charProp.setIsPageNumber (true);
						// if you don't do this it will print out the character literally (char 1)
						if (!m_generator->writeCharInfoBegin (&charProp)) return false;

						// only variable Write supports (and only in headers/footers)
						// if you don't write out char 1, Write will not treat it as a
						// variable anchor and will print the character out literally
						if (!m_generator->writePageNumber ()) return false;

						if (!m_generator->writeCharInfoEnd (&charProp)) return false;
						charProp.setIsPageNumber (false);
						if (!m_generator->writeCharInfoBegin (&charProp)) return false;

						justPrintText = false;
					}

					if (justPrintText)
					{
						if (!processText ((*formatIt).variable.m_text))
							return false;
					}

					startOfWRIParagraph = false;
					break;
				}
				case 5:	// footnote (KOffice 1.1)
					m_device->error (MSWrite::Error::Warn, "Footnote unsupported\n");
					break;
				case 6:	// anchor for frame
					//
					// Write does not support inline frames so:
					//
					// - we end the current paragraph
					// - dump the inline frame in a paragraph of its own
					// - continue with a new paragraph
					//

					if (!startOfWRIParagraph)
					{
						kdDebug (30509) << "Writing CRLF to end text paragraph" << endl;

						// If you don't have CRLF at the end of the text
						// paragraph, Write will think that the next paragraph
						// (the image) is part of the text...
						if (!m_generator->writeCarriageReturn ()) return false;
						if (!m_generator->writeNewLine (true/*end of paragraph*/)) return false;
					}
					else
						kdDebug (30509) << "Inline frame is anchored at start of paragraph, no CRLF" << endl;

					if (!m_generator->writeCharInfoEnd (&charProp)) return false;
					if (!m_generator->writeParaInfoEnd (&paraProp)) return false;


					if ((*formatIt).frameAnchor.type == 6)
					{
						kdDebug (30509) << "Table detected" << endl;

						// this will make its own paragraph(s)...
						processTable ((*formatIt).frameAnchor.table);

						// HACK: inline tables are flushed to the left and right
						// margins, despite being inline, hence the next image
						// indent will be sensible and should not be ignored.
						kdDebug (30509) << "Table hack: resetting image-ignore-indent flag" << endl;
						exportedAtLeastOneImage = false;
					}
					else if ((*formatIt).frameAnchor.type == 2)
					{
						kdDebug (30509) << "Image detected" << endl;

						// this will make its own paragraph...
						if (!processImage ((*formatIt).frameAnchor, &paraProp, &charProp,
													exportedAtLeastOneImage)) return false;

						exportedAtLeastOneImage = true;
					}
					else
						kdWarning (30509) << "Unknown type of anchor: " << (*formatIt).frameAnchor.type << endl;


					// recontinue paragraph
					if (!m_generator->writeParaInfoBegin (&paraProp)) return false;
					startOfWRIParagraph = true;
					if (!m_generator->writeCharInfoBegin (&charProp)) return false;

					textSegment = false;
					break;
				}

				if (uptoByte == numBytes)
				{
					if (textSegment)
					{
						// end of line
						if (!m_generator->writeCarriageReturn ()) return false;
						if (!m_generator->writeNewLine (true/*end of paragraph*/)) return false;
					}

					// page break at end of paragraph?
					if (layout.pageBreakAfter)
						if (!m_generator->writePageBreak ()) return false;
				}

				if (!m_generator->writeCharInfoEnd (&charProp, uptoByte == numBytes)) return false;
			}
		}

		if (!m_generator->writeParaInfoEnd (&paraProp)) return false;
		//if (numBytes) kdDebug (30509) << "Just Output " << uptoByte << "/" << numBytes << " with text \'" << paraText.utf8 () << "\'" << endl;

		return true;
	}

	template <class dtype>
	dtype min (const dtype a, const dtype b, const dtype c)
	{
		if (a <= b && a <= c) return a;
		if (b <= a && b <= c) return b;
		return c;
	}

	#ifndef NDEBUG
		//#define DEBUG_PROCESS_TEXT
	#endif
	bool processText (const TQString &stringUnicode)
	{
		//
		// Look out for characters in the string and emit signals as appropriate:
		//
		// 1  pageNumber (already taken care of as a variable)
		// 10 newLine
		// 13 carriageReturn (TODO: Oh no!  Can't happen!)
		// 12 pageBreak (TODO: we are in real trouble: this can actually happen without forcing a new paragraph!)
		// 31 optionalHyphen
		// ?  text
		//

		int softHyphen = -2, nonBreakingSpace = -2, newLine = -2;

		int upto = 0;
		int stringUnicodeLength = stringUnicode.length ();
		while (upto < stringUnicodeLength)
		{
			//
			// look for KWord's special characters as defined in the DTD
			//

			if (softHyphen == -2)
			{
				softHyphen = stringUnicode.find (TQChar (0xAD), upto);
				if (softHyphen == -1) softHyphen = INT_MAX;
			}

			if (nonBreakingSpace == -2)
			{
				nonBreakingSpace = stringUnicode.find (TQChar (0xA0), upto);
				if (nonBreakingSpace == -1) nonBreakingSpace = INT_MAX;
			}

			if (newLine == -2)
			{
				newLine = stringUnicode.find (TQChar ('\n'), upto);
				if (newLine == -1) newLine = INT_MAX;
			}

			// look for the closest one
			int specialLocation = min (softHyphen, nonBreakingSpace, newLine);

			// get substring (either to the end of the original string or before
			// the next closest special character, if any)
			int length = stringUnicodeLength - upto;
			if (specialLocation != INT_MAX)
				length = specialLocation - upto;
			TQString substring = stringUnicode.mid (upto, length);

		#ifdef DEBUG_PROCESS_TEXT
			kdDebug (30509) << "Parent string:  upto=" << upto
									<< ",length=" << stringUnicode.length () << endl;
			kdDebug (30509) << "Child string:   length=" << length
									<< " (specialLoc=" << specialLocation << ")" << endl;
		#endif

			//
			// convert substring to windows-1251
			//

			TQCString stringWin;

			// there is a codec, therefore there is an encoder...
			if (m_codec)
			{
				int len;	// don't overwrite length, we need it later

				// convert from Unicode (UTF8)
				stringWin = m_encoder->fromUnicode (substring, len = length);
			}
			else
			{
				// output a plain string still in wrong Character Set
				// (hopefully the user won't notice)
				stringWin = substring.utf8 ();
			}


			// output encoded text
			if (!m_generator->writeText ((const MSWrite::Byte *) (const char *) stringWin))
				return false;

			upto += length;

			// special character?
			if (specialLocation != INT_MAX)
			{
			#ifdef DEBUG_PROCESS_TEXT
				kdDebug (30509) << "Found special character!" << endl;
			#endif

				// output special character
				if (specialLocation == softHyphen)
				{
				#ifdef DEBUG_PROCESS_TEXT
					kdDebug (30509) << "\tSoft Hyphen" << endl;
				#endif
					if (!m_generator->writeOptionalHyphen ()) return false;
					softHyphen = -2;
				}
				else if (specialLocation == nonBreakingSpace)
				{
				#ifdef DEBUG_PROCESS_TEXT
					kdDebug (30509) << "\tNon-breaking Space" << endl;
				#endif
					// don't think Write supports nonBreakingSpace
					if (!m_generator->writeText ((const MSWrite::Byte *) " ")) return false;
					nonBreakingSpace = -2;
				}
				else if (specialLocation == newLine)
				{
				#ifdef DEBUG_PROCESS_TEXT
					kdDebug (30509) << "\tNew Line" << endl;
				#endif
					// \r\n, not just \n
					if (!m_generator->writeCarriageReturn ()) return false;
					if (!m_generator->writeNewLine (true/*InternalGenerator doesn't care*/)) return false;
					newLine = -2;
				}
				else
				{
					ErrorAndQuit (MSWrite::Error::InternalError, "simply impossible specialLocation\n");
				}

				// skip past special character
				upto++;
			}
		}	// while (upto < stringUnicodeLength)	{

		return true;
	}
};


MSWriteExport::MSWriteExport (KoFilter *, const char *, const TQStringList &)
					: KoFilter()
{
}

MSWriteExport::~MSWriteExport ()
{
}

KoFilter::ConversionStatus MSWriteExport::convert (const TQCString &from, const TQCString &to)
{
	kdDebug (30509) << "MSWriteExport $Date: 2006-02-12 19:28:12 +0100 (Sun, 12 Feb 2006) $ using LibMSWrite "
			  				<< MSWrite::Version << endl;

	if (to != "application/x-mswrite" || from != "application/x-kword")
	{
		kdError (30509) << "Internal error!  Filter not implemented?" << endl;
		return KoFilter::NotImplemented;
	}

	KWordMSWriteWorker *worker = new KWordMSWriteWorker;
	if (!worker)
	{
		kdError (30509) << "Could not allocate memory for worker" << endl;
		return KoFilter::OutOfMemory;
	}

	KWEFKWordLeader *leader = new KWEFKWordLeader (worker);
	if (!leader)
	{
		kdError (30509) << "Could not allocate memory for leader" << endl;
		delete worker;
		return KoFilter::OutOfMemory;
	}

	KoFilter::ConversionStatus ret = leader->convert (m_chain, from, to);
	int errorCode = worker->getError ();

	delete leader;
	delete worker;

	// try to return somewhat more meaningful errors than KoFilter::StupidError
	// for the day that KOffice actually reports them to the user properly
	switch (errorCode)
	{
	case MSWrite::Error::Ok:
		kdDebug (30509) << "Returning error code " << ret << endl;
		return ret;	// not KoFilter::OK in case KWEFKWordLeader wants to report something

	case MSWrite::Error::Warn:
		kdDebug (30509) << "Error::Warn" << endl;
		return KoFilter::InternalError;	// warnings should _never_ set m_error

	case MSWrite::Error::InvalidFormat:
		kdDebug (30509) << "Error::InvalidFormat" << endl;
		return KoFilter::InternalError;	// how can the file I'm _writing_ be of an invalid format?

	case MSWrite::Error::OutOfMemory:
		kdDebug (30509) << "Error::OutOfMemory" << endl;
		return KoFilter::OutOfMemory;

	case MSWrite::Error::InternalError:
		kdDebug (30509) << "Error::InternalError" << endl;
		return KoFilter::InternalError;

	case MSWrite::Error::Unsupported:
		kdDebug (30509) << "Error::Unsupported" << endl;
		return KoFilter::InternalError;

	case MSWrite::Error::FileError:
		kdDebug (30509) << "Error::FileError" << endl;
		return KoFilter::CreationError;
	}

	kdWarning (30509) << "Unknown error" << endl;
	return KoFilter::StupidError;
}

#include <mswriteexport.moc>
