/* This file is part of the KDE project Copyright (C) 2001, 2002, 2003, 2004 Nicolas GOUTTE This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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 for more details. You should have received a copy of the GNU Library General Public License 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. */ /* This file is based on the old file: /home/kde/koffice/filters/kword/ascii/asciiexport.cc The old file was copyrighted by Copyright (C) 1998, 1999 Reginald Stadlbauer Copyright (c) 2000 ID-PRO Deutschland GmbH. All rights reserved. Contact: Wolf-Michael Bolle The old file was licensed under the terms of the GNU Library General Public License version 2. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ExportFilter.h" OOWriterWorker::OOWriterWorker(void) : m_streamOut(NULL), m_paperBorderTop(0.0),m_paperBorderLeft(0.0), m_paperBorderBottom(0.0),m_paperBorderRight(0.0), m_zip(NULL), m_pictureNumber(0), m_automaticParagraphStyleNumber(0), m_automaticTextStyleNumber(0), m_footnoteNumber(0), m_tableNumber(0), m_textBoxNumber( 0 ), m_columnspacing( 36.0 ), m_columns( 1 ) { } TQString OOWriterWorker::escapeOOText(const TQString& strText) const { // Escape quotes (needed in attributes) // Escape apostrophs (allowed by XML) return KWEFUtil::EscapeSgmlText(NULL,strText,true,true); } TQString OOWriterWorker::escapeOOSpan(const TQString& strText) const // We need not only to escape the classical XML stuff but also to take care of spaces and tabs. { TQString strReturn; TQChar ch; int spaceNumber = 0; // How many spaces should be written for (uint i=0; i 0 ) { strReturn += ' '; --spaceNumber; if ( spaceNumber > 0 ) { strReturn += ""; } spaceNumber = 0; } } // ### TODO: would be switch/case or if/elseif the best? switch (ch.unicode()) { case 9: // Tab { strReturn+=""; break; } case 10: // Line-feed { strReturn+=""; break; } case 32: // Space { if ( spaceNumber > 0 ) { ++spaceNumber; } else { spaceNumber = 1; } break; } case 38: // & { strReturn+="&"; break; } case 60: // < { strReturn+="<"; break; } case 62: // > { strReturn+=">"; break; } case 34: // " { strReturn+="""; break; } case 39: // ' { strReturn+="'"; break; } case 1: // (Non-XML-compatible) replacement character from KWord 0.8 { strReturn += '#'; //use KWord 1.[123] replacement character instead break; } // Following characters are not allowed in XML (but some files from KWord 0.8 have some of them.) case 0: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 11: case 12: case 14: case 15: case 16: case 17: case 18: case 19: case 20: case 21: case 22: case 23: case 24: case 25: case 26: case 27: case 28: case 29: case 30: case 31: { kdWarning(30518) << "Not allowed XML character: " << ch.unicode() << endl; strReturn += '?'; break; } case 13: // ### TODO: what to do with it? default: { strReturn+=ch; break; } } } if ( spaceNumber > 0 ) { // The last characters were spaces strReturn += ' '; --spaceNumber; if ( spaceNumber > 0 ) { strReturn += ""; } spaceNumber = 0; } return strReturn; } bool OOWriterWorker::doOpenFile(const TQString& filenameOut, const TQString& ) { kdDebug(30518) << "Opening file: " << filenameOut << " (in OOWriterWorker::doOpenFile)" << endl; m_zip=new KZip(filenameOut); // How to check failure? if (!m_zip->open(IO_WriteOnly)) { kdError(30518) << "Could not open ZIP file for writing! Aborting!" << endl; delete m_zip; m_zip=NULL; return false; } m_zip->setCompression( KZip::NoCompression ); m_zip->setExtraField( KZip::NoExtraField ); const TQCString appId( "application/vnd.sun.xml.writer" ); m_zip->writeFile( "mimetype", TQString(), TQString(), appId.length(), appId.data() ); m_zip->setCompression( KZip::DeflateCompression ); m_streamOut=new TQTextStream(m_contentBody, IO_WriteOnly); m_streamOut->setEncoding( TQTextStream::UnicodeUTF8 ); return true; } bool OOWriterWorker::zipPrepareWriting(const TQString& name) { if (!m_zip) return false; m_size=0; return m_zip->prepareWriting(name, TQString(), TQString(), 0); } bool OOWriterWorker::zipDoneWriting(void) { if (!m_zip) return false; return m_zip->doneWriting(m_size); } bool OOWriterWorker::zipWriteData(const char* str) { if (!m_zip) return false; const uint size=strlen(str); m_size+=size; return m_zip->writeData(str,size); } bool OOWriterWorker::zipWriteData(const TQByteArray& array) { if (!m_zip) return false; const uint size=array.size(); m_size+=size; return m_zip->writeData(array.data(),size); } bool OOWriterWorker::zipWriteData(const TQCString& cstr) { if (!m_zip) return false; const uint size=cstr.length(); m_size+=size; return m_zip->writeData(cstr.data(),size); } bool OOWriterWorker::zipWriteData(const TQString& str) { return zipWriteData(str.utf8()); } void OOWriterWorker::writeStartOfFile(const TQString& type) { const bool noType=type.isEmpty(); zipWriteData("\n"); zipWriteData("\n"); zipWriteData("\n"); } void OOWriterWorker::writeFontDeclaration(void) { zipWriteData( " \n"); TQMap::ConstIterator end(m_fontNames.end()); for (TQMap::ConstIterator it=m_fontNames.begin(); it!=end; ++it) { const bool space=(it.key().find(' ')>=0); // Does the font has at least a space in its name const TQString fontName(escapeOOText(it.key())); zipWriteData(" \n"); } zipWriteData(" \n"); } void OOWriterWorker::writeStylesXml(void) { if (!m_zip) return; zipPrepareWriting("styles.xml"); writeStartOfFile("styles"); writeFontDeclaration(); zipWriteData(m_styles); zipWriteData(" \n"); zipWriteData(" \n"); // ### TODO: verify if style name is unique zipWriteData(" \n" ); if ( m_columns > 1 ) { zipWriteData( " \n" ); for (int i=0; i < m_columns; ++i) { zipWriteData( " \n" ); } zipWriteData( " \n" ); } zipWriteData(" \n"); zipWriteData(" \n"); zipWriteData(" \n"); zipWriteData(" \n"); zipWriteData(" \n"); zipWriteData(" \n"); zipWriteData( "\n" ); zipDoneWriting(); } void OOWriterWorker::writeContentXml(void) { if (!m_zip) return; zipPrepareWriting("content.xml"); writeStartOfFile("content"); writeFontDeclaration(); zipWriteData(" \n"); zipWriteData(m_contentAutomaticStyles); m_contentAutomaticStyles = TQString(); // Release memory zipWriteData(" \n"); zipWriteData(m_contentBody); m_contentBody.resize( 0 ); // Release memory zipWriteData( "\n" ); zipDoneWriting(); } void OOWriterWorker::writeMetaXml(void) { if (!m_zip) return; zipPrepareWriting("meta.xml"); writeStartOfFile("meta"); zipWriteData(" \n"); // Tell who we are in case that we have a bug in our filter output! zipWriteData(" KWord's OOWriter Export Filter"); zipWriteData(TQString("$Revision: 515673 $").mid(10).remove('$')); // has a leading and a trailing space. zipWriteData("\n"); if (!m_docInfo.title.isEmpty()) { zipWriteData(" "); zipWriteData(escapeOOText(m_docInfo.title)); zipWriteData("\n"); } if (!m_docInfo.abstract.isEmpty()) { zipWriteData(" "); zipWriteData(escapeOOText(m_docInfo.abstract)); zipWriteData("\n"); } if (m_varSet.creationTime.isValid()) { zipWriteData(" "); zipWriteData(escapeOOText(m_varSet.creationTime.toString(Qt::ISODate))); zipWriteData("\n"); } if (m_varSet.modificationTime.isValid()) { zipWriteData(" "); zipWriteData(escapeOOText(m_varSet.modificationTime.toString(Qt::ISODate))); zipWriteData("\n"); } if (m_varSet.printTime.isValid()) { zipWriteData(" "); zipWriteData(escapeOOText(m_varSet.printTime.toString(Qt::ISODate))); zipWriteData("\n"); } zipWriteData( " 0 ) { zipWriteData( " meta:page-count=\"" ); zipWriteData( TQString::number ( m_numPages ) ); zipWriteData( "\"" ); } zipWriteData( " meta:image-count=\"" ); // This is not specified in the OO specification section 2.1.19 zipWriteData( TQString::number ( m_pictureNumber ) ); zipWriteData( "\"" ); zipWriteData( " meta:table-count=\"" ); zipWriteData( TQString::number ( m_tableNumber ) ); zipWriteData( "\"" ); zipWriteData( "/>\n" ); // meta:document-statistic zipWriteData(" \n"); zipWriteData("\n"); zipDoneWriting(); } bool OOWriterWorker::doCloseFile(void) { kdDebug(30518)<< "OOWriterWorker::doCloseFile" << endl; if (m_zip) { writeContentXml(); writeMetaXml(); writeStylesXml(); m_zip->close(); } delete m_zip; m_zip=NULL; return true; } bool OOWriterWorker::doOpenDocument(void) { kdDebug(30518)<< "OOWriterWorker::doOpenDocument" << endl; *m_streamOut << " \n"; return true; } bool OOWriterWorker::doCloseDocument(void) { *m_streamOut << " \n"; return true; } bool OOWriterWorker::doOpenBody(void) { TQValueList::Iterator it; // We have to process all non-inline pictures kdDebug(30518) << "=== Processing non-inlined pictures ===" << endl; for ( it = m_nonInlinedPictureAnchors.begin(); it != m_nonInlinedPictureAnchors.end(); ++it ) { *m_streamOut << " "; makePicture( *it, AnchorNonInlined ); *m_streamOut << "\n"; } kdDebug(30518) << "=== Non-inlined pictures processed ===" << endl; // We have to process all non-inline tables kdDebug(30518) << "=== Processing non-inlined tables ===" << endl; for ( it = m_nonInlinedTableAnchors.begin(); it != m_nonInlinedTableAnchors.end(); ++it ) { *m_streamOut << " "; makeTable( *it, AnchorNonInlined ); *m_streamOut << "\n"; } kdDebug(30518) << "=== Non-inlined tables processed ===" << endl; return true; } TQString OOWriterWorker::textFormatToStyle(const TextFormatting& formatOrigin, const TextFormatting& formatData, const bool force, TQString& key) { // TODO: rename variable formatData TQString strElement; // TODO: rename this variable // Font name TQString fontName = formatData.fontName; declareFont(fontName); if ( !fontName.isEmpty() && (force || (formatOrigin.fontName!=formatData.fontName))) { strElement+="style:font-name=\""; strElement+= escapeOOText(fontName); strElement+="\" "; key += fontName; } key += ","; if (force || (formatOrigin.italic!=formatData.italic)) { // Font style strElement+="fo:font-style=\""; if ( formatData.italic ) { strElement+="italic"; key+='I'; } else { strElement+="normal"; key+='N'; } strElement+="\" "; } key += ","; if (force || ((formatOrigin.weight>=75)!=(formatData.weight>=75))) { strElement+="fo:font-weight=\""; if ( formatData.weight >= 75 ) { strElement+="bold"; key+='B'; } else { strElement+="normal"; key+='N'; } strElement+="\" "; } key += ","; if (force || (formatOrigin.fontSize!=formatData.fontSize)) { const int size=formatData.fontSize; if (size>0) { strElement+="fo:font-size=\""; strElement+=TQString::number(size,10); strElement+="pt\" "; key+=TQString::number(size,10); } } key += ","; if (force || (formatOrigin.fgColor!=formatData.fgColor)) { if ( formatData.fgColor.isValid() ) { strElement+="fo:color=\""; strElement+=formatData.fgColor.name(); strElement+="\" "; key+=formatData.fgColor.name(); } } key += ","; if (force || (formatOrigin.bgColor!=formatData.bgColor)) { if ( formatData.bgColor.isValid() ) { strElement+="style:text-background-color=\""; // ### what is fo:background-color ? strElement+=formatData.bgColor.name(); strElement+="\" "; key+=formatData.bgColor.name(); } } key += ';'; // Another separator if ( force || ( formatOrigin.underline != formatData.underline ) || ( formatOrigin.underlineColor != formatData.underlineColor ) || ( formatOrigin.underlineValue != formatData.underlineValue ) || ( formatOrigin.underlineStyle != formatData.underlineStyle ) ) { strElement+="style:text-underline=\""; if ( formatData.underline ) { TQString underlineValue ( formatData.underlineValue ); TQString underlineStyle ( formatData.underlineStyle ); if ( underlineStyle.isEmpty() ) underlineStyle = "solid"; if ( underlineValue == "1" ) underlineValue = "single"; if ( underlineValue == "single" ) { if ( underlineStyle == "dash" ) { strElement += "dash"; key += "DA"; } else if ( underlineStyle == "dot" ) { strElement += "dotted"; key += "DO"; } else if ( underlineStyle == "dashdot" ) { strElement += "dot-dash"; key += "DDA"; } else if ( underlineStyle == "dashdotdot" ) { strElement += "dot-dot-dash"; key += "DDDA"; } else { strElement += "single"; key += "1"; } } else if ( underlineValue == "double" ) { strElement += "double"; key += "2"; } else if ( underlineValue == "single-bold" ) { strElement += "bold"; key += "BL"; } else if ( underlineValue == "wave" ) { strElement += "wave"; key += "WV"; } else { strElement += "single"; key += "?"; } } else { strElement+="none"; key += 'N'; } strElement += "\" "; if ( formatData.underline && formatData.underlineColor.isValid() ) { const TQString colorName( formatData.underlineColor.name() ); strElement += "style:text-underline-color=\""; strElement += colorName; strElement += "\" "; key += colorName; } } key += ','; if ( force || (formatOrigin.strikeout != formatData.strikeout ) || (formatOrigin.strikeoutType != formatData.strikeoutType ) ) { // OOWriter can only do single, double, thick (and slash and X that KWord cannot do.) // So no dash, dot and friends. strElement+="style:text-crossing-out=\""; if ( ( formatData.strikeoutType == "single" ) || ( formatData.strikeoutType == "1" ) ) { strElement+="single-line"; key += "1"; } else if ( formatData.strikeoutType == "double" ) { strElement+="double-line"; key += "2"; } else if ( formatData.strikeoutType == "single-bold" ) { strElement+="thick"; key += "T"; } else { strElement+="none"; key += 'N'; } strElement+="\" "; } key += ','; // It seems that OOWriter 1.1 does have problems with word-by-word (OO Issue #11873, #25187) // It is supposed to be fixed in development versions of OO if (force || ( formatOrigin.underlineWord != formatData.underlineWord ) || (formatOrigin.strikeoutWord != formatData.strikeoutWord ) ) { // Strikeout and underline can only have one word-by-word behaviour in OO // (OO Issue #????? ; will not be changed.) strElement+="fo:score-spaces=\""; // Are space processed? if ( formatData.underlineWord || formatData.strikeoutWord ) { strElement += "false"; key += 'W'; } else { strElement += "true"; key += 'N'; } strElement += "\" "; } key += ','; if ( force || ( formatOrigin.language != formatData.language ) ) { const TQString lang ( formatData.language ); if ( ! lang.isEmpty() ) { const int res = lang.find( '_' ); if ( res >= 0 ) { kdDebug(30518) << "Language: " << lang << " => " << lang.left( res ) << " - " << lang.mid( res + 1 ) << endl; strElement += "fo:language=\""; strElement += lang.left( res ); strElement += "\" "; strElement += "fo:country=\""; strElement += lang.mid( res + 1 ); strElement += "\" "; } else { kdDebug(30518) << "Language without country: " << lang << endl; strElement += "fo:language=\""; strElement += lang; strElement += "\" "; } key+=formatData.language; } } key += ","; if ( force || ( formatOrigin.fontAttribute != formatData.fontAttribute ) ) { // Note: OOWriter does not like when both fo:text-transform and fo:font-variant exist (except if both are none/normal) // (It is documented so, see sections 3.10.1 and 3.10.2) if ( formatData.fontAttribute == "uppercase" ) { strElement += "fo:text-transform=\"uppercase\" "; key += 'U'; } else if ( formatData.fontAttribute == "lowercase" ) { strElement += "fo:text-transform=\"lowercase\" "; key += 'L'; } else if ( formatData.fontAttribute == "smallcaps" ) { strElement += "fo:font-variant=\"small-caps\" "; key += 'S'; } else { strElement += "fo:text-transform=\"none\" "; strElement += "fo:font-variant=\"normal\" "; key += 'N'; } } key += ","; if ( force || ( formatOrigin.verticalAlignment != formatData.verticalAlignment ) ) { if ( 1 == formatData.verticalAlignment ) { //Subscript strElement += "style:text-position=\"sub\" "; key += 'B'; } else if ( 2 == formatData.verticalAlignment ) { //Superscript strElement += "style:text-position=\"super\" "; key += 'P'; } // ### TODO: how to reset it? "0pt" ? } return strElement.stripWhiteSpace(); // Remove especially trailing spaces } #define ALLOW_TABLE TQString OOWriterWorker::cellToProperties( const TableCell& cell, TQString& key) const { #ifdef ALLOW_TABLE const FrameData& frame = cell.frame; TQString properties; key += "!L"; // left border key += frame.lColor.name(); key += ","; key += TQString::number( frame.lWidth ); properties += " fo:border-left=\""; if ( frame.lColor.isValid() && frame.lWidth > 0.0 ) { properties += TQString::number( frame.lWidth ); properties += "pt"; properties += " solid "; // ### TODO properties += frame.lColor.name(); } else { properties += "0pt none #000000"; } properties += "\""; key += "!R"; // right border key += frame.rColor.name(); key += ","; key += TQString::number( frame.rWidth ); properties += " fo:border-right=\""; if ( frame.rColor.isValid() && frame.rWidth > 0.0 ) { properties += TQString::number( frame.rWidth ); properties += "pt"; properties += " solid "; // ### TODO properties += frame.rColor.name(); } else { properties += "0pt none #000000"; } properties += "\""; key += "!T"; // top border key += frame.tColor.name(); key += ","; key += TQString::number( frame.tWidth ); properties += " fo:border-top=\""; if ( frame.tColor.isValid() && frame.tWidth > 0.0 ) { properties += TQString::number( frame.tWidth ); properties += "pt"; properties += " solid "; // ### TODO properties += frame.tColor.name(); } else { properties += "0pt none #000000"; } properties += "\""; key += "!B"; // bottom border key += frame.bColor.name(); key += ","; key += TQString::number( frame.bWidth ); properties += " fo:border-bottom=\""; if ( frame.bColor.isValid() && frame.bWidth > 0.0 ) { properties += TQString::number( frame.bWidth ); properties += "pt"; properties += " solid "; // ### TODO properties += frame.bColor.name(); } else { properties += "0pt none #000000"; } properties += "\""; return properties; #else return TQString(); #endif } bool OOWriterWorker::makeTableRows( const TQString& tableName, const Table& table, int firstRowNumber ) { #ifdef ALLOW_TABLE // ### TODO: rows // ### TODO: be careful that covered-cell can be due vertical spanning. // ### TODO: One way to find is the difference between the row variables (or against the last known column) // ### TODO: be careful that fully-covered rows might exist. *m_streamOut << "\n"; int rowCurrent = firstRowNumber; ulong cellNumber = 0L; TQMap mapCellStyleKeys; for ( TQValueList::ConstIterator itCell ( table.cellList.begin() ); itCell != table.cellList.end(); ++itCell) { if ( rowCurrent != (*itCell).row ) { rowCurrent = (*itCell).row; *m_streamOut << "\n"; *m_streamOut << "\n"; } TQString key; const TQString props ( cellToProperties( (*itCell), key ) ); TQString automaticCellStyle; TQMap::ConstIterator it ( mapCellStyleKeys.find( key ) ); if ( it == mapCellStyleKeys.end() ) { automaticCellStyle = makeAutomaticStyleName( tableName + ".Cell", cellNumber ); mapCellStyleKeys [ key ] = automaticCellStyle; kdDebug(30518) << "Creating automatic cell style: " << automaticCellStyle << " key: " << key << endl; m_contentAutomaticStyles += " \n"; if (!doFullAllParagraphs(*(*itCell).paraList)) { return false; } *m_streamOut << "\n"; if ( (*itCell).m_cols > 1 ) { // We need to add some placeholder for the "covered" cells for (int i = 1; i < (*itCell).m_cols; ++i) { *m_streamOut << ""; } } } *m_streamOut << "\n"; return true; #else return false; #endif } #ifdef ALLOW_TABLE static uint getColumnWidths( const Table& table, TQMemArray& widthArray, int firstRowNumber ) { bool uniqueColumns = true; // We have not found any horizontally spanned cells yet. uint currentColumn = 0; int tryingRow = firstRowNumber; // We are trying the first row TQValueList::ConstIterator itCell; for ( itCell = table.cellList.begin(); itCell != table.cellList.end(); ++itCell ) { kdDebug(30518) << "Column: " << (*itCell).col << " (Row: " << (*itCell).row << ")" << endl; if ( (*itCell).row != tryingRow ) { if ( uniqueColumns ) { // We had a full row without any horizontally spanned cell, so we have the needed data return currentColumn; } else { // No luck in the previous row, so now try this new one tryingRow = (*itCell).row; uniqueColumns = true; currentColumn = 0; } } if ( (*itCell).m_cols > 1 ) { // We have a horizontally spanned cell uniqueColumns = false; // Do not waste the time to calculate the width continue; } const double width = ( (*itCell).frame.right - (*itCell).frame.left ); if ( currentColumn >= widthArray.size() ) widthArray.resize( currentColumn + 4, TQGArray::SpeedOptim); widthArray.at( currentColumn ) = width; ++currentColumn; } // If we are here, it can be: // - the table is either empty or there is not any row without horizontally spanned cells // - we have needed the last row for getting something usable return uniqueColumns ? currentColumn : 0; } #endif #ifdef ALLOW_TABLE static uint getFirstRowColumnWidths( const Table& table, TQMemArray& widthArray, int firstRowNumber ) // Get the column widths only by the first row. // This is used when all table rows have horizontally spanned cells. { uint currentColumn = 0; TQValueList::ConstIterator itCell; for ( itCell = table.cellList.begin(); itCell != table.cellList.end(); ++itCell ) { kdDebug(30518) << "Column: " << (*itCell).col << " (Row: " << (*itCell).row << ")" << endl; if ( (*itCell).row != firstRowNumber ) break; // We have finished the first row int cols = (*itCell).m_cols; if ( cols < 1) cols = 1; // ### FIXME: the columns behind a larger cell do not need to be symmetrical const double width = ( (*itCell).frame.right - (*itCell).frame.left ) / cols; if ( currentColumn + cols > widthArray.size() ) widthArray.resize( currentColumn + 4, TQGArray::SpeedOptim); for ( int i = 0; i < cols; ++i ) { widthArray.at( currentColumn ) = width; ++currentColumn; } } return currentColumn; } #endif bool OOWriterWorker::makeTable( const FrameAnchor& anchor, const AnchorType anchorType ) { #ifdef ALLOW_TABLE // Be careful that while being similar the following 5 strings have different purposes const TQString automaticTableStyle ( makeAutomaticStyleName( "Table", m_tableNumber ) ); // It also increases m_tableNumber const TQString tableName( TQString( "Table" ) + TQString::number( m_tableNumber ) ); // m_tableNumber was already increased const TQString translatedName( i18n( "Object name", "Table %1").arg( m_tableNumber ) ); const TQString automaticFrameStyle ( makeAutomaticStyleName( "TableFrame", m_textBoxNumber ) ); // It also increases m_textBoxNumber const TQString translatedFrameName( i18n( "Object name", "Table Frame %1").arg( m_textBoxNumber ) ); kdDebug(30518) << "Processing table " << anchor.key.toString() << " => " << tableName << endl; const TQValueList::ConstIterator firstCell ( anchor.table.cellList.begin() ); if ( firstCell == anchor.table.cellList.end() ) { kdError(30518) << "Table has not any cell!" << endl; return false; } const int firstRowNumber = (*firstCell).row; kdDebug(30518) << "First row: " << firstRowNumber << endl; TQMemArray widthArray(4); uint numberColumns = getColumnWidths( anchor.table, widthArray, firstRowNumber ); if ( numberColumns <= 0 ) { kdDebug(30518) << "Could not get correct column widths, so approximating" << endl; // There was a problem, the width array cannot be trusted, so try to do a column width array with the first row numberColumns = getFirstRowColumnWidths( anchor.table, widthArray, firstRowNumber ); if ( numberColumns <= 0 ) { // Still not right? Then it is an error! kdError(30518) << "Cannot get column widths of table " << anchor.key.toString() << endl; return false; } } kdDebug(30518) << "Number of columns: " << numberColumns << endl; double tableWidth = 0.0; // total width of table uint i; // We need the loop variable 2 times for ( i=0; i < numberColumns; ++i ) { tableWidth += widthArray.at( i ); } kdDebug(30518) << "Table width: " << tableWidth << endl; // An inlined table, is an "as-char" text-box *m_streamOut << "\n"; *m_streamOut << "\n"; // Now we have enough information to generate the style for the table and its frame kdDebug(30518) << "Creating automatic frame style: " << automaticFrameStyle /* << " key: " << styleKey */ << endl; m_contentAutomaticStyles += " ::ConstIterator itCell; ulong columnNumber = 0L; for ( i=0; i < numberColumns; ++i ) { const TQString automaticColumnStyle ( makeAutomaticStyleName( tableName + ".Column", columnNumber ) ); kdDebug(30518) << "Creating automatic column style: " << automaticColumnStyle /* << " key: " << styleKey */ << endl; m_contentAutomaticStyles += " \n"; } makeTableRows( tableName, anchor.table, firstRowNumber ); *m_streamOut << "\n"; *m_streamOut << ""; // End of inline #endif return true; } bool OOWriterWorker::makePicture( const FrameAnchor& anchor, const AnchorType anchorType ) { kdDebug(30518) << "New picture: " << anchor.picture.koStoreName << " , " << anchor.picture.key.toString() << endl; const TQString koStoreName(anchor.picture.koStoreName); TQByteArray image; TQString strExtension(koStoreName.lower()); const int result=koStoreName.findRev("."); if (result>=0) { strExtension=koStoreName.mid(result+1); } bool isImageLoaded=false; if (strExtension=="png") { isImageLoaded=loadSubFile(koStoreName,image); } else if ((strExtension=="jpg") || (strExtension=="jpeg")) { isImageLoaded=loadSubFile(koStoreName,image); strExtension="jpg"; // ### TODO: verify } else if ((strExtension=="tif") || (strExtension=="tiff")) { isImageLoaded=loadSubFile(koStoreName,image); strExtension="tif"; // ### TODO: verify } else if ((strExtension=="gif") || (strExtension=="wmf")) // ### TODO: Which other image formats does OOWriter support directly? { isImageLoaded=loadSubFile(koStoreName,image); } else { // All other picture types must be converted to PNG isImageLoaded=loadAndConvertToImage(koStoreName,strExtension,"PNG",image); strExtension="png"; } if (!isImageLoaded) { kdWarning(30518) << "Unable to load picture: " << koStoreName << endl; return true; } kdDebug(30518) << "Picture loaded: " << koStoreName << endl; double height = 0.0; double width = 0.0; if ( anchorType == AnchorTextImage ) { // Text image have no frameset, so the only size information is in the picture itself. TQBuffer buffer( image.copy() ); // Be more safe than sorry and do not allow shallow copy KoPicture pic; buffer.open( IO_ReadOnly ); if ( pic.load( TQT_TQIODEVICE(&buffer), strExtension ) ) { const TQSize size ( pic.getOriginalSize() ); height = size.height(); width = size.width(); } else { kdWarning(30518) << "Could not load KoPicture: " << koStoreName << endl; } buffer.close(); } else { // Use frame size height=anchor.frame.bottom - anchor.frame.top; width =anchor.frame.right - anchor.frame.left; } if ( height < 1.0 ) { kdWarning(30518) << "Silly height for " << koStoreName << " : " << height << endl; height = 72.0; } if ( width < 1.0 ) { kdWarning(30518) << "Silly width for " << koStoreName << " : " << width << endl; width = 72.0; } // We need a 32 digit hex value of the picture number // Please note: it is an exact 32 digit value, truncated if the value is more than 512 bits wide. :-) TQString number; number.fill('0',32); number += TQString::number(++m_pictureNumber,16); // in hex TQString ooName("Pictures/"); ooName += number.right(32); ooName += '.'; ooName += strExtension; kdDebug(30518) << "Picture " << koStoreName << " => " << ooName << endl; // TODO: we are only using the filename, not the rest of the key // TODO: (bad if there are two images of the same name, but of a different key) *m_streamOut << ""; // NO end of line! if (m_zip) { #if 0 // ### FIXME Why is the following line not working (at least with KDE 3.1)? (It makes unzip having problems with meta.xml) m_zip->writeFile(ooName,TQString(), TQString(), image.size(), image.data()); #else zipPrepareWriting(ooName); zipWriteData( image ); zipDoneWriting(); #endif } return true; } void OOWriterWorker::processNormalText ( const TQString ¶Text, const TextFormatting& formatLayout, const FormatData& formatData) { // Retrieve text and escape it (and necessary space, tabs and line-break tags) const TQString partialText( escapeOOSpan( paraText.mid( formatData.pos, formatData.len ) ) ); if (formatData.text.missing) { // It's just normal text, so we do not need a element! *m_streamOut << partialText; } else { // Text with properties, so use a element! *m_streamOut << "::ConstIterator it ( m_mapTextStyleKeys.find(styleKey) ); kdDebug(30518) << "Searching text key: " << styleKey << endl; TQString automaticStyle; if (it==m_mapTextStyleKeys.end()) { // We have not any match, so we need a new automatic text style automaticStyle=makeAutomaticStyleName("T", m_automaticTextStyleNumber); kdDebug(30518) << "Creating automatic text style: " << automaticStyle << " key: " << styleKey << endl; m_mapTextStyleKeys[styleKey]=automaticStyle; m_contentAutomaticStyles += " " << partialText << ""; } } void OOWriterWorker::processFootnote( const VariableData& variable ) { // Footnote const TQValueList *paraList = variable.getFootnotePara(); if( paraList ) { const TQString value ( variable.getFootnoteValue() ); //const bool automatic = formatData.variable.getFootnoteAuto(); const bool flag = variable.getFootnoteType(); if ( flag ) { *m_streamOut << ""; *m_streamOut << "" << escapeOOText( value ) << ""; *m_streamOut << "\n"; doFullAllParagraphs( *paraList ); *m_streamOut << "\n"; *m_streamOut << ""; } else { *m_streamOut << ""; *m_streamOut << "" << escapeOOText( value ) << ""; *m_streamOut << "\n"; doFullAllParagraphs( *paraList ); *m_streamOut << "\n"; *m_streamOut << ""; } } } void OOWriterWorker::processNote( const VariableData& variable ) { // KWord 1.3's annotations are anonymous and undated, // however the OO specification tells that author and date are mandatory (even if OOWriter 1.1 consider them optional) *m_streamOut << "\n"; *m_streamOut << "" << escapeOOSpan( variable.getGenericData( "note" ) ) << "\n" << ""; } void OOWriterWorker::processVariable ( const TQString&, const TextFormatting& /*formatLayout*/, const FormatData& formatData) { if (0==formatData.variable.m_type) { *m_streamOut << ""; // ### TODO: parameters } else if (2==formatData.variable.m_type) { *m_streamOut << ""; // ### TODO: parameters } else if (4==formatData.variable.m_type) { // ### TODO: the other under-types, other parameters if (formatData.variable.isPageNumber()) { *m_streamOut << ""; } else if (formatData.variable.isPageCount()) { *m_streamOut << ""; } else { // Unknown subtype, therefore write out the result *m_streamOut << formatData.variable.m_text; } } else if (9==formatData.variable.m_type) { // A link *m_streamOut << "" << escapeOOText(formatData.variable.getLinkName()) << ""; } else if ( 10 == formatData.variable.m_type ) { // Note (OOWriter: annotation) processNote ( formatData.variable ); } else if (11==formatData.variable.m_type) { // Footnote processFootnote ( formatData.variable ); } else { // Generic variable *m_streamOut << formatData.variable.m_text; } } void OOWriterWorker::processAnchor ( const TQString&, const TextFormatting& /*formatLayout*/, //TODO const FormatData& formatData) { // We have a picture or a table if ( (2==formatData.frameAnchor.type) // or || (5==formatData.frameAnchor.type) ) // { makePicture( formatData.frameAnchor, AnchorInlined ); } else if (6==formatData.frameAnchor.type) { makeTable( formatData.frameAnchor, AnchorInlined ); } else { kdWarning(30518) << "Unsupported anchor type: " << formatData.frameAnchor.type << endl; } } void OOWriterWorker::processTextImage ( const TQString&, const TextFormatting& /*formatLayout*/, const FormatData& formatData) { kdDebug(30518) << "Text Image: " << formatData.frameAnchor.key.toString() << endl; makePicture( formatData.frameAnchor, AnchorTextImage ); } void OOWriterWorker::processParagraphData ( const TQString ¶Text, const TextFormatting& formatLayout, const ValueListFormatData ¶FormatDataList) { if ( paraText.length () > 0 ) { ValueListFormatData::ConstIterator paraFormatDataIt; for ( paraFormatDataIt = paraFormatDataList.begin (); paraFormatDataIt != paraFormatDataList.end (); paraFormatDataIt++ ) { if (1==(*paraFormatDataIt).id) { processNormalText(paraText, formatLayout, (*paraFormatDataIt)); } else if (2==(*paraFormatDataIt).id) { processTextImage(paraText, formatLayout, (*paraFormatDataIt)); } else if ( 3 == (*paraFormatDataIt).id ) { // Just a (KWord 0.8) tab stop, nothing else to do! *m_streamOut << ""; } else if (4==(*paraFormatDataIt).id) { processVariable(paraText, formatLayout, (*paraFormatDataIt)); } else if (6==(*paraFormatDataIt).id) { processAnchor(paraText, formatLayout, (*paraFormatDataIt)); } else if ( 1001 == (*paraFormatDataIt).id ) // Start of bookmark { *m_streamOut << ""; } else if ( 1002 == (*paraFormatDataIt).id ) // End of bookmark { *m_streamOut << ""; } } } } TQString OOWriterWorker::layoutToParagraphStyle(const LayoutData& layoutOrigin, const LayoutData& layout, const bool force, TQString& styleKey) { TQString props; // Props has to remain empty, if there is no difference. styleKey += layout.styleName; styleKey += ','; if (force || (layoutOrigin.alignment!=layout.alignment)) { // NOTE: OO 1.0.x uses start and end like left and right (section 3.11.4) // Unfortunately in XSL-FO's text-align, they are really supposed to be the start and the end. if (layout.alignment == "left") { props += "fo:text-align=\"start\" "; styleKey += 'L'; } else if (layout.alignment == "right") { props += "fo:text-align=\"end\" "; styleKey += 'R'; } else if (layout.alignment == "center") { props += "fo:text-align=\"center\" "; styleKey += 'C'; } else if (layout.alignment == "justify") { props += "fo:text-align=\"justify\" "; styleKey += 'J'; } else if (layout.alignment == "auto") { props += "fo:text-align=\"start\" "; #ifndef STRICT_OOWRITER_VERSION_1 props += "style:text-auto-align=\"true\" "; // rejected draft OASIS extension #endif styleKey += 'A'; } else { kdWarning(30518) << "Unknown alignment: " << layout.alignment << endl; } } styleKey += ','; if ((layout.indentLeft>=0.0) && (force || (layoutOrigin.indentLeft!=layout.indentLeft))) { props += TQString("fo:margin-left=\"%1pt\" ").arg(layout.indentLeft); styleKey += TQString::number(layout.indentLeft); } styleKey += ','; if ((layout.indentRight>=0.0) && (force || (layoutOrigin.indentRight!=layout.indentRight))) { props += TQString("fo:margin-right=\"%1pt\" ").arg(layout.indentRight); styleKey += TQString::number(layout.indentRight); } styleKey += ','; if (force || (layoutOrigin.indentLeft!=layout.indentLeft)) { props += "fo:text-indent=\""; props += TQString::number(layout.indentFirst); props += "\" "; styleKey += TQString::number(layout.indentFirst); } styleKey += ','; if ((layout.marginBottom>=0.0) && ( force || ( layoutOrigin.marginBottom != layout.marginBottom ) ) ) { props += TQString("fo:margin-bottom=\"%1pt\" ").arg(layout.marginBottom); styleKey += TQString::number(layout.marginBottom); } styleKey += ','; if ((layout.marginTop>=0.0) && ( force || ( layoutOrigin.marginTop != layout.marginTop ) ) ) { props += TQString("fo:margin-top=\"%1pt\" ").arg(layout.marginTop); styleKey += TQString::number(layout.marginTop); } styleKey += ','; if (force || ( layoutOrigin.lineSpacingType != layout.lineSpacingType ) || ( layoutOrigin.lineSpacing != layout.lineSpacing ) ) { switch ( layout.lineSpacingType ) { case LayoutData::LS_CUSTOM: { // We have a custom line spacing (in points) const TQString height ( TQString::number(layout.lineSpacing) ); // ### TODO: rounding? props += "style:line-spacing=\""; props += height; props += "pt\" "; styleKey += height; styleKey += 'C'; break; } case LayoutData::LS_SINGLE: { props += "fo:line-height=\"normal\" "; // One styleKey += "100%"; // One break; } case LayoutData::LS_ONEANDHALF: { props += "fo:line-height=\"150%\" "; // One-and-half styleKey += "150%"; break; } case LayoutData::LS_DOUBLE: { props += "fo:line-height=\"200%\" "; // Two styleKey += "200%"; break; } case LayoutData::LS_MULTIPLE: { // OOWriter 1.1 only allows up to 200% const TQString mult ( TQString::number( tqRound( layout.lineSpacing * 100 ) ) ); props += "fo:line-height=\""; props += mult; props += "%\" "; styleKey += mult; styleKey += "%"; break; } case LayoutData::LS_FIXED: { // We have a fixed line height (in points) const TQString height ( TQString::number(layout.lineSpacing) ); // ### TODO: rounding? props += "fo:line-height=\""; props += height; props += "pt\" "; styleKey += height; styleKey += 'F'; break; } case LayoutData::LS_ATLEAST: { // We have a at-least line height (in points) const TQString height ( TQString::number(layout.lineSpacing) ); // ### TODO: rounding? props += "style:line-height-at-least=\""; props += height; props += "pt\" "; styleKey += height; styleKey += 'A'; break; } default: { kdWarning(30518) << "Unsupported lineSpacingType: " << layout.lineSpacingType << " (Ignoring!)" << endl; break; } } } styleKey += ','; if ( layout.pageBreakBefore ) { // We have a page break before the paragraph props += "fo:page-break-before=\"page\" "; styleKey += 'B'; } styleKey += ','; if ( layout.pageBreakAfter ) { // We have a page break after the paragraph props += "fo:page-break-after=\"page\" "; styleKey += 'A'; } styleKey += '@'; // A more visible seperator props += textFormatToStyle(layoutOrigin.formatData.text,layout.formatData.text,force,styleKey); props += ">"; styleKey += '@'; // A more visible seperator // ### TODO/FIXME: what if all tabulators must be erased? if (!layout.tabulatorList.isEmpty() && (force || (layoutOrigin.tabulatorList!=layout.tabulatorList) )) { props += "\n \n"; TabulatorList::ConstIterator it; TabulatorList::ConstIterator end(layout.tabulatorList.end()); for (it=layout.tabulatorList.begin();it!=end;++it) { props+=" ::ConstIterator it ( m_mapParaStyleKeys.find(styleKey) ); kdDebug(30518) << "Searching paragraph key: " << styleKey << endl; TQString automaticStyle; if (it==m_mapParaStyleKeys.end()) { // We have additional properties, so we need an automatic style for the paragraph automaticStyle = makeAutomaticStyleName("P", m_automaticParagraphStyleNumber); kdDebug(30518) << "Creating automatic paragraph style: " << automaticStyle << " key: " << styleKey << endl; m_mapParaStyleKeys[styleKey]=automaticStyle; m_contentAutomaticStyles += " "; processParagraphData(paraText, layout.formatData.text, paraFormatDataList); if (header) *m_streamOut << "\n"; else *m_streamOut << "\n"; return true; } bool OOWriterWorker::doOpenStyles(void) { m_styles += " \n"; m_styles += " \n"; // ### TODO: what if Graphics is a normal style m_styles += " \n"; m_styles += " \n"; m_styles += " \n"; // ### TODO: what if Frame is a normal style m_styles += " \n"; m_styles += " \n"; return true; } bool OOWriterWorker::doFullDefineStyle(LayoutData& layout) { //Register style in the style map m_styleMap[layout.styleName]=layout; m_styles += " PG_LAST_FORMAT ) ) { // Bad or unknown format, so assume ISO A4 newFormat = PG_DIN_A4; } m_paperWidth = KoPageFormat::width ( newFormat, KoOrientation( orientation ) ) * 72.0 / 25.4 ; m_paperHeight = KoPageFormat::height ( newFormat, KoOrientation( orientation ) ) * 72.0 / 25.4 ; m_paperFormat = newFormat; } else { m_paperFormat=format; m_paperWidth=width; m_paperHeight=height; } m_paperOrientation=orientation; // ### TODO: check if OOWriter needs the orignal size (without landscape) or the real size return true; } bool OOWriterWorker::doFullPaperBorders (const double top, const double left, const double bottom, const double right) { m_paperBorderTop=top; m_paperBorderLeft=left; m_paperBorderBottom=bottom; m_paperBorderRight=right; return true; } bool OOWriterWorker::doFullPaperFormatOther ( const int columns, const double columnspacing, const int numPages ) { m_columns = columns; m_columnspacing = columnspacing; m_numPages = numPages; return true; } bool OOWriterWorker::doFullDocumentInfo(const KWEFDocumentInfo& docInfo) { m_docInfo=docInfo; return true; } bool OOWriterWorker::doVariableSettings(const VariableSettingsData& vs) { m_varSet=vs; return true; } bool OOWriterWorker::doDeclareNonInlinedFramesets( TQValueList& pictureAnchors, TQValueList& tableAnchors ) { m_nonInlinedPictureAnchors = pictureAnchors; m_nonInlinedTableAnchors = tableAnchors; return true; } void OOWriterWorker::declareFont(const TQString& fontName) { if (fontName.isEmpty()) return; if (m_fontNames.find(fontName)==m_fontNames.end()) { TQString props; // Disabled, as TQFontInfo::styleHint() cannot guess #if 0 TQFont font(fontName); TQFontInfo info(font); props+="style:font-family-generic=\"" switch (info.styleHint()) { case TQFont::SansSerif: default: { props += "swiss"; break; } case TQFont::Serif: { props += "roman"; break; } case TQFont::Courier: { props += "modern"; break; } case TQFont::OldEnglish: { props += "decorative"; break; } } props +="\" "; #endif props +="style:font-pitch=\"variable\""; // ### TODO: check if font is variable or fixed // New font, so register it m_fontNames[fontName]=props; } } TQString OOWriterWorker::makeAutomaticStyleName(const TQString& prefix, ulong& counter) const { const TQString str (prefix + TQString::number(++counter,10)); // Checks if the automatic style has not the same name as a user one. // If it is the case, change it! if (m_styleMap.find(str)==m_styleMap.end()) return str; // Unique, so let's go! TQString str2(str+"_bis"); if (m_styleMap.find(str2)==m_styleMap.end()) return str2; str2 = str+"_ter"; if (m_styleMap.find(str2)==m_styleMap.end()) return str2; // If it is still not unique, try a time stamp. const TQDateTime dt(TQDateTime::currentDateTime(Qt::UTC)); str2 = str + "_" + TQString::number(dt.toTime_t(),16); if (m_styleMap.find(str2)==m_styleMap.end()) return str2; kdWarning(30518) << "Could not make an unique style name: " << str2 << endl; return str2; // Still return, as we have nothing better }