4
This file is part of the KDE project
5
Copuright 2001 Michael Johnson <mikej@xnet.com>
6
Copyright 2001, 2002, 2003, 2004 Nicolas GOUTTE <goutte@kde.org>
7
Copyright 2002 Ariya Hidayat <ariya@kde.org>
9
This library is free software; you can redistribute it and/or
10
modify it under the terms of the GNU Library General Public
11
License as published by the Free Software Foundation; either
12
version 2 of the License, or (at your option) any later version.
14
This library is distributed in the hope that it will be useful,
15
but WITHOUT ANY WARRANTY; without even the implied warranty of
16
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17
Library General Public License for more details.
19
You should have received a copy of the GNU Library General Public License
20
along with this library; see the file COPYING.LIB. If not, write to
21
the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22
Boston, MA 02111-1307, USA.
26
#include <qtextcodec.h>
28
#include <qfileinfo.h>
29
#include <qfontinfo.h>
31
#include <qregion.h> // for #include <kdebugclasses.h>
35
#include <qdatetime.h>
40
#include <kdebugclasses.h>
43
#include <KWEFBaseWorker.h>
45
#include "ExportFilter.h"
47
// 1 twip = 1/20 pt = 1/1400 inch
50
// some conversion macros
51
#define TWIP_TO_MM(x) (x)*25.4/1440.0
52
#define MM_TO_TWIP(x) (x)*1440.0/25.4
53
#define PT_TO_TWIP(x) (x)*20
54
#define TWIP_TO_PT(x) (x)/20
56
// map KWord field name to RTF field name
57
// e.g authorName -> AUTHOR
58
static QString mapFieldName( const QString& kwordField )
62
if( kwordField == "fileName" ) rtfField = "FILENAME";
63
else if( kwordField == "authorName" ) rtfField = "AUTHOR";
64
else if( kwordField == "docTitle" ) rtfField = "TITLE";
69
RTFWorker::RTFWorker():
70
m_ioDevice(NULL), m_streamOut(NULL), m_eol("\r\n"), m_inTable(false),
71
m_paperOrientation(false), m_paperWidth(20), m_paperHeight(20),
72
m_paperMarginTop(72), m_paperMarginLeft(72),
73
m_paperMarginBottom(72), m_paperMarginRight(72), m_startPageNumber(1)
77
static QString WritePositiveKeyword(const QString& keyword, const int value)
82
if (value>0) // The value of the keyword cannot be negative
83
str += QString::number( value );
90
QString RTFWorker::writeRow(const QString& textCellHeader, const QString& rowText, const FrameData& frame)
94
row += "\\trowd\\trgaph60\\trql"; // start new row
95
row += WritePositiveKeyword("\\trrh", qRound(PT_TO_TWIP(frame.minHeight)));
96
row += WritePositiveKeyword("\\trleft", qRound(PT_TO_TWIP(frame.left) - m_paperMarginLeft));
97
//row += "\\trautofit0"; // ### VERIFY
98
row += textCellHeader;
99
row += " "; // End of keyword
105
QString RTFWorker::writeBorder(const char whichBorder, const int borderWidth, const QColor& color)
110
str += "\\clbrdr"; // Define border
111
str += whichBorder; // t=top, l=left, b=bottom, r=right
112
str += "\\brdrs\\brdrw"; // Single border; thickness
113
str += QString::number(borderWidth);
116
str += lookupColor("\\brdrcf",color);
122
QString RTFWorker::makeTable(const FrameAnchor& anchor)
124
QString textBody; // Text to be returned
125
textBody += m_prefix;
126
m_prefix = QString::null;
130
bool firstCellInRow = true;
131
FrameData firstFrameData;
132
int debugCellCurrent = 0; //DEBUG
133
int debugRowCurrent = 0; //DEBUG
134
QString textCellHeader; // <celldef>
136
const bool oldInTable = m_inTable;
139
QValueList<TableCell>::ConstIterator itCell;
140
for (itCell=anchor.table.cellList.begin();
141
itCell!=anchor.table.cellList.end(); itCell++)
143
// ### TODO: rowspan, colspan
144
if (rowCurrent!=(*itCell).row)
146
rowCurrent = (*itCell).row;
147
textBody += writeRow( textCellHeader, rowText, firstFrameData);
150
rowText = QString::null;
151
textCellHeader = QString::null;
153
debugRowCurrent ++; // DEBUG
154
debugCellCurrent = 0; //DEBUG
157
const FrameData& frame = (*itCell).frame;
159
if (firstCellInRow) // Not yet set, so set the position of the left border.
161
firstFrameData=frame;
162
firstCellInRow=false;
165
kdDebug(30515) << "Cell: " << debugRowCurrent << "," << debugCellCurrent
166
<< " left: " << frame.left << " right: " << frame.right << " top: " << frame.top << " bottom " << frame.bottom << endl;
167
textCellHeader += writeBorder('t',qRound(PT_TO_TWIP(frame.tWidth)),frame.tColor);
168
textCellHeader += writeBorder('l',qRound(PT_TO_TWIP(frame.lWidth)),frame.lColor);
169
textCellHeader += writeBorder('b',qRound(PT_TO_TWIP(frame.bWidth)),frame.bColor);
170
textCellHeader += writeBorder('r',qRound(PT_TO_TWIP(frame.rWidth)),frame.rColor);
171
textCellHeader += WritePositiveKeyword("\\cellx",qRound(PT_TO_TWIP(frame.right) - m_paperMarginRight)); //right border of cell
173
QString endOfParagraph;
174
QValueList<ParaData> *paraList = (*itCell).paraList;
175
QValueList<ParaData>::ConstIterator it;
176
for (it=paraList->begin();it!=paraList->end();it++)
178
rowText += endOfParagraph;
179
rowText += ProcessParagraphData( (*it).text,(*it).layout,(*it).formattingList);
181
endOfParagraph = "\\par"; // The problem is that the last paragraph ends with \cell not with \par
184
debugCellCurrent ++; // DEBUG
187
textBody += writeRow( textCellHeader, rowText, firstFrameData);
188
textBody += "\\row\\pard"; // delimit last row
190
m_inTable = oldInTable;
195
QString RTFWorker::makeImage(const FrameAnchor& anchor)
197
QString textBody; // Text to be returned
199
QString strImageName(anchor.picture.koStoreName);
203
kdDebug(30515) << "RTFWorker::makeImage" << endl << anchor.picture.koStoreName << endl;
205
const int pos=strImageName.findRev('.');
206
if(pos!=-1) strExt = strImageName.mid(pos+1).lower();
212
else if (strExt=="bmp")
215
else if ( (strExt=="jpeg") || (strExt=="jpg") )
217
else if (strExt=="wmf")
218
strTag="\\wmetafile8"; // 8 == anisotropic
221
// either without extension or format is unknown
222
// let's try to convert it to PNG format
223
kdDebug(30515) << "Converting image " << anchor.picture.koStoreName << endl;
226
if( !loadAndConvertToImage(anchor.picture.koStoreName,strExt,"PNG",image) )
228
kdWarning(30515) << "Unable to convert " << anchor.picture.koStoreName << endl;
229
return QString::null;
232
// ### TODO: SVG, QPicture
234
// load the image, this isn't necessary for converted image
236
if (!loadSubFile(anchor.picture.koStoreName,image))
238
kdWarning(30515) << "Unable to load picture " << anchor.picture.koStoreName << endl;
239
return QString::null;
243
// find displayed width and height (in twips)
244
const long width = (long)(PT_TO_TWIP(anchor.frame.right - anchor.frame.left));
245
const long height = (long)(PT_TO_TWIP(anchor.frame.bottom - anchor.frame.top));
247
// find original image width and height (in twips)
248
long origWidth = width;
249
long origHeight = height;
250
if( strExt == "wmf" )
252
// special treatment for WMF with metaheader
253
// d7cdc69a is metaheader magic id
254
Q_UINT8* data = (Q_UINT8*) image.data();
255
if( ( data[0] == 0xd7 ) && ( data[1] == 0xcd ) &&
256
( data[2] == 0xc6 ) && ( data[3] == 0x9a ) &&
257
( image.size() > 22 ) )
259
// grab bounding box, find original size
260
unsigned left = data[6]+(data[7]<<8);
261
unsigned top = data[8]+(data[9]<<8);
262
unsigned right = data[10]+(data[11]<<8);
263
unsigned bottom = data[12]+(data[13]<<8);
264
origWidth = (long) (MM_TO_TWIP(right-left)/100);
265
origHeight = (long) (MM_TO_TWIP(bottom-top)/100);
267
// throw away WMF metaheader (22 bytes)
268
for( uint i=0; i<image.size()-22; i++)
269
image.at(i) = image.at(i+22); // As we use uint, we need at() ( [] uses int only .)
270
image.resize( image.size()-22 );
275
// It must be an image
279
kdWarning(30515) << "Unable to load picture as image " << anchor.picture.koStoreName << endl;
280
return QString::null;
282
// check resolution, assume 2835 dpm (72 dpi) if not available
283
int resx = img.dotsPerMeterX();
284
int resy = img.dotsPerMeterY();
285
if( resx <= 0 ) resx = 2835;
286
if( resy <= 0 ) resy = 2835;
288
origWidth = long(img.width() * 2834.65 * 20 / resx);
289
origHeight = long(img.height() * 2834.65 * 20 / resy);
292
// Now that we are sure to have a valid image, we can write the RTF tags
293
textBody += "{\\pict";
296
// calculate scaling factor (in percentage)
297
int scaleX = width * 100 / origWidth;
298
int scaleY = height * 100 / origHeight;
301
int picw = (int)(100 * TWIP_TO_MM(origWidth));
302
int pich = (int)(100 * TWIP_TO_MM(origHeight));
304
textBody += "\\picscalex";
305
textBody += QString::number(scaleX, 10);
306
textBody += "\\picscaley";
307
textBody += QString::number(scaleY, 10);
308
textBody += "\\picw";
309
textBody += QString::number(picw,10);
310
textBody += "\\pich";
311
textBody += QString::number(pich,10);
312
textBody += "\\picwgoal";
313
textBody += QString::number(origWidth, 10);
314
textBody += "\\pichgoal";
315
textBody += QString::number(origHeight, 10);
318
const char hex[] = "0123456789abcdef";
319
for (uint i=0; i<image.size(); i++)
323
const char ch=image.at(i);
324
textBody += hex[(ch>>4)&0x0f]; // Done this way to avoid signed/unsigned problems
325
textBody += hex[(ch&0x0f)];
334
QString RTFWorker::formatTextParagraph(const QString& strText,
335
const FormatData& formatOrigin, const FormatData& format)
339
if (!format.text.missing)
342
str+=openSpan(formatOrigin,format);
345
QString strEscaped = escapeRtfText(strText);
347
// Replace line feeds by forced line breaks
349
QString strBr("\\line ");
350
while ((pos=strEscaped.find(QChar(10)))>-1)
352
strEscaped.replace(pos,1,strBr);
357
if (!format.text.missing)
360
str+=closeSpan(formatOrigin,format);
366
QString RTFWorker::ProcessParagraphData ( const QString ¶Text,
367
const LayoutData& layout, const ValueListFormatData ¶FormatDataList)
379
if (layout.counter.style)
381
markup += "{\\pntext\\pard\\plain";
382
if( layout.formatData.text.fontSize >= 0)
385
markup += QString::number((2 * layout.formatData.text.fontSize));
386
markup += lookupFont("\\f",layout.formatData.text.fontName);
389
markup += layout.counter.text;
390
markup += "\\tab}{\\*\\pn";
391
if (layout.counter.style > 5)
393
markup += "\\pnlvlblt ";
394
markup += "{\\pntxtb ";
395
if (!layout.counter.lefttext.isEmpty() && layout.counter.lefttext != "{" && layout.counter.lefttext != "}")
397
markup += layout.counter.lefttext;
399
switch (layout.counter.style)
403
//custom bullets (one char)
404
//TODO: use correct character/sign for bullet
406
markup += layout.counter.customCharacter;
411
//custom bullets (complex)
412
markup += layout.counter.text;
418
//TODO: use correct character/sign for bullet
419
markup += "\\bullet";
425
//TODO: use correct character/sign for bullet
426
markup += "\\bullet";
432
//TODO: make work in OO
433
markup += "\\bullet";
439
//TODO: use correct character/sign for bullet
440
markup += layout.counter.text;
444
markup += "\\bullet";
450
if (layout.counter.numbering!=0)
453
markup += QString::number(layout.counter.depth + 1);
454
markup += "\\pnprev1";
456
else if (layout.counter.style==1)
458
markup += "\\pnlvlbody";
463
markup += QString::number(11 - layout.counter.style);
466
switch (layout.counter.style)
475
markup += "\\pnlcltr";
480
markup += "\\pnucltr";
485
markup += "\\pnlcrm";
490
markup += "\\pnucrm";
496
markup += "{\\pntxtb ";
497
markup += layout.counter.lefttext;
500
markup += "{\\pntxta ";
501
markup += layout.counter.righttext;
504
if (layout.counter.start!=0)
506
markup += "\\pnstart";
507
markup += QString::number(layout.counter.start);
511
markup += "\\pnstart1";
513
markup += "\\pnindent0\\pnhang";
515
if( layout.formatData.text.fontSize > 0 )
518
markup += QString::number((2 * layout.formatData.text.fontSize));
521
if( !layout.formatData.text.fontName.isEmpty() )
523
markup += lookupFont("\\pnf", layout.formatData.text.fontName);
530
LayoutData styleLayout;
531
markup += lookupStyle(layout.styleName, styleLayout);
532
markup += layoutToRtf(styleLayout,layout,true);
534
if ( 1==layout.formatData.text.verticalAlignment )
536
markup += "\\sub"; //Subscript
538
else if ( 2==layout.formatData.text.verticalAlignment )
540
markup += "\\super"; //Superscript
544
if (layout.pageBreakBefore)
545
content += "\\page ";
551
if (!paraText.isEmpty())
554
ValueListFormatData::ConstIterator paraFormatDataIt;
558
FormatData formatRef = layout.formatData;
560
for ( paraFormatDataIt = paraFormatDataList.begin ();
561
paraFormatDataIt != paraFormatDataList.end ();
564
if (1==(*paraFormatDataIt).id)
567
partialText=paraText.mid ( (*paraFormatDataIt).pos, (*paraFormatDataIt).len );
568
content +=formatTextParagraph(partialText, formatRef, *paraFormatDataIt);
570
else if (4==(*paraFormatDataIt).id)
572
// ### TODO: put dtae/time fields into own method.
573
if ( (0==(*paraFormatDataIt).variable.m_type) // date
574
|| (2==(*paraFormatDataIt).variable.m_type) ) // time
576
// ### TODO: fixed/variable date
577
content += "{\\field{\\*\\fldinst ";
578
if (0==(*paraFormatDataIt).variable.m_type)
582
QString key((*paraFormatDataIt).variable.m_key.mid(4));
583
kdDebug(30515) << "Time format: " << key << endl;
584
if (key.startsWith("locale"))
586
if (key == "locale" )
588
if (0==(*paraFormatDataIt).variable.m_type)
589
key = KGlobal::locale()->dateFormat();
591
key = KGlobal::locale()->timeFormat();
593
else if ( key == "localeshort" )
594
key = KGlobal::locale()->dateFormatShort();
595
else if ( key == "localedatetime" )
597
key = KGlobal::locale()->dateFormat();
599
key += KGlobal::locale()->timeFormat();
601
else if ( key == "localedatetimeshort" )
603
key = KGlobal::locale()->dateFormat();
605
key += KGlobal::locale()->timeFormat();
607
kdDebug(30515) << "KDE format: " << key << endl;
608
key=QString::null; // ### FIXME: KControl's key seems to differ from KWord :-(
612
const QRegExp regexp("AP",false); // Not case-sensitive
613
if (regexp.search(key)!=-1)
616
key.replace("ap","am/pm");
617
key.replace("AP","AM/PM");
622
key.replace('h','H'); // MS Word uses H for 24hour times
624
// MS Word do not know possesive months
625
key.replace("PPP","MMM");
626
key.replace("PPPP","MMMM");
627
key.replace("zzz","000"); // replace microseconds by 000
628
kdDebug(30515) << "New format: " << key << endl;
633
content += "\\* MERGEFORMAT }{\\fldrslt ";
634
content += escapeRtfText((*paraFormatDataIt).variable.m_text);
637
else if (4==(*paraFormatDataIt).variable.m_type)
639
QString strFieldType;
640
if ((*paraFormatDataIt).variable.isPageNumber())
642
content += "{\\field{\\*\\fldinst PAGE \\* MERGEFORMAT }{\\fldrslt ";
643
content += escapeRtfText((*paraFormatDataIt).variable.m_text);
646
else if ((*paraFormatDataIt).variable.isPageCount())
648
content += "{\\field{\\*\\fldinst NUMPAGES \\* MERGEFORMAT }{\\fldrslt ";
649
content += escapeRtfText((*paraFormatDataIt).variable.m_text);
654
// Unknown subtype, therefore write out the result
655
content += escapeRtfText((*paraFormatDataIt).variable.m_text);
658
else if (8==(*paraFormatDataIt).variable.m_type)
661
QString name = escapeRtfText((*paraFormatDataIt).variable.getFieldName());
662
QString value = escapeRtfText((*paraFormatDataIt).variable.getFieldValue());
663
QString rtfField = mapFieldName(name);
665
if( rtfField.isEmpty() )
666
// Couldn't map field name, just write out the value
667
content += escapeRtfText((*paraFormatDataIt).variable.m_text);
670
content += "{\\field";
671
content += "{\\*\\fldinst ";
673
content += " \\* MERGEFORMAT }";
674
content += "{\\fldrslt {";
679
else if (9==(*paraFormatDataIt).variable.m_type)
682
content += "{\\field";
683
content += "{\\*\\fldinst HYPERLINK ";
684
content += escapeRtfText((*paraFormatDataIt).variable.getHrefName());
686
content += "{\\fldrslt ";
687
content += "{\\ul"; // underline, ### TODO: use style Hyperlink
688
content += lookupColor("\\cf",QColor(0,0,255));// blue
689
content += escapeRtfText((*paraFormatDataIt).variable.getLinkName());
692
else if (11==(*paraFormatDataIt).variable.m_type)
695
QString value = (*paraFormatDataIt).variable.getFootnoteValue();
696
bool automatic = (*paraFormatDataIt).variable.getFootnoteAuto();
697
QValueList<ParaData> *paraList = (*paraFormatDataIt).variable.getFootnotePara();
701
QValueList<ParaData>::ConstIterator it;
702
for (it=paraList->begin();it!=paraList->end();it++)
703
fstr += ProcessParagraphData( (*it).text, (*it).layout,(*it).formattingList);
704
content += "{\\super ";
705
content += automatic ? "\\chftn " : value;
706
content += "{\\footnote ";
707
content += "{\\super ";
708
content += automatic ? "\\chftn " : value;
718
content += escapeRtfText((*paraFormatDataIt).variable.m_text);
721
else if (6==(*paraFormatDataIt).id)
723
kdDebug(30515) << "Found an anchor of type: " << (*paraFormatDataIt).frameAnchor.type << endl;
724
// We have an image, a clipart or a table
726
if (6==(*paraFormatDataIt).frameAnchor.type)
730
if (!content.isEmpty())
738
content = QString::null;
744
str += makeTable((*paraFormatDataIt).frameAnchor);
746
else if ((2==(*paraFormatDataIt).frameAnchor.type) || (5==(*paraFormatDataIt).frameAnchor.type))
748
content += makeImage((*paraFormatDataIt).frameAnchor);
755
if (layout.pageBreakAfter)
758
if (!content.isEmpty())
766
if (m_inTable==false)
773
str ="\\par\\pard\\plain";
774
if (m_inTable==false)
782
bool RTFWorker::doFullParagraph(const QString& paraText,
783
const LayoutData& layout, const ValueListFormatData& paraFormatDataList)
785
kdDebug(30515) << "Entering RTFWorker::doFullParagraph" << endl << paraText << endl;
786
QString par = ProcessParagraphData( paraText, layout, paraFormatDataList);
788
kdDebug(30515) << "Quiting RTFWorker::doFullParagraph" << endl;
792
bool RTFWorker::doHeader(const HeaderData& header)
796
if( header.page == HeaderData::PAGE_ODD )
797
str = "\\facingp{\\headerr";
798
else if( header.page == HeaderData::PAGE_EVEN )
799
str = "\\facingp{\\headerl";
800
else if( header.page == HeaderData::PAGE_FIRST )
801
str = "\\facingp{\\headerl";
802
else if( header.page == HeaderData::PAGE_ALL )
809
QValueList<ParaData>::ConstIterator it;
810
for (it=header.para.begin();it!=header.para.end();it++)
811
content += ProcessParagraphData( (*it).text,(*it).layout,(*it).formattingList);
813
if (content!="\\par\\pard\\plain")
822
m_prefix=QString::null;
826
bool RTFWorker::doFooter(const FooterData& footer)
830
if( footer.page == FooterData::PAGE_ODD )
831
str = "\\facingp{\\footerr";
832
else if( footer.page == FooterData::PAGE_EVEN )
833
str = "\\facingp{\\footerl";
834
else if( footer.page == FooterData::PAGE_FIRST )
835
str = "\\facingp{\\headerl";
836
else if( footer.page == FooterData::PAGE_ALL )
843
QValueList<ParaData>::ConstIterator it;
844
for (it=footer.para.begin();it!=footer.para.end();it++)
845
content += ProcessParagraphData( (*it).text,(*it).layout,(*it).formattingList);
847
if (content!="\\par\\pard\\plain")
856
m_prefix=QString::null;
860
bool RTFWorker::doOpenFile(const QString& filenameOut, const QString& /*to*/)
862
m_ioDevice=new QFile(filenameOut);
866
kdError(30515) << "No output file! Aborting!" << endl;
870
if ( !m_ioDevice->open (IO_WriteOnly) )
872
kdError(30515) << "Unable to open output file!" << endl;
876
m_streamOut=new QTextStream(m_ioDevice);
878
m_streamOut->setEncoding(QTextStream::UnicodeUTF8); // We want ASCII, so use UTF-8
880
m_fileName=filenameOut;
885
bool RTFWorker::doCloseFile(void)
887
kdDebug(30515) << __FILE__ << ":" << __LINE__ << endl;
895
bool RTFWorker::doOpenDocument(void)
897
// Make the file header
899
// Note: we use \\ansicpg1252 because 1200 is not supposed to be supported
900
// Note2: we assume using \\ansicpg1252 in RTFWorker::escapeRtfText
901
*m_streamOut << "{\\rtf1\\ansi\\ansicpg1252\\uc1\\deff0" << m_eol;
903
// Default color table
905
<< QColor(0,0,0) << QColor(0,0,255) << QColor(0,255,255)
906
<< QColor(0,255,0) << QColor(255,0,255) << QColor(255,0,0)
907
<< QColor(255,255,0) << QColor(255,255,255) << QColor(0,0,128)
908
<< QColor(0,128,128) << QColor(0,128,0) << QColor(128,0,128)
909
<< QColor(128,0,0) << QColor(128,128,0) << QColor(128,128,128);
914
void RTFWorker::writeFontData(void)
916
kdDebug(30515) << "Fonts:" << m_fontList << endl;
917
*m_streamOut << "{\\fonttbl";
919
QStringList::ConstIterator it;
920
for (count=0, it=m_fontList.begin();
921
it!=m_fontList.end();
925
QString strLower(info.family().lower());
926
*m_streamOut << "{\\f" << count;
927
if ( (strLower.find("symbol")>-1) || (strLower.find("dingbat")>-1) )
928
*m_streamOut << "\\ftech";
929
else if ( (strLower.find("script")>-1) )
930
*m_streamOut << "\\fscript";
935
// We do not know the font type that we have and we cannot even tell if it is TrueType
936
*m_streamOut << "\\fnil";
940
// QFontInfo:styleHint() does not guess anything, it just returns what is in the QFont. Nothing put in, nothing gets out.
942
switch (info.styleHint())
944
case QFont::SansSerif:
947
*m_streamOut << "\\fswiss";
952
*m_streamOut << "\\froman";
957
*m_streamOut << "\\fmodern";
960
case QFont::OldEnglish:
962
*m_streamOut << "\\fdecor";
968
// ### TODO: check if QFontInfo::fixedPitch is really working
969
*m_streamOut << "\\fprq" << (info.fixedPitch()?1:2) << " "; // font definition
970
*m_streamOut << escapeRtfText(info.family()); // ### TODO: does RTF allows brackets in the font names?
971
*m_streamOut << ";}" << m_eol; // end font table entry
973
*m_streamOut << "}"; // end of font table
976
void RTFWorker::writeColorData(void)
978
*m_streamOut << "{\\colortbl;";
980
QValueList<QColor>::ConstIterator it;
981
for (count=0, it=m_colorList.begin();
982
it!=m_colorList.end();
985
*m_streamOut << "\\red" << (*it).red();
986
*m_streamOut << "\\green" << (*it).green();
987
*m_streamOut << "\\blue" << (*it).blue();
988
*m_streamOut << ";"; // end of entry
990
*m_streamOut << "}"; // end of color table
993
void RTFWorker::writeStyleData(void)
995
*m_streamOut << "{\\stylesheet" << m_eol;
998
QValueList<LayoutData>::ConstIterator it;
999
for (count=0, it=m_styleList.begin();
1000
it!=m_styleList.end();
1003
*m_streamOut << "{";
1004
if (count>0) // \s0 is not written out
1005
*m_streamOut << "\\s" << count;
1007
*m_streamOut << layoutToRtf((*it),(*it),true);
1009
// \snext must be the last keyword before the style name
1010
// Find the number of the following style
1011
uint counter=0; // counts position in style table starting at 0
1012
QValueList < LayoutData > ::ConstIterator it2;
1013
for( it2 = m_styleList.begin(); it2 != m_styleList.end(); counter++, it2++ )
1015
if ( (*it2).styleName == (*it).styleFollowing )
1017
*m_streamOut << "\\snext" << counter;
1022
*m_streamOut << " " << (*it).styleName << ";";
1023
*m_streamOut << "}";
1024
*m_streamOut << m_eol;
1027
*m_streamOut << "}";
1030
bool RTFWorker::doCloseDocument(void)
1037
if (!m_textDocInfo.isEmpty())
1039
*m_streamOut << "{\\info "; // string of document information markup
1040
*m_streamOut << m_textDocInfo; // add document author, title, operator
1041
*m_streamOut << "}";
1043
*m_streamOut << "\\paperw" << int(m_paperWidth);
1044
*m_streamOut << "\\paperh" << int(m_paperHeight);
1045
if (1==m_paperOrientation)
1046
*m_streamOut << "\\landscape";
1047
*m_streamOut << "\\margl" << int(m_paperMarginLeft);
1048
*m_streamOut << "\\margr" << int(m_paperMarginRight);
1049
*m_streamOut << "\\margt" << int(m_paperMarginTop);
1050
*m_streamOut << "\\margb" << int(m_paperMarginBottom);
1051
*m_streamOut << m_textPage; // add page size, margins, etc.
1052
*m_streamOut << "\\widowctrl\\ftnbj\\aenddoc\\formshade \\fet0\\sectd\n";
1053
if (m_startPageNumber >= 1)
1054
*m_streamOut << "\\pgnstart" << m_startPageNumber << endl;
1055
//*m_streamOut << "\\linex0\\endnhere\\plain";
1056
*m_streamOut << "\\pard\\plain";
1057
*m_streamOut << m_textBody;
1059
*m_streamOut << "}" << m_eol;
1063
bool RTFWorker::doFullDocumentInfo(const KWEFDocumentInfo& docInfo)
1066
if ( !docInfo.title.isEmpty() )
1068
m_textDocInfo += "{\\title ";
1069
m_textDocInfo += escapeRtfText( docInfo.title );
1070
m_textDocInfo += "}";
1073
if ( !docInfo.fullName.isEmpty() )
1075
m_textDocInfo += "{\\author ";
1076
m_textDocInfo += escapeRtfText( docInfo.fullName );
1077
m_textDocInfo += "}";
1080
if ( !docInfo.company.isEmpty() )
1082
m_textDocInfo += "{\\company ";
1083
m_textDocInfo += escapeRtfText( docInfo.company );
1084
m_textDocInfo += "}";
1087
// Now add who we are in a \comment
1088
QString revision("$Revision: 2.96.2.2 $");
1089
m_textDocInfo += "{\\comment ";
1090
m_textDocInfo += "Generated by KWord's RTF Export Filter";
1091
m_textDocInfo += revision.mid(10).remove('$'); // has a leading and a trailing space.
1092
m_textDocInfo += "}";
1094
if ( !docInfo.abstract.isEmpty() )
1096
m_textDocInfo += "{\\doccomm ";
1097
m_textDocInfo += escapeRtfText( docInfo.abstract );
1098
m_textDocInfo += "}";
1104
bool RTFWorker::doOpenTextFrameSet(void)
1109
bool RTFWorker::doCloseTextFrameSet(void)
1114
QString RTFWorker::openSpan(const FormatData& formatOrigin, const FormatData& format)
1119
result += textFormatToRtf(formatOrigin.text,format.text,false);
1121
if ( 1==format.text.verticalAlignment )
1123
result += "\\sub"; //Subscript
1125
else if ( 2==format.text.verticalAlignment )
1127
result += "\\super"; //Superscript
1134
QString RTFWorker::closeSpan(const FormatData& , const FormatData& )
1141
// The following function encodes the kword unicode characters into
1142
// RTF seven bit ASCII. This affects any 8 bit characters.
1143
// They are encoded either with \' or with \u
1144
QString RTFWorker::escapeRtfText ( const QString& text ) const
1146
// initialize strings
1147
QString escapedText;
1148
const uint length = text.length();
1149
for ( uint i = 0; i < length; i++ )
1151
QChar QCh ( text.at( i ) ); // get out one unicode char from the string
1152
const ushort ch = QCh.unicode(); // take unicode value of the char
1154
if ( QCh == '\\' ) escapedText += "\\\\"; // back-slash
1155
else if ( QCh == '{' ) escapedText += "\\{";
1156
else if ( QCh == '}' ) escapedText += "\\}";
1157
else if ( ch >= 32 && ch <= 127) // ASCII character
1159
else if ( ch == 0x0009 ) escapedText += "\\tab "; // tabulator
1160
else if ( ch == 0x00a0 ) escapedText += "\\~"; // Non-breaking space
1161
else if ( ch == 0x00ad ) escapedText += "\\-"; // Soft hyphen
1162
else if ( ch == 0x00b7 ) escapedText += "\\|";
1163
else if ( ch == 0x2011 ) escapedText += "\\_"; // Non-breaking hyphen
1164
else if ( ch == 0x2002 ) escapedText += "\\enspace ";
1165
else if ( ch == 0x2003 ) escapedText += "\\emspace ";
1166
else if ( ch == 0x2004 ) escapedText += "\\qmspace ";
1167
else if ( ch == 0x200c ) escapedText += "\\zwnj ";
1168
else if ( ch == 0x200d ) escapedText += "\\zwj ";
1169
else if ( ch == 0x200e ) escapedText += "\\ltrmark ";
1170
else if ( ch == 0x200f ) escapedText += "\\rtlmark ";
1171
else if ( ch == 0x2013 ) escapedText += "\\endash ";
1172
else if ( ch == 0x2014 ) escapedText += "\\emdash ";
1173
else if ( ch == 0x2018 ) escapedText += "\\lquote ";
1174
else if ( ch == 0x2019 ) escapedText += "\\rquote ";
1175
else if ( ch == 0x201c ) escapedText += "\\ldblquote ";
1176
else if ( ch == 0x201d ) escapedText += "\\rdblquote ";
1177
else if ( ch == 0x2022 ) escapedText += "\\bullet ";
1178
else if ( ch >= 160 && ch < 256) // check for characters common between ISO-8859-1 and CP1252
1179
{ // NOTE: 128 to 159 in CP1252 are somewhere else in UTF-8 and do not exist in ISO-8859-1 (### TODO?)
1180
escapedText += "\\\'"; // escape upper page character to 7 bit
1181
escapedText += QString::number ( ch, 16 );
1183
else if ( ch >= 256) // check for a higher code non-ASCII character
1185
// encode this as decimal unicode with a replacement character.
1186
escapedText += "\\u";
1187
escapedText += QString::number ( ch, 10 );
1188
// We decompose the character. If it works, the first character is whitout any accent.
1189
// (Of course this only works with Latin letters.)
1190
// WARNING: QChar::decomposition is not re-entrant in Qt 3.x
1191
QChar replacement ( QCh.decomposition().at(0) );
1192
kdDebug(30515) << "Proposed replacement character: " << QString(replacement) << endl;
1194
if (replacement.isNull() || replacement<=' ' || replacement>=char(127)
1195
|| replacement=='{' || replacement=='}' || replacement=='\\')
1196
replacement='?'; // Not a normal ASCII character, so default to show a ? sign
1198
escapedText += replacement; // The \uc1 dummy character.
1202
escapedText += QCh ;
1210
bool RTFWorker::doFullPaperFormat(const int /*format*/,
1211
const double width, const double height, const int orientation)
1213
m_paperWidth=width*20;
1214
m_paperHeight=height*20;
1215
m_paperOrientation=orientation;
1219
bool RTFWorker::doFullPaperBorders (const double top, const double left,
1220
const double bottom, const double right)
1222
m_paperMarginTop=top*20;
1223
m_paperMarginLeft=left*20;
1224
m_paperMarginBottom=bottom*20;
1225
m_paperMarginRight=right*20;
1229
bool RTFWorker::doFullDefineStyle(LayoutData& layout)
1231
//Register the new style in the style list
1232
m_styleList << layout;
1234
// Now we must register a few things (with help of the lookup methods.)
1235
lookupFont("\\f", layout.formatData.text.fontName);
1236
lookupColor(QString::null, layout.formatData.text.fgColor);
1237
lookupColor(QString::null, layout.formatData.text.bgColor);
1242
static QString writeDate(const QString keyword, const QDateTime& now)
1247
kdDebug(30515) << "Date " << keyword << " " << now.toString() << endl;
1250
const QDate nowDate(now.date());
1252
str += QString::number(nowDate.year());
1254
str += QString::number(nowDate.month());
1256
str += QString::number(nowDate.day());
1257
const QTime nowTime(now.time());
1259
str += QString::number(nowTime.hour());
1261
str += QString::number(nowTime.minute());
1263
str += QString::number(nowTime.second());
1267
kdWarning(30515) << "Date " << keyword << " is not valid! Skipping!" << endl;
1272
bool RTFWorker::doVariableSettings(const VariableSettingsData& vs)
1274
m_textDocInfo += writeDate("\\creatim",vs.creationTime);
1275
m_textDocInfo += writeDate("\\revtim", vs.modificationTime);
1276
m_textDocInfo += writeDate("\\printim",vs.printTime);
1277
m_startPageNumber = vs.startingPageNumber;
1282
QString RTFWorker::textFormatToRtf(const TextFormatting& formatOrigin,
1283
const TextFormatting& formatData, const bool force)
1285
// TODO: rename variable formatData
1286
QString strElement; // TODO: rename this variable
1289
const QString fontName(formatData.fontName);
1290
if (!fontName.isEmpty()
1291
&& (force || (formatOrigin.fontName!=formatData.fontName)))
1293
strElement+=lookupFont("\\f", fontName);
1296
if (force || (formatOrigin.fontSize!=formatData.fontSize))
1298
const int size=formatData.fontSize;
1302
strElement+=QString::number(2*size,10);
1306
if (force || (formatOrigin.italic!=formatData.italic))
1309
if ( formatData.italic )
1319
if (force || ((formatOrigin.weight>=75)!=(formatData.weight>=75)))
1321
if ( formatData.weight >= 75 )
1331
if (force || (formatOrigin.fgColor!=formatData.fgColor))
1333
if ( formatData.fgColor.isValid() )
1335
strElement+=lookupColor("\\cf", formatData.fgColor);
1339
if (force || (formatOrigin.bgColor!=formatData.bgColor))
1341
if ( formatData.bgColor.isValid() )
1343
strElement+=lookupColor("\\cb", formatData.bgColor);
1344
strElement+=lookupColor("\\highlight", formatData.bgColor); // MS Word wants this
1345
// ### FIXME: \highlight is not allowed in style definitions (RTF 1.6)
1350
|| ( formatOrigin.underline != formatData.underline )
1351
|| ( formatOrigin.underlineValue != formatData.underlineValue )
1352
|| ( formatOrigin.underlineStyle != formatData.underlineStyle )
1353
|| ( formatOrigin.underlineWord != formatData.underlineWord ) )
1355
if ( formatData.underline )
1357
QString underlineValue = formatData.underlineValue;
1358
QString underlineStyle = formatData.underlineStyle;
1359
bool underlineWord = formatData.underlineWord;
1360
QString ul ( "\\ul" ); // fall-back: simple underline
1362
if( underlineStyle.isEmpty() ) underlineStyle = "solid";
1363
if( underlineValue == "1" ) underlineValue = "single";
1365
if( underlineValue == "double" )
1367
else if( underlineValue == "single-bold" )
1369
else if( underlineValue == "wave" )
1371
else if( underlineValue == "single" )
1373
if( underlineStyle == "dash" )
1375
else if( underlineStyle == "dot" )
1377
else if( underlineStyle == "dashdot" )
1379
else if( underlineStyle == "dashdotdot" )
1381
else if( underlineWord )
1386
if (formatData.underlineColor.isValid())
1388
strElement+=lookupColor("\\ulc",formatData.underlineColor);
1393
strElement+="\\ul0";
1398
|| ( formatOrigin.strikeout != formatData.strikeout )
1399
|| ( formatOrigin.strikeoutType != formatData.strikeoutType ) )
1401
if ( formatData.strikeout )
1403
if( formatData.strikeoutType == "double" )
1404
strElement+="\\striked1"; // 1 needed! (The exception that confirms the rule!)
1406
strElement+="\\strike";
1410
strElement+="\\strike0"; // ### TODO: \striked0 too?
1417
QString RTFWorker::layoutToRtf(const LayoutData& layoutOrigin,
1418
const LayoutData& layout, const bool force)
1423
if (force || (layoutOrigin.alignment!=layout.alignment))
1425
if (layout.alignment=="left")
1426
strLayout += "\\ql";
1427
else if (layout.alignment== "right")
1428
strLayout += "\\qr";
1429
else if (layout.alignment=="center")
1430
strLayout += "\\qc";
1431
else if (layout.alignment=="justify")
1432
strLayout += "\\qj";
1433
else if ( layout.alignment=="auto")
1435
// ### TODO: what for BIDI?
1436
//strLayout += "\\ql";
1440
kdWarning(30515) << "Unknown alignment: " << layout.alignment << endl;
1444
if ((layout.indentLeft>=0.0)
1445
&& (force || (layoutOrigin.indentLeft!=layout.indentLeft)))
1447
strLayout += "\\li";
1448
strLayout += QString::number(int(layout.indentLeft)*20, 10);
1451
if ((layout.indentRight>=0.0)
1452
&& (force || (layoutOrigin.indentRight!=layout.indentRight)))
1454
strLayout += "\\ri";
1455
strLayout += QString::number(int(layout.indentRight)*20, 10);
1458
if (force || (layoutOrigin.indentFirst!=layout.indentFirst))
1460
strLayout += "\\fi";
1461
strLayout += QString::number(int(layout.indentFirst)*20, 10);
1464
if ((layout.marginBottom>=0.0)
1465
&& (force || (layoutOrigin.marginBottom!=layout.marginBottom)))
1467
strLayout += "\\sa";
1468
strLayout += QString::number(int(layout.marginBottom)*20 ,10);
1471
if ((layout.marginTop>=0.0)
1472
&& (force || (layoutOrigin.marginTop!=layout.marginTop)))
1474
strLayout += "\\sb";
1475
strLayout += QString::number(int(layout.marginTop)*20, 10);
1478
if (force || (layoutOrigin.keepLinesTogether!=layout.keepLinesTogether))
1480
if(layout.keepLinesTogether) strLayout += "\\keep";
1483
// Note: there seems to be too many problems of using a page break in a layout
1484
// - KWord's RTF import filter makes the page break immediately (also in styles)
1485
// - AbiWord's RTF import does not like \*\pgbrk
1486
// ### TODO: decide if we really remove this code
1488
if (force || (layoutOrigin.pageBreakBefore!=layout.pageBreakBefore))
1490
if(layout.pageBreakBefore) strLayout += "\\pagebb";
1493
// Note: RTF doesn't specify "page break after"
1494
// \*\pgbrk0 is used after OpenOffice.org Writer
1495
if (force || (layoutOrigin.pageBreakAfter!=layout.pageBreakAfter))
1497
if(layout.pageBreakAfter) strLayout += "\\*\\pgbrk0";
1502
|| (layoutOrigin.lineSpacingType!=layoutOrigin.lineSpacingType)
1503
|| (layoutOrigin.lineSpacing!=layoutOrigin.lineSpacing))
1505
if ( layout.lineSpacingType==LayoutData::LS_SINGLE )
1506
;// do nothing, single linespace is default in RTF
1508
else if ( layout.lineSpacingType==LayoutData::LS_ONEANDHALF )
1509
strLayout += "\\sl360\\slmult1"; // one-and-half linespace
1511
else if ( layout.lineSpacingType==LayoutData::LS_DOUBLE )
1512
strLayout += "\\sl480\\slmult1"; // double linespace
1514
else if ( layout.lineSpacingType==LayoutData::LS_ATLEAST )
1515
strLayout += QString("\\sl%1\\slmult0").arg(int(layout.lineSpacing)*20);
1517
else if ( layout.lineSpacingType==LayoutData::LS_MULTIPLE )
1518
strLayout += QString("\\sl%1\\slmult1").arg( int(layout.lineSpacing)*240 );
1520
else if ( layout.lineSpacingType==LayoutData::LS_CUSTOM )
1521
// "Custom" in KWord is like "Exactly" in MS Word
1522
strLayout += QString("\\sl-%1\\slmult0").arg(int(layout.lineSpacing)*20);
1525
kdWarning(30515) << "Curious lineSpacingType: " << layout.lineSpacingType << " (Ignoring!)" << endl;
1528
if (!layout.tabulatorList.isEmpty()
1529
&& (force || (layoutOrigin.tabulatorList!=layout.tabulatorList) ))
1531
TabulatorList::ConstIterator it;
1532
for (it=layout.tabulatorList.begin();it!=layout.tabulatorList.end();it++)
1534
switch ((*it).m_type)
1536
case 0: default: break; // left tab is default
1537
case 1: strLayout += "\\tqc"; break;
1538
case 2: strLayout += "\\tqr"; break;
1539
case 3: strLayout += "\\tqdec"; break;
1542
switch ((*it).m_filling)
1544
case TabulatorData::TF_NONE: default: break; // without leader/filling
1545
case TabulatorData::TF_DOT: strLayout += "\\tldot"; break;
1546
case TabulatorData::TF_LINE: strLayout += "\\tlul"; break;
1548
// these belows are all treated as RTF's \tqul
1549
case TabulatorData::TF_DASH:
1550
case TabulatorData::TF_DASHDOT:
1551
case TabulatorData::TF_DASHDOTDOT:
1552
strLayout += "\\tlul"; break;
1556
strLayout += "\\tx";
1557
strLayout += QString::number(int((*it).m_ptpos)*20, 10);
1563
// note shadow in KWord is more full-feature/sophisticated than RTF
1564
// here we just treat KWord's shadow as simple \shad mark-up
1565
if( layout.shadowDistance > 0 )
1567
strLayout += "\\shad";
1572
// This must remain last, as it adds a terminating space.
1573
strLayout+=textFormatToRtf(layoutOrigin.formatData.text,
1574
layout.formatData.text,force);
1580
QString RTFWorker::lookupFont(const QString& markup, const QString& fontName)
1582
if (fontName.isEmpty())
1583
return QString::null;
1585
// First we have to remove Qt-typical foundry names, as some RTF readers are confused by them.
1586
QString cookedFontName(fontName);
1587
QRegExp regexp("\\s*\\[\\S*\\]"); // Some white space, opening square bracket, some non-white-space, ending square bracket
1588
cookedFontName.remove(regexp);
1589
// But we cannot have an empty name font
1590
if (cookedFontName.isEmpty())
1591
cookedFontName=fontName;
1593
kdDebug(30515) << "RTFWorker::lookupFont " << fontName << " cooked: " << cookedFontName << endl;
1595
uint counter=0; // counts position in font table (starts at 0)
1596
QString strFont(markup); // markup for font selection
1597
QStringList::ConstIterator it;
1599
// search font table for this font
1600
for( it = m_fontList.begin(); it != m_fontList.end(); counter++, it++ )
1602
if((*it) == cookedFontName) // check for match
1604
strFont += QString::number(counter);
1605
kdDebug(30515) << strFont << endl;
1610
kdDebug(30515) << "New font: " << cookedFontName << " count: " << counter << endl;
1611
m_fontList << cookedFontName;
1613
strFont += QString::number(counter);
1617
QString RTFWorker::lookupColor(const QString& markup, const QColor& color)
1619
if (!color.isValid())
1620
return QString::null;
1622
uint counter=1; // counts position in color table starting at 1
1623
QString strColor(markup); // Holds RTF markup for the color
1625
QValueList < QColor > ::ConstIterator it;
1627
// search color table for this color
1628
for( it = m_colorList.begin(); it != m_colorList.end(); counter++, it++ )
1630
if ( (*it) == color )
1632
strColor += QString::number(counter);
1637
kdDebug(30515) << "New color: " << color.name() << " count: " << counter << endl;
1638
m_colorList << color;
1640
strColor += QString::number(counter);
1644
QString RTFWorker::lookupStyle(const QString& styleName, LayoutData& returnLayout)
1646
if (styleName.isEmpty())
1647
return QString::null;
1649
uint counter=0; // counts position in style table starting at 0
1650
QString strMarkup("\\s"); // Holds RTF markup for the style
1652
QValueList < LayoutData > ::ConstIterator it;
1654
// search color table for this color
1655
for( it = m_styleList.begin(); it != m_styleList.end(); counter++, it++ )
1657
if ( (*it).styleName == styleName )
1659
strMarkup += QString::number(counter);
1665
kdDebug(30515) << "New style: " << styleName << " count: " << counter << endl;
1667
m_styleList << layout;
1668
returnLayout=layout;
1670
strMarkup += QString::number(counter);