filters

texthandler.cpp

00001 /* This file is part of the KOffice project
00002    Copyright (C) 2002 Werner Trobin <trobin@kde.org>
00003    Copyright (C) 2002 David Faure <faure@kde.org>
00004 
00005    This program is free software; you can redistribute it and/or
00006    modify it under the terms of the GNU General Public
00007    License version 2 as published by the Free Software Foundation.
00008 
00009    This program is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY; without even the implied warranty of
00011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012     General Public License for more details.
00013 
00014    You should have received a copy of the GNU General Public License
00015    along with this program; see the file COPYING.  If not, write to
00016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00017  * Boston, MA 02110-1301, USA.
00018 */
00019 
00020 #include "texthandler.h"
00021 #include "conversion.h"
00022 
00023 #include <wv2/styles.h>
00024 #include <wv2/lists.h>
00025 #include <wv2/paragraphproperties.h>
00026 #include <wv2/functor.h>
00027 #include <wv2/functordata.h>
00028 #include <wv2/ustring.h>
00029 #include <wv2/parser.h>
00030 #include <wv2/fields.h>
00031 
00032 #include <qfont.h>
00033 #include <qfontinfo.h>
00034 #include <kdebug.h>
00035 #include <klocale.h>
00036 
00037 
00038 wvWare::U8 KWordReplacementHandler::hardLineBreak()
00039 {
00040     return '\n';
00041 }
00042 
00043 wvWare::U8 KWordReplacementHandler::nonBreakingHyphen()
00044 {
00045     return '-'; // normal hyphen for now
00046 }
00047 
00048 wvWare::U8 KWordReplacementHandler::nonRequiredHyphen()
00049 {
00050     return 0xad; // soft hyphen, according to kword.dtd
00051 }
00052 
00053 
00054 KWordTextHandler::KWordTextHandler( wvWare::SharedPtr<wvWare::Parser> parser )
00055     : m_parser( parser ), m_sectionNumber( 0 ), m_footNoteNumber( 0 ), m_endNoteNumber( 0 ),
00056       m_previousOutlineLSID( 0 ), m_previousEnumLSID( 0 ),
00057       m_currentStyle( 0L ), m_index( 0 ),
00058       m_currentTable( 0L ),
00059       m_bInParagraph( false ),
00060       m_insideField( false ), m_fieldAfterSeparator( false ), m_fieldType( 0 )
00061 {
00062 }
00063 
00064 void KWordTextHandler::sectionStart( wvWare::SharedPtr<const wvWare::Word97::SEP> sep )
00065 {
00066     m_sectionNumber++;
00067 
00068     if ( m_sectionNumber == 1 )
00069     {
00070         // KWord doesn't support a different paper format per section.
00071         // So we use the paper format of the first section,
00072         // and we apply it to the whole document.
00073         emit firstSectionFound( sep );
00074     }
00075     else
00076     {
00077         // Not the first section. Check for a page break
00078         if ( sep->bkc >= 1 ) // 1=new column, 2=new page, 3=even page, 4=odd page
00079         {
00080             pageBreak();
00081         }
00082     }
00083 }
00084 
00085 void KWordTextHandler::sectionEnd()
00086 {
00087 
00088 }
00089 
00090 void KWordTextHandler::pageBreak()
00091 {
00092     // Check if PAGEBREAKING already exists (e.g. due to linesTogether)
00093     QDomElement pageBreak = m_oldLayout.namedItem( "PAGEBREAKING" ).toElement();
00094     if ( pageBreak.isNull() )
00095     {
00096         pageBreak = mainDocument().createElement( "PAGEBREAKING" );
00097         m_oldLayout.appendChild( pageBreak );
00098     }
00099     pageBreak.setAttribute( "hardFrameBreakAfter", "true" );
00100 }
00101 
00102 void KWordTextHandler::headersFound( const wvWare::HeaderFunctor& parseHeaders )
00103 {
00104     // Currently we only care about headers in the first section
00105     if ( m_sectionNumber == 1 )
00106     {
00107         emit subDocFound( new wvWare::HeaderFunctor( parseHeaders ), 0 );
00108     }
00109 }
00110 
00111 void KWordTextHandler::footnoteFound( wvWare::FootnoteData::Type type,
00112                                       wvWare::UChar character, wvWare::SharedPtr<const wvWare::Word97::CHP> chp,
00113                                       const wvWare::FootnoteFunctor& parseFootnote )
00114 {
00115     bool autoNumbered = (character.unicode() == 2);
00116     QDomElement varElem = insertVariable( 11 /*KWord code for footnotes*/, chp, "STRI" );
00117     QDomElement footnoteElem = varElem.ownerDocument().createElement( "FOOTNOTE" );
00118     if ( autoNumbered )
00119         footnoteElem.setAttribute( "value", 1 ); // KWord will renumber anyway
00120     else
00121         footnoteElem.setAttribute( "value", QString(QChar(character.unicode())) );
00122     footnoteElem.setAttribute( "notetype", type == wvWare::FootnoteData::Endnote ? "endnote" : "footnote" );
00123     footnoteElem.setAttribute( "numberingtype", autoNumbered ? "auto" : "manual" );
00124     if ( type == wvWare::FootnoteData::Endnote )
00125         // Keep name in sync with Document::startFootnote
00126         footnoteElem.setAttribute( "frameset", i18n("Endnote %1").arg( ++m_endNoteNumber ) );
00127     else
00128         // Keep name in sync with Document::startFootnote
00129         footnoteElem.setAttribute( "frameset", i18n("Footnote %1").arg( ++m_footNoteNumber ) );
00130     varElem.appendChild( footnoteElem );
00131 
00132     // Remember to parse the footnote text later
00133     emit subDocFound( new wvWare::FootnoteFunctor( parseFootnote ), type );
00134 }
00135 
00136 QDomElement KWordTextHandler::insertVariable( int type, wvWare::SharedPtr<const wvWare::Word97::CHP> chp, const QString& format )
00137 {
00138     m_paragraph += '#';
00139 
00140     QDomElement formatElem;
00141     writeFormat( m_formats, chp, m_currentStyle ? &m_currentStyle->chp() : 0, m_index, 1, 4 /*id*/, &formatElem );
00142 
00143     m_index += 1;
00144 
00145     QDomElement varElem = m_formats.ownerDocument().createElement( "VARIABLE" );
00146     QDomElement typeElem = m_formats.ownerDocument().createElement( "TYPE" );
00147     typeElem.setAttribute( "type", type );
00148     typeElem.setAttribute( "key", format );
00149     varElem.appendChild( typeElem );
00150     formatElem.appendChild( varElem );
00151     return varElem;
00152 }
00153 
00154 void KWordTextHandler::tableRowFound( const wvWare::TableRowFunctor& functor, wvWare::SharedPtr<const wvWare::Word97::TAP> tap )
00155 {
00156     if ( !m_currentTable )
00157     {
00158         // We need to put the table in a paragraph. For wv2 tables are between paragraphs.
00159         Q_ASSERT( !m_bInParagraph );
00160         paragraphStart( 0L );
00161         static int s_tableNumber = 0;
00162         m_currentTable = new KWord::Table();
00163         m_currentTable->name = i18n("Table %1").arg( ++s_tableNumber );
00164         insertAnchor( m_currentTable->name );
00165     }
00166 
00167     // Add all cell edges to our array.
00168     for (int i = 0; i <= tap->itcMac; i++)
00169         m_currentTable->cacheCellEdge( tap->rgdxaCenter[ i ] );
00170 
00171     KWord::Row row( new wvWare::TableRowFunctor( functor ), tap );
00172     m_currentTable->rows.append( row );
00173 }
00174 
00175 #ifdef IMAGE_IMPORT
00176 void KWordTextHandler::pictureFound( const wvWare::PictureFunctor& pictureFunctor,
00177                                      wvWare::SharedPtr<const wvWare::Word97::PICF> picf,
00178                                      wvWare::SharedPtr<const wvWare::Word97::CHP> /*chp*/ )
00179 {
00180     static unsigned int s_pictureNumber = 0;
00181     QString pictureName = "pictures/picture";
00182     pictureName += QString::number( s_pictureNumber ); // filenames start at 0
00183     // looks better to the user if frame names start at 1
00184     QString frameName = i18n("Picture %1").arg( ++s_pictureNumber );
00185     insertAnchor( frameName );
00186 
00187     switch ( picf->mfp.mm ) {
00188         case 98:
00189             pictureName += ".tif"; // not implemented!
00190             break;
00191         case 99:
00192             pictureName += ".bmp";
00193             break;
00194         default:
00195             pictureName += ".wmf";
00196             break;
00197     }
00198 
00199     emit pictureFound( frameName, pictureName, new wvWare::PictureFunctor( pictureFunctor ) );
00200 }
00201 #endif // IMAGE_IMPORT
00202 
00203 QDomElement KWordTextHandler::insertAnchor( const QString& fsname )
00204 {
00205     m_paragraph += '#';
00206 
00207     // Can't call writeFormat, we have no chp.
00208     QDomElement format( mainDocument().createElement( "FORMAT" ) );
00209     format.setAttribute( "id", 6 );
00210     format.setAttribute( "pos", m_index );
00211     format.setAttribute( "len", 1 );
00212     m_formats.appendChild( format );
00213     QDomElement formatElem = format;
00214 
00215     m_index += 1;
00216 
00217     QDomElement anchorElem = m_formats.ownerDocument().createElement( "ANCHOR" );
00218     anchorElem.setAttribute( "type", "frameset" );
00219     anchorElem.setAttribute( "instance", fsname );
00220     formatElem.appendChild( anchorElem );
00221     return anchorElem;
00222 }
00223 
00224 void KWordTextHandler::paragLayoutBegin()
00225 {
00226 }
00227 
00228 void KWordTextHandler::paragraphStart( wvWare::SharedPtr<const wvWare::ParagraphProperties> paragraphProperties )
00229 {
00230     if ( m_bInParagraph )
00231         paragraphEnd();
00232     m_bInParagraph = true;
00233     //kdDebug(30513) << "paragraphStart. style index:" << paragraphProperties->pap().istd << endl;
00234     m_formats = mainDocument().createElement( "FORMATS" );
00235     m_paragraphProperties = paragraphProperties;
00236     const wvWare::StyleSheet& styles = m_parser->styleSheet();
00237     m_currentStyle = 0;
00238     if ( paragraphProperties ) // Always set when called by wv2. But not set when called by tableStart.
00239     {
00240         m_currentStyle = styles.styleByIndex( paragraphProperties->pap().istd );
00241         Q_ASSERT( m_currentStyle );
00242     }
00243     paragLayoutBegin();
00244 }
00245 
00246 void KWordTextHandler::paragraphEnd()
00247 {
00248     Q_ASSERT( m_bInParagraph );
00249     if ( m_currentTable )
00250     {
00251         emit tableFound( *m_currentTable );
00252         delete m_currentTable;
00253         m_currentTable = 0L;
00254     }
00255     if ( m_currentStyle ) {
00256         QConstString styleName = Conversion::string( m_currentStyle->name() );
00257         writeOutParagraph( styleName.string(), m_paragraph );
00258     } else
00259         writeOutParagraph( "Standard", m_paragraph );
00260     m_bInParagraph = false;
00261 }
00262 
00263 void KWordTextHandler::fieldStart( const wvWare::FLD* fld, wvWare::SharedPtr<const wvWare::Word97::CHP> /*chp*/ )
00264 {
00265     m_fieldType = Conversion::fldToFieldType( fld );
00266     m_insideField = true;
00267     m_fieldAfterSeparator = false;
00268     m_fieldValue = "";
00269 }
00270 
00271 void KWordTextHandler::fieldSeparator( const wvWare::FLD* /*fld*/, wvWare::SharedPtr<const wvWare::Word97::CHP> /*chp*/ )
00272 {
00273     m_fieldAfterSeparator = true;
00274 }
00275 
00276 void KWordTextHandler::fieldEnd( const wvWare::FLD* /*fld*/, wvWare::SharedPtr<const wvWare::Word97::CHP> chp )
00277 {
00278     // only for handled field type
00279     if( m_fieldType >= 0 )
00280     {
00281         QDomElement varElem = insertVariable( 8, chp, "STRING" );
00282         QDomElement fieldElem = varElem.ownerDocument().createElement( "FIELD" );
00283         fieldElem.setAttribute( "subtype", m_fieldType );
00284         fieldElem.setAttribute( "value", m_fieldValue );
00285         varElem.appendChild( fieldElem );
00286     }
00287 
00288     // reset
00289     m_fieldValue = "";
00290     m_fieldType = -1;
00291     m_insideField = false;
00292     m_fieldAfterSeparator = false;
00293 }
00294 
00295 void KWordTextHandler::runOfText( const wvWare::UString& text, wvWare::SharedPtr<const wvWare::Word97::CHP> chp )
00296 {
00297     QConstString newText( Conversion::string( text ) );
00298     //kdDebug(30513) << "runOfText: " << newText.string() << endl;
00299 
00300     // text after fieldStart and before fieldSeparator is useless
00301     if( m_insideField && !m_fieldAfterSeparator ) return;
00302 
00303     // if we can handle the field, consume the text
00304     if( m_insideField && m_fieldAfterSeparator && ( m_fieldType >= 0 ) )
00305     {
00306         m_fieldValue.append( newText.string() );
00307         return;
00308     }
00309 
00310     m_paragraph += newText.string();
00311 
00312     writeFormat( m_formats, chp, m_currentStyle ? &m_currentStyle->chp() : 0, m_index, text.length(), 1, 0L );
00313 
00314     m_index += text.length();
00315 
00316 }
00317 
00318 void KWordTextHandler::writeFormat( QDomElement& parentElement, const wvWare::Word97::CHP* chp, const wvWare::Word97::CHP* refChp, int pos, int len, int formatId, QDomElement* pChildElement )
00319 {
00320     QDomElement format( mainDocument().createElement( "FORMAT" ) );
00321     format.setAttribute( "id", formatId );
00322     format.setAttribute( "pos", pos );
00323     format.setAttribute( "len", len );
00324 
00325     if ( !refChp || refChp->ico != chp->ico )
00326     {
00327         QColor color = Conversion::color( chp->ico, -1 );
00328         QDomElement colorElem( mainDocument().createElement( "COLOR" ) );
00329         colorElem.setAttribute( "red", color.red() );
00330         colorElem.setAttribute( "blue", color.blue() );
00331         colorElem.setAttribute( "green", color.green() );
00332         format.appendChild( colorElem );
00333     }
00334 
00335     // Font name
00336     // TBD: We only use the Ascii font code. We should work out how/when to use the FE and Other font codes.
00337     if ( !refChp || refChp->ftcAscii != chp->ftcAscii )
00338     {
00339         QString fontName = getFont( chp->ftcAscii );
00340 
00341         if ( !fontName.isEmpty() )
00342         {
00343             QDomElement fontElem( mainDocument().createElement( "FONT" ) );
00344             fontElem.setAttribute( "name", fontName );
00345             format.appendChild( fontElem );
00346         }
00347     }
00348 
00349     if ( !refChp || refChp->hps != chp->hps )
00350     {
00351         //kdDebug(30513) << "        font size: " << chp->hps/2 << endl;
00352         QDomElement fontSize( mainDocument().createElement( "SIZE" ) );
00353         fontSize.setAttribute( "value", (int)(chp->hps / 2) ); // hps is in half points
00354         format.appendChild( fontSize );
00355     }
00356 
00357     if ( !refChp || refChp->fBold != chp->fBold ) {
00358         QDomElement weight( mainDocument().createElement( "WEIGHT" ) );
00359         weight.setAttribute( "value", chp->fBold ? 75 : 50 );
00360         format.appendChild( weight );
00361     }
00362     if ( !refChp || refChp->fItalic != chp->fItalic ) {
00363         QDomElement italic( mainDocument().createElement( "ITALIC" ) );
00364         italic.setAttribute( "value", chp->fItalic ? 1 : 0 );
00365         format.appendChild( italic );
00366     }
00367     if ( !refChp || refChp->kul != chp->kul ) {
00368         QDomElement underline( mainDocument().createElement( "UNDERLINE" ) );
00369         QString val = (chp->kul == 0) ? "0" : "1";
00370         switch ( chp->kul ) {
00371         case 3: // double
00372             underline.setAttribute( "styleline", "solid" );
00373             val = "double";
00374             break;
00375         case 6: // thick
00376             underline.setAttribute( "styleline", "solid" );
00377             val = "single-bold";
00378             break;
00379         case 7:
00380             underline.setAttribute( "styleline", "dash" );
00381             break;
00382         case 4: // dotted
00383         case 8: // dot (not used, says the docu)
00384             underline.setAttribute( "styleline", "dot" );
00385             break;
00386         case 9:
00387             underline.setAttribute( "styleline", "dashdot" );
00388             break;
00389         case 10:
00390             underline.setAttribute( "styleline", "dashdotdot" );
00391             break;
00392         case 11: // wave
00393             underline.setAttribute( "styleline", "wave" );
00394             break;
00395         case 5: // hidden - This makes no sense as an underline property!
00396             val = "0";
00397             break;
00398         case 1: // single
00399         case 2: // by word - TODO
00400         default:
00401             underline.setAttribute( "styleline", "solid" );
00402         };
00403         underline.setAttribute( "value", val );
00404         format.appendChild( underline );
00405     }
00406     if ( !refChp || refChp->fStrike != chp->fStrike || refChp->fDStrike != chp->fDStrike ) {
00407         QDomElement strikeout( mainDocument().createElement( "STRIKEOUT" ) );
00408         if ( chp->fDStrike ) // double strikethrough
00409         {
00410             strikeout.setAttribute( "value", "double" );
00411             strikeout.setAttribute( "styleline", "solid" );
00412         }
00413         else if ( chp->fStrike )
00414         {
00415             strikeout.setAttribute( "value", "single" );
00416             strikeout.setAttribute( "styleline", "solid" );
00417         }
00418         else
00419             strikeout.setAttribute( "value", "0" );
00420         format.appendChild( strikeout );
00421     }
00422 
00423     // font attribute (uppercase, lowercase (not in MSWord), small caps)
00424     if ( !refChp || refChp->fCaps != chp->fCaps || refChp->fSmallCaps != chp->fSmallCaps )
00425     {
00426         QDomElement fontAttrib( mainDocument().createElement( "FONTATTRIBUTE" ) );
00427         fontAttrib.setAttribute( "value", chp->fSmallCaps ? "smallcaps" : chp->fCaps ? "uppercase" : "none" );
00428         format.appendChild( fontAttrib );
00429     }
00430     if ( !refChp || refChp->iss != chp->iss ) { // superscript/subscript
00431         QDomElement vertAlign( mainDocument().createElement( "VERTALIGN" ) );
00432         // Obviously the values are reversed between the two file formats :)
00433         int kwordVAlign = (chp->iss==1 ? 2 : chp->iss==2 ? 1 : 0);
00434         vertAlign.setAttribute( "value", kwordVAlign );
00435         format.appendChild( vertAlign );
00436     }
00437 
00438     // background color is known as "highlight" in msword
00439     if ( !refChp || refChp->fHighlight != chp->fHighlight || refChp->icoHighlight != chp->icoHighlight ) {
00440         QDomElement bgcolElem( mainDocument().createElement( "TEXTBACKGROUNDCOLOR" ) );
00441         if ( chp->fHighlight )
00442         {
00443             QColor color = Conversion::color( chp->icoHighlight, -1 );
00444             bgcolElem.setAttribute( "red", color.red() );
00445             bgcolElem.setAttribute( "blue", color.blue() );
00446             bgcolElem.setAttribute( "green", color.green() );
00447         } else {
00448             bgcolElem.setAttribute( "red", -1 );
00449             bgcolElem.setAttribute( "blue", -1 );
00450             bgcolElem.setAttribute( "green", -1 );
00451         }
00452         format.appendChild( bgcolElem );
00453     }
00454 
00455     // Shadow text. Only on/off. The properties are defined at the paragraph level (in KWord).
00456     if ( !refChp || refChp->fShadow != chp->fShadow || refChp->fImprint != chp->fImprint ) {
00457         QDomElement shadowElem( mainDocument().createElement( "SHADOW" ) );
00458         QString css = "none";
00459         // Generate a shadow with hardcoded values that make it look like in MSWord.
00460         // We need to make the distance depend on the font size though, to look good
00461         if (chp->fShadow || chp->fImprint)
00462         {
00463             int fontSize = (int)(chp->hps / 2);
00464             int dist = fontSize > 20 ? 2 : 1;
00465             if (chp->fImprint) // ## no real support for imprint, we use a topleft shadow
00466                 dist = -dist;
00467             css = QString::fromLatin1("#bebebe %1pt %1pt").arg(dist).arg(dist);
00468         }
00469         shadowElem.setAttribute( "text-shadow", css );
00470         format.appendChild( shadowElem );
00471     }
00472 
00473     if ( pChildElement || !format.firstChild().isNull() ) // Don't save an empty format tag, unless the caller asked for it
00474         parentElement.appendChild( format );
00475     if ( pChildElement )
00476         *pChildElement = format;
00477 }
00478 
00479 //#define FONT_DEBUG
00480 
00481 // Return the name of a font. We have to convert the Microsoft font names to
00482 // something that might just be present under X11.
00483 QString KWordTextHandler::getFont(unsigned fc) const
00484 {
00485     Q_ASSERT( m_parser );
00486     if ( !m_parser )
00487         return QString::null;
00488     const wvWare::Word97::FFN& ffn ( m_parser->font( fc ) );
00489 
00490     QConstString fontName( Conversion::string( ffn.xszFfn ) );
00491     QString font = fontName.string();
00492 
00493 #ifdef FONT_DEBUG
00494     kdDebug(30513) << "    MS-FONT: " << font << endl;
00495 #endif
00496 
00497     static const unsigned ENTRIES = 6;
00498     static const char* const fuzzyLookup[ENTRIES][2] =
00499     {
00500         // MS contains      X11 font family
00501         // substring.       non-Xft name.
00502         { "times",          "times" },
00503         { "courier",        "courier" },
00504         { "andale",         "monotype" },
00505         { "monotype.com",   "monotype" },
00506         { "georgia",        "times" },
00507         { "helvetica",      "helvetica" }
00508     };
00509 
00510     // When Xft is available, Qt will do a good job of looking up our local
00511     // equivalent of the MS font. But, we want to work even without Xft.
00512     // So, first, we do a fuzzy match of some common MS font names.
00513     unsigned i;
00514 
00515     for (i = 0; i < ENTRIES; i++)
00516     {
00517         // The loop will leave unchanged any MS font name not fuzzy-matched.
00518         if (font.find(fuzzyLookup[i][0], 0, FALSE) != -1)
00519         {
00520             font = fuzzyLookup[i][1];
00521             break;
00522         }
00523     }
00524 
00525 #ifdef FONT_DEBUG
00526     kdDebug(30513) << "    FUZZY-FONT: " << font << endl;
00527 #endif
00528 
00529     // Use Qt to look up our canonical equivalent of the font name.
00530     QFont xFont( font );
00531     QFontInfo info( xFont );
00532 
00533 #ifdef FONT_DEBUG
00534     kdDebug(30513) << "    QT-FONT: " << info.family() << endl;
00535 #endif
00536 
00537     return info.family();
00538 }
00539 
00540 void KWordTextHandler::writeOutParagraph( const QString& styleName, const QString& text )
00541 {
00542     if ( m_framesetElement.isNull() )
00543     {
00544         if ( !text.isEmpty() ) // vertically merged table cells are ignored, and have empty text -> no warning on those
00545             kdWarning(30513) << "KWordTextHandler: no frameset element to write to! text=" << text << endl;
00546         return;
00547     }
00548     QDomElement paragraphElementOut=mainDocument().createElement("PARAGRAPH");
00549     m_framesetElement.appendChild(paragraphElementOut);
00550     QDomElement textElement=mainDocument().createElement("TEXT");
00551     textElement.setAttribute( "xml:space", "preserve" );
00552     paragraphElementOut.appendChild(textElement);
00553     paragraphElementOut.appendChild( m_formats );
00554     QDomElement layoutElement=mainDocument().createElement("LAYOUT");
00555     paragraphElementOut.appendChild(layoutElement);
00556 
00557     QDomElement nameElement = mainDocument().createElement("NAME");
00558     nameElement.setAttribute("value", styleName);
00559     layoutElement.appendChild(nameElement);
00560 
00561     if ( m_paragraphProperties )
00562     {
00563         // Write out the properties of the paragraph
00564         writeLayout( layoutElement, *m_paragraphProperties, m_currentStyle );
00565     }
00566 
00567     textElement.appendChild(mainDocument().createTextNode(text));
00568 
00569     m_paragraph = QString( "" );
00570     m_index = 0;
00571     m_oldLayout = layoutElement; // Keep a reference to the old layout for some hacks
00572 }
00573 
00574 void KWordTextHandler::writeLayout( QDomElement& parentElement, const wvWare::ParagraphProperties& paragraphProperties, const wvWare::Style* style )
00575 {
00576     const wvWare::Word97::PAP& pap = paragraphProperties.pap();
00577     // Always write out the alignment, it's required
00578     QDomElement flowElement = mainDocument().createElement("FLOW");
00579     QString alignment = Conversion::alignment( pap.jc );
00580     flowElement.setAttribute( "align", alignment );
00581     parentElement.appendChild( flowElement );
00582 
00583     //kdDebug(30513) << k_funcinfo << " dxaLeft1=" << pap.dxaLeft1 << " dxaLeft=" << pap.dxaLeft << " dxaRight=" << pap.dxaRight << " dyaBefore=" << pap.dyaBefore << " dyaAfter=" << pap.dyaAfter << " lspd=" << pap.lspd.dyaLine << "/" << pap.lspd.fMultLinespace << endl;
00584 
00585     if ( pap.dxaLeft1 || pap.dxaLeft || pap.dxaRight )
00586     {
00587         QDomElement indentsElement = mainDocument().createElement("INDENTS");
00588         // 'first' is relative to 'left' in both formats
00589         indentsElement.setAttribute( "first", (double)pap.dxaLeft1 / 20.0 );
00590         indentsElement.setAttribute( "left", (double)pap.dxaLeft / 20.0 );
00591         indentsElement.setAttribute( "right", (double)pap.dxaRight / 20.0 );
00592         parentElement.appendChild( indentsElement );
00593     }
00594     if ( pap.dyaBefore || pap.dyaAfter )
00595     {
00596         QDomElement offsetsElement = mainDocument().createElement("OFFSETS");
00597         offsetsElement.setAttribute( "before", (double)pap.dyaBefore / 20.0 );
00598         offsetsElement.setAttribute( "after", (double)pap.dyaAfter / 20.0 );
00599         parentElement.appendChild( offsetsElement );
00600     }
00601 
00602     // Linespacing
00603     QString lineSpacing = Conversion::lineSpacing( pap.lspd );
00604     if ( lineSpacing != "0" )
00605     {
00606         QDomElement lineSpacingElem = mainDocument().createElement( "LINESPACING" );
00607         lineSpacingElem.setAttribute("value", lineSpacing );
00608         parentElement.appendChild( lineSpacingElem );
00609     }
00610 
00611     if ( pap.fKeep || pap.fKeepFollow || pap.fPageBreakBefore )
00612     {
00613         QDomElement pageBreak = mainDocument().createElement( "PAGEBREAKING" );
00614         if ( pap.fKeep )
00615             pageBreak.setAttribute("linesTogether", "true");
00616         if ( pap.fPageBreakBefore )
00617             pageBreak.setAttribute("hardFrameBreak", "true" );
00618         if ( pap.fKeepFollow )
00619             pageBreak.setAttribute("keepWithNext", "true" );
00620         parentElement.appendChild( pageBreak );
00621     }
00622 
00623     // Borders
00624     if ( pap.brcTop.brcType )
00625     {
00626         QDomElement borderElement = mainDocument().createElement( "TOPBORDER" );
00627         Conversion::setBorderAttributes( borderElement, pap.brcTop );
00628         parentElement.appendChild( borderElement );
00629     }
00630     if ( pap.brcBottom.brcType )
00631     {
00632         QDomElement borderElement = mainDocument().createElement( "BOTTOMBORDER" );
00633         Conversion::setBorderAttributes( borderElement, pap.brcBottom );
00634         parentElement.appendChild( borderElement );
00635     }
00636     if ( pap.brcLeft.brcType )
00637     {
00638         QDomElement borderElement = mainDocument().createElement( "LEFTBORDER" );
00639         Conversion::setBorderAttributes( borderElement, pap.brcLeft );
00640         parentElement.appendChild( borderElement );
00641     }
00642     if ( pap.brcRight.brcType )
00643     {
00644         QDomElement borderElement = mainDocument().createElement( "RIGHTBORDER" );
00645         Conversion::setBorderAttributes( borderElement, pap.brcRight );
00646         parentElement.appendChild( borderElement );
00647     }
00648 
00649     // Tabulators
00650     if ( pap.itbdMac )
00651     {
00652         for ( int i = 0 ; i < pap.itbdMac ; ++i )
00653         {
00654             const wvWare::Word97::TabDescriptor &td = pap.rgdxaTab[i];
00655             QDomElement tabElement = mainDocument().createElement( "TABULATOR" );
00656             tabElement.setAttribute( "ptpos", (double)td.dxaTab / 20.0 );
00657             //kdDebug(30513) << "ptpos=" << (double)td.dxaTab / 20.0 << endl;
00658             // Wow, lucky here. The type enum matches. Only, MSWord has 4=bar,
00659             // which kword doesn't support. We map it to 0 with a clever '%4' :)
00660             tabElement.setAttribute( "type", td.tbd.jc % 4 );
00661             int filling = 0;
00662             double width = 0.5; // default kword value, see koparaglayout.cc
00663             switch ( td.tbd.tlc ) {
00664             case 1: // dots
00665             case 2: // hyphenated
00666                 filling = 1; // KWord: dots
00667                 break;
00668             case 3: // single line
00669                 filling = 2; // KWord: line
00670                 break;
00671             case 4: // heavy line
00672                 filling = 2; // KWord: line
00673                 width = 2; // make it heavy. To be tested.
00674             }
00675             tabElement.setAttribute( "filling", filling );
00676             tabElement.setAttribute( "width", width );
00677             parentElement.appendChild( tabElement );
00678         }
00679     }
00680 
00681     if ( pap.ilfo > 0 )
00682     {
00683         writeCounter( parentElement, paragraphProperties, style );
00684     }
00685 }
00686 
00687 void KWordTextHandler::writeCounter( QDomElement& parentElement, const wvWare::ParagraphProperties& paragraphProperties, const wvWare::Style* style )
00688 {
00689     const wvWare::ListInfo* listInfo = paragraphProperties.listInfo();
00690     if ( !listInfo )
00691         return;
00692 
00693 #ifndef NDEBUG
00694     listInfo->dump();
00695 #endif
00696 
00697     QDomElement counterElement = mainDocument().createElement( "COUNTER" );
00698     // numbering type: 0==list 1==chapter. First we determine it for word6 docs.
00699     // But we can also activate it if the text() looks that way
00700     int numberingType = listInfo->isWord6() && listInfo->prev() ? 1 : 0;
00701     wvWare::UString text = listInfo->text().text;
00702     int nfc = listInfo->numberFormat();
00703     if ( nfc == 23 ) // bullet
00704     {
00705         if ( text.length() == 1 )
00706         {
00707             unsigned int code = text[0].unicode();
00708             if ( (code & 0xFF00) == 0xF000 ) // see wv2
00709                 code &= 0x00FF;
00710             // Some well-known bullet codes. Better turn them into real
00711             // KWord bullets, it looks much nicer (than crappy fonts).
00712             if ( code == 0xB7 ) // Round black bullet
00713             {
00714                 counterElement.setAttribute( "type", 10 ); // disc bullet
00715             } else if ( code == 0xD8 ) // Triangle
00716             {
00717                 counterElement.setAttribute( "type", 11 ); // Box. We have no triangle.
00718             } else {
00719                 // Map all other bullets to a "custom bullet" in kword.
00720                 kdDebug(30513) << "custom bullet, code=" << QString::number(code,16) << endl;
00721                 counterElement.setAttribute( "type", 6 ); // custom
00722                 counterElement.setAttribute( "bullet", code );
00723                 QString paragFont = getFont( style->chp().ftcAscii );
00724                 counterElement.setAttribute( "bulletfont", paragFont );
00725             }
00726         } else
00727             kdWarning(30513) << "Bullet with more than one character, not supported" << endl;
00728     }
00729     else
00730     {
00731         const wvWare::Word97::PAP& pap = paragraphProperties.pap();
00732         counterElement.setAttribute( "start", listInfo->startAt() );
00733 
00734         int depth = pap.ilvl; /*both are 0 based*/
00735         // Heading styles don't set the ilvl, but must have a depth coming
00736         // from their heading level (the style's STI)
00737         bool isHeading = style->sti() >= 1 && style->sti() <= 9;
00738         if ( depth == 0 && isHeading )
00739         {
00740             depth = style->sti() - 1;
00741         }
00742         kdDebug(30513) << "  ilfo=" << pap.ilfo << " ilvl=" << pap.ilvl << " sti=" << style->sti() << " depth=" << depth << " numberingType=" << numberingType << endl;
00743         counterElement.setAttribute( "depth", depth );
00744 
00745         // Now we need to parse the text, to try and convert msword's powerful list template
00746         // stuff, into what KWord can do right now.
00747         QString prefix, suffix;
00748         bool depthFound = false;
00749         int displayLevels = 1;
00750         // We parse <0>.<2>.<1>. as "level 2 with suffix='.'" (no prefix)
00751         // But "Section <0>)" has both prefix and suffix.
00752         // The common case is <0>.<1>.<2> (display-levels=3)
00753         for ( int i = 0 ; i < text.length() ; ++i )
00754         {
00755             short ch = text[i].unicode();
00756             //kdDebug(30513) << i << ":" << ch << endl;
00757             if ( ch < 10 ) { // List level place holder
00758                 if ( ch == pap.ilvl ) {
00759                     if ( depthFound )
00760                         kdWarning(30513) << "ilvl " << pap.ilvl << " found twice in listInfo text..." << endl;
00761                     else
00762                         depthFound = true;
00763                     suffix = QString::null;
00764                 } else {
00765                     Q_ASSERT( ch < pap.ilvl ); // Can't see how level 1 would have a <0> in it...
00766                     if ( ch < pap.ilvl )
00767                         ++displayLevels; // we found a 'parent level', to be displayed
00768                     prefix = QString::null; // get rid of previous prefixes
00769                 }
00770             } else { // Normal character
00771                 if ( depthFound )
00772                     suffix += QChar(ch);
00773                 else
00774                     prefix += QChar(ch);
00775             }
00776         }
00777         if ( displayLevels > 1 )
00778         {
00779             // This is a hierarchical list numbering e.g. <0>.<1>.
00780             // (unless this is about a heading, in which case we've set numberingtype to 1 already
00781             // so it will indeed look like that).
00782             // The question is whether the '.' is the suffix of the parent level already..
00783             if ( depth > 0 && !prefix.isEmpty() && m_listSuffixes[ depth - 1 ] == prefix )  {
00784                 prefix = QString::null; // it's already the parent's suffix -> remove it
00785                 kdDebug(30513) << "depth=" << depth << " parent suffix is " << prefix << " -> clearing" << endl;
00786             }
00787         }
00788         if ( isHeading )
00789             numberingType = 1;
00790         if ( depthFound )
00791         {
00792             // Word6 models "1." as nfc=5
00793             if ( nfc == 5 && suffix.isEmpty() )
00794                 suffix = ".";
00795             kdDebug(30513) << " prefix=" << prefix << " suffix=" << suffix << endl;
00796             counterElement.setAttribute( "type", Conversion::numberFormatCode( nfc ) );
00797             counterElement.setAttribute( "lefttext", prefix );
00798             counterElement.setAttribute( "righttext", suffix );
00799             counterElement.setAttribute( "display-levels", displayLevels );
00800             kdDebug(30513) << "storing suffix " << suffix << " for depth " << depth << endl;
00801             m_listSuffixes[ depth ] = suffix;
00802         }
00803         else
00804         {
00805             kdWarning(30513) << "Not supported: counter text without the depth in it:" << Conversion::string(text).string() << endl;
00806         }
00807 
00808         if ( listInfo->startAtOverridden() ||
00809              ( numberingType == 1 && m_previousOutlineLSID != 0 && m_previousOutlineLSID != listInfo->lsid() ) ||
00810              ( numberingType == 0 &&m_previousEnumLSID != 0 && m_previousEnumLSID != listInfo->lsid() ) )
00811             counterElement.setAttribute( "restart", "true" );
00812 
00813         // listInfo->alignment() is not supported in KWord
00814         // listInfo->isLegal() hmm
00815         // listInfo->notRestarted() [by higher level of lists] not supported
00816         // listInfo->followingchar() ignored, it's always a space in KWord currently
00817     }
00818     if ( numberingType == 1 )
00819         m_previousOutlineLSID = listInfo->lsid();
00820     else
00821         m_previousEnumLSID = listInfo->lsid();
00822     counterElement.setAttribute( "numberingtype", numberingType );
00823     parentElement.appendChild( counterElement );
00824 }
00825 
00826 void KWordTextHandler::setFrameSetElement( const QDomElement& frameset )
00827 {
00828     m_framesetElement = frameset;
00829     for ( uint i = 0 ; i < 9 ; ++i )
00830         m_listSuffixes[i] = QString::null;
00831 }
00832 
00833 QDomDocument KWordTextHandler::mainDocument() const
00834 {
00835     return m_framesetElement.ownerDocument();
00836 }
00837 
00838 #include "texthandler.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys