texthandler.cpp
1/* This file is part of the KOffice project
2 Copyright (C) 2002 Werner Trobin <trobin@kde.org>
3 Copyright (C) 2002 David Faure <faure@kde.org>
4
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public
7 License version 2 as published by the Free Software Foundation.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; see the file COPYING. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18*/
19
20#include "texthandler.h"
21#include "conversion.h"
22
23#include <wv2/styles.h>
24#include <wv2/lists.h>
25#include <wv2/paragraphproperties.h>
26#include <wv2/functor.h>
27#include <wv2/functordata.h>
28#include <wv2/ustring.h>
29#include <wv2/parser.h>
30#include <wv2/fields.h>
31
32#include <tqfont.h>
33#include <tqfontinfo.h>
34#include <kdebug.h>
35#include <tdelocale.h>
36
37
38wvWare::U8 KWordReplacementHandler::hardLineBreak()
39{
40 return '\n';
41}
42
43wvWare::U8 KWordReplacementHandler::nonBreakingHyphen()
44{
45 return '-'; // normal hyphen for now
46}
47
48wvWare::U8 KWordReplacementHandler::nonRequiredHyphen()
49{
50 return 0xad; // soft hyphen, according to kword.dtd
51}
52
53
54KWordTextHandler::KWordTextHandler( wvWare::SharedPtr<wvWare::Parser> parser )
55 : m_parser( parser ), m_sectionNumber( 0 ), m_footNoteNumber( 0 ), m_endNoteNumber( 0 ),
56 m_previousOutlineLSID( 0 ), m_previousEnumLSID( 0 ),
57 m_currentStyle( 0L ), m_index( 0 ),
58 m_currentTable( 0L ),
59 m_bInParagraph( false ),
60 m_insideField( false ), m_fieldAfterSeparator( false ), m_fieldType( 0 )
61{
62}
63
64void KWordTextHandler::sectionStart( wvWare::SharedPtr<const wvWare::Word97::SEP> sep )
65{
66 m_sectionNumber++;
67
68 if ( m_sectionNumber == 1 )
69 {
70 // KWord doesn't support a different paper format per section.
71 // So we use the paper format of the first section,
72 // and we apply it to the whole document.
73 emit firstSectionFound( sep );
74 }
75 else
76 {
77 // Not the first section. Check for a page break
78 if ( sep->bkc >= 1 ) // 1=new column, 2=new page, 3=even page, 4=odd page
79 {
80 pageBreak();
81 }
82 }
83}
84
85void KWordTextHandler::sectionEnd()
86{
87
88}
89
90void KWordTextHandler::pageBreak()
91{
92 // Check if PAGEBREAKING already exists (e.g. due to linesTogether)
93 TQDomElement pageBreak = m_oldLayout.namedItem( "PAGEBREAKING" ).toElement();
94 if ( pageBreak.isNull() )
95 {
96 pageBreak = mainDocument().createElement( "PAGEBREAKING" );
97 m_oldLayout.appendChild( pageBreak );
98 }
99 pageBreak.setAttribute( "hardFrameBreakAfter", "true" );
100}
101
102void KWordTextHandler::headersFound( const wvWare::HeaderFunctor& parseHeaders )
103{
104 // Currently we only care about headers in the first section
105 if ( m_sectionNumber == 1 )
106 {
107 emit subDocFound( new wvWare::HeaderFunctor( parseHeaders ), 0 );
108 }
109}
110
111void KWordTextHandler::footnoteFound( wvWare::FootnoteData::Type type,
112 wvWare::UChar character, wvWare::SharedPtr<const wvWare::Word97::CHP> chp,
113 const wvWare::FootnoteFunctor& parseFootnote )
114{
115 bool autoNumbered = (character.unicode() == 2);
116 TQDomElement varElem = insertVariable( 11 /*KWord code for footnotes*/, chp, "STRI" );
117 TQDomElement footnoteElem = varElem.ownerDocument().createElement( "FOOTNOTE" );
118 if ( autoNumbered )
119 footnoteElem.setAttribute( "value", 1 ); // KWord will renumber anyway
120 else
121 footnoteElem.setAttribute( "value", TQString(TQChar(character.unicode())) );
122 footnoteElem.setAttribute( "notetype", type == wvWare::FootnoteData::Endnote ? "endnote" : "footnote" );
123 footnoteElem.setAttribute( "numberingtype", autoNumbered ? "auto" : "manual" );
124 if ( type == wvWare::FootnoteData::Endnote )
125 // Keep name in sync with Document::startFootnote
126 footnoteElem.setAttribute( "frameset", i18n("Endnote %1").arg( ++m_endNoteNumber ) );
127 else
128 // Keep name in sync with Document::startFootnote
129 footnoteElem.setAttribute( "frameset", i18n("Footnote %1").arg( ++m_footNoteNumber ) );
130 varElem.appendChild( footnoteElem );
131
132 // Remember to parse the footnote text later
133 emit subDocFound( new wvWare::FootnoteFunctor( parseFootnote ), type );
134}
135
136TQDomElement KWordTextHandler::insertVariable( int type, wvWare::SharedPtr<const wvWare::Word97::CHP> chp, const TQString& format )
137{
138 m_paragraph += '#';
139
140 TQDomElement formatElem;
141 writeFormat( m_formats, chp, m_currentStyle ? &m_currentStyle->chp() : 0, m_index, 1, 4 /*id*/, &formatElem );
142
143 m_index += 1;
144
145 TQDomElement varElem = m_formats.ownerDocument().createElement( "VARIABLE" );
146 TQDomElement typeElem = m_formats.ownerDocument().createElement( "TYPE" );
147 typeElem.setAttribute( "type", type );
148 typeElem.setAttribute( "key", format );
149 varElem.appendChild( typeElem );
150 formatElem.appendChild( varElem );
151 return varElem;
152}
153
154void KWordTextHandler::tableRowFound( const wvWare::TableRowFunctor& functor, wvWare::SharedPtr<const wvWare::Word97::TAP> tap )
155{
156 if ( !m_currentTable )
157 {
158 // We need to put the table in a paragraph. For wv2 tables are between paragraphs.
159 Q_ASSERT( !m_bInParagraph );
160 paragraphStart( 0L );
161 static int s_tableNumber = 0;
162 m_currentTable = new KWord::Table();
163 m_currentTable->name = i18n("Table %1").arg( ++s_tableNumber );
164 insertAnchor( m_currentTable->name );
165 }
166
167 // Add all cell edges to our array.
168 for (int i = 0; i <= tap->itcMac; i++)
169 m_currentTable->cacheCellEdge( tap->rgdxaCenter[ i ] );
170
171 KWord::Row row( new wvWare::TableRowFunctor( functor ), tap );
172 m_currentTable->rows.append( row );
173}
174
175#ifdef IMAGE_IMPORT
176void KWordTextHandler::pictureFound( const wvWare::PictureFunctor& pictureFunctor,
177 wvWare::SharedPtr<const wvWare::Word97::PICF> picf,
178 wvWare::SharedPtr<const wvWare::Word97::CHP> /*chp*/ )
179{
180 static unsigned int s_pictureNumber = 0;
181 TQString pictureName = "pictures/picture";
182 pictureName += TQString::number( s_pictureNumber ); // filenames start at 0
183 // looks better to the user if frame names start at 1
184 TQString frameName = i18n("Picture %1").arg( ++s_pictureNumber );
185 insertAnchor( frameName );
186
187 switch ( picf->mfp.mm ) {
188 case 98:
189 pictureName += ".tif"; // not implemented!
190 break;
191 case 99:
192 pictureName += ".bmp";
193 break;
194 default:
195 pictureName += ".wmf";
196 break;
197 }
198
199 emit pictureFound( frameName, pictureName, new wvWare::PictureFunctor( pictureFunctor ) );
200}
201#endif // IMAGE_IMPORT
202
203TQDomElement KWordTextHandler::insertAnchor( const TQString& fsname )
204{
205 m_paragraph += '#';
206
207 // Can't call writeFormat, we have no chp.
208 TQDomElement format( mainDocument().createElement( "FORMAT" ) );
209 format.setAttribute( "id", 6 );
210 format.setAttribute( "pos", m_index );
211 format.setAttribute( "len", 1 );
212 m_formats.appendChild( format );
213 TQDomElement formatElem = format;
214
215 m_index += 1;
216
217 TQDomElement anchorElem = m_formats.ownerDocument().createElement( "ANCHOR" );
218 anchorElem.setAttribute( "type", "frameset" );
219 anchorElem.setAttribute( "instance", fsname );
220 formatElem.appendChild( anchorElem );
221 return anchorElem;
222}
223
224void KWordTextHandler::paragLayoutBegin()
225{
226}
227
228void KWordTextHandler::paragraphStart( wvWare::SharedPtr<const wvWare::ParagraphProperties> paragraphProperties )
229{
230 if ( m_bInParagraph )
231 paragraphEnd();
232 m_bInParagraph = true;
233 //kdDebug(30513) << "paragraphStart. style index:" << paragraphProperties->pap().istd << endl;
234 m_formats = mainDocument().createElement( "FORMATS" );
235 m_paragraphProperties = paragraphProperties;
236 const wvWare::StyleSheet& styles = m_parser->styleSheet();
237 m_currentStyle = 0;
238 if ( paragraphProperties ) // Always set when called by wv2. But not set when called by tableStart.
239 {
240 m_currentStyle = styles.styleByIndex( paragraphProperties->pap().istd );
241 Q_ASSERT( m_currentStyle );
242 }
243 paragLayoutBegin();
244}
245
246void KWordTextHandler::paragraphEnd()
247{
248 Q_ASSERT( m_bInParagraph );
249 if ( m_currentTable )
250 {
251 emit tableFound( *m_currentTable );
252 delete m_currentTable;
253 m_currentTable = 0L;
254 }
255 if ( m_currentStyle ) {
256 TQConstString styleName = Conversion::string( m_currentStyle->name() );
257 writeOutParagraph( styleName.string(), m_paragraph );
258 } else
259 writeOutParagraph( "Standard", m_paragraph );
260 m_bInParagraph = false;
261}
262
263void KWordTextHandler::fieldStart( const wvWare::FLD* fld, wvWare::SharedPtr<const wvWare::Word97::CHP> /*chp*/ )
264{
265 m_fieldType = Conversion::fldToFieldType( fld );
266 m_insideField = true;
267 m_fieldAfterSeparator = false;
268 m_fieldValue = "";
269}
270
271void KWordTextHandler::fieldSeparator( const wvWare::FLD* /*fld*/, wvWare::SharedPtr<const wvWare::Word97::CHP> /*chp*/ )
272{
273 m_fieldAfterSeparator = true;
274}
275
276void KWordTextHandler::fieldEnd( const wvWare::FLD* /*fld*/, wvWare::SharedPtr<const wvWare::Word97::CHP> chp )
277{
278 // only for handled field type
279 if( m_fieldType >= 0 )
280 {
281 TQDomElement varElem = insertVariable( 8, chp, "STRING" );
282 TQDomElement fieldElem = varElem.ownerDocument().createElement( "FIELD" );
283 fieldElem.setAttribute( "subtype", m_fieldType );
284 fieldElem.setAttribute( "value", m_fieldValue );
285 varElem.appendChild( fieldElem );
286 }
287
288 // reset
289 m_fieldValue = "";
290 m_fieldType = -1;
291 m_insideField = false;
292 m_fieldAfterSeparator = false;
293}
294
295void KWordTextHandler::runOfText( const wvWare::UString& text, wvWare::SharedPtr<const wvWare::Word97::CHP> chp )
296{
297 TQConstString newText( Conversion::string( text ) );
298 //kdDebug(30513) << "runOfText: " << newText.string() << endl;
299
300 // text after fieldStart and before fieldSeparator is useless
301 if( m_insideField && !m_fieldAfterSeparator ) return;
302
303 // if we can handle the field, consume the text
304 if( m_insideField && m_fieldAfterSeparator && ( m_fieldType >= 0 ) )
305 {
306 m_fieldValue.append( newText.string() );
307 return;
308 }
309
310 m_paragraph += newText.string();
311
312 writeFormat( m_formats, chp, m_currentStyle ? &m_currentStyle->chp() : 0, m_index, text.length(), 1, 0L );
313
314 m_index += text.length();
315
316}
317
318void KWordTextHandler::writeFormat( TQDomElement& parentElement, const wvWare::Word97::CHP* chp, const wvWare::Word97::CHP* refChp, int pos, int len, int formatId, TQDomElement* pChildElement )
319{
320 TQDomElement format( mainDocument().createElement( "FORMAT" ) );
321 format.setAttribute( "id", formatId );
322 format.setAttribute( "pos", pos );
323 format.setAttribute( "len", len );
324
325 if ( !refChp || refChp->cv != chp->cv )
326 {
327 TQColor color = Conversion::color( chp->cv, -1 );
328 TQDomElement colorElem( mainDocument().createElement( "COLOR" ) );
329 colorElem.setAttribute( "red", color.red() );
330 colorElem.setAttribute( "blue", color.blue() );
331 colorElem.setAttribute( "green", color.green() );
332 format.appendChild( colorElem );
333 }
334
335 // Font name
336 // TBD: We only use the Ascii font code. We should work out how/when to use the FE and Other font codes.
337 if ( !refChp || refChp->ftcAscii != chp->ftcAscii )
338 {
339 TQString fontName = getFont( chp->ftcAscii );
340
341 if ( !fontName.isEmpty() )
342 {
343 TQDomElement fontElem( mainDocument().createElement( "FONT" ) );
344 fontElem.setAttribute( "name", fontName );
345 format.appendChild( fontElem );
346 }
347 }
348
349 if ( !refChp || refChp->hps != chp->hps )
350 {
351 //kdDebug(30513) << " font size: " << chp->hps/2 << endl;
352 TQDomElement fontSize( mainDocument().createElement( "SIZE" ) );
353 fontSize.setAttribute( "value", (int)(chp->hps / 2) ); // hps is in half points
354 format.appendChild( fontSize );
355 }
356
357 if ( !refChp || refChp->fBold != chp->fBold ) {
358 TQDomElement weight( mainDocument().createElement( "WEIGHT" ) );
359 weight.setAttribute( "value", chp->fBold ? 75 : 50 );
360 format.appendChild( weight );
361 }
362 if ( !refChp || refChp->fItalic != chp->fItalic ) {
363 TQDomElement italic( mainDocument().createElement( "ITALIC" ) );
364 italic.setAttribute( "value", chp->fItalic ? 1 : 0 );
365 format.appendChild( italic );
366 }
367 if ( !refChp || refChp->kul != chp->kul ) {
368 TQDomElement underline( mainDocument().createElement( "UNDERLINE" ) );
369 TQString val = (chp->kul == 0) ? "0" : "1";
370 switch ( chp->kul ) {
371 case 3: // double
372 underline.setAttribute( "styleline", "solid" );
373 val = "double";
374 break;
375 case 6: // thick
376 underline.setAttribute( "styleline", "solid" );
377 val = "single-bold";
378 break;
379 case 7:
380 underline.setAttribute( "styleline", "dash" );
381 break;
382 case 4: // dotted
383 case 8: // dot (not used, says the docu)
384 underline.setAttribute( "styleline", "dot" );
385 break;
386 case 9:
387 underline.setAttribute( "styleline", "dashdot" );
388 break;
389 case 10:
390 underline.setAttribute( "styleline", "dashdotdot" );
391 break;
392 case 11: // wave
393 underline.setAttribute( "styleline", "wave" );
394 break;
395 case 5: // hidden - This makes no sense as an underline property!
396 val = "0";
397 break;
398 case 1: // single
399 case 2: // by word - TODO
400 default:
401 underline.setAttribute( "styleline", "solid" );
402 };
403 underline.setAttribute( "value", val );
404 format.appendChild( underline );
405 }
406 if ( !refChp || refChp->fStrike != chp->fStrike || refChp->fDStrike != chp->fDStrike ) {
407 TQDomElement strikeout( mainDocument().createElement( "STRIKEOUT" ) );
408 if ( chp->fDStrike ) // double strikethrough
409 {
410 strikeout.setAttribute( "value", "double" );
411 strikeout.setAttribute( "styleline", "solid" );
412 }
413 else if ( chp->fStrike )
414 {
415 strikeout.setAttribute( "value", "single" );
416 strikeout.setAttribute( "styleline", "solid" );
417 }
418 else
419 strikeout.setAttribute( "value", "0" );
420 format.appendChild( strikeout );
421 }
422
423 // font attribute (uppercase, lowercase (not in MSWord), small caps)
424 if ( !refChp || refChp->fCaps != chp->fCaps || refChp->fSmallCaps != chp->fSmallCaps )
425 {
426 TQDomElement fontAttrib( mainDocument().createElement( "FONTATTRIBUTE" ) );
427 fontAttrib.setAttribute( "value", chp->fSmallCaps ? "smallcaps" : chp->fCaps ? "uppercase" : "none" );
428 format.appendChild( fontAttrib );
429 }
430 if ( !refChp || refChp->iss != chp->iss ) { // superscript/subscript
431 TQDomElement vertAlign( mainDocument().createElement( "VERTALIGN" ) );
432 // Obviously the values are reversed between the two file formats :)
433 int kwordVAlign = (chp->iss==1 ? 2 : chp->iss==2 ? 1 : 0);
434 vertAlign.setAttribute( "value", kwordVAlign );
435 format.appendChild( vertAlign );
436 }
437
438 // background color is known as "highlight" in msword
439 if ( !refChp || refChp->fHighlight != chp->fHighlight || refChp->icoHighlight != chp->icoHighlight ) {
440 TQDomElement bgcolElem( mainDocument().createElement( "TEXTBACKGROUNDCOLOR" ) );
441 if ( chp->fHighlight )
442 {
443 TQColor color = Conversion::color( chp->icoHighlight, -1 );
444 bgcolElem.setAttribute( "red", color.red() );
445 bgcolElem.setAttribute( "blue", color.blue() );
446 bgcolElem.setAttribute( "green", color.green() );
447 } else {
448 bgcolElem.setAttribute( "red", -1 );
449 bgcolElem.setAttribute( "blue", -1 );
450 bgcolElem.setAttribute( "green", -1 );
451 }
452 format.appendChild( bgcolElem );
453 }
454
455 // Shadow text. Only on/off. The properties are defined at the paragraph level (in KWord).
456 if ( !refChp || refChp->fShadow != chp->fShadow || refChp->fImprint != chp->fImprint ) {
457 TQDomElement shadowElem( mainDocument().createElement( "SHADOW" ) );
458 TQString css = "none";
459 // Generate a shadow with hardcoded values that make it look like in MSWord.
460 // We need to make the distance depend on the font size though, to look good
461 if (chp->fShadow || chp->fImprint)
462 {
463 int fontSize = (int)(chp->hps / 2);
464 int dist = fontSize > 20 ? 2 : 1;
465 if (chp->fImprint) // ## no real support for imprint, we use a topleft shadow
466 dist = -dist;
467 css = TQString::fromLatin1("#bebebe %1pt %1pt").arg(dist).arg(dist);
468 }
469 shadowElem.setAttribute( "text-shadow", css );
470 format.appendChild( shadowElem );
471 }
472
473 if ( pChildElement || !format.firstChild().isNull() ) // Don't save an empty format tag, unless the caller asked for it
474 parentElement.appendChild( format );
475 if ( pChildElement )
476 *pChildElement = format;
477}
478
479//#define FONT_DEBUG
480
481// Return the name of a font. We have to convert the Microsoft font names to
482// something that might just be present under X11.
483TQString KWordTextHandler::getFont(unsigned fc) const
484{
485 Q_ASSERT( m_parser );
486 if ( !m_parser )
487 return TQString();
488 const wvWare::Word97::FFN& ffn ( m_parser->font( fc ) );
489
490 TQConstString fontName( Conversion::string( ffn.xszFfn ) );
491 TQString font = fontName.string();
492
493#ifdef FONT_DEBUG
494 kdDebug(30513) << " MS-FONT: " << font << endl;
495#endif
496
497 static const unsigned ENTRIES = 6;
498 static const char* const fuzzyLookup[ENTRIES][2] =
499 {
500 // MS contains X11 font family
501 // substring. non-Xft name.
502 { "times", "times" },
503 { "courier", "courier" },
504 { "andale", "monotype" },
505 { "monotype.com", "monotype" },
506 { "georgia", "times" },
507 { "helvetica", "helvetica" }
508 };
509
510 // When Xft is available, TQt will do a good job of looking up our local
511 // equivalent of the MS font. But, we want to work even without Xft.
512 // So, first, we do a fuzzy match of some common MS font names.
513 unsigned i;
514
515 for (i = 0; i < ENTRIES; i++)
516 {
517 // The loop will leave unchanged any MS font name not fuzzy-matched.
518 if (font.find(fuzzyLookup[i][0], 0, false) != -1)
519 {
520 font = fuzzyLookup[i][1];
521 break;
522 }
523 }
524
525#ifdef FONT_DEBUG
526 kdDebug(30513) << " FUZZY-FONT: " << font << endl;
527#endif
528
529 // Use TQt to look up our canonical equivalent of the font name.
530 TQFont xFont( font );
531 TQFontInfo info( xFont );
532
533#ifdef FONT_DEBUG
534 kdDebug(30513) << " TQt-FONT: " << info.family() << endl;
535#endif
536
537 return info.family();
538}
539
540void KWordTextHandler::writeOutParagraph( const TQString& styleName, const TQString& text )
541{
542 if ( m_framesetElement.isNull() )
543 {
544 if ( !text.isEmpty() ) // vertically merged table cells are ignored, and have empty text -> no warning on those
545 kdWarning(30513) << "KWordTextHandler: no frameset element to write to! text=" << text << endl;
546 return;
547 }
548 TQDomElement paragraphElementOut=mainDocument().createElement("PARAGRAPH");
549 m_framesetElement.appendChild(paragraphElementOut);
550 TQDomElement textElement=mainDocument().createElement("TEXT");
551 textElement.setAttribute( "xml:space", "preserve" );
552 paragraphElementOut.appendChild(textElement);
553 paragraphElementOut.appendChild( m_formats );
554 TQDomElement layoutElement=mainDocument().createElement("LAYOUT");
555 paragraphElementOut.appendChild(layoutElement);
556
557 TQDomElement nameElement = mainDocument().createElement("NAME");
558 nameElement.setAttribute("value", styleName);
559 layoutElement.appendChild(nameElement);
560
561 if ( m_paragraphProperties )
562 {
563 // Write out the properties of the paragraph
564 writeLayout( layoutElement, *m_paragraphProperties, m_currentStyle );
565 }
566
567 textElement.appendChild(mainDocument().createTextNode(text));
568
569 m_paragraph = TQString( "" );
570 m_index = 0;
571 m_oldLayout = layoutElement; // Keep a reference to the old layout for some hacks
572}
573
574void KWordTextHandler::writeLayout( TQDomElement& parentElement, const wvWare::ParagraphProperties& paragraphProperties, const wvWare::Style* style )
575{
576 const wvWare::Word97::PAP& pap = paragraphProperties.pap();
577 // Always write out the alignment, it's required
578 TQDomElement flowElement = mainDocument().createElement("FLOW");
579 TQString alignment = Conversion::alignment( pap.jc );
580 flowElement.setAttribute( "align", alignment );
581 parentElement.appendChild( flowElement );
582
583 //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;
584
585 if ( pap.dxaLeft1 || pap.dxaLeft || pap.dxaRight )
586 {
587 TQDomElement indentsElement = mainDocument().createElement("INDENTS");
588 // 'first' is relative to 'left' in both formats
589 indentsElement.setAttribute( "first", (double)pap.dxaLeft1 / 20.0 );
590 indentsElement.setAttribute( "left", (double)pap.dxaLeft / 20.0 );
591 indentsElement.setAttribute( "right", (double)pap.dxaRight / 20.0 );
592 parentElement.appendChild( indentsElement );
593 }
594 if ( pap.dyaBefore || pap.dyaAfter )
595 {
596 TQDomElement offsetsElement = mainDocument().createElement("OFFSETS");
597 offsetsElement.setAttribute( "before", (double)pap.dyaBefore / 20.0 );
598 offsetsElement.setAttribute( "after", (double)pap.dyaAfter / 20.0 );
599 parentElement.appendChild( offsetsElement );
600 }
601
602 // Linespacing
603 TQString lineSpacing = Conversion::lineSpacing( pap.lspd );
604 if ( lineSpacing != "0" )
605 {
606 TQDomElement lineSpacingElem = mainDocument().createElement( "LINESPACING" );
607 lineSpacingElem.setAttribute("value", lineSpacing );
608 parentElement.appendChild( lineSpacingElem );
609 }
610
611 if ( pap.fKeep || pap.fKeepFollow || pap.fPageBreakBefore )
612 {
613 TQDomElement pageBreak = mainDocument().createElement( "PAGEBREAKING" );
614 if ( pap.fKeep )
615 pageBreak.setAttribute("linesTogether", "true");
616 if ( pap.fPageBreakBefore )
617 pageBreak.setAttribute("hardFrameBreak", "true" );
618 if ( pap.fKeepFollow )
619 pageBreak.setAttribute("keepWithNext", "true" );
620 parentElement.appendChild( pageBreak );
621 }
622
623 // Borders
624 if ( pap.brcTop.brcType )
625 {
626 TQDomElement borderElement = mainDocument().createElement( "TOPBORDER" );
627 Conversion::setBorderAttributes( borderElement, pap.brcTop );
628 parentElement.appendChild( borderElement );
629 }
630 if ( pap.brcBottom.brcType )
631 {
632 TQDomElement borderElement = mainDocument().createElement( "BOTTOMBORDER" );
633 Conversion::setBorderAttributes( borderElement, pap.brcBottom );
634 parentElement.appendChild( borderElement );
635 }
636 if ( pap.brcLeft.brcType )
637 {
638 TQDomElement borderElement = mainDocument().createElement( "LEFTBORDER" );
639 Conversion::setBorderAttributes( borderElement, pap.brcLeft );
640 parentElement.appendChild( borderElement );
641 }
642 if ( pap.brcRight.brcType )
643 {
644 TQDomElement borderElement = mainDocument().createElement( "RIGHTBORDER" );
645 Conversion::setBorderAttributes( borderElement, pap.brcRight );
646 parentElement.appendChild( borderElement );
647 }
648
649 // Tabulators
650 if ( pap.itbdMac )
651 {
652 for ( int i = 0 ; i < pap.itbdMac ; ++i )
653 {
654 const wvWare::Word97::TabDescriptor &td = pap.rgdxaTab[i];
655 TQDomElement tabElement = mainDocument().createElement( "TABULATOR" );
656 tabElement.setAttribute( "ptpos", (double)td.dxaTab / 20.0 );
657 //kdDebug(30513) << "ptpos=" << (double)td.dxaTab / 20.0 << endl;
658 // Wow, lucky here. The type enum matches. Only, MSWord has 4=bar,
659 // which kword doesn't support. We map it to 0 with a clever '%4' :)
660 tabElement.setAttribute( "type", td.tbd.jc % 4 );
661 int filling = 0;
662 double width = 0.5; // default kword value, see koparaglayout.cpp
663 switch ( td.tbd.tlc ) {
664 case 1: // dots
665 case 2: // hyphenated
666 filling = 1; // KWord: dots
667 break;
668 case 3: // single line
669 filling = 2; // KWord: line
670 break;
671 case 4: // heavy line
672 filling = 2; // KWord: line
673 width = 2; // make it heavy. To be tested.
674 }
675 tabElement.setAttribute( "filling", filling );
676 tabElement.setAttribute( "width", width );
677 parentElement.appendChild( tabElement );
678 }
679 }
680
681 if ( pap.ilfo > 0 )
682 {
683 writeCounter( parentElement, paragraphProperties, style );
684 }
685}
686
687void KWordTextHandler::writeCounter( TQDomElement& parentElement, const wvWare::ParagraphProperties& paragraphProperties, const wvWare::Style* style )
688{
689 const wvWare::ListInfo* listInfo = paragraphProperties.listInfo();
690 if ( !listInfo )
691 return;
692
693#ifndef NDEBUG
694 listInfo->dump();
695#endif
696
697 TQDomElement counterElement = mainDocument().createElement( "COUNTER" );
698 // numbering type: 0==list 1==chapter. First we determine it for word6 docs.
699 // But we can also activate it if the text() looks that way
700 int numberingType = listInfo->isWord6() && listInfo->prev() ? 1 : 0;
701 wvWare::UString text = listInfo->text().text;
702 int nfc = listInfo->numberFormat();
703 if ( nfc == 23 ) // bullet
704 {
705 if ( text.length() == 1 )
706 {
707 unsigned int code = text[0].unicode();
708 if ( (code & 0xFF00) == 0xF000 ) // see wv2
709 code &= 0x00FF;
710 // Some well-known bullet codes. Better turn them into real
711 // KWord bullets, it looks much nicer (than crappy fonts).
712 if ( code == 0xB7 ) // Round black bullet
713 {
714 counterElement.setAttribute( "type", 10 ); // disc bullet
715 } else if ( code == 0xD8 ) // Triangle
716 {
717 counterElement.setAttribute( "type", 11 ); // Box. We have no triangle.
718 } else {
719 // Map all other bullets to a "custom bullet" in kword.
720 kdDebug(30513) << "custom bullet, code=" << TQString::number(code,16) << endl;
721 counterElement.setAttribute( "type", 6 ); // custom
722 counterElement.setAttribute( "bullet", code );
723 TQString paragFont = getFont( style->chp().ftcAscii );
724 counterElement.setAttribute( "bulletfont", paragFont );
725 }
726 } else
727 kdWarning(30513) << "Bullet with more than one character, not supported" << endl;
728 }
729 else
730 {
731 const wvWare::Word97::PAP& pap = paragraphProperties.pap();
732 counterElement.setAttribute( "start", listInfo->startAt() );
733
734 int depth = pap.ilvl; /*both are 0 based*/
735 // Heading styles don't set the ilvl, but must have a depth coming
736 // from their heading level (the style's STI)
737 bool isHeading = style->sti() >= 1 && style->sti() <= 9;
738 if ( depth == 0 && isHeading )
739 {
740 depth = style->sti() - 1;
741 }
742 kdDebug(30513) << " ilfo=" << pap.ilfo << " ilvl=" << pap.ilvl << " sti=" << style->sti() << " depth=" << depth << " numberingType=" << numberingType << endl;
743 counterElement.setAttribute( "depth", depth );
744
745 // Now we need to parse the text, to try and convert msword's powerful list template
746 // stuff, into what KWord can do right now.
747 TQString prefix, suffix;
748 bool depthFound = false;
749 int displayLevels = 1;
750 // We parse <0>.<2>.<1>. as "level 2 with suffix='.'" (no prefix)
751 // But "Section <0>)" has both prefix and suffix.
752 // The common case is <0>.<1>.<2> (display-levels=3)
753 for ( int i = 0 ; i < text.length() ; ++i )
754 {
755 short ch = text[i].unicode();
756 //kdDebug(30513) << i << ":" << ch << endl;
757 if ( ch < 10 ) { // List level place holder
758 if ( ch == pap.ilvl ) {
759 if ( depthFound )
760 kdWarning(30513) << "ilvl " << pap.ilvl << " found twice in listInfo text..." << endl;
761 else
762 depthFound = true;
763 suffix = TQString();
764 } else {
765 Q_ASSERT( ch < pap.ilvl ); // Can't see how level 1 would have a <0> in it...
766 if ( ch < pap.ilvl )
767 ++displayLevels; // we found a 'parent level', to be displayed
768 prefix = TQString(); // get rid of previous prefixes
769 }
770 } else { // Normal character
771 if ( depthFound )
772 suffix += TQChar(ch);
773 else
774 prefix += TQChar(ch);
775 }
776 }
777 if ( displayLevels > 1 )
778 {
779 // This is a hierarchical list numbering e.g. <0>.<1>.
780 // (unless this is about a heading, in which case we've set numberingtype to 1 already
781 // so it will indeed look like that).
782 // The question is whether the '.' is the suffix of the parent level already..
783 if ( depth > 0 && !prefix.isEmpty() && m_listSuffixes[ depth - 1 ] == prefix ) {
784 prefix = TQString(); // it's already the parent's suffix -> remove it
785 kdDebug(30513) << "depth=" << depth << " parent suffix is " << prefix << " -> clearing" << endl;
786 }
787 }
788 if ( isHeading )
789 numberingType = 1;
790 if ( depthFound )
791 {
792 // Word6 models "1." as nfc=5
793 if ( nfc == 5 && suffix.isEmpty() )
794 suffix = ".";
795 kdDebug(30513) << " prefix=" << prefix << " suffix=" << suffix << endl;
796 counterElement.setAttribute( "type", Conversion::numberFormatCode( nfc ) );
797 counterElement.setAttribute( "lefttext", prefix );
798 counterElement.setAttribute( "righttext", suffix );
799 counterElement.setAttribute( "display-levels", displayLevels );
800 kdDebug(30513) << "storing suffix " << suffix << " for depth " << depth << endl;
801 m_listSuffixes[ depth ] = suffix;
802 }
803 else
804 {
805 kdWarning(30513) << "Not supported: counter text without the depth in it:" << Conversion::string(text).string() << endl;
806 }
807
808 if ( listInfo->startAtOverridden() ||
809 ( numberingType == 1 && m_previousOutlineLSID != 0 && m_previousOutlineLSID != listInfo->lsid() ) ||
810 ( numberingType == 0 &&m_previousEnumLSID != 0 && m_previousEnumLSID != listInfo->lsid() ) )
811 counterElement.setAttribute( "restart", "true" );
812
813 // listInfo->alignment() is not supported in KWord
814 // listInfo->isLegal() hmm
815 // listInfo->notRestarted() [by higher level of lists] not supported
816 // listInfo->followingchar() ignored, it's always a space in KWord currently
817 }
818 if ( numberingType == 1 )
819 m_previousOutlineLSID = listInfo->lsid();
820 else
821 m_previousEnumLSID = listInfo->lsid();
822 counterElement.setAttribute( "numberingtype", numberingType );
823 parentElement.appendChild( counterElement );
824}
825
826void KWordTextHandler::setFrameSetElement( const TQDomElement& frameset )
827{
828 m_framesetElement = frameset;
829 for ( uint i = 0 ; i < 9 ; ++i )
830 m_listSuffixes[i] = TQString();
831}
832
833TQDomDocument KWordTextHandler::mainDocument() const
834{
835 return m_framesetElement.ownerDocument();
836}
837
838#include "texthandler.moc"