|
|
|
/* This file is part of the KOffice project
|
|
|
|
Copyright (C) 2002 Werner Trobin <trobin@kde.org>
|
|
|
|
Copyright (C) 2002 David Faure <faure@kde.org>
|
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
|
|
modify it under the terms of the GNU General Public
|
|
|
|
License version 2 as published by the Free Software Foundation.
|
|
|
|
|
|
|
|
This program 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
|
|
|
|
General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program; see the file COPYING. If not, write to
|
|
|
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
|
|
* Boston, MA 02110-1301, USA.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "texthandler.h"
|
|
|
|
#include "conversion.h"
|
|
|
|
|
|
|
|
#include <wv2/styles.h>
|
|
|
|
#include <wv2/lists.h>
|
|
|
|
#include <wv2/paragraphproperties.h>
|
|
|
|
#include <wv2/functor.h>
|
|
|
|
#include <wv2/functordata.h>
|
|
|
|
#include <wv2/ustring.h>
|
|
|
|
#include <wv2/parser.h>
|
|
|
|
#include <wv2/fields.h>
|
|
|
|
|
|
|
|
#include <tqfont.h>
|
|
|
|
#include <tqfontinfo.h>
|
|
|
|
#include <kdebug.h>
|
|
|
|
#include <tdelocale.h>
|
|
|
|
|
|
|
|
|
|
|
|
wvWare::U8 KWordReplacementHandler::hardLineBreak()
|
|
|
|
{
|
|
|
|
return '\n';
|
|
|
|
}
|
|
|
|
|
|
|
|
wvWare::U8 KWordReplacementHandler::nonBreakingHyphen()
|
|
|
|
{
|
|
|
|
return '-'; // normal hyphen for now
|
|
|
|
}
|
|
|
|
|
|
|
|
wvWare::U8 KWordReplacementHandler::nonRequiredHyphen()
|
|
|
|
{
|
|
|
|
return 0xad; // soft hyphen, according to kword.dtd
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
KWordTextHandler::KWordTextHandler( wvWare::SharedPtr<wvWare::Parser> parser )
|
|
|
|
: m_parser( parser ), m_sectionNumber( 0 ), m_footNoteNumber( 0 ), m_endNoteNumber( 0 ),
|
|
|
|
m_previousOutlineLSID( 0 ), m_previousEnumLSID( 0 ),
|
|
|
|
m_currentStyle( 0L ), m_index( 0 ),
|
|
|
|
m_currentTable( 0L ),
|
|
|
|
m_bInParagraph( false ),
|
|
|
|
m_insideField( false ), m_fieldAfterSeparator( false ), m_fieldType( 0 )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void KWordTextHandler::sectionStart( wvWare::SharedPtr<const wvWare::Word97::SEP> sep )
|
|
|
|
{
|
|
|
|
m_sectionNumber++;
|
|
|
|
|
|
|
|
if ( m_sectionNumber == 1 )
|
|
|
|
{
|
|
|
|
// KWord doesn't support a different paper format per section.
|
|
|
|
// So we use the paper format of the first section,
|
|
|
|
// and we apply it to the whole document.
|
|
|
|
emit firstSectionFound( sep );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Not the first section. Check for a page break
|
|
|
|
if ( sep->bkc >= 1 ) // 1=new column, 2=new page, 3=even page, 4=odd page
|
|
|
|
{
|
|
|
|
pageBreak();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void KWordTextHandler::sectionEnd()
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void KWordTextHandler::pageBreak()
|
|
|
|
{
|
|
|
|
// Check if PAGEBREAKING already exists (e.g. due to linesTogether)
|
|
|
|
TQDomElement pageBreak = m_oldLayout.namedItem( "PAGEBREAKING" ).toElement();
|
|
|
|
if ( pageBreak.isNull() )
|
|
|
|
{
|
|
|
|
pageBreak = mainDocument().createElement( "PAGEBREAKING" );
|
|
|
|
m_oldLayout.appendChild( pageBreak );
|
|
|
|
}
|
|
|
|
pageBreak.setAttribute( "hardFrameBreakAfter", "true" );
|
|
|
|
}
|
|
|
|
|
|
|
|
void KWordTextHandler::headersFound( const wvWare::HeaderFunctor& parseHeaders )
|
|
|
|
{
|
|
|
|
// Currently we only care about headers in the first section
|
|
|
|
if ( m_sectionNumber == 1 )
|
|
|
|
{
|
|
|
|
emit subDocFound( new wvWare::HeaderFunctor( parseHeaders ), 0 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void KWordTextHandler::footnoteFound( wvWare::FootnoteData::Type type,
|
|
|
|
wvWare::UChar character, wvWare::SharedPtr<const wvWare::Word97::CHP> chp,
|
|
|
|
const wvWare::FootnoteFunctor& parseFootnote )
|
|
|
|
{
|
|
|
|
bool autoNumbered = (character.unicode() == 2);
|
|
|
|
TQDomElement varElem = insertVariable( 11 /*KWord code for footnotes*/, chp, "STRI" );
|
|
|
|
TQDomElement footnoteElem = varElem.ownerDocument().createElement( "FOOTNOTE" );
|
|
|
|
if ( autoNumbered )
|
|
|
|
footnoteElem.setAttribute( "value", 1 ); // KWord will renumber anyway
|
|
|
|
else
|
|
|
|
footnoteElem.setAttribute( "value", TQString(TQChar(character.unicode())) );
|
|
|
|
footnoteElem.setAttribute( "notetype", type == wvWare::FootnoteData::Endnote ? "endnote" : "footnote" );
|
|
|
|
footnoteElem.setAttribute( "numberingtype", autoNumbered ? "auto" : "manual" );
|
|
|
|
if ( type == wvWare::FootnoteData::Endnote )
|
|
|
|
// Keep name in sync with Document::startFootnote
|
|
|
|
footnoteElem.setAttribute( "frameset", i18n("Endnote %1").arg( ++m_endNoteNumber ) );
|
|
|
|
else
|
|
|
|
// Keep name in sync with Document::startFootnote
|
|
|
|
footnoteElem.setAttribute( "frameset", i18n("Footnote %1").arg( ++m_footNoteNumber ) );
|
|
|
|
varElem.appendChild( footnoteElem );
|
|
|
|
|
|
|
|
// Remember to parse the footnote text later
|
|
|
|
emit subDocFound( new wvWare::FootnoteFunctor( parseFootnote ), type );
|
|
|
|
}
|
|
|
|
|
|
|
|
TQDomElement KWordTextHandler::insertVariable( int type, wvWare::SharedPtr<const wvWare::Word97::CHP> chp, const TQString& format )
|
|
|
|
{
|
|
|
|
m_paragraph += '#';
|
|
|
|
|
|
|
|
TQDomElement formatElem;
|
|
|
|
writeFormat( m_formats, chp, m_currentStyle ? &m_currentStyle->chp() : 0, m_index, 1, 4 /*id*/, &formatElem );
|
|
|
|
|
|
|
|
m_index += 1;
|
|
|
|
|
|
|
|
TQDomElement varElem = m_formats.ownerDocument().createElement( "VARIABLE" );
|
|
|
|
TQDomElement typeElem = m_formats.ownerDocument().createElement( "TYPE" );
|
|
|
|
typeElem.setAttribute( "type", type );
|
|
|
|
typeElem.setAttribute( "key", format );
|
|
|
|
varElem.appendChild( typeElem );
|
|
|
|
formatElem.appendChild( varElem );
|
|
|
|
return varElem;
|
|
|
|
}
|
|
|
|
|
|
|
|
void KWordTextHandler::tableRowFound( const wvWare::TableRowFunctor& functor, wvWare::SharedPtr<const wvWare::Word97::TAP> tap )
|
|
|
|
{
|
|
|
|
if ( !m_currentTable )
|
|
|
|
{
|
|
|
|
// We need to put the table in a paragraph. For wv2 tables are between paragraphs.
|
|
|
|
Q_ASSERT( !m_bInParagraph );
|
|
|
|
paragraphStart( 0L );
|
|
|
|
static int s_tableNumber = 0;
|
|
|
|
m_currentTable = new KWord::Table();
|
|
|
|
m_currentTable->name = i18n("Table %1").arg( ++s_tableNumber );
|
|
|
|
insertAnchor( m_currentTable->name );
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add all cell edges to our array.
|
|
|
|
for (int i = 0; i <= tap->itcMac; i++)
|
|
|
|
m_currentTable->cacheCellEdge( tap->rgdxaCenter[ i ] );
|
|
|
|
|
|
|
|
KWord::Row row( new wvWare::TableRowFunctor( functor ), tap );
|
|
|
|
m_currentTable->rows.append( row );
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef IMAGE_IMPORT
|
|
|
|
void KWordTextHandler::pictureFound( const wvWare::PictureFunctor& pictureFunctor,
|
|
|
|
wvWare::SharedPtr<const wvWare::Word97::PICF> picf,
|
|
|
|
wvWare::SharedPtr<const wvWare::Word97::CHP> /*chp*/ )
|
|
|
|
{
|
|
|
|
static unsigned int s_pictureNumber = 0;
|
|
|
|
TQString pictureName = "pictures/picture";
|
|
|
|
pictureName += TQString::number( s_pictureNumber ); // filenames start at 0
|
|
|
|
// looks better to the user if frame names start at 1
|
|
|
|
TQString frameName = i18n("Picture %1").arg( ++s_pictureNumber );
|
|
|
|
insertAnchor( frameName );
|
|
|
|
|
|
|
|
switch ( picf->mfp.mm ) {
|
|
|
|
case 98:
|
|
|
|
pictureName += ".tif"; // not implemented!
|
|
|
|
break;
|
|
|
|
case 99:
|
|
|
|
pictureName += ".bmp";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
pictureName += ".wmf";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
emit pictureFound( frameName, pictureName, new wvWare::PictureFunctor( pictureFunctor ) );
|
|
|
|
}
|
|
|
|
#endif // IMAGE_IMPORT
|
|
|
|
|
|
|
|
TQDomElement KWordTextHandler::insertAnchor( const TQString& fsname )
|
|
|
|
{
|
|
|
|
m_paragraph += '#';
|
|
|
|
|
|
|
|
// Can't call writeFormat, we have no chp.
|
|
|
|
TQDomElement format( mainDocument().createElement( "FORMAT" ) );
|
|
|
|
format.setAttribute( "id", 6 );
|
|
|
|
format.setAttribute( "pos", m_index );
|
|
|
|
format.setAttribute( "len", 1 );
|
|
|
|
m_formats.appendChild( format );
|
|
|
|
TQDomElement formatElem = format;
|
|
|
|
|
|
|
|
m_index += 1;
|
|
|
|
|
|
|
|
TQDomElement anchorElem = m_formats.ownerDocument().createElement( "ANCHOR" );
|
|
|
|
anchorElem.setAttribute( "type", "frameset" );
|
|
|
|
anchorElem.setAttribute( "instance", fsname );
|
|
|
|
formatElem.appendChild( anchorElem );
|
|
|
|
return anchorElem;
|
|
|
|
}
|
|
|
|
|
|
|
|
void KWordTextHandler::paragLayoutBegin()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void KWordTextHandler::paragraphStart( wvWare::SharedPtr<const wvWare::ParagraphProperties> paragraphProperties )
|
|
|
|
{
|
|
|
|
if ( m_bInParagraph )
|
|
|
|
paragraphEnd();
|
|
|
|
m_bInParagraph = true;
|
|
|
|
//kdDebug(30513) << "paragraphStart. style index:" << paragraphProperties->pap().istd << endl;
|
|
|
|
m_formats = mainDocument().createElement( "FORMATS" );
|
|
|
|
m_paragraphProperties = paragraphProperties;
|
|
|
|
const wvWare::StyleSheet& styles = m_parser->styleSheet();
|
|
|
|
m_currentStyle = 0;
|
|
|
|
if ( paragraphProperties ) // Always set when called by wv2. But not set when called by tableStart.
|
|
|
|
{
|
|
|
|
m_currentStyle = styles.styleByIndex( paragraphProperties->pap().istd );
|
|
|
|
Q_ASSERT( m_currentStyle );
|
|
|
|
}
|
|
|
|
paragLayoutBegin();
|
|
|
|
}
|
|
|
|
|
|
|
|
void KWordTextHandler::paragraphEnd()
|
|
|
|
{
|
|
|
|
Q_ASSERT( m_bInParagraph );
|
|
|
|
if ( m_currentTable )
|
|
|
|
{
|
|
|
|
emit tableFound( *m_currentTable );
|
|
|
|
delete m_currentTable;
|
|
|
|
m_currentTable = 0L;
|
|
|
|
}
|
|
|
|
if ( m_currentStyle ) {
|
|
|
|
TQConstString styleName = Conversion::string( m_currentStyle->name() );
|
|
|
|
writeOutParagraph( styleName.string(), m_paragraph );
|
|
|
|
} else
|
|
|
|
writeOutParagraph( "Standard", m_paragraph );
|
|
|
|
m_bInParagraph = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void KWordTextHandler::fieldStart( const wvWare::FLD* fld, wvWare::SharedPtr<const wvWare::Word97::CHP> /*chp*/ )
|
|
|
|
{
|
|
|
|
m_fieldType = Conversion::fldToFieldType( fld );
|
|
|
|
m_insideField = true;
|
|
|
|
m_fieldAfterSeparator = false;
|
|
|
|
m_fieldValue = "";
|
|
|
|
}
|
|
|
|
|
|
|
|
void KWordTextHandler::fieldSeparator( const wvWare::FLD* /*fld*/, wvWare::SharedPtr<const wvWare::Word97::CHP> /*chp*/ )
|
|
|
|
{
|
|
|
|
m_fieldAfterSeparator = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void KWordTextHandler::fieldEnd( const wvWare::FLD* /*fld*/, wvWare::SharedPtr<const wvWare::Word97::CHP> chp )
|
|
|
|
{
|
|
|
|
// only for handled field type
|
|
|
|
if( m_fieldType >= 0 )
|
|
|
|
{
|
|
|
|
TQDomElement varElem = insertVariable( 8, chp, "STRING" );
|
|
|
|
TQDomElement fieldElem = varElem.ownerDocument().createElement( "FIELD" );
|
|
|
|
fieldElem.setAttribute( "subtype", m_fieldType );
|
|
|
|
fieldElem.setAttribute( "value", m_fieldValue );
|
|
|
|
varElem.appendChild( fieldElem );
|
|
|
|
}
|
|
|
|
|
|
|
|
// reset
|
|
|
|
m_fieldValue = "";
|
|
|
|
m_fieldType = -1;
|
|
|
|
m_insideField = false;
|
|
|
|
m_fieldAfterSeparator = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void KWordTextHandler::runOfText( const wvWare::UString& text, wvWare::SharedPtr<const wvWare::Word97::CHP> chp )
|
|
|
|
{
|
|
|
|
TQConstString newText( Conversion::string( text ) );
|
|
|
|
//kdDebug(30513) << "runOfText: " << newText.string() << endl;
|
|
|
|
|
|
|
|
// text after fieldStart and before fieldSeparator is useless
|
|
|
|
if( m_insideField && !m_fieldAfterSeparator ) return;
|
|
|
|
|
|
|
|
// if we can handle the field, consume the text
|
|
|
|
if( m_insideField && m_fieldAfterSeparator && ( m_fieldType >= 0 ) )
|
|
|
|
{
|
|
|
|
m_fieldValue.append( newText.string() );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_paragraph += newText.string();
|
|
|
|
|
|
|
|
writeFormat( m_formats, chp, m_currentStyle ? &m_currentStyle->chp() : 0, m_index, text.length(), 1, 0L );
|
|
|
|
|
|
|
|
m_index += text.length();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void KWordTextHandler::writeFormat( TQDomElement& parentElement, const wvWare::Word97::CHP* chp, const wvWare::Word97::CHP* refChp, int pos, int len, int formatId, TQDomElement* pChildElement )
|
|
|
|
{
|
|
|
|
TQDomElement format( mainDocument().createElement( "FORMAT" ) );
|
|
|
|
format.setAttribute( "id", formatId );
|
|
|
|
format.setAttribute( "pos", pos );
|
|
|
|
format.setAttribute( "len", len );
|
|
|
|
|
|
|
|
if ( !refChp || refChp->cv != chp->cv )
|
|
|
|
{
|
|
|
|
TQColor color = Conversion::color( chp->cv, -1 );
|
|
|
|
TQDomElement colorElem( mainDocument().createElement( "COLOR" ) );
|
|
|
|
colorElem.setAttribute( "red", color.red() );
|
|
|
|
colorElem.setAttribute( "blue", color.blue() );
|
|
|
|
colorElem.setAttribute( "green", color.green() );
|
|
|
|
format.appendChild( colorElem );
|
|
|
|
}
|
|
|
|
|
|
|
|
// Font name
|
|
|
|
// TBD: We only use the Ascii font code. We should work out how/when to use the FE and Other font codes.
|
|
|
|
if ( !refChp || refChp->ftcAscii != chp->ftcAscii )
|
|
|
|
{
|
|
|
|
TQString fontName = getFont( chp->ftcAscii );
|
|
|
|
|
|
|
|
if ( !fontName.isEmpty() )
|
|
|
|
{
|
|
|
|
TQDomElement fontElem( mainDocument().createElement( "FONT" ) );
|
|
|
|
fontElem.setAttribute( "name", fontName );
|
|
|
|
format.appendChild( fontElem );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !refChp || refChp->hps != chp->hps )
|
|
|
|
{
|
|
|
|
//kdDebug(30513) << " font size: " << chp->hps/2 << endl;
|
|
|
|
TQDomElement fontSize( mainDocument().createElement( "SIZE" ) );
|
|
|
|
fontSize.setAttribute( "value", (int)(chp->hps / 2) ); // hps is in half points
|
|
|
|
format.appendChild( fontSize );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !refChp || refChp->fBold != chp->fBold ) {
|
|
|
|
TQDomElement weight( mainDocument().createElement( "WEIGHT" ) );
|
|
|
|
weight.setAttribute( "value", chp->fBold ? 75 : 50 );
|
|
|
|
format.appendChild( weight );
|
|
|
|
}
|
|
|
|
if ( !refChp || refChp->fItalic != chp->fItalic ) {
|
|
|
|
TQDomElement italic( mainDocument().createElement( "ITALIC" ) );
|
|
|
|
italic.setAttribute( "value", chp->fItalic ? 1 : 0 );
|
|
|
|
format.appendChild( italic );
|
|
|
|
}
|
|
|
|
if ( !refChp || refChp->kul != chp->kul ) {
|
|
|
|
TQDomElement underline( mainDocument().createElement( "UNDERLINE" ) );
|
|
|
|
TQString val = (chp->kul == 0) ? "0" : "1";
|
|
|
|
switch ( chp->kul ) {
|
|
|
|
case 3: // double
|
|
|
|
underline.setAttribute( "styleline", "solid" );
|
|
|
|
val = "double";
|
|
|
|
break;
|
|
|
|
case 6: // thick
|
|
|
|
underline.setAttribute( "styleline", "solid" );
|
|
|
|
val = "single-bold";
|
|
|
|
break;
|
|
|
|
case 7:
|
|
|
|
underline.setAttribute( "styleline", "dash" );
|
|
|
|
break;
|
|
|
|
case 4: // dotted
|
|
|
|
case 8: // dot (not used, says the docu)
|
|
|
|
underline.setAttribute( "styleline", "dot" );
|
|
|
|
break;
|
|
|
|
case 9:
|
|
|
|
underline.setAttribute( "styleline", "dashdot" );
|
|
|
|
break;
|
|
|
|
case 10:
|
|
|
|
underline.setAttribute( "styleline", "dashdotdot" );
|
|
|
|
break;
|
|
|
|
case 11: // wave
|
|
|
|
underline.setAttribute( "styleline", "wave" );
|
|
|
|
break;
|
|
|
|
case 5: // hidden - This makes no sense as an underline property!
|
|
|
|
val = "0";
|
|
|
|
break;
|
|
|
|
case 1: // single
|
|
|
|
case 2: // by word - TODO
|
|
|
|
default:
|
|
|
|
underline.setAttribute( "styleline", "solid" );
|
|
|
|
};
|
|
|
|
underline.setAttribute( "value", val );
|
|
|
|
format.appendChild( underline );
|
|
|
|
}
|
|
|
|
if ( !refChp || refChp->fStrike != chp->fStrike || refChp->fDStrike != chp->fDStrike ) {
|
|
|
|
TQDomElement strikeout( mainDocument().createElement( "STRIKEOUT" ) );
|
|
|
|
if ( chp->fDStrike ) // double strikethrough
|
|
|
|
{
|
|
|
|
strikeout.setAttribute( "value", "double" );
|
|
|
|
strikeout.setAttribute( "styleline", "solid" );
|
|
|
|
}
|
|
|
|
else if ( chp->fStrike )
|
|
|
|
{
|
|
|
|
strikeout.setAttribute( "value", "single" );
|
|
|
|
strikeout.setAttribute( "styleline", "solid" );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
strikeout.setAttribute( "value", "0" );
|
|
|
|
format.appendChild( strikeout );
|
|
|
|
}
|
|
|
|
|
|
|
|
// font attribute (uppercase, lowercase (not in MSWord), small caps)
|
|
|
|
if ( !refChp || refChp->fCaps != chp->fCaps || refChp->fSmallCaps != chp->fSmallCaps )
|
|
|
|
{
|
|
|
|
TQDomElement fontAttrib( mainDocument().createElement( "FONTATTRIBUTE" ) );
|
|
|
|
fontAttrib.setAttribute( "value", chp->fSmallCaps ? "smallcaps" : chp->fCaps ? "uppercase" : "none" );
|
|
|
|
format.appendChild( fontAttrib );
|
|
|
|
}
|
|
|
|
if ( !refChp || refChp->iss != chp->iss ) { // superscript/subscript
|
|
|
|
TQDomElement vertAlign( mainDocument().createElement( "VERTALIGN" ) );
|
|
|
|
// Obviously the values are reversed between the two file formats :)
|
|
|
|
int kwordVAlign = (chp->iss==1 ? 2 : chp->iss==2 ? 1 : 0);
|
|
|
|
vertAlign.setAttribute( "value", kwordVAlign );
|
|
|
|
format.appendChild( vertAlign );
|
|
|
|
}
|
|
|
|
|
|
|
|
// background color is known as "highlight" in msword
|
|
|
|
if ( !refChp || refChp->fHighlight != chp->fHighlight || refChp->icoHighlight != chp->icoHighlight ) {
|
|
|
|
TQDomElement bgcolElem( mainDocument().createElement( "TEXTBACKGROUNDCOLOR" ) );
|
|
|
|
if ( chp->fHighlight )
|
|
|
|
{
|
|
|
|
TQColor color = Conversion::color( chp->icoHighlight, -1 );
|
|
|
|
bgcolElem.setAttribute( "red", color.red() );
|
|
|
|
bgcolElem.setAttribute( "blue", color.blue() );
|
|
|
|
bgcolElem.setAttribute( "green", color.green() );
|
|
|
|
} else {
|
|
|
|
bgcolElem.setAttribute( "red", -1 );
|
|
|
|
bgcolElem.setAttribute( "blue", -1 );
|
|
|
|
bgcolElem.setAttribute( "green", -1 );
|
|
|
|
}
|
|
|
|
format.appendChild( bgcolElem );
|
|
|
|
}
|
|
|
|
|
|
|
|
// Shadow text. Only on/off. The properties are defined at the paragraph level (in KWord).
|
|
|
|
if ( !refChp || refChp->fShadow != chp->fShadow || refChp->fImprint != chp->fImprint ) {
|
|
|
|
TQDomElement shadowElem( mainDocument().createElement( "SHADOW" ) );
|
|
|
|
TQString css = "none";
|
|
|
|
// Generate a shadow with hardcoded values that make it look like in MSWord.
|
|
|
|
// We need to make the distance depend on the font size though, to look good
|
|
|
|
if (chp->fShadow || chp->fImprint)
|
|
|
|
{
|
|
|
|
int fontSize = (int)(chp->hps / 2);
|
|
|
|
int dist = fontSize > 20 ? 2 : 1;
|
|
|
|
if (chp->fImprint) // ## no real support for imprint, we use a topleft shadow
|
|
|
|
dist = -dist;
|
|
|
|
css = TQString::fromLatin1("#bebebe %1pt %1pt").arg(dist).arg(dist);
|
|
|
|
}
|
|
|
|
shadowElem.setAttribute( "text-shadow", css );
|
|
|
|
format.appendChild( shadowElem );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( pChildElement || !format.firstChild().isNull() ) // Don't save an empty format tag, unless the caller asked for it
|
|
|
|
parentElement.appendChild( format );
|
|
|
|
if ( pChildElement )
|
|
|
|
*pChildElement = format;
|
|
|
|
}
|
|
|
|
|
|
|
|
//#define FONT_DEBUG
|
|
|
|
|
|
|
|
// Return the name of a font. We have to convert the Microsoft font names to
|
|
|
|
// something that might just be present under X11.
|
|
|
|
TQString KWordTextHandler::getFont(unsigned fc) const
|
|
|
|
{
|
|
|
|
Q_ASSERT( m_parser );
|
|
|
|
if ( !m_parser )
|
|
|
|
return TQString();
|
|
|
|
const wvWare::Word97::FFN& ffn ( m_parser->font( fc ) );
|
|
|
|
|
|
|
|
TQConstString fontName( Conversion::string( ffn.xszFfn ) );
|
|
|
|
TQString font = fontName.string();
|
|
|
|
|
|
|
|
#ifdef FONT_DEBUG
|
|
|
|
kdDebug(30513) << " MS-FONT: " << font << endl;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static const unsigned ENTRIES = 6;
|
|
|
|
static const char* const fuzzyLookup[ENTRIES][2] =
|
|
|
|
{
|
|
|
|
// MS contains X11 font family
|
|
|
|
// substring. non-Xft name.
|
|
|
|
{ "times", "times" },
|
|
|
|
{ "courier", "courier" },
|
|
|
|
{ "andale", "monotype" },
|
|
|
|
{ "monotype.com", "monotype" },
|
|
|
|
{ "georgia", "times" },
|
|
|
|
{ "helvetica", "helvetica" }
|
|
|
|
};
|
|
|
|
|
|
|
|
// When Xft is available, TQt will do a good job of looking up our local
|
|
|
|
// equivalent of the MS font. But, we want to work even without Xft.
|
|
|
|
// So, first, we do a fuzzy match of some common MS font names.
|
|
|
|
unsigned i;
|
|
|
|
|
|
|
|
for (i = 0; i < ENTRIES; i++)
|
|
|
|
{
|
|
|
|
// The loop will leave unchanged any MS font name not fuzzy-matched.
|
|
|
|
if (font.find(fuzzyLookup[i][0], 0, FALSE) != -1)
|
|
|
|
{
|
|
|
|
font = fuzzyLookup[i][1];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef FONT_DEBUG
|
|
|
|
kdDebug(30513) << " FUZZY-FONT: " << font << endl;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// Use TQt to look up our canonical equivalent of the font name.
|
|
|
|
TQFont xFont( font );
|
|
|
|
TQFontInfo info( xFont );
|
|
|
|
|
|
|
|
#ifdef FONT_DEBUG
|
|
|
|
kdDebug(30513) << " QT-FONT: " << info.family() << endl;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return info.family();
|
|
|
|
}
|
|
|
|
|
|
|
|
void KWordTextHandler::writeOutParagraph( const TQString& styleName, const TQString& text )
|
|
|
|
{
|
|
|
|
if ( m_framesetElement.isNull() )
|
|
|
|
{
|
|
|
|
if ( !text.isEmpty() ) // vertically merged table cells are ignored, and have empty text -> no warning on those
|
|
|
|
kdWarning(30513) << "KWordTextHandler: no frameset element to write to! text=" << text << endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
TQDomElement paragraphElementOut=mainDocument().createElement("PARAGRAPH");
|
|
|
|
m_framesetElement.appendChild(paragraphElementOut);
|
|
|
|
TQDomElement textElement=mainDocument().createElement("TEXT");
|
|
|
|
textElement.setAttribute( "xml:space", "preserve" );
|
|
|
|
paragraphElementOut.appendChild(textElement);
|
|
|
|
paragraphElementOut.appendChild( m_formats );
|
|
|
|
TQDomElement layoutElement=mainDocument().createElement("LAYOUT");
|
|
|
|
paragraphElementOut.appendChild(layoutElement);
|
|
|
|
|
|
|
|
TQDomElement nameElement = mainDocument().createElement("NAME");
|
|
|
|
nameElement.setAttribute("value", styleName);
|
|
|
|
layoutElement.appendChild(nameElement);
|
|
|
|
|
|
|
|
if ( m_paragraphProperties )
|
|
|
|
{
|
|
|
|
// Write out the properties of the paragraph
|
|
|
|
writeLayout( layoutElement, *m_paragraphProperties, m_currentStyle );
|
|
|
|
}
|
|
|
|
|
|
|
|
textElement.appendChild(mainDocument().createTextNode(text));
|
|
|
|
|
|
|
|
m_paragraph = TQString( "" );
|
|
|
|
m_index = 0;
|
|
|
|
m_oldLayout = layoutElement; // Keep a reference to the old layout for some hacks
|
|
|
|
}
|
|
|
|
|
|
|
|
void KWordTextHandler::writeLayout( TQDomElement& parentElement, const wvWare::ParagraphProperties& paragraphProperties, const wvWare::Style* style )
|
|
|
|
{
|
|
|
|
const wvWare::Word97::PAP& pap = paragraphProperties.pap();
|
|
|
|
// Always write out the alignment, it's required
|
|
|
|
TQDomElement flowElement = mainDocument().createElement("FLOW");
|
|
|
|
TQString alignment = Conversion::alignment( pap.jc );
|
|
|
|
flowElement.setAttribute( "align", alignment );
|
|
|
|
parentElement.appendChild( flowElement );
|
|
|
|
|
|
|
|
//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;
|
|
|
|
|
|
|
|
if ( pap.dxaLeft1 || pap.dxaLeft || pap.dxaRight )
|
|
|
|
{
|
|
|
|
TQDomElement indentsElement = mainDocument().createElement("INDENTS");
|
|
|
|
// 'first' is relative to 'left' in both formats
|
|
|
|
indentsElement.setAttribute( "first", (double)pap.dxaLeft1 / 20.0 );
|
|
|
|
indentsElement.setAttribute( "left", (double)pap.dxaLeft / 20.0 );
|
|
|
|
indentsElement.setAttribute( "right", (double)pap.dxaRight / 20.0 );
|
|
|
|
parentElement.appendChild( indentsElement );
|
|
|
|
}
|
|
|
|
if ( pap.dyaBefore || pap.dyaAfter )
|
|
|
|
{
|
|
|
|
TQDomElement offsetsElement = mainDocument().createElement("OFFSETS");
|
|
|
|
offsetsElement.setAttribute( "before", (double)pap.dyaBefore / 20.0 );
|
|
|
|
offsetsElement.setAttribute( "after", (double)pap.dyaAfter / 20.0 );
|
|
|
|
parentElement.appendChild( offsetsElement );
|
|
|
|
}
|
|
|
|
|
|
|
|
// Linespacing
|
|
|
|
TQString lineSpacing = Conversion::lineSpacing( pap.lspd );
|
|
|
|
if ( lineSpacing != "0" )
|
|
|
|
{
|
|
|
|
TQDomElement lineSpacingElem = mainDocument().createElement( "LINESPACING" );
|
|
|
|
lineSpacingElem.setAttribute("value", lineSpacing );
|
|
|
|
parentElement.appendChild( lineSpacingElem );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( pap.fKeep || pap.fKeepFollow || pap.fPageBreakBefore )
|
|
|
|
{
|
|
|
|
TQDomElement pageBreak = mainDocument().createElement( "PAGEBREAKING" );
|
|
|
|
if ( pap.fKeep )
|
|
|
|
pageBreak.setAttribute("linesTogether", "true");
|
|
|
|
if ( pap.fPageBreakBefore )
|
|
|
|
pageBreak.setAttribute("hardFrameBreak", "true" );
|
|
|
|
if ( pap.fKeepFollow )
|
|
|
|
pageBreak.setAttribute("keepWithNext", "true" );
|
|
|
|
parentElement.appendChild( pageBreak );
|
|
|
|
}
|
|
|
|
|
|
|
|
// Borders
|
|
|
|
if ( pap.brcTop.brcType )
|
|
|
|
{
|
|
|
|
TQDomElement borderElement = mainDocument().createElement( "TOPBORDER" );
|
|
|
|
Conversion::setBorderAttributes( borderElement, pap.brcTop );
|
|
|
|
parentElement.appendChild( borderElement );
|
|
|
|
}
|
|
|
|
if ( pap.brcBottom.brcType )
|
|
|
|
{
|
|
|
|
TQDomElement borderElement = mainDocument().createElement( "BOTTOMBORDER" );
|
|
|
|
Conversion::setBorderAttributes( borderElement, pap.brcBottom );
|
|
|
|
parentElement.appendChild( borderElement );
|
|
|
|
}
|
|
|
|
if ( pap.brcLeft.brcType )
|
|
|
|
{
|
|
|
|
TQDomElement borderElement = mainDocument().createElement( "LEFTBORDER" );
|
|
|
|
Conversion::setBorderAttributes( borderElement, pap.brcLeft );
|
|
|
|
parentElement.appendChild( borderElement );
|
|
|
|
}
|
|
|
|
if ( pap.brcRight.brcType )
|
|
|
|
{
|
|
|
|
TQDomElement borderElement = mainDocument().createElement( "RIGHTBORDER" );
|
|
|
|
Conversion::setBorderAttributes( borderElement, pap.brcRight );
|
|
|
|
parentElement.appendChild( borderElement );
|
|
|
|
}
|
|
|
|
|
|
|
|
// Tabulators
|
|
|
|
if ( pap.itbdMac )
|
|
|
|
{
|
|
|
|
for ( int i = 0 ; i < pap.itbdMac ; ++i )
|
|
|
|
{
|
|
|
|
const wvWare::Word97::TabDescriptor &td = pap.rgdxaTab[i];
|
|
|
|
TQDomElement tabElement = mainDocument().createElement( "TABULATOR" );
|
|
|
|
tabElement.setAttribute( "ptpos", (double)td.dxaTab / 20.0 );
|
|
|
|
//kdDebug(30513) << "ptpos=" << (double)td.dxaTab / 20.0 << endl;
|
|
|
|
// Wow, lucky here. The type enum matches. Only, MSWord has 4=bar,
|
|
|
|
// which kword doesn't support. We map it to 0 with a clever '%4' :)
|
|
|
|
tabElement.setAttribute( "type", td.tbd.jc % 4 );
|
|
|
|
int filling = 0;
|
|
|
|
double width = 0.5; // default kword value, see koparaglayout.cc
|
|
|
|
switch ( td.tbd.tlc ) {
|
|
|
|
case 1: // dots
|
|
|
|
case 2: // hyphenated
|
|
|
|
filling = 1; // KWord: dots
|
|
|
|
break;
|
|
|
|
case 3: // single line
|
|
|
|
filling = 2; // KWord: line
|
|
|
|
break;
|
|
|
|
case 4: // heavy line
|
|
|
|
filling = 2; // KWord: line
|
|
|
|
width = 2; // make it heavy. To be tested.
|
|
|
|
}
|
|
|
|
tabElement.setAttribute( "filling", filling );
|
|
|
|
tabElement.setAttribute( "width", width );
|
|
|
|
parentElement.appendChild( tabElement );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( pap.ilfo > 0 )
|
|
|
|
{
|
|
|
|
writeCounter( parentElement, paragraphProperties, style );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void KWordTextHandler::writeCounter( TQDomElement& parentElement, const wvWare::ParagraphProperties& paragraphProperties, const wvWare::Style* style )
|
|
|
|
{
|
|
|
|
const wvWare::ListInfo* listInfo = paragraphProperties.listInfo();
|
|
|
|
if ( !listInfo )
|
|
|
|
return;
|
|
|
|
|
|
|
|
#ifndef NDEBUG
|
|
|
|
listInfo->dump();
|
|
|
|
#endif
|
|
|
|
|
|
|
|
TQDomElement counterElement = mainDocument().createElement( "COUNTER" );
|
|
|
|
// numbering type: 0==list 1==chapter. First we determine it for word6 docs.
|
|
|
|
// But we can also activate it if the text() looks that way
|
|
|
|
int numberingType = listInfo->isWord6() && listInfo->prev() ? 1 : 0;
|
|
|
|
wvWare::UString text = listInfo->text().text;
|
|
|
|
int nfc = listInfo->numberFormat();
|
|
|
|
if ( nfc == 23 ) // bullet
|
|
|
|
{
|
|
|
|
if ( text.length() == 1 )
|
|
|
|
{
|
|
|
|
unsigned int code = text[0].unicode();
|
|
|
|
if ( (code & 0xFF00) == 0xF000 ) // see wv2
|
|
|
|
code &= 0x00FF;
|
|
|
|
// Some well-known bullet codes. Better turn them into real
|
|
|
|
// KWord bullets, it looks much nicer (than crappy fonts).
|
|
|
|
if ( code == 0xB7 ) // Round black bullet
|
|
|
|
{
|
|
|
|
counterElement.setAttribute( "type", 10 ); // disc bullet
|
|
|
|
} else if ( code == 0xD8 ) // Triangle
|
|
|
|
{
|
|
|
|
counterElement.setAttribute( "type", 11 ); // Box. We have no triangle.
|
|
|
|
} else {
|
|
|
|
// Map all other bullets to a "custom bullet" in kword.
|
|
|
|
kdDebug(30513) << "custom bullet, code=" << TQString::number(code,16) << endl;
|
|
|
|
counterElement.setAttribute( "type", 6 ); // custom
|
|
|
|
counterElement.setAttribute( "bullet", code );
|
|
|
|
TQString paragFont = getFont( style->chp().ftcAscii );
|
|
|
|
counterElement.setAttribute( "bulletfont", paragFont );
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
kdWarning(30513) << "Bullet with more than one character, not supported" << endl;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
const wvWare::Word97::PAP& pap = paragraphProperties.pap();
|
|
|
|
counterElement.setAttribute( "start", listInfo->startAt() );
|
|
|
|
|
|
|
|
int depth = pap.ilvl; /*both are 0 based*/
|
|
|
|
// Heading styles don't set the ilvl, but must have a depth coming
|
|
|
|
// from their heading level (the style's STI)
|
|
|
|
bool isHeading = style->sti() >= 1 && style->sti() <= 9;
|
|
|
|
if ( depth == 0 && isHeading )
|
|
|
|
{
|
|
|
|
depth = style->sti() - 1;
|
|
|
|
}
|
|
|
|
kdDebug(30513) << " ilfo=" << pap.ilfo << " ilvl=" << pap.ilvl << " sti=" << style->sti() << " depth=" << depth << " numberingType=" << numberingType << endl;
|
|
|
|
counterElement.setAttribute( "depth", depth );
|
|
|
|
|
|
|
|
// Now we need to parse the text, to try and convert msword's powerful list template
|
|
|
|
// stuff, into what KWord can do right now.
|
|
|
|
TQString prefix, suffix;
|
|
|
|
bool depthFound = false;
|
|
|
|
int displayLevels = 1;
|
|
|
|
// We parse <0>.<2>.<1>. as "level 2 with suffix='.'" (no prefix)
|
|
|
|
// But "Section <0>)" has both prefix and suffix.
|
|
|
|
// The common case is <0>.<1>.<2> (display-levels=3)
|
|
|
|
for ( int i = 0 ; i < text.length() ; ++i )
|
|
|
|
{
|
|
|
|
short ch = text[i].unicode();
|
|
|
|
//kdDebug(30513) << i << ":" << ch << endl;
|
|
|
|
if ( ch < 10 ) { // List level place holder
|
|
|
|
if ( ch == pap.ilvl ) {
|
|
|
|
if ( depthFound )
|
|
|
|
kdWarning(30513) << "ilvl " << pap.ilvl << " found twice in listInfo text..." << endl;
|
|
|
|
else
|
|
|
|
depthFound = true;
|
|
|
|
suffix = TQString();
|
|
|
|
} else {
|
|
|
|
Q_ASSERT( ch < pap.ilvl ); // Can't see how level 1 would have a <0> in it...
|
|
|
|
if ( ch < pap.ilvl )
|
|
|
|
++displayLevels; // we found a 'parent level', to be displayed
|
|
|
|
prefix = TQString(); // get rid of previous prefixes
|
|
|
|
}
|
|
|
|
} else { // Normal character
|
|
|
|
if ( depthFound )
|
|
|
|
suffix += TQChar(ch);
|
|
|
|
else
|
|
|
|
prefix += TQChar(ch);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( displayLevels > 1 )
|
|
|
|
{
|
|
|
|
// This is a hierarchical list numbering e.g. <0>.<1>.
|
|
|
|
// (unless this is about a heading, in which case we've set numberingtype to 1 already
|
|
|
|
// so it will indeed look like that).
|
|
|
|
// The question is whether the '.' is the suffix of the parent level already..
|
|
|
|
if ( depth > 0 && !prefix.isEmpty() && m_listSuffixes[ depth - 1 ] == prefix ) {
|
|
|
|
prefix = TQString(); // it's already the parent's suffix -> remove it
|
|
|
|
kdDebug(30513) << "depth=" << depth << " parent suffix is " << prefix << " -> clearing" << endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( isHeading )
|
|
|
|
numberingType = 1;
|
|
|
|
if ( depthFound )
|
|
|
|
{
|
|
|
|
// Word6 models "1." as nfc=5
|
|
|
|
if ( nfc == 5 && suffix.isEmpty() )
|
|
|
|
suffix = ".";
|
|
|
|
kdDebug(30513) << " prefix=" << prefix << " suffix=" << suffix << endl;
|
|
|
|
counterElement.setAttribute( "type", Conversion::numberFormatCode( nfc ) );
|
|
|
|
counterElement.setAttribute( "lefttext", prefix );
|
|
|
|
counterElement.setAttribute( "righttext", suffix );
|
|
|
|
counterElement.setAttribute( "display-levels", displayLevels );
|
|
|
|
kdDebug(30513) << "storing suffix " << suffix << " for depth " << depth << endl;
|
|
|
|
m_listSuffixes[ depth ] = suffix;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
kdWarning(30513) << "Not supported: counter text without the depth in it:" << Conversion::string(text).string() << endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( listInfo->startAtOverridden() ||
|
|
|
|
( numberingType == 1 && m_previousOutlineLSID != 0 && m_previousOutlineLSID != listInfo->lsid() ) ||
|
|
|
|
( numberingType == 0 &&m_previousEnumLSID != 0 && m_previousEnumLSID != listInfo->lsid() ) )
|
|
|
|
counterElement.setAttribute( "restart", "true" );
|
|
|
|
|
|
|
|
// listInfo->alignment() is not supported in KWord
|
|
|
|
// listInfo->isLegal() hmm
|
|
|
|
// listInfo->notRestarted() [by higher level of lists] not supported
|
|
|
|
// listInfo->followingchar() ignored, it's always a space in KWord currently
|
|
|
|
}
|
|
|
|
if ( numberingType == 1 )
|
|
|
|
m_previousOutlineLSID = listInfo->lsid();
|
|
|
|
else
|
|
|
|
m_previousEnumLSID = listInfo->lsid();
|
|
|
|
counterElement.setAttribute( "numberingtype", numberingType );
|
|
|
|
parentElement.appendChild( counterElement );
|
|
|
|
}
|
|
|
|
|
|
|
|
void KWordTextHandler::setFrameSetElement( const TQDomElement& frameset )
|
|
|
|
{
|
|
|
|
m_framesetElement = frameset;
|
|
|
|
for ( uint i = 0 ; i < 9 ; ++i )
|
|
|
|
m_listSuffixes[i] = TQString();
|
|
|
|
}
|
|
|
|
|
|
|
|
TQDomDocument KWordTextHandler::mainDocument() const
|
|
|
|
{
|
|
|
|
return m_framesetElement.ownerDocument();
|
|
|
|
}
|
|
|
|
|
|
|
|
#include "texthandler.moc"
|