/* This file is part of the KDE project Copuright (C) 2001 Michael Johnson Copyright (C) 2001, 2002, 2003, 2004 Nicolas GOUTTE Copyright (C) 2002 Ariya Hidayat 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ExportFilter.h" // 1 twip = 1/20 pt = 1/1400 inch // 1 inch = 25.4 mm // some conversion macros #define TWIP_TO_MM(x) (x)*25.4/1440.0 #define MM_TO_TWIP(x) (x)*1440.0/25.4 #define PT_TO_TWIP(x) (x)*20 #define TWIP_TO_PT(x) (x)/20 // map KWord field name to RTF field name // e.g authorName -> AUTHOR static TQString mapFieldName( const TQString& kwordField ) { TQString rtfField; if( kwordField == "fileName" ) rtfField = "FILENAME"; else if( kwordField == "authorName" ) rtfField = "AUTHOR"; else if( kwordField == "docTitle" ) rtfField = "TITLE"; return rtfField; } RTFWorker::RTFWorker(): m_ioDevice(NULL), m_streamOut(NULL), m_eol("\r\n"), m_inTable(false), m_paperOrientation(false), m_paperWidth(20), m_paperHeight(20), m_paperMarginTop(72), m_paperMarginLeft(72), m_paperMarginBottom(72), m_paperMarginRight(72), m_startPageNumber(1) { } static TQString WritePositiveKeyword(const TQString& keyword, const int value) { TQString str; str += keyword; if (value>0) // The value of the keyword cannot be negative str += TQString::number( value ); else str += '0'; return str; } TQString RTFWorker::writeRow(const TQString& textCellHeader, const TQString& rowText, const FrameData& frame) { TQString row; row += "\\trowd\\trgaph60\\trql"; // start new row row += WritePositiveKeyword("\\trrh", tqRound(PT_TO_TWIP(frame.minHeight))); row += WritePositiveKeyword("\\trleft", tqRound(PT_TO_TWIP(frame.left) - m_paperMarginLeft)); //row += "\\trautofit0"; // ### VERIFY row += textCellHeader; row += " "; // End of keyword row += rowText; return row; } TQString RTFWorker::writeBorder(const char whichBorder, const int borderWidth, const TQColor& color) { TQString str; if (borderWidth > 0) { str += "\\clbrdr"; // Define border str += whichBorder; // t=top, l=left, b=bottom, r=right str += "\\brdrs\\brdrw"; // Single border; thickness str += TQString::number(borderWidth); if (color.isValid()) { str += lookupColor("\\brdrcf",color); } } return str; } TQString RTFWorker::makeTable(const FrameAnchor& anchor) { TQString textBody; // Text to be returned textBody += m_prefix; m_prefix = TQString(); TQString rowText; int rowCurrent = 0; bool firstCellInRow = true; FrameData firstFrameData; int debugCellCurrent = 0; //DEBUG int debugRowCurrent = 0; //DEBUG TQString textCellHeader; // const bool oldInTable = m_inTable; m_inTable = true; TQValueList::ConstIterator itCell; for (itCell=anchor.table.cellList.begin(); itCell!=anchor.table.cellList.end(); itCell++) { // ### TODO: rowspan, colspan if (rowCurrent!=(*itCell).row) { rowCurrent = (*itCell).row; textBody += writeRow( textCellHeader, rowText, firstFrameData); textBody += "\\row"; textBody += m_eol; rowText = TQString(); textCellHeader = TQString(); firstCellInRow=true; debugRowCurrent ++; // DEBUG debugCellCurrent = 0; //DEBUG } const FrameData& frame = (*itCell).frame; if (firstCellInRow) // Not yet set, so set the position of the left border. { firstFrameData=frame; firstCellInRow=false; } kdDebug(30515) << "Cell: " << debugRowCurrent << "," << debugCellCurrent << " left: " << frame.left << " right: " << frame.right << " top: " << frame.top << " bottom " << frame.bottom << endl; textCellHeader += writeBorder('t',tqRound(PT_TO_TWIP(frame.tWidth)),frame.tColor); textCellHeader += writeBorder('l',tqRound(PT_TO_TWIP(frame.lWidth)),frame.lColor); textCellHeader += writeBorder('b',tqRound(PT_TO_TWIP(frame.bWidth)),frame.bColor); textCellHeader += writeBorder('r',tqRound(PT_TO_TWIP(frame.rWidth)),frame.rColor); textCellHeader += WritePositiveKeyword("\\cellx",tqRound(PT_TO_TWIP(frame.right) - m_paperMarginRight)); //right border of cell TQString endOfParagraph; TQValueList *paraList = (*itCell).paraList; TQValueList::ConstIterator it; TQValueList::ConstIterator end(paraList->end()); for (it=paraList->begin();it!=end;++it) { rowText += endOfParagraph; rowText += ProcessParagraphData( (*it).text,(*it).layout,(*it).formattingList); rowText += m_eol; endOfParagraph = "\\par"; // The problem is that the last paragraph ends with \cell not with \par } rowText += "\\cell"; debugCellCurrent ++; // DEBUG } textBody += writeRow( textCellHeader, rowText, firstFrameData); textBody += "\\row\\pard"; // delimit last row textBody += m_eol; m_inTable = oldInTable; return textBody; } TQString RTFWorker::makeImage(const FrameAnchor& anchor) { TQString textBody; // Text to be returned TQString strImageName(anchor.picture.koStoreName); TQString strExt; TQByteArray image; kdDebug(30515) << "RTFWorker::makeImage" << endl << anchor.picture.koStoreName << endl; const int pos=strImageName.findRev('.'); if(pos!=-1) strExt = strImageName.mid(pos+1).lower(); TQString strTag; if (strExt=="png") strTag="\\pngblip"; #if 0 else if (strExt=="bmp") strTag="\\dibitmap"; #endif else if ( (strExt=="jpeg") || (strExt=="jpg") ) strTag="\\jpegblip"; else if (strExt=="wmf") strTag="\\wmetafile8"; // 8 == anisotropic else { // either without extension or format is unknown // let's try to convert it to PNG format kdDebug(30515) << "Converting image " << anchor.picture.koStoreName << endl; strTag="\\pngblip"; if( !loadAndConvertToImage(anchor.picture.koStoreName,strExt,"PNG",image) ) { kdWarning(30515) << "Unable to convert " << anchor.picture.koStoreName << endl; return TQString(); } } // ### TODO: SVG, TQPicture // load the image, this isn't necessary for converted image if( !image.size() ) if (!loadSubFile(anchor.picture.koStoreName,image)) { kdWarning(30515) << "Unable to load picture " << anchor.picture.koStoreName << endl; return TQString(); } // find displayed width and height (in twips) const long width = (long)(PT_TO_TWIP(anchor.frame.right - anchor.frame.left)); const long height = (long)(PT_TO_TWIP(anchor.frame.bottom - anchor.frame.top)); // find original image width and height (in twips) long origWidth = width; long origHeight = height; if( strExt == "wmf" ) { // special treatment for WMF with metaheader // d7cdc69a is metaheader magic id TQ_UINT8* data = (TQ_UINT8*) image.data(); if( ( data[0] == 0xd7 ) && ( data[1] == 0xcd ) && ( data[2] == 0xc6 ) && ( data[3] == 0x9a ) && ( image.size() > 22 ) ) { // grab bounding box, find original size unsigned left = data[6]+(data[7]<<8); unsigned top = data[8]+(data[9]<<8); unsigned right = data[10]+(data[11]<<8); unsigned bottom = data[12]+(data[13]<<8); origWidth = (long) (MM_TO_TWIP(right-left)/100); origHeight = (long) (MM_TO_TWIP(bottom-top)/100); // throw away WMF metaheader (22 bytes) for( uint i=0; i>4)&0x0f]; // Done this way to avoid signed/unsigned problems textBody += hex[(ch&0x0f)]; } textBody+="}"; return textBody; } TQString RTFWorker::formatTextParagraph(const TQString& strText, const FormatData& formatOrigin, const FormatData& format) { TQString str; if (!format.text.missing) { // Opening elements str+=openSpan(formatOrigin,format); } TQString strEscaped = escapeRtfText(strText); // Replace line feeds by forced line breaks int pos; TQString strBr("\\line "); while ((pos=strEscaped.find(TQChar(10)))>-1) { strEscaped.replace(pos,1,strBr); } str+=strEscaped; if (!format.text.missing) { // Closing elements str+=closeSpan(formatOrigin,format); } return str; } TQString RTFWorker::ProcessParagraphData ( const TQString ¶Text, const LayoutData& layout, const ValueListFormatData ¶FormatDataList) { TQString str; TQString content; TQString markup; // open paragraph markup += "\\pard"; markup += "\\plain"; if (m_inTable) markup += "\\intbl"; //lists if (layout.counter.style) { markup += "{\\pntext\\pard\\plain"; if( layout.formatData.text.fontSize >= 0) { markup += "\\fs"; markup += TQString::number((2 * layout.formatData.text.fontSize)); markup += lookupFont("\\f",layout.formatData.text.fontName); } markup += " "; markup += layout.counter.text; markup += "\\tab}{\\*\\pn"; if (layout.counter.style > 5) { markup += "\\pnlvlblt "; markup += "{\\pntxtb "; if (!layout.counter.lefttext.isEmpty() && layout.counter.lefttext != "{" && layout.counter.lefttext != "}") { markup += layout.counter.lefttext; } switch (layout.counter.style) { case 6: { //custom bullets (one char) //TODO: use correct character/sign for bullet markup += layout.counter.customCharacter; break; } case 7: { //custom bullets (complex) markup += layout.counter.text; break; } case 8: { //circle bullets //TODO: use correct character/sign for bullet markup += "\\bullet"; break; } case 9: { //square bullets //TODO: use correct character/sign for bullet markup += "\\bullet"; break; } case 10: { //disc bullets //TODO: make work in OO markup += "\\bullet"; break; } case 11: { //disc bullets //TODO: use correct character/sign for bullet markup += layout.counter.text; break; } default: markup += "\\bullet"; } markup += "}"; } else { if (layout.counter.numbering!=0) { markup += "\\pnlvl"; markup += TQString::number(layout.counter.depth + 1); markup += "\\pnprev1"; } else if (layout.counter.style==1) { markup += "\\pnlvlbody"; } else { markup += "\\pnlvl"; markup += TQString::number(11 - layout.counter.style); } switch (layout.counter.style) { case 1: { markup += "\\pndec"; break; } case 2: { markup += "\\pnlcltr"; break; } case 3: { markup += "\\pnucltr"; break; } case 4: { markup += "\\pnlcrm"; break; } case 5: { markup += "\\pnucrm"; break; } default: markup += "\\pndec"; } markup += "{\\pntxtb "; markup += layout.counter.lefttext; markup += " }"; } markup += "{\\pntxta "; markup += layout.counter.righttext; markup += " }"; // ### FIXME: that is too late! And why at every list paragraph? (See bug #88241) if (layout.counter.start!=0) { markup += "\\pnstart"; markup += TQString::number(layout.counter.start); } else { markup += "\\pnstart1"; } markup += "\\pnindent0\\pnhang"; if( layout.formatData.text.fontSize > 0 ) { markup += "\\pnfs"; markup += TQString::number((2 * layout.formatData.text.fontSize)); } if( !layout.formatData.text.fontName.isEmpty() ) { markup += lookupFont("\\pnf", layout.formatData.text.fontName); } markup += "}"; } LayoutData styleLayout; markup += lookupStyle(layout.styleName, styleLayout); markup += layoutToRtf(styleLayout,layout,true); if ( 1==layout.formatData.text.verticalAlignment ) { markup += "\\sub"; //Subscript } else if ( 2==layout.formatData.text.verticalAlignment ) { markup += "\\super"; //Superscript } if (layout.pageBreakBefore) content += "\\page "; if (!paraText.isEmpty()) { ValueListFormatData::ConstIterator paraFormatDataIt; TQString partialText; FormatData formatRef = layout.formatData; for ( paraFormatDataIt = paraFormatDataList.begin (); paraFormatDataIt != paraFormatDataList.end (); paraFormatDataIt++ ) { if (1==(*paraFormatDataIt).id) { //Retrieve text partialText=paraText.mid ( (*paraFormatDataIt).pos, (*paraFormatDataIt).len ); content +=formatTextParagraph(partialText, formatRef, *paraFormatDataIt); } else if (4==(*paraFormatDataIt).id) { // ### TODO: put date/time fields into own method. if ( (0==(*paraFormatDataIt).variable.m_type) // date || (2==(*paraFormatDataIt).variable.m_type) ) // time { // ### TODO: fixed/variable date content += "{\\field{\\*\\fldinst "; if (0==(*paraFormatDataIt).variable.m_type) content += "DATE "; else content += "TIME "; TQString key((*paraFormatDataIt).variable.m_key.mid(4)); kdDebug(30515) << "Time format: " << key << endl; if (key.startsWith("locale")) { if (key == "locale" ) { if (0==(*paraFormatDataIt).variable.m_type) key = TDEGlobal::locale()->dateFormat(); else key = TDEGlobal::locale()->timeFormat(); } else if ( key == "localeshort" ) key = TDEGlobal::locale()->dateFormatShort(); else if ( key == "localedatetime" ) { key = TDEGlobal::locale()->dateFormat(); key += ' '; key += TDEGlobal::locale()->timeFormat(); } else if ( key == "localedatetimeshort" ) { key = TDEGlobal::locale()->dateFormat(); key += ' '; key += TDEGlobal::locale()->timeFormat(); } kdDebug(30515) << "Locale date in TDELocale format: " << key << endl; // TDELocale's key differ from KWord // Date key.replace( "%Y", "yyyy" ); // Year 4 digits key.replace( "%y", "yy" ); // Year 2 digits key.replace( "%n", "M" ); // Month 1 digit key.replace( "%m", "MM" ); // Month 2 digits key.replace( "%e", "d" ); // Day 1 digit key.replace( "%d", "dd" ); // Day 2 digits key.replace( "%b", "MMM" ); // Month 3 letters key.replace( "%B", "MMMM" ); // Month all letters key.replace( "%a", "ddd" ); // Day 3 letters key.replace( "%A", "dddd" ); // Day all letters // 12h key.replace( "%p", "am/pm" ); // AM/PM (TDELocale knows it only lower case) key.replace( "%I", "hh" ); // 12 hour 2 digits key.replace( "%l", "h" ); // 12 hour 1 digits // 24h key.replace( "%H", "HH" ); // 24 hour 2 digits key.replace( "%k", "H" ); // 24 hour 1 digit // Other times key.replace( "%M", "mm" ); // minute 2 digits (TDELocale knows it with 2 digits) key.replace( "%S", "ss" ); // second 2 digits (TDELocale knows it with 2 digits) kdDebug(30515) << "Locale date in RTF format: " << key << endl; } else if (!key.isEmpty()) { const TQRegExp regexp("AP",false); // Not case-sensitive if (regexp.search(key)!=-1) { // 12h key.replace("ap","am/pm"); key.replace("AP","AM/PM"); } else { //24h key.replace('h','H'); // MS Word uses H for 24hour times } // MS Word do not know possesive months key.replace("PPP","MMM"); key.replace("PPPP","MMMM"); key.replace("zzz","000"); // replace microseconds by 000 kdDebug(30515) << "New format: " << key << endl; content += "\\@ \""; content += key; content += "\" "; } content += "\\* MERGEFORMAT }{\\fldrslt "; content += escapeRtfText((*paraFormatDataIt).variable.m_text); content += "}}"; } else if (4==(*paraFormatDataIt).variable.m_type) { TQString strFieldType; if ((*paraFormatDataIt).variable.isPageNumber()) { content += "{\\field{\\*\\fldinst PAGE \\* MERGEFORMAT }{\\fldrslt "; content += escapeRtfText((*paraFormatDataIt).variable.m_text); content += "}}"; } else if ((*paraFormatDataIt).variable.isPageCount()) { content += "{\\field{\\*\\fldinst NUMPAGES \\* MERGEFORMAT }{\\fldrslt "; content += escapeRtfText((*paraFormatDataIt).variable.m_text); content += "}}"; } else { // Unknown subtype, therefore write out the result content += escapeRtfText((*paraFormatDataIt).variable.m_text); } } else if (8==(*paraFormatDataIt).variable.m_type) { // Field TQString name = escapeRtfText((*paraFormatDataIt).variable.getFieldName()); TQString value = escapeRtfText((*paraFormatDataIt).variable.getFieldValue()); TQString rtfField = mapFieldName(name); if( rtfField.isEmpty() ) // Couldn't map field name, just write out the value content += escapeRtfText((*paraFormatDataIt).variable.m_text); else { content += "{\\field"; content += "{\\*\\fldinst "; content += rtfField; content += " \\* MERGEFORMAT }"; content += "{\\fldrslt {"; content += value; content += "}}}"; } } else if (9==(*paraFormatDataIt).variable.m_type) { // A link content += "{\\field"; content += "{\\*\\fldinst HYPERLINK "; content += escapeRtfText((*paraFormatDataIt).variable.getHrefName()); content += "}"; content += "{\\fldrslt "; content += "{\\ul"; // underline, ### TODO: use style Hyperlink content += lookupColor("\\cf",TQColor(0,0,255));// blue content += escapeRtfText((*paraFormatDataIt).variable.getLinkName()); content += "}}}"; } else if (11==(*paraFormatDataIt).variable.m_type) { // Footnote TQString value = (*paraFormatDataIt).variable.getFootnoteValue(); bool automatic = (*paraFormatDataIt).variable.getFootnoteAuto(); TQValueList *paraList = (*paraFormatDataIt).variable.getFootnotePara(); if( paraList ) { TQString fstr; TQValueList::ConstIterator it; TQValueList::ConstIterator end(paraList->end()); const TQString prefixSaved = m_prefix; m_prefix = TQString(); for (it=paraList->begin();it!=end;++it) fstr += ProcessParagraphData( (*it).text, (*it).layout,(*it).formattingList); m_prefix = prefixSaved; content += "{\\super "; content += automatic ? "\\chftn " : value; content += "{\\footnote "; content += "{\\super "; content += automatic ? "\\chftn " : value; content += fstr; content += "}}}"; } } else { // Generic variable content += escapeRtfText((*paraFormatDataIt).variable.m_text); } } else if (6==(*paraFormatDataIt).id) { kdDebug(30515) << "Found an anchor of type: " << (*paraFormatDataIt).frameAnchor.type << endl; // We have an image, a clipart or a table if (6==(*paraFormatDataIt).frameAnchor.type) { if (!content.isEmpty()) { str += m_prefix; str += markup; str += " {"; str += content; str += "}"; str += m_eol; content = TQString(); if (!m_inTable) { m_prefix = "\\par"; } } str += makeTable((*paraFormatDataIt).frameAnchor); } else if ((2==(*paraFormatDataIt).frameAnchor.type) || (5==(*paraFormatDataIt).frameAnchor.type)) { content += makeImage((*paraFormatDataIt).frameAnchor); } } } } if (layout.pageBreakAfter) content += "\\page"; if (!content.isEmpty()) { str += m_prefix; str += markup; str += " {"; str += content; str += "}"; str += m_eol; if (m_inTable==false) { m_prefix = "\\par"; } } if (str.isEmpty()) { str ="\\par\\pard\\plain"; if (m_inTable==false) { m_prefix = "\\par"; } } return str; } bool RTFWorker::doFullParagraph(const TQString& paraText, const LayoutData& layout, const ValueListFormatData& paraFormatDataList) { kdDebug(30515) << "Entering RTFWorker::doFullParagraph" << endl << paraText << endl; TQString par = ProcessParagraphData( paraText, layout, paraFormatDataList); m_textBody += par; kdDebug(30515) << "Quiting RTFWorker::doFullParagraph" << endl; return true; } bool RTFWorker::doHeader(const HeaderData& header) { TQString str; TQString content; if( header.page == HeaderData::PAGE_ODD ) str = "\\facingp{\\headerr"; else if( header.page == HeaderData::PAGE_EVEN ) str = "\\facingp{\\headerl"; else if( header.page == HeaderData::PAGE_FIRST ) str = "\\facingp{\\headerl"; else if( header.page == HeaderData::PAGE_ALL ) str = "{\\header"; else return false; str += " {"; TQValueList::ConstIterator it; TQValueList::ConstIterator end(header.para.end()); for (it=header.para.begin();it!=end;++it) content += ProcessParagraphData( (*it).text,(*it).layout,(*it).formattingList); if (content!="\\par\\pard\\plain") { str += content; str += "}"; str += "}"; m_textBody += str; } m_prefix=TQString(); return true; } bool RTFWorker::doFooter(const FooterData& footer) { TQString str; TQString content; if( footer.page == FooterData::PAGE_ODD ) str = "\\facingp{\\footerr"; else if( footer.page == FooterData::PAGE_EVEN ) str = "\\facingp{\\footerl"; else if( footer.page == FooterData::PAGE_FIRST ) str = "\\facingp{\\headerl"; else if( footer.page == FooterData::PAGE_ALL ) str = "{\\footer"; else return false; str += " {"; TQValueList::ConstIterator it; TQValueList::ConstIterator end(footer.para.end()); for (it=footer.para.begin();it!=end;++it) content += ProcessParagraphData( (*it).text,(*it).layout,(*it).formattingList); if (content!="\\par\\pard\\plain") { str += content; str += "}"; str += "}"; m_textBody += str; } m_prefix=TQString(); return true; } bool RTFWorker::doOpenFile(const TQString& filenameOut, const TQString& /*to*/) { m_ioDevice=TQT_TQIODEVICE(new TQFile(filenameOut)); if (!m_ioDevice) { kdError(30515) << "No output file! Aborting!" << endl; return false; } if ( !m_ioDevice->open (IO_WriteOnly) ) { kdError(30515) << "Unable to open output file!" << endl; return false; } m_streamOut=new TQTextStream(m_ioDevice); // ### TODO: should "CP 1252" be used directly? (But RTFWorker::escapeRtfText is beased on ISO-8859-1 only.) m_streamOut->setEncoding(TQTextStream::Latin1); // We are declaring the RTF document as CP 1252, so use ISO-8859-1 m_fileName=filenameOut; return true; } bool RTFWorker::doCloseFile(void) { kdDebug(30515) << __FILE__ << ":" << __LINE__ << endl; delete m_streamOut; m_streamOut=NULL; if (m_ioDevice) m_ioDevice->close(); return true; } bool RTFWorker::doOpenDocument(void) { // Make the file header // Note: we use \\ansicpg1252 because 1200 is not supposed to be supported // Note2: we assume using \\ansicpg1252 in RTFWorker::escapeRtfText and in RTFWorker::doOpenFile *m_streamOut << "{\\rtf1\\ansi\\ansicpg1252\\uc1\\deff0" << m_eol; // Default color table m_colorList << TQColor(0,0,0) << TQColor(0,0,255) << TQColor(0,255,255) << TQColor(0,255,0) << TQColor(255,0,255) << TQColor(255,0,0) << TQColor(255,255,0) << TQColor(255,255,255) << TQColor(0,0,128) << TQColor(0,128,128) << TQColor(0,128,0) << TQColor(128,0,128) << TQColor(128,0,0) << TQColor(128,128,0) << TQColor(128,128,128); return true; } void RTFWorker::writeFontData(void) { kdDebug(30515) << "Fonts:" << m_fontList << endl; *m_streamOut << "{\\fonttbl"; uint count; TQFontDatabase fontDatabase; TQStringList::ConstIterator it; for (count=0, it=m_fontList.begin(); it!=m_fontList.end(); count++, it++) { const TQString strLower( (*it).lower() ); *m_streamOut << "{\\f" << count; if ( (strLower.find("symbol")>-1) || (strLower.find("dingbat")>-1) ) *m_streamOut << "\\ftech"; else if ( (strLower.find("script")>-1) ) *m_streamOut << "\\fscript"; #if 1 else { // We do not know the font type that we have *m_streamOut << "\\fnil"; } #else else // TQFontInfo:styleHint() does not guess anything, it just returns what is in the TQFont. Nothing put in, nothing gets out. { TQFontInfo info(*it); switch (info.styleHint()) { case TQFont::SansSerif: default: { *m_streamOut << "\\fswiss"; break; } case TQFont::Serif: { *m_streamOut << "\\froman"; break; } case TQFont::Courier: { *m_streamOut << "\\fmodern"; break; } case TQFont::OldEnglish: { *m_streamOut << "\\fdecor"; break; } } } #endif // ### TODO: \fcharset would be mandatory but TQt3 does not give us the font charset. :-( *m_streamOut << "\\fprq" << ( fontDatabase.isFixedPitch( *it ) ? 1 : 2 ) << " "; // font definition *m_streamOut << escapeRtfText( *it ); *m_streamOut << ";}" << m_eol; // end font table entry } *m_streamOut << "}"; // end of font table } void RTFWorker::writeColorData(void) { *m_streamOut << "{\\colortbl;"; uint count; TQValueList::ConstIterator it; for (count=0, it=m_colorList.begin(); it!=m_colorList.end(); count++, it++) { *m_streamOut << "\\red" << (*it).red(); *m_streamOut << "\\green" << (*it).green(); *m_streamOut << "\\blue" << (*it).blue(); *m_streamOut << ";"; // end of entry } *m_streamOut << "}"; // end of color table } void RTFWorker::writeStyleData(void) { *m_streamOut << "{\\stylesheet" << m_eol; uint count; TQValueList::ConstIterator it; for (count=0, it=m_styleList.begin(); it!=m_styleList.end(); count++, it++) { *m_streamOut << "{"; if (count>0) // \s0 is not written out *m_streamOut << "\\s" << count; *m_streamOut << layoutToRtf((*it),(*it),true); // \snext must be the last keyword before the style name // Find the number of the following style uint counter=0; // counts position in style table starting at 0 TQValueList < LayoutData > ::ConstIterator it2; for( it2 = m_styleList.begin(); it2 != m_styleList.end(); counter++, ++it2 ) { if ( (*it2).styleName == (*it).styleFollowing ) { *m_streamOut << "\\snext" << counter; break; } } *m_streamOut << " " << (*it).styleName << ";"; *m_streamOut << "}"; *m_streamOut << m_eol; } *m_streamOut << "}"; } bool RTFWorker::doCloseDocument(void) { writeFontData(); writeColorData(); writeStyleData(); if (!m_textDocInfo.isEmpty()) { *m_streamOut << "{\\info "; // string of document information markup *m_streamOut << m_textDocInfo; // add document author, title, operator *m_streamOut << "}"; } *m_streamOut << "\\paperw" << int(m_paperWidth); *m_streamOut << "\\paperh" << int(m_paperHeight); if (1==m_paperOrientation) *m_streamOut << "\\landscape"; *m_streamOut << "\\margl" << int(m_paperMarginLeft); *m_streamOut << "\\margr" << int(m_paperMarginRight); *m_streamOut << "\\margt" << int(m_paperMarginTop); *m_streamOut << "\\margb" << int(m_paperMarginBottom); *m_streamOut << m_textPage; // add page size, margins, etc. *m_streamOut << "\\widowctrl\\ftnbj\\aenddoc\\formshade \\fet0\\sectd\n"; if (m_startPageNumber >= 1) *m_streamOut << "\\pgnstart" << m_startPageNumber << endl; //*m_streamOut << "\\linex0\\endnhere\\plain"; *m_streamOut << "\\pard\\plain"; *m_streamOut << m_textBody; *m_streamOut << "}" << m_eol; return true; } bool RTFWorker::doFullDocumentInfo(const KWEFDocumentInfo& docInfo) { if ( !docInfo.title.isEmpty() ) { m_textDocInfo += "{\\title "; m_textDocInfo += escapeRtfText( docInfo.title ); m_textDocInfo += "}"; } if ( !docInfo.fullName.isEmpty() ) { m_textDocInfo += "{\\author "; m_textDocInfo += escapeRtfText( docInfo.fullName ); m_textDocInfo += "}"; } if ( !docInfo.keywords.isEmpty() ) { m_textDocInfo += "{\\keywords "; m_textDocInfo += escapeRtfText( docInfo.keywords ); m_textDocInfo += "}"; } if ( !docInfo.subject.isEmpty() ) { m_textDocInfo += "{\\subject "; m_textDocInfo += escapeRtfText( docInfo.subject ); m_textDocInfo += "}"; } if ( !docInfo.company.isEmpty() ) { m_textDocInfo += "{\\company "; m_textDocInfo += escapeRtfText( docInfo.company ); m_textDocInfo += "}"; } // Now add who we are in a \comment TQString revision("$Revision: 549975 $"); m_textDocInfo += "{\\comment "; m_textDocInfo += "Generated by KWord's RTF Export Filter"; m_textDocInfo += revision.mid(10).remove('$'); // has a leading and a trailing space. m_textDocInfo += "}"; if ( !docInfo.abstract.isEmpty() ) { m_textDocInfo += "{\\doccomm "; m_textDocInfo += escapeRtfText( docInfo.abstract ); m_textDocInfo += "}"; } return true; } bool RTFWorker::doOpenTextFrameSet(void) { return true; } bool RTFWorker::doCloseTextFrameSet(void) { return true; } TQString RTFWorker::openSpan(const FormatData& formatOrigin, const FormatData& format) { TQString result; result += "{"; result += textFormatToRtf(formatOrigin.text,format.text,false); if ( 1==format.text.verticalAlignment ) { result += "\\sub"; //Subscript } else if ( 2==format.text.verticalAlignment ) { result += "\\super"; //Superscript } result += " "; return result; } TQString RTFWorker::closeSpan(const FormatData& , const FormatData& ) { TQString result; result += "}"; return result; } // The following function encodes the kword unicode characters into // RTF seven bit ASCII. This affects any 8 bit characters. // They are encoded either with \' or with \u TQString RTFWorker::escapeRtfText ( const TQString& text ) const { // initialize strings TQString escapedText; const uint length = text.length(); for ( uint i = 0; i < length; i++ ) { TQChar TQCh ( text.at( i ) ); // get out one unicode char from the string const ushort ch = TQCh.unicode(); // take unicode value of the char if ( TQCh == '\\' ) escapedText += "\\\\"; // back-slash else if ( TQCh == '{' ) escapedText += "\\{"; else if ( TQCh == '}' ) escapedText += "\\}"; else if ( ch >= 32 && ch <= 127) // ASCII character escapedText += TQCh; else if ( ch == 0x0009 ) escapedText += "\\tab "; // tabulator else if ( ch == 0x00a0 ) escapedText += "\\~"; // Non-breaking space else if ( ch == 0x00ad ) escapedText += "\\-"; // Soft hyphen else if ( ch == 0x00b7 ) escapedText += "\\|"; else if ( ch == 0x2011 ) escapedText += "\\_"; // Non-breaking hyphen else if ( ch == 0x2002 ) escapedText += "\\enspace "; else if ( ch == 0x2003 ) escapedText += "\\emspace "; else if ( ch == 0x2004 ) escapedText += "\\qmspace "; else if ( ch == 0x200c ) escapedText += "\\zwnj "; else if ( ch == 0x200d ) escapedText += "\\zwj "; else if ( ch == 0x200e ) escapedText += "\\ltrmark "; else if ( ch == 0x200f ) escapedText += "\\rtlmark "; else if ( ch == 0x2013 ) escapedText += "\\endash "; else if ( ch == 0x2014 ) escapedText += "\\emdash "; else if ( ch == 0x2018 ) escapedText += "\\lquote "; else if ( ch == 0x2019 ) escapedText += "\\rquote "; else if ( ch == 0x201c ) escapedText += "\\ldblquote "; else if ( ch == 0x201d ) escapedText += "\\rdblquote "; else if ( ch == 0x2022 ) escapedText += "\\bullet "; else if ( ch >= 160 && ch < 256) // check for characters common between ISO-8859-1 and CP1252 { // NOTE: 128 to 159 in CP1252 are somewhere else in UTF-8 and do not exist in ISO-8859-1 (### TODO?) escapedText += "\\\'"; // escape upper page character to 7 bit escapedText += TQString::number ( ch, 16 ); } else if ( ch >= 256) // check for a higher code non-ASCII character { // encode this as decimal unicode with a replacement character. escapedText += "\\u"; escapedText += TQString::number ( ch, 10 ); // We decompose the character. If it works, the first character is whitout any accent. // (Of course this only works with Latin letters.) // WARNING: TQChar::decomposition is not re-entrant in TQt 3.x TQChar replacement ( TQCh.decomposition().at(0) ); kdDebug(30515) << "Proposed replacement character: " << TQString(replacement) << endl; if (replacement.isNull() || (int)replacement<=' ' || (int)replacement>=char(127) || replacement=='{' || replacement=='}' || replacement=='\\') replacement='?'; // Not a normal ASCII character, so default to show a ? sign escapedText += replacement; // The \uc1 dummy character. } else escapedText += TQCh ; } return escapedText; } bool RTFWorker::doFullPaperFormat(const int /*format*/, const double width, const double height, const int orientation) { m_paperWidth=width*20; m_paperHeight=height*20; m_paperOrientation=orientation; return true; } bool RTFWorker::doFullPaperBorders (const double top, const double left, const double bottom, const double right) { m_paperMarginTop=top*20; m_paperMarginLeft=left*20; m_paperMarginBottom=bottom*20; m_paperMarginRight=right*20; return true; } bool RTFWorker::doFullDefineStyle(LayoutData& layout) { //Register the new style in the style list m_styleList << layout; // Now we must register a few things (with help of the lookup methods.) lookupFont("\\f", layout.formatData.text.fontName); lookupColor(TQString(), layout.formatData.text.fgColor); lookupColor(TQString(), layout.formatData.text.bgColor); return true; } static TQString writeDate(const TQString keyword, const TQDateTime& now) { TQString str; if (now.isValid()) { kdDebug(30515) << "Date " << keyword << " " << now.toString() << endl; str += '{'; str += keyword; const TQDate nowDate(now.date()); str += "\\yr"; str += TQString::number(nowDate.year()); str += "\\mo"; str += TQString::number(nowDate.month()); str += "\\dy"; str += TQString::number(nowDate.day()); const TQTime nowTime(now.time()); str += "\\hr"; str += TQString::number(nowTime.hour()); str += "\\min"; str += TQString::number(nowTime.minute()); str += "\\sec"; str += TQString::number(nowTime.second()); str += '}'; } else kdWarning(30515) << "Date " << keyword << " is not valid! Skipping!" << endl; return str; } bool RTFWorker::doVariableSettings(const VariableSettingsData& vs) { m_textDocInfo += writeDate("\\creatim",vs.creationTime); m_textDocInfo += writeDate("\\revtim", vs.modificationTime); m_textDocInfo += writeDate("\\printim",vs.printTime); m_startPageNumber = vs.startingPageNumber; return true; } TQString RTFWorker::textFormatToRtf(const TextFormatting& formatOrigin, const TextFormatting& formatData, const bool force) { // TODO: rename variable formatData TQString strElement; // TODO: rename this variable // Font name const TQString fontName(formatData.fontName); if (!fontName.isEmpty() && (force || (formatOrigin.fontName!=formatData.fontName))) { strElement+=lookupFont("\\f", fontName); } if (force || (formatOrigin.fontSize!=formatData.fontSize)) { const int size=formatData.fontSize; if (size>0) { strElement+="\\fs"; strElement+=TQString::number(2*size,10); } } if (force || (formatOrigin.italic!=formatData.italic)) { // Font style if ( formatData.italic ) { strElement+="\\i"; } else { strElement+="\\i0"; } } if (force || ((formatOrigin.weight>=75)!=(formatData.weight>=75))) { if ( formatData.weight >= 75 ) { strElement+="\\b"; } else { strElement+="\\b0"; } } if (force || (formatOrigin.fgColor!=formatData.fgColor)) { if ( formatData.fgColor.isValid() ) { strElement+=lookupColor("\\cf", formatData.fgColor); } } if (force || (formatOrigin.bgColor!=formatData.bgColor)) { if ( formatData.bgColor.isValid() ) { strElement+=lookupColor("\\cb", formatData.bgColor); strElement+=lookupColor("\\highlight", formatData.bgColor); // MS Word wants this // ### FIXME: \highlight is not allowed in style definitions (RTF 1.6) } } if ( force || ( formatOrigin.underline != formatData.underline ) || ( formatOrigin.underlineValue != formatData.underlineValue ) || ( formatOrigin.underlineStyle != formatData.underlineStyle ) || ( formatOrigin.underlineWord != formatData.underlineWord ) ) { if ( formatData.underline ) { TQString underlineValue = formatData.underlineValue; TQString underlineStyle = formatData.underlineStyle; bool underlineWord = formatData.underlineWord; TQString ul ( "\\ul" ); // fall-back: simple underline if( underlineStyle.isEmpty() ) underlineStyle = "solid"; if( underlineValue == "1" ) underlineValue = "single"; if( underlineValue == "double" ) ul = "\\uldb"; else if( underlineValue == "single-bold" ) ul = "\\ulth"; else if( underlineValue == "wave" ) ul = "\\ulwave"; else if( underlineValue == "single" ) { if( underlineStyle == "dash" ) ul = "\\uldash"; else if( underlineStyle == "dot" ) ul = "\\uld"; else if( underlineStyle == "dashdot" ) ul = "\\uldashd"; else if( underlineStyle == "dashdotdot" ) ul = "\\uldashdd"; else if( underlineWord ) ul = "\\ulw"; }; strElement+= ul; if (formatData.underlineColor.isValid()) { strElement+=lookupColor("\\ulc",formatData.underlineColor); } } else { strElement+="\\ul0"; } } if ( force || ( formatOrigin.strikeout != formatData.strikeout ) || ( formatOrigin.strikeoutType != formatData.strikeoutType ) ) { if ( formatData.strikeout ) { if( formatData.strikeoutType == "double" ) strElement+="\\striked1"; // 1 needed! (The exception that confirms the rule!) else strElement+="\\strike"; } else { strElement+="\\strike0"; // ### TODO: \striked0 too? } } return strElement; } TQString RTFWorker::layoutToRtf(const LayoutData& layoutOrigin, const LayoutData& layout, const bool force) { TQString strLayout; if (force || (layoutOrigin.alignment!=layout.alignment)) { if (layout.alignment=="left") strLayout += "\\ql"; else if (layout.alignment== "right") strLayout += "\\qr"; else if (layout.alignment=="center") strLayout += "\\qc"; else if (layout.alignment=="justify") strLayout += "\\qj"; else if ( layout.alignment=="auto") { // ### TODO: what for BIDI? //strLayout += "\\ql"; } else { kdWarning(30515) << "Unknown alignment: " << layout.alignment << endl; } } if ((layout.indentLeft>=0.0) && (force || (layoutOrigin.indentLeft!=layout.indentLeft))) { strLayout += "\\li"; strLayout += TQString::number(int(layout.indentLeft)*20, 10); } if ((layout.indentRight>=0.0) && (force || (layoutOrigin.indentRight!=layout.indentRight))) { strLayout += "\\ri"; strLayout += TQString::number(int(layout.indentRight)*20, 10); } if (force || (layoutOrigin.indentFirst!=layout.indentFirst)) { strLayout += "\\fi"; strLayout += TQString::number(int(layout.indentFirst)*20, 10); } if ((layout.marginBottom>=0.0) && (force || (layoutOrigin.marginBottom!=layout.marginBottom))) { strLayout += "\\sa"; strLayout += TQString::number(int(layout.marginBottom)*20 ,10); } if ((layout.marginTop>=0.0) && (force || (layoutOrigin.marginTop!=layout.marginTop))) { strLayout += "\\sb"; strLayout += TQString::number(int(layout.marginTop)*20, 10); } if (force || (layoutOrigin.keepLinesTogether!=layout.keepLinesTogether)) { if(layout.keepLinesTogether) strLayout += "\\keep"; } // Note: there seems to be too many problems of using a page break in a layout // - KWord's RTF import filter makes the page break immediately (also in styles) // - AbiWord's RTF import does not like \*\pgbrk // ### TODO: decide if we really remove this code #if 0 if (force || (layoutOrigin.pageBreakBefore!=layout.pageBreakBefore)) { if(layout.pageBreakBefore) strLayout += "\\pagebb"; } // Note: RTF doesn't specify "page break after" // \*\pgbrk0 is used after OpenOffice.org Writer if (force || (layoutOrigin.pageBreakAfter!=layout.pageBreakAfter)) { if(layout.pageBreakAfter) strLayout += "\\*\\pgbrk0"; } #endif if (force || ( layoutOrigin.lineSpacingType != layout.lineSpacingType ) || ( layoutOrigin.lineSpacing != layout.lineSpacing ) ) { if ( layout.lineSpacingType==LayoutData::LS_SINGLE ) ;// do nothing, single linespace is default in RTF else if ( layout.lineSpacingType==LayoutData::LS_ONEANDHALF ) strLayout += "\\sl360\\slmult1"; // one-and-half linespace else if ( layout.lineSpacingType==LayoutData::LS_DOUBLE ) strLayout += "\\sl480\\slmult1"; // double linespace else if ( layout.lineSpacingType==LayoutData::LS_ATLEAST ) strLayout += TQString("\\sl%1\\slmult0").arg(int(layout.lineSpacing)*20); else if ( layout.lineSpacingType==LayoutData::LS_MULTIPLE ) strLayout += TQString("\\sl%1\\slmult1").arg( int(layout.lineSpacing)*240 ); else if ( layout.lineSpacingType==LayoutData::LS_CUSTOM ) // "Custom" in KWord is like "Exactly" in MS Word strLayout += TQString("\\sl-%1\\slmult0").arg(int(layout.lineSpacing)*20); else kdWarning(30515) << "Unsupported lineSpacingType: " << layout.lineSpacingType << " (Ignoring!)" << endl; } if (!layout.tabulatorList.isEmpty() && (force || (layoutOrigin.tabulatorList!=layout.tabulatorList) )) { TabulatorList::ConstIterator it; for (it=layout.tabulatorList.begin();it!=layout.tabulatorList.end();++it) { switch ((*it).m_type) { case 0: default: break; // left tab is default case 1: strLayout += "\\tqc"; break; case 2: strLayout += "\\tqr"; break; case 3: strLayout += "\\tqdec"; break; } switch ((*it).m_filling) { case TabulatorData::TF_NONE: default: break; // without leader/filling case TabulatorData::TF_DOT: strLayout += "\\tldot"; break; case TabulatorData::TF_LINE: strLayout += "\\tlul"; break; // these belows are all treated as RTF's \tqul case TabulatorData::TF_DASH: case TabulatorData::TF_DASHDOT: case TabulatorData::TF_DASHDOTDOT: strLayout += "\\tlul"; break; } // must be the last strLayout += "\\tx"; strLayout += TQString::number(int((*it).m_ptpos)*20, 10); } } // shadow support // note shadow in KWord is more full-feature/sophisticated than RTF // here we just treat KWord's shadow as simple \shad mark-up if( layout.shadowDistance > 0 ) { strLayout += "\\shad"; } // TODO: borders // This must remain last, as it adds a terminating space. strLayout+=textFormatToRtf(layoutOrigin.formatData.text, layout.formatData.text,force); return strLayout; } TQString RTFWorker::lookupFont(const TQString& markup, const TQString& fontName) { if (fontName.isEmpty()) return TQString(); // First we have to remove TQt-typical foundry names, as some RTF readers are confused by them. TQString cookedFontName(fontName); TQRegExp regexp("\\s*\\[\\S*\\]"); // Some white space, opening square bracket, some non-white-space, ending square bracket cookedFontName.remove(regexp); // But we cannot have an empty name font if (cookedFontName.isEmpty()) cookedFontName=fontName; kdDebug(30515) << "RTFWorker::lookupFont " << fontName << " cooked: " << cookedFontName << endl; uint counter=0; // counts position in font table (starts at 0) TQString strFont(markup); // markup for font selection TQStringList::ConstIterator it; // search font table for this font for( it = m_fontList.begin(); it != m_fontList.end(); counter++, ++it ) { if((*it) == cookedFontName) // check for match { strFont += TQString::number(counter); kdDebug(30515) << strFont << endl; return strFont; } } // end for() kdDebug(30515) << "New font: " << cookedFontName << " count: " << counter << endl; m_fontList << cookedFontName; strFont += TQString::number(counter); return strFont; } TQString RTFWorker::lookupColor(const TQString& markup, const TQColor& color) { if (!color.isValid()) return TQString(); uint counter=1; // counts position in color table starting at 1 TQString strColor(markup); // Holds RTF markup for the color TQValueList < TQColor > ::ConstIterator it; // search color table for this color for( it = m_colorList.begin(); it != m_colorList.end(); counter++, ++it ) { if ( (*it) == color ) { strColor += TQString::number(counter); return strColor; } } kdDebug(30515) << "New color: " << TQString(color.name()) << " count: " << counter << endl; m_colorList << color; strColor += TQString::number(counter); return strColor; } TQString RTFWorker::lookupStyle(const TQString& styleName, LayoutData& returnLayout) { if (styleName.isEmpty()) return TQString(); uint counter=0; // counts position in style table starting at 0 TQString strMarkup("\\s"); // Holds RTF markup for the style TQValueList < LayoutData > ::ConstIterator it; TQValueList < LayoutData > ::ConstIterator end(m_styleList.end()); // search color table for this color for( it = m_styleList.begin(); it != end; counter++, ++it ) { if ( (*it).styleName == styleName ) { strMarkup += TQString::number(counter); returnLayout=(*it); return strMarkup; } } kdDebug(30515) << "New style: " << styleName << " count: " << counter << endl; LayoutData layout; m_styleList << layout; returnLayout=layout; strMarkup += TQString::number(counter); return strMarkup; }