diff options
Diffstat (limited to 'filters/kword/mswrite/mswriteexport.cpp')
-rw-r--r-- | filters/kword/mswrite/mswriteexport.cpp | 1975 |
1 files changed, 1975 insertions, 0 deletions
diff --git a/filters/kword/mswrite/mswriteexport.cpp b/filters/kword/mswrite/mswriteexport.cpp new file mode 100644 index 000000000..abf007f88 --- /dev/null +++ b/filters/kword/mswrite/mswriteexport.cpp @@ -0,0 +1,1975 @@ +/* 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> ¶) + { + 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 (TQT_TQIODEVICE(&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 (¶Prop, 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 (¶Prop, 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> ¶List) + { + for (TQValueList <ParaData>::ConstIterator it = paraList.begin (); + it != paraList.end (); + it ++) + { + if (!doFullParagraph (*it)) return false; + } + + return true; + } + + bool doFullParagraph (const ParaData ¶Data) + { + return doFullParagraph (paraData.text, + paraData.layout, + paraData.formattingList); + } + + bool doFullParagraph (const TQString ¶Text, + const LayoutData &layout, + const ValueListFormatData ¶FormatDataList) + { + 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 (¶Prop)) 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 (¶Prop)) 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, ¶Prop, &charProp, + exportedAtLeastOneImage)) return false; + + exportedAtLeastOneImage = true; + } + else + kdWarning (30509) << "Unknown type of anchor: " << (*formatIt).frameAnchor.type << endl; + + + // recontinue paragraph + if (!m_generator->writeParaInfoBegin (¶Prop)) 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 (¶Prop)) 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> |