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

#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <QBuffer>
#include <QFile>
#include <QFont>
#include <QImage>
#include <QTextCodec>
#include <QList>

#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 : public KGenericFactory <MSWriteExport>
{
public:
    MSWriteExportFactory() : KGenericFactory <MSWriteExport> ("kwordmswriteexport") {
    }

protected:
    virtual void setupTranslations(void) {
        KGlobal::locale()->insertCatalog("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) {
            kDebug(30509) << "Want to seek to" << absloc
            << " but EOF is at " << m_outfp_eof
            << "; so writing " << absloc - m_outfp_eof
            << " zeros";

            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) {
        kDebug(30509) << s;
    }
    void debug(const int i) {
        kDebug(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)
            kWarning(30509) << message;
        else {
            m_error = errorCode;
            kError(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
    QTextCodec *m_codec;
    QTextEncoder *m_encoder;

    QList <HeaderData> m_headerData;
    QList <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 = QTextCodec::codecForName("CP 1252");

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

        m_device = new WRIDevice;
        if (!m_device) {
            kError(30509) << "Could not allocate memory for Device";
            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 QString &outFileName, const QString &) {
        // constructor failed?
        if (!m_device || !m_generator)
            return false;

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

        return true;
    }

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

    bool doOpenDocument(void) {
        kDebug(30509) << "doOpenDocument ()";

        // 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) {
        kDebug(30509) << "doCloseDocument ()";

        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) {
        kDebug(30509) << "doFullPaperFormat ("
        << format << ", "
        << width << ", "
        << height << ", "
        << orientation << ")";

        // 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) {
        kDebug(30509) << "doFullPaperBorders ("
        << top << ", "
        << left << ", "
        << bottom << ", "
        << right << ")";

        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);

        kDebug(30509) << "doVariableSettings pageNumberStart="
        << m_pageNumberStart;
        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) {
        kDebug(30509) << "doPageInfo (headerType=" << headerType
        << ", footerType=" << footerType
        << ")";

        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:
            kWarning(30509) << "Unknown headerType: " << headerType;
            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:
            kWarning(30590) << "Unknown footerType: " << footerType;
            m_isFooterOnFirstPage = false; // just a guess
            break;
        }

        return true;
    }

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

        return false;
    }

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

        if (isParaListEmpty(header.para)) {
            kDebug(30509) << "\tEmpty, ignoring";
            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) {
        kDebug(30509) << "doFooter (footer.page=" << footer.page << ")";

        if (isParaListEmpty(footer.para)) {
            kDebug(30509) << "\tEmpty, ignoring";
            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) {
        kDebug(30509) << "doOpenBody ()";

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


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

        bool wroteFooter = false;
        m_inWhat = Footer;

        for (QList <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 (QList <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 (QList <HeaderData>::Iterator it = m_headerData.begin();
                it != m_headerData.end();
                it++) {
            kDebug(30509) << "BODY START ADDING HEADER:" << (*it).page;
            if (!doFullParagraphList((*it).para)) return false;
            it = --m_headerData.erase(it);
        }

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

        return true;
    }

    bool doCloseBody(void) {
        kDebug(30509) << "doCloseBody ()";

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

        return true;
    }

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

    public:
        QBufferDevice(QBuffer *buffer) {
            m_buffer = buffer;
        }

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

        bool write(const MSWrite::Byte *buf, const MSWrite::DWord numBytes) {
            if (m_buffer->write((char *) buf, (Q_ULONG) numBytes) != Q_LONG(numBytes)) {
                error(MSWrite::Error::FileError, "could not write to QBuffer (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->pos() + offset;
                break;
            case SEEK_END:
                absoffset = m_buffer->size() + offset;
                break;
            default:
                error(MSWrite::Error::InternalError, "unknown seek\n");
                return false;
            }

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

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

            return true;
        }

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

        void debug(const char *s) {
            kDebug(30509) << s;
        }
        void debug(const int i) {
            kDebug(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)
                kWarning(30509) << message;
            else {
                m_error = errorCode;
                kError(30509) << message;
            }
        }
    };

    // (from LibMSWrite's wmf.cpp)
    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
    // (from LibMSWrite's wmf.cpp)
    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
        kDebug(30509) << "\t\tBIH: width(pt)=" << width
        << " height(pt)=" << height
        << " BPP=" << bih.getBitsPerPixel();
        kDebug(30509) << "\t\tBIH: xPixelsPerMeter=" << bih.getXPixelsPerMeter()
        << " yPixelsPerMeter=" << bih.getYPixelsPerMeter();


        // 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) {
        kDebug(30509) << "\t\tgetDimen72DPI (measurement=" << measurement
        << ",dotsPerMeter=" << dotsPerMeter << ")";

        // 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) {
        kDebug(30509) << "--------------------------" << endl
        << "processImage()";


        // 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;

        QString imageType;
        int pos = frameAnchor.picture.koStoreName.lastIndexOf('.');
        if (pos != -1) imageType = frameAnchor.picture.koStoreName.mid(pos).toLower();
        kDebug(30509) << "\timageType:" << imageType;

        QByteArray imageData;
        kDebug(30509) << "\tReading image:" << frameAnchor.picture.koStoreName;
        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)) {
                        kError(30509) << "Could not open WMF - Invalid Format!";
                        return true;
                    }

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

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

                        // convert twip measurements that aren't in 72dpi
                        int defaultDpi = wmf.defaultDpi();
                        if (defaultDpi <= 0) {
                            kWarning(30509) << "Invalid defaultDPI: " << defaultDpi;
                            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()) {
                        kError(30509) << "Enhanced WMF unsupported by QWmf, internal error!";

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

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

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

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

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

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

                QByteArray imageWMF;
                // input device
                QBuffer inBuffer(&imageData);
                inBuffer.open(QIODevice::ReadOnly);
                QBufferDevice inDevice(&inBuffer);

                // output device
                QBuffer outBuffer(&imageWMF);
                outBuffer.open(QIODevice::WriteOnly);
                QBufferDevice outDevice(&outBuffer);

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

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

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

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

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

                // read foreign image
                QImage inImage;
                if (!inImage.loadFromData(imageData)) {
                    kError(30509) << "Could not read foreign format";
                    return true;
                }

                // output image
                QByteArray imageBMP;
                QBuffer outBuffer(&imageBMP);
                outBuffer.open(QIODevice::WriteOnly);

                // write BMP
                if (!inImage.save(&outBuffer, "BMP")) {
                    kError(30509) << "Could not convert to BMP";
                    return true;
                }

                outBuffer.close();
                imageData = imageBMP;
                imageType = ".bmp";
            }
        }


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

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

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

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

        kDebug(30509) << "\tdisplayedWidth=" << displayedWidth
        << " displayedHeight=" << displayedHeight;


        //
        // 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::Center)
                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)?
                kDebug(30509) << "\tCentered paragraph, cannot position image";
            }
        }

        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;


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

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

        return true;
    }

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

        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.toUtf8());
#ifdef KMF_DEBUG_FONT
            kDebug(30509) << "FontName" << f.fontName;
#endif

            // get Font Family
            QFont QTFontInfo(f.fontName);
            switch (QTFontInfo.styleHint()) {
            case QFont::Serif:
#ifdef KMF_DEBUG_FONT
                kDebug(30509) << "FontFamily Serif";
#endif
                font.setFamily(MSWrite::Font::Roman);
                break;
            case QFont::SansSerif:
#ifdef KMF_DEBUG_FONT
                kDebug(30509) << "FontFamily SansSerif";
#endif
                font.setFamily(MSWrite::Font::Swiss);
                break;
            case QFont::Courier:
#ifdef KMF_DEBUG_FONT
                kDebug(30509) << "FontFamily Courier";
#endif
                font.setFamily(MSWrite::Font::Modern);
                break;
            case QFont::OldEnglish:
#ifdef KMF_DEBUG_FONT
                kDebug(30509) << "FontFamily OldEnglish";
#endif
                font.setFamily(MSWrite::Font::Decorative);
                break;
            default:
#ifdef KMF_DEBUG_FONT
                kDebug(30509) << "FontFamily DontKnow";
#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 QList <ParaData> &paraList) {
        for (QList <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 QString &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
                kWarning(30509) << "Unknown Alignment: " << layout.alignment;
        }

        // 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
        kDebug(30509) << "Indent:" << Point2Twip(layout.indentFirst) << ""
        << Point2Twip(layout.indentLeft) << " "
        << Point2Twip(layout.indentRight);
#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:
            kWarning(30509) << "unknown lineSpacingType \'" << layout.lineSpacingType << "\'";
        }
        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) {
                kWarning(30509) << "Write does not support more 12 tabulators, not writing out all tabulators";
                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)
                kWarning(30509) <<  "Write does not support Tabulator Filling";

            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: QString     styleName;
        // TODO: QString     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) {
            //kDebug (30509) <<"Outputting empty paragraph!";

            // 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) {
                        kDebug(30509) << "Writing CRLF to end text paragraph";

                        // 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
                        kDebug(30509) << "Inline frame is anchored at start of paragraph, no CRLF";

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


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

                        // 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.
                        kDebug(30509) << "Table hack: resetting image-ignore-indent flag";
                        exportedAtLeastOneImage = false;
                    } else if ((*formatIt).frameAnchor.type == 2) {
                        kDebug(30509) << "Image detected";

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

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


                    // 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) kDebug (30509) <<"Just Output" << uptoByte <<"/" << numBytes <<" with text \'" << paraText.utf8 () <<"\'";

        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 QString &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.indexOf(QChar(0xAD), upto);
                if (softHyphen == -1) softHyphen = INT_MAX;
            }

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

            if (newLine == -2) {
                newLine = stringUnicode.indexOf(QChar('\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;
            QString substring = stringUnicode.mid(upto, length);

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

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

            QByteArray 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.toUtf8();
            }


            // 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
                kDebug(30509) << "Found special character!";
#endif

                // output special character
                if (specialLocation == softHyphen) {
#ifdef DEBUG_PROCESS_TEXT
                    kDebug(30509) << "\tSoft Hyphen";
#endif
                    if (!m_generator->writeOptionalHyphen()) return false;
                    softHyphen = -2;
                } else if (specialLocation == nonBreakingSpace) {
#ifdef DEBUG_PROCESS_TEXT
                    kDebug(30509) << "\tNon-breaking Space";
#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
                    kDebug(30509) << "\tNew Line";
#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(QObject* parent, const QStringList &)
        : KoFilter(parent)
{
}

MSWriteExport::~MSWriteExport()
{
}

KoFilter::ConversionStatus MSWriteExport::convert(const QByteArray &from, const QByteArray &to)
{
    kDebug(30509) << "MSWriteExport $Date: 2009-12-11 16:30:21 +0100 (Fri, 11 Dec 2009) $ using LibMSWrite"
    << MSWrite::Version;

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

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

    KWEFKWordLeader *leader = new KWEFKWordLeader(worker);
    if (!leader) {
        kError(30509) << "Could not allocate memory for leader";
        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:
        kDebug(30509) << "Returning error code" << ret;
        return ret; // not KoFilter::OK in case KWEFKWordLeader wants to report something

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

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

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

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

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

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

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

#include <mswriteexport.moc>
