lib

KoParagLayout.cpp

00001 /* This file is part of the KDE project
00002    Copyright (C) 2001 David Faure <faure@kde.org>
00003 
00004    This library is free software; you can redistribute it and/or
00005    modify it under the terms of the GNU Library General Public
00006    License as published by the Free Software Foundation; either
00007    version 2 of the License, or (at your option) any later version.
00008 
00009    This library 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    Library General Public License for more details.
00013 
00014    You should have received a copy of the GNU Library General Public License
00015    along with this library; see the file COPYING.LIB.  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 "KoParagLayout.h"
00021 #include "KoRichText.h"
00022 #include "KoParagCounter.h"
00023 #include "KoStyleCollection.h"
00024 #include "KoOasisContext.h"
00025 #include <KoXmlWriter.h>
00026 #include <KoXmlNS.h>
00027 #include <KoDom.h>
00028 #include <KoGenStyles.h>
00029 
00030 #include <kglobal.h>
00031 #include <klocale.h>
00032 #include <kdebug.h>
00033 #include <qdom.h>
00034 #include <qbuffer.h>
00035 #include <qcolor.h>
00036 
00037 #include <float.h>
00038 
00039 QString* KoParagLayout::shadowCssCompat = 0L;
00040 
00041 // Create a default KoParagLayout.
00042 KoParagLayout::KoParagLayout()
00043 {
00044     initialise();
00045 }
00046 
00047 void KoParagLayout::operator=( const KoParagLayout &layout )
00048 {
00049     alignment = layout.alignment;
00050     for ( int i = 0 ; i < 5 ; ++i )
00051         margins[i] = layout.margins[i];
00052     pageBreaking = layout.pageBreaking;
00053     leftBorder = layout.leftBorder;
00054     rightBorder = layout.rightBorder;
00055     topBorder = layout.topBorder;
00056     bottomBorder = layout.bottomBorder;
00057     joinBorder = layout.joinBorder;
00058     backgroundColor = layout.backgroundColor;
00059     if ( layout.counter )
00060         counter = new KoParagCounter( *layout.counter );
00061     else
00062         counter = 0L;
00063     lineSpacing = layout.lineSpacing;
00064     lineSpacingType = layout.lineSpacingType;
00065     style = layout.style;
00066     direction = layout.direction;
00067     setTabList( layout.tabList() );
00068 }
00069 
00070 int KoParagLayout::compare( const KoParagLayout & layout ) const
00071 {
00072     int flags = 0;
00073     if ( alignment != layout.alignment )
00074         flags |= Alignment;
00075     for ( int i = 0 ; i < 5 ; ++i )
00076         if ( margins[i] != layout.margins[i] )
00077         {
00078             flags |= Margins;
00079             break;
00080         }
00081     if ( pageBreaking != layout.pageBreaking )
00082         flags |= PageBreaking;
00083     if ( leftBorder != layout.leftBorder
00084          || rightBorder != layout.rightBorder
00085          || topBorder != layout.topBorder
00086          || bottomBorder != layout.bottomBorder
00087          || joinBorder != layout.joinBorder )
00088         flags |= Borders;
00089 
00090     if ( layout.counter )
00091     {
00092         if ( counter )
00093         {
00094             if ( ! ( *layout.counter == *counter ) )
00095                 flags |= BulletNumber;
00096         } else
00097             if ( layout.counter->numbering() != KoParagCounter::NUM_NONE )
00098                 flags |= BulletNumber;
00099     }
00100     else
00101         if ( counter && counter->numbering() != KoParagCounter::NUM_NONE )
00102             flags |= BulletNumber;
00103 
00104     if ( lineSpacing != layout.lineSpacing
00105         || lineSpacingType != layout.lineSpacingType )
00106         flags |= LineSpacing;
00107     //if ( style != layout.style )
00108     //    flags |= Style;
00109     if ( m_tabList != layout.m_tabList )
00110         flags |= Tabulator;
00111 
00112     if ( backgroundColor != layout.backgroundColor)
00113         flags |= BackgroundColor;
00114 
00115     // This method is used for the GUI stuff only, so we don't have a flag
00116     // for the Direction value.
00117     return flags;
00118 }
00119 
00120 void KoParagLayout::initialise()
00121 {
00122     alignment = Qt::AlignAuto;
00123     for ( int i = 0 ; i < 5 ; ++i ) // use memset ?
00124         margins[i] = 0;
00125     lineSpacingType = LS_SINGLE;
00126     lineSpacing = 0;
00127     counter = 0L;
00128     leftBorder.setPenWidth( 0);
00129     rightBorder.setPenWidth( 0);
00130     topBorder.setPenWidth( 0);
00131     bottomBorder.setPenWidth( 0);
00132     joinBorder = true;
00133     pageBreaking = 0;
00134     style = 0L;
00135     direction = QChar::DirON;
00136     m_tabList.clear();
00137 }
00138 
00139 KoParagLayout::~KoParagLayout()
00140 {
00141     delete counter;
00142 }
00143 
00144 void KoParagLayout::loadParagLayout( KoParagLayout& layout, const QDomElement& parentElem, int docVersion )
00145 {
00146     // layout is an input and output parameter
00147     // It can have been initialized already, e.g. by copying from a style
00148     // (we don't do that anymore though).
00149 
00150     // Load the paragraph tabs - we load into a clean list, not mixing with those already in "layout"
00151     // We can't apply the 'default comes from the style' in this case, because
00152     // there is no way to differentiate between "I want no tabs in the parag"
00153     // and "use default from style".
00154     KoTabulatorList tabList;
00155     QDomElement element = parentElem.firstChild().toElement();
00156     for ( ; !element.isNull() ; element = element.nextSibling().toElement() )
00157     {
00158         if ( element.tagName() == "TABULATOR" )
00159         {
00160             KoTabulator tab;
00161             tab.type = static_cast<KoTabulators>( getAttribute( element, "type", T_LEFT ) );
00162             tab.ptPos = getAttribute( element, "ptpos", 0.0 );
00163             tab.filling = static_cast<KoTabulatorFilling>( getAttribute( element, "filling", TF_BLANK ) );
00164             tab.ptWidth = getAttribute( element, "width", 0.5 );
00165             QString alignCharStr = element.attribute("alignchar");
00166             if ( alignCharStr.isEmpty() )
00167                 tab.alignChar = KGlobal::locale()->decimalSymbol()[0];
00168             else
00169                 tab.alignChar = alignCharStr[0];
00170             tabList.append( tab );
00171         }
00172     }
00173     qHeapSort( tabList );
00174     layout.setTabList( tabList );
00175     layout.alignment = Qt::AlignAuto;
00176     element = parentElem.namedItem( "FLOW" ).toElement(); // Flow is what is now called alignment internally
00177     if ( !element.isNull() )
00178     {
00179         QString flow = element.attribute( "align" ); // KWord-1.0 DTD
00180         if ( !flow.isEmpty() )
00181         {
00182             layout.alignment = flow=="right" ? Qt::AlignRight :
00183                          flow=="center" ? Qt::AlignHCenter :
00184                          flow=="justify" ? Qt::AlignJustify :
00185                          flow=="left" ? Qt::AlignLeft : Qt::AlignAuto;
00186 
00187             QString dir = element.attribute( "dir" ); // KWord-1.2
00188             if ( !dir.isEmpty() ) {
00189                 if ( dir == "L" )
00190                     layout.direction = QChar::DirL;
00191                 else if ( dir == "R" )
00192                     layout.direction = QChar::DirR;
00193                 else
00194                     kdWarning() << "Unexpected value for paragraph direction: " << dir << endl;
00195             }
00196         } else {
00197             flow = element.attribute( "value" ); // KWord-0.8
00198             static const int flow2align[] = { Qt::AlignAuto, Qt::AlignRight, Qt::AlignHCenter, Qt::AlignJustify };
00199             if ( !flow.isEmpty() && flow.toInt() < 4 )
00200                 layout.alignment = flow2align[flow.toInt()];
00201         }
00202     }
00203 
00204     if ( docVersion < 2 )
00205     {
00206         element = parentElem.namedItem( "OHEAD" ).toElement(); // used by KWord-0.8
00207         if ( !element.isNull() )
00208             layout.margins[QStyleSheetItem::MarginTop] = getAttribute( element, "pt", 0.0 );
00209 
00210         element = parentElem.namedItem( "OFOOT" ).toElement(); // used by KWord-0.8
00211         if ( !element.isNull() )
00212             layout.margins[QStyleSheetItem::MarginBottom] = getAttribute( element, "pt", 0.0 );
00213 
00214         element = parentElem.namedItem( "IFIRST" ).toElement(); // used by KWord-0.8
00215         if ( !element.isNull() )
00216             layout.margins[QStyleSheetItem::MarginFirstLine] = getAttribute( element, "pt", 0.0 );
00217 
00218         element = parentElem.namedItem( "ILEFT" ).toElement(); // used by KWord-0.8
00219         if ( !element.isNull() )
00220             layout.margins[QStyleSheetItem::MarginLeft] = getAttribute( element, "pt", 0.0 );
00221     }
00222 
00223     // KWord-1.0 DTD
00224     element = parentElem.namedItem( "INDENTS" ).toElement();
00225     if ( !element.isNull() )
00226     {
00227         layout.margins[QStyleSheetItem::MarginFirstLine] = getAttribute( element, "first", 0.0 );
00228         layout.margins[QStyleSheetItem::MarginLeft] = getAttribute( element, "left", 0.0 );
00229         layout.margins[QStyleSheetItem::MarginRight] = getAttribute( element, "right", 0.0 );
00230     }
00231     element = parentElem.namedItem( "OFFSETS" ).toElement();
00232     if ( !element.isNull() )
00233     {
00234         layout.margins[QStyleSheetItem::MarginTop] = getAttribute( element, "before", 0.0 );
00235         layout.margins[QStyleSheetItem::MarginBottom] = getAttribute( element, "after", 0.0 );
00236     }
00237 
00238     if ( docVersion < 2 )
00239     {
00240         element = parentElem.namedItem( "LINESPACE" ).toElement(); // used by KWord-0.8
00241         if ( !element.isNull() )
00242         {
00243             layout.lineSpacingType = KoParagLayout::LS_CUSTOM;
00244             layout.lineSpacing = getAttribute( element, "pt", 0.0 );
00245         }
00246     }
00247 
00248     element = parentElem.namedItem( "LINESPACING" ).toElement(); // KWord-1.0 DTD
00249     if ( !element.isNull() )
00250     {
00251         //compatibility with koffice 1.1
00252         if ( element.hasAttribute( "value" ))
00253         {
00254             QString value = element.attribute( "value" );
00255             if ( value == "oneandhalf" )
00256             {
00257                 layout.lineSpacingType = KoParagLayout::LS_ONEANDHALF;
00258                 layout.lineSpacing = 0;
00259             }
00260             else if ( value == "double" )
00261             {
00262                 layout.lineSpacingType = KoParagLayout::LS_DOUBLE;
00263                 layout.lineSpacing = 0;
00264             }
00265             else
00266             {
00267                 layout.lineSpacingType = KoParagLayout::LS_CUSTOM;
00268                 layout.lineSpacing = value.toDouble();
00269             }
00270         }
00271         else
00272         {
00273             QString type = element.attribute( "type" );
00274             if ( type == "oneandhalf" )
00275             {
00276                 layout.lineSpacingType = KoParagLayout::LS_ONEANDHALF;
00277                 layout.lineSpacing = 0;
00278             }
00279             else if ( type == "double" )
00280             {
00281                 layout.lineSpacingType = KoParagLayout::LS_DOUBLE;
00282                 layout.lineSpacing = 0;
00283             }
00284             else if ( type == "custom" )
00285             {
00286                 layout.lineSpacingType = KoParagLayout::LS_CUSTOM;
00287                 layout.lineSpacing = element.attribute( "spacingvalue" ).toDouble();
00288             }
00289             else if ( type == "atleast" )
00290             {
00291                 layout.lineSpacingType = KoParagLayout::LS_AT_LEAST;
00292                 layout.lineSpacing = element.attribute( "spacingvalue" ).toDouble();
00293             }
00294             else if ( type == "multiple" )
00295             {
00296                 layout.lineSpacingType = KoParagLayout::LS_MULTIPLE;
00297                 layout.lineSpacing = element.attribute( "spacingvalue" ).toDouble();
00298             }
00299             else if ( type == "fixed" )
00300             {
00301                 layout.lineSpacingType = KoParagLayout::LS_FIXED;
00302                 layout.lineSpacing = element.attribute( "spacingvalue" ).toDouble();
00303             }
00304             else if ( type == "single" ) // not used; just in case future versions use it.
00305                 layout.lineSpacingType = KoParagLayout::LS_SINGLE;
00306         }
00307     }
00308 
00309     int pageBreaking = 0;
00310     element = parentElem.namedItem( "PAGEBREAKING" ).toElement();
00311     if ( !element.isNull() )
00312     {
00313         if ( element.attribute( "linesTogether" ) == "true" )
00314             pageBreaking |= KoParagLayout::KeepLinesTogether;
00315         if ( element.attribute( "hardFrameBreak" ) == "true" )
00316             pageBreaking |= KoParagLayout::HardFrameBreakBefore;
00317         if ( element.attribute( "hardFrameBreakAfter" ) == "true" )
00318             pageBreaking |= KoParagLayout::HardFrameBreakAfter;
00319     }
00320     if ( docVersion < 2 )
00321     {
00322         element = parentElem.namedItem( "HARDBRK" ).toElement(); // KWord-0.8
00323         if ( !element.isNull() )
00324             pageBreaking |= KoParagLayout::HardFrameBreakBefore;
00325     }
00326     layout.pageBreaking = pageBreaking;
00327 
00328     element = parentElem.namedItem( "LEFTBORDER" ).toElement();
00329     if ( !element.isNull() )
00330         layout.leftBorder = KoBorder::loadBorder( element );
00331     else
00332         layout.leftBorder.setPenWidth(0);
00333 
00334     element = parentElem.namedItem( "RIGHTBORDER" ).toElement();
00335     if ( !element.isNull() )
00336         layout.rightBorder = KoBorder::loadBorder( element );
00337     else
00338         layout.rightBorder.setPenWidth(0);
00339 
00340     element = parentElem.namedItem( "TOPBORDER" ).toElement();
00341     if ( !element.isNull() )
00342         layout.topBorder = KoBorder::loadBorder( element );
00343     else
00344         layout.topBorder.setPenWidth(0);
00345 
00346     element = parentElem.namedItem( "BOTTOMBORDER" ).toElement();
00347     if ( !element.isNull() )
00348         layout.bottomBorder = KoBorder::loadBorder( element );
00349     else
00350         layout.bottomBorder.setPenWidth(0);
00351 
00352     element = parentElem.namedItem( "COUNTER" ).toElement();
00353     if ( !element.isNull() )
00354     {
00355         layout.counter = new KoParagCounter;
00356         layout.counter->load( element );
00357     }
00358 
00359     // Compatibility with KOffice-1.2
00360     element = parentElem.namedItem( "SHADOW" ).toElement();
00361     if ( !element.isNull() && element.hasAttribute("direction") )
00362     {
00363         int shadowDistance = element.attribute("distance").toInt();
00364         int shadowDirection = element.attribute("direction").toInt();
00365         QColor shadowColor;
00366         if ( element.hasAttribute("red") )
00367         {
00368             int r = element.attribute("red").toInt();
00369             int g = element.attribute("green").toInt();
00370             int b = element.attribute("blue").toInt();
00371             shadowColor.setRgb( r, g, b );
00372         }
00373         int distanceX = 0;
00374         int distanceY = 0;
00375         switch ( shadowDirection )
00376         {
00377         case 1: // KoParagLayout::SD_LEFT_UP:
00378         case 2: // KoParagLayout::SD_UP:
00379         case 3: // KoParagLayout::SD_RIGHT_UP:
00380             distanceX = - shadowDistance;
00381             break;
00382         case 7: // KoParagLayout::SD_LEFT_BOTTOM:
00383         case 6: // KoParagLayout::SD_BOTTOM:
00384         case 5: // KoParagLayout::SD_RIGHT_BOTTOM:
00385             distanceX = shadowDistance;
00386             break;
00387         }
00388         switch ( shadowDirection )
00389         {
00390         case 7: // KoParagLayout::SD_LEFT_BOTTOM:
00391         case 8: // KoParagLayout::SD_LEFT:
00392         case 1: //KoParagLayout::SD_LEFT_UP:
00393             distanceY = - shadowDistance;
00394             break;
00395         case 3: // KoParagLayout::SD_RIGHT_UP:
00396         case 4: // KoParagLayout::SD_RIGHT:
00397         case 5: // KoParagLayout::SD_RIGHT_BOTTOM:
00398             distanceY = shadowDistance;
00399             break;
00400         }
00401         if ( !shadowCssCompat )
00402             shadowCssCompat = new QString;
00403         *shadowCssCompat = KoTextFormat::shadowAsCss( distanceX, distanceY, shadowColor );
00404         kdDebug(32500) << "setting shadow compat to " << ( *shadowCssCompat ) << endl;
00405     }
00406     else
00407     {
00408         delete shadowCssCompat;
00409         shadowCssCompat = 0L;
00410     }
00411 }
00412 
00413 //static
00414 Qt::AlignmentFlags KoParagLayout::loadOasisAlignment( const QCString& str )
00415 {
00416     return
00417         str == "left" ? Qt::AlignLeft :
00418         str == "right" ? Qt::AlignRight :
00419         str == "start" ? Qt::AlignLeft :
00420         str == "end" ? Qt::AlignRight :
00421         str == "center" ? Qt::AlignHCenter :
00422         str == "justify" ? Qt::AlignJustify :
00423         str == "start" ? Qt::AlignAuto // i.e. direction-dependent
00424         : Qt::AlignAuto; // default (can't happen unless spec is extended)
00425 }
00426 
00427 //static
00428 QCString KoParagLayout::saveOasisAlignment( Qt::AlignmentFlags alignment )
00429 {
00430    return alignment == Qt::AlignLeft ? "left" :
00431        alignment == Qt::AlignRight ? "right" :
00432        alignment == Qt::AlignHCenter ? "center" :
00433        alignment == Qt::AlignJustify ? "justify" :
00434        "start"; // i.e. direction-dependent
00435 }
00436 
00437 void KoParagLayout::loadOasisParagLayout( KoParagLayout& layout, KoOasisContext& context )
00438 {
00439     context.styleStack().setTypeProperties( "paragraph" );
00440     // layout is an input and output parameter
00441     // It can have been initialized already, e.g. by copying from a style
00442 
00443     // code from OoWriterImport::writeLayout
00444     if ( context.styleStack().hasAttributeNS( KoXmlNS::fo, "text-align" ) ) {
00445         QCString align = context.styleStack().attributeNS( KoXmlNS::fo, "text-align" ).latin1();
00446         layout.alignment = loadOasisAlignment( align );
00447     }
00448 
00449     if ( context.styleStack().hasAttributeNS( KoXmlNS::style, "writing-mode" ) ) { // http://web4.w3.org/TR/xsl/slice7.html#writing-mode
00450         // LTR is lr-tb. RTL is rl-tb
00451         QString writingMode = context.styleStack().attributeNS( KoXmlNS::style, "writing-mode" );
00452         layout.direction = ( writingMode=="rl-tb" || writingMode=="rl" ) ? QChar::DirR : QChar::DirL;
00453     }
00454 
00455     // Indentation (margins)
00456     if ( context.styleStack().hasAttributeNS( KoXmlNS::fo, "margin-left" ) || // 3.11.19
00457          context.styleStack().hasAttributeNS( KoXmlNS::fo, "margin-right" ) ) {
00458         layout.margins[QStyleSheetItem::MarginLeft] = KoUnit::parseValue( context.styleStack().attributeNS( KoXmlNS::fo, "margin-left" ) );
00459         layout.margins[QStyleSheetItem::MarginRight] = KoUnit::parseValue( context.styleStack().attributeNS( KoXmlNS::fo, "margin-right" ) );
00460         // *text-indent must always be bound to either margin-left or margin-right
00461         double first = 0;
00462         if ( context.styleStack().attributeNS( KoXmlNS::style, "auto-text-indent") == "true" ) // style:auto-text-indent takes precedence
00463             // ### "indented by a value that is based on the current font size"
00464             // ### and "requires margin-left and margin-right
00465             // ### but how much is the indent?
00466             first = 10;
00467         else if ( context.styleStack().hasAttributeNS( KoXmlNS::fo, "text-indent") )
00468             first = KoUnit::parseValue( context.styleStack().attributeNS( KoXmlNS::fo, "text-indent") );
00469 
00470         layout.margins[QStyleSheetItem::MarginFirstLine] = first;
00471     }
00472 
00473     // Offset before and after paragraph
00474     if( context.styleStack().hasAttributeNS( KoXmlNS::fo, "margin-top") || // 3.11.22
00475         context.styleStack().hasAttributeNS( KoXmlNS::fo, "margin-bottom")) {
00476         layout.margins[QStyleSheetItem::MarginTop] = KoUnit::parseValue( context.styleStack().attributeNS( KoXmlNS::fo, "margin-top" ) );
00477         layout.margins[QStyleSheetItem::MarginBottom] = KoUnit::parseValue( context.styleStack().attributeNS( KoXmlNS::fo, "margin-bottom" ) );
00478     }
00479 
00480     // Line spacing
00481     if( context.styleStack().hasAttributeNS( KoXmlNS::fo, "line-height") ) {  // 3.11.1
00482         // Fixed line height
00483         QString value = context.styleStack().attributeNS( KoXmlNS::fo, "line-height" );
00484         if ( value != "normal" ) {
00485             if ( value == "100%" )
00486                 layout.lineSpacingType = KoParagLayout::LS_SINGLE;
00487             else if( value=="150%")
00488                 layout.lineSpacingType = KoParagLayout::LS_ONEANDHALF;
00489             else if( value=="200%")
00490                 layout.lineSpacingType = KoParagLayout::LS_DOUBLE;
00491             else if ( value.find('%') > -1 )
00492             {
00493                 value = value.remove( '%' );
00494                 double percent = value.toDouble();
00495                 layout.lineSpacingType = KoParagLayout::LS_MULTIPLE;
00496                 layout.lineSpacing = percent / 100.0;
00497                 kdDebug(33001) << "line-height =" << percent << ", " << layout.lineSpacing << ", " << percent/100 << endl;
00498             }
00499             else // fixed value
00500             {
00501                 layout.lineSpacingType = KoParagLayout::LS_FIXED;
00502                 layout.lineSpacing = KoUnit::parseValue( value );
00503             }
00504         }
00505     }
00506     // Line-height-at-least is mutually exclusive with line-height
00507     else if ( context.styleStack().hasAttributeNS( KoXmlNS::style, "line-height-at-least") ) // 3.11.2
00508     {
00509         QString value = context.styleStack().attributeNS( KoXmlNS::style, "line-height-at-least" );
00510         // kotext has "at least" but that's for the linespacing, not for the entire line height!
00511         // Strange. kotext also has "at least" for the whole line height....
00512         // Did we make the wrong choice in kotext?
00513         //kdWarning() << "Unimplemented support for style:line-height-at-least: " << value << endl;
00514         // Well let's see if this makes a big difference.
00515         layout.lineSpacingType = KoParagLayout::LS_AT_LEAST;
00516         layout.lineSpacing = KoUnit::parseValue( value );
00517     }
00518     // Line-spacing is mutually exclusive with line-height and line-height-at-least
00519     else if ( context.styleStack().hasAttributeNS( KoXmlNS::style, "line-spacing") ) // 3.11.3
00520     {
00521         double value = KoUnit::parseValue( context.styleStack().attributeNS( KoXmlNS::style, "line-spacing" ) );
00522         if ( value != 0.0 )
00523         {
00524             layout.lineSpacingType = KoParagLayout::LS_CUSTOM;
00525             layout.lineSpacing = value;
00526         }
00527     }
00528 
00529     // Tabulators
00530     KoTabulatorList tabList;
00531     if ( context.styleStack().hasChildNodeNS( KoXmlNS::style, "tab-stops" ) ) { // 3.11.10
00532         QDomElement tabStops = context.styleStack().childNodeNS( KoXmlNS::style, "tab-stops" );
00533         //kdDebug(30519) << k_funcinfo << tabStops.childNodes().count() << " tab stops in layout." << endl;
00534         QDomElement tabStop;
00535         forEachElement( tabStop, tabStops )
00536         {
00537             Q_ASSERT( tabStop.localName() == "tab-stop" );
00538             const QString type = tabStop.attributeNS( KoXmlNS::style, "type", QString::null ); // left, right, center or char
00539 
00540             KoTabulator tab;
00541             tab.ptPos = KoUnit::parseValue( tabStop.attributeNS( KoXmlNS::style, "position", QString::null ) );
00542             // Tab stop positions in the XML are relative to the left-margin
00543             tab.ptPos += layout.margins[QStyleSheetItem::MarginLeft];
00544             if ( type == "center" )
00545                 tab.type = T_CENTER;
00546             else if ( type == "right" )
00547                 tab.type = T_RIGHT;
00548             else if ( type == "char" ) {
00549                 QString delimiterChar = tabStop.attributeNS( KoXmlNS::style, "char", QString::null ); // single character
00550                 if ( !delimiterChar.isEmpty() )
00551                     tab.alignChar = delimiterChar[0];
00552                 tab.type = T_DEC_PNT; // "alignment on decimal point"
00553             }
00554             else //if ( type == "left" )
00555                 tab.type = T_LEFT;
00556 
00557             tab.ptWidth = KoUnit::parseValue( tabStop.attributeNS( KoXmlNS::style, "leader-width", QString::null ), 0.5 );
00558 
00559             tab.filling = TF_BLANK;
00560             if ( tabStop.attributeNS( KoXmlNS::style, "leader-type", QString::null ) == "single" )
00561             {
00562                 QString leaderStyle = tabStop.attributeNS( KoXmlNS::style, "leader-style", QString::null );
00563                 if ( leaderStyle == "solid" )
00564                     tab.filling = TF_LINE;
00565                 else if ( leaderStyle == "dotted" )
00566                     tab.filling = TF_DOTS;
00567                 else if ( leaderStyle == "dash" )
00568                     tab.filling = TF_DASH;
00569                 else if ( leaderStyle == "dot-dash" )
00570                     tab.filling = TF_DASH_DOT;
00571                 else if ( leaderStyle == "dot-dot-dash" )
00572                     tab.filling = TF_DASH_DOT_DOT;
00573             }
00574             else
00575             {
00576                 // Fallback: convert leaderChar's unicode value
00577                 QString leaderChar = tabStop.attributeNS( KoXmlNS::style, "leader-text", QString::null );
00578                 if ( !leaderChar.isEmpty() )
00579                 {
00580                     QChar ch = leaderChar[0];
00581                     switch (ch.latin1()) {
00582                     case '.':
00583                         tab.filling = TF_DOTS; break;
00584                     case '-':
00585                     case '_':  // TODO in KWord: differentiate --- and ___
00586                         tab.filling = TF_LINE; break;
00587                     default:
00588                         // KWord doesn't have support for "any char" as filling.
00589                         break;
00590                     }
00591                 }
00592             }
00593             tabList.append( tab );
00594         } //for
00595     }
00596     qHeapSort( tabList );
00597     layout.setTabList( tabList );
00598 
00599     layout.joinBorder = !( context.styleStack().attributeNS( KoXmlNS::style, "join-border") == "false" );
00600 
00601     // Borders
00602     if ( context.styleStack().hasAttributeNS( KoXmlNS::fo, "border","left") )
00603         layout.leftBorder.loadFoBorder( context.styleStack().attributeNS( KoXmlNS::fo, "border","left") );
00604     else
00605         layout.leftBorder.setPenWidth(0);
00606     if ( context.styleStack().hasAttributeNS( KoXmlNS::fo, "border","right") )
00607         layout.rightBorder.loadFoBorder( context.styleStack().attributeNS( KoXmlNS::fo, "border","right") );
00608     else
00609         layout.rightBorder.setPenWidth(0);
00610     if ( context.styleStack().hasAttributeNS( KoXmlNS::fo, "border","top") )
00611         layout.topBorder.loadFoBorder( context.styleStack().attributeNS( KoXmlNS::fo, "border","top") );
00612     else
00613         layout.topBorder.setPenWidth(0);
00614     if ( context.styleStack().hasAttributeNS( KoXmlNS::fo, "border","bottom") )
00615         layout.bottomBorder.loadFoBorder( context.styleStack().attributeNS( KoXmlNS::fo, "border","bottom") );
00616     else
00617         layout.bottomBorder.setPenWidth(0);
00618 
00619 
00620     // Page breaking
00621     int pageBreaking = 0;
00622     if( context.styleStack().hasAttributeNS( KoXmlNS::fo, "break-before") ||
00623         context.styleStack().hasAttributeNS( KoXmlNS::fo, "break-after") ||
00624         context.styleStack().hasAttributeNS( KoXmlNS::fo, "keep-together") ||
00625         context.styleStack().hasAttributeNS( KoXmlNS::style, "keep-with-next") ||
00626         context.styleStack().hasAttributeNS( KoXmlNS::fo, "keep-with-next") )
00627     {
00628         if ( context.styleStack().hasAttributeNS( KoXmlNS::fo, "break-before") ) { // 3.11.24
00629             // TODO in KWord: implement difference between "column" and "page"
00630             if ( context.styleStack().attributeNS( KoXmlNS::fo, "break-before" ) != "auto" )
00631                 pageBreaking |= KoParagLayout::HardFrameBreakBefore;
00632         }
00633         else if ( context.styleStack().hasAttributeNS( KoXmlNS::fo, "break-after") ) { // 3.11.24
00634             // TODO in KWord: implement difference between "column" and "page"
00635             if ( context.styleStack().attributeNS( KoXmlNS::fo, "break-after" ) != "auto" )
00636                 pageBreaking |= KoParagLayout::HardFrameBreakAfter;
00637         }
00638 
00639         if ( context.styleStack().hasAttributeNS( KoXmlNS::fo, "keep-together" ) ) { // was style:break-inside in OOo-1.1, renamed in OASIS
00640             if ( context.styleStack().attributeNS( KoXmlNS::fo, "keep-together" ) != "auto" )
00641                  pageBreaking |= KoParagLayout::KeepLinesTogether;
00642         }
00643         if ( context.styleStack().hasAttributeNS( KoXmlNS::fo, "keep-with-next" ) ) {
00644             // OASIS spec says it's "auto"/"always", not a boolean.
00645             QString val = context.styleStack().attributeNS( KoXmlNS::fo, "keep-with-next" );
00646             if ( val == "true" || val == "always" )
00647                 pageBreaking |= KoParagLayout::KeepWithNext;
00648         }
00649     }
00650     layout.pageBreaking = pageBreaking;
00651 
00652     // Paragraph background color -  fo:background-color
00653     // The background color for parts of a paragraph that have no text underneath
00654     if ( context.styleStack().hasAttributeNS( KoXmlNS::fo, "background-color" ) ) {
00655         QString bgColor = context.styleStack().attributeNS( KoXmlNS::fo, "background-color");
00656         if (bgColor != "transparent")
00657             layout.backgroundColor.setNamedColor( bgColor );
00658     }
00659 }
00660 
00661 void KoParagLayout::saveParagLayout( QDomElement & parentElem, int alignment ) const
00662 {
00663     const KoParagLayout& layout = *this; // code moved from somewhere else;)
00664     QDomDocument doc = parentElem.ownerDocument();
00665     QDomElement element = doc.createElement( "NAME" );
00666     parentElem.appendChild( element );
00667     if ( layout.style )
00668         element.setAttribute( "value", layout.style->displayName() );
00669     //else
00670     //    kdWarning() << "KoParagLayout::saveParagLayout: style==0!" << endl;
00671 
00672     element = doc.createElement( "FLOW" );
00673     parentElem.appendChild( element );
00674 
00675     element.setAttribute( "align", alignment==Qt::AlignRight ? "right" :
00676                           alignment==Qt::AlignHCenter ? "center" :
00677                           alignment==Qt::AlignJustify ? "justify" :
00678                           alignment==Qt::AlignAuto ? "auto" : "left" ); // Note: styles can have AlignAuto. Not paragraphs.
00679 
00680     if ( static_cast<QChar::Direction>(layout.direction) == QChar::DirR )
00681         element.setAttribute( "dir", "R" );
00682     else
00683     if ( static_cast<QChar::Direction>(layout.direction) == QChar::DirL )
00684             element.setAttribute( "dir", "L" );
00685 
00686     if ( layout.margins[QStyleSheetItem::MarginFirstLine] != 0 ||
00687          layout.margins[QStyleSheetItem::MarginLeft] != 0 ||
00688          layout.margins[QStyleSheetItem::MarginRight] != 0 )
00689     {
00690         element = doc.createElement( "INDENTS" );
00691         parentElem.appendChild( element );
00692         if ( layout.margins[QStyleSheetItem::MarginFirstLine] != 0 )
00693             element.setAttribute( "first", layout.margins[QStyleSheetItem::MarginFirstLine] );
00694         if ( layout.margins[QStyleSheetItem::MarginLeft] != 0 )
00695             element.setAttribute( "left", layout.margins[QStyleSheetItem::MarginLeft] );
00696         if ( layout.margins[QStyleSheetItem::MarginRight] != 0 )
00697             element.setAttribute( "right", layout.margins[QStyleSheetItem::MarginRight] );
00698     }
00699 
00700     if ( layout.margins[QStyleSheetItem::MarginTop] != 0 ||
00701          layout.margins[QStyleSheetItem::MarginBottom] != 0 )
00702     {
00703         element = doc.createElement( "OFFSETS" );
00704         parentElem.appendChild( element );
00705         if ( layout.margins[QStyleSheetItem::MarginTop] != 0 )
00706             element.setAttribute( "before", layout.margins[QStyleSheetItem::MarginTop] );
00707         if ( layout.margins[QStyleSheetItem::MarginBottom] != 0 )
00708             element.setAttribute( "after", layout.margins[QStyleSheetItem::MarginBottom] );
00709     }
00710     if ( layout.lineSpacingType != LS_SINGLE )
00711     {
00712         element = doc.createElement( "LINESPACING" );
00713         parentElem.appendChild( element );
00714         if ( layout.lineSpacingType == KoParagLayout::LS_ONEANDHALF )  {
00715             element.setAttribute( "type", "oneandhalf" );
00716             element.setAttribute( "value", "oneandhalf" ); //compatibility with koffice 1.2
00717         }
00718         else if ( layout.lineSpacingType == KoParagLayout::LS_DOUBLE ) {
00719             element.setAttribute( "type", "double" );
00720             element.setAttribute( "value", "double" ); //compatibility with koffice 1.2
00721         }
00722         else if ( layout.lineSpacingType == KoParagLayout::LS_CUSTOM )
00723         {
00724             element.setAttribute( "type", "custom" );
00725             element.setAttribute( "spacingvalue", layout.lineSpacing);
00726             element.setAttribute( "value", layout.lineSpacing ); //compatibility with koffice 1.2
00727         }
00728         else if ( layout.lineSpacingType == KoParagLayout::LS_AT_LEAST )
00729         {
00730             element.setAttribute( "type", "atleast" );
00731             element.setAttribute( "spacingvalue", layout.lineSpacing);
00732         }
00733         else if ( layout.lineSpacingType == KoParagLayout::LS_MULTIPLE )
00734         {
00735             element.setAttribute( "type", "multiple" );
00736             element.setAttribute( "spacingvalue", layout.lineSpacing);
00737         }
00738         else if ( layout.lineSpacingType == KoParagLayout::LS_FIXED )
00739         {
00740             element.setAttribute( "type", "fixed" );
00741             element.setAttribute( "spacingvalue", layout.lineSpacing);
00742         }
00743         else
00744             kdDebug()<<" error in lineSpacing Type\n";
00745     }
00746 
00747     if ( layout.pageBreaking != 0 )
00748     {
00749         element = doc.createElement( "PAGEBREAKING" );
00750         parentElem.appendChild( element );
00751         if ( layout.pageBreaking & KoParagLayout::KeepLinesTogether )
00752             element.setAttribute( "linesTogether",  "true" );
00753         if ( layout.pageBreaking & KoParagLayout::HardFrameBreakBefore )
00754             element.setAttribute( "hardFrameBreak", "true" );
00755         if ( layout.pageBreaking & KoParagLayout::HardFrameBreakAfter )
00756             element.setAttribute( "hardFrameBreakAfter", "true" );
00757     }
00758 
00759     if ( layout.leftBorder.penWidth() > 0 )
00760     {
00761         element = doc.createElement( "LEFTBORDER" );
00762         parentElem.appendChild( element );
00763         layout.leftBorder.save( element );
00764     }
00765     if ( layout.rightBorder.penWidth() > 0 )
00766     {
00767         element = doc.createElement( "RIGHTBORDER" );
00768         parentElem.appendChild( element );
00769         layout.rightBorder.save( element );
00770     }
00771     if ( layout.topBorder.penWidth() > 0 )
00772     {
00773         element = doc.createElement( "TOPBORDER" );
00774         parentElem.appendChild( element );
00775         layout.topBorder.save( element );
00776     }
00777     if ( layout.bottomBorder.penWidth() > 0 )
00778     {
00779         element = doc.createElement( "BOTTOMBORDER" );
00780         parentElem.appendChild( element );
00781         layout.bottomBorder.save( element );
00782     }
00783     if ( layout.counter && layout.counter->numbering() != KoParagCounter::NUM_NONE )
00784     {
00785         element = doc.createElement( "COUNTER" );
00786         parentElem.appendChild( element );
00787         if ( layout.counter )
00788             layout.counter->save( element );
00789     }
00790 
00791     KoTabulatorList tabList = layout.tabList();
00792     KoTabulatorList::ConstIterator it = tabList.begin();
00793     for ( ; it != tabList.end() ; it++ )
00794     {
00795         element = doc.createElement( "TABULATOR" );
00796         parentElem.appendChild( element );
00797         element.setAttribute( "type", (*it).type );
00798         element.setAttribute( "ptpos", (*it).ptPos );
00799         element.setAttribute( "filling", (*it).filling );
00800         if ( (*it).filling != TF_BLANK )
00801             element.setAttribute( "width", QString::number( (*it).ptWidth, 'g', DBL_DIG ) );
00802         if ( (*it).type == T_DEC_PNT && !(*it).alignChar.isNull() )
00803             element.setAttribute( "alignchar", QString((*it).alignChar) );
00804     }
00805 }
00806 
00807 void KoParagLayout::saveOasis( KoGenStyle& gs, KoSavingContext& context, bool savingStyle ) const
00808 {
00809     gs.addProperty( "fo:text-align", saveOasisAlignment( (Qt::AlignmentFlags)alignment ).data() );
00810     // Don't save the direction for a style, if "auto", so that the
00811     // auto-determination of direction based on first char, works.
00812     if ( !savingStyle || (QChar::Direction) direction != QChar::DirON )
00813         gs.addProperty( "style:writing-mode", (QChar::Direction)direction == QChar::DirR ? "rl-tb" : "lr-tb" );
00814     gs.addPropertyPt( "fo:margin-left", margins[QStyleSheetItem::MarginLeft] );
00815     gs.addPropertyPt( "fo:margin-right", margins[QStyleSheetItem::MarginRight] );
00816     gs.addPropertyPt( "fo:text-indent", margins[QStyleSheetItem::MarginFirstLine] );
00817     gs.addPropertyPt( "fo:margin-top", margins[QStyleSheetItem::MarginTop] );
00818     gs.addPropertyPt( "fo:margin-bottom", margins[QStyleSheetItem::MarginBottom] );
00819 
00820     switch ( lineSpacingType ) {
00821     case KoParagLayout::LS_SINGLE:
00822         gs.addProperty( "fo:line-height", "100%" );
00823         break;
00824     case KoParagLayout::LS_ONEANDHALF:
00825         gs.addProperty( "fo:line-height", "150%" );
00826         break;
00827     case KoParagLayout::LS_DOUBLE:
00828         gs.addProperty( "fo:line-height", "200%" );
00829         break;
00830     case KoParagLayout::LS_MULTIPLE:
00831         gs.addProperty( "fo:line-height", QString::number( lineSpacing * 100.0 ) + '%' );
00832         break;
00833     case KoParagLayout::LS_FIXED:
00834         gs.addPropertyPt( "fo:line-height", lineSpacing );
00835         break;
00836     case KoParagLayout::LS_CUSTOM:
00837         gs.addPropertyPt( "style:line-spacing", lineSpacing );
00838         break;
00839     case KoParagLayout::LS_AT_LEAST:
00840         gs.addPropertyPt( "style:line-height-at-least", lineSpacing );
00841         break;
00842     }
00843 
00844     QBuffer buffer;
00845     buffer.open( IO_WriteOnly );
00846     KoXmlWriter tabsWriter( &buffer, 4 ); // indent==4: root,autostyle,style,parag-props
00847     tabsWriter.startElement( "style:tab-stops" );
00848     KoTabulatorList::ConstIterator it = m_tabList.begin();
00849     for ( ; it != m_tabList.end() ; it++ )
00850     {
00851         tabsWriter.startElement( "style:tab-stop" );
00852         // Tab stop positions in the XML are relative to the left-margin
00853         double pos = (*it).ptPos - margins[QStyleSheetItem::MarginLeft];
00854         tabsWriter.addAttributePt( "style:position", pos );
00855 
00856         switch ( (*it).type ) {
00857         case T_LEFT:
00858             tabsWriter.addAttribute( "style:type", "left" );
00859             break;
00860         case T_CENTER:
00861             tabsWriter.addAttribute( "style:type", "center" );
00862             break;
00863         case T_RIGHT:
00864             tabsWriter.addAttribute( "style:type", "right" );
00865             break;
00866         case T_DEC_PNT:  // "alignment on decimal point"
00867             tabsWriter.addAttribute( "style:type", "char" );
00868             if ( !(*it).alignChar.isNull() )
00869               tabsWriter.addAttribute( "style:char", QString( (*it).alignChar ) );
00870             break;
00871         case T_INVALID: // keep compiler happy, this can't happen
00872             break;
00873         }
00874         switch( (*it).filling ) {
00875         case TF_BLANK:
00876             tabsWriter.addAttribute( "style:leader-type", "none" );
00877             break;
00878         case TF_LINE:
00879             tabsWriter.addAttribute( "style:leader-type", "single" );
00880             tabsWriter.addAttribute( "style:leader-style", "solid" );
00881             // Give OOo a chance to show something, since it doesn't support lines here.
00882             tabsWriter.addAttribute( "style:leader-text", "_" );
00883             break;
00884         case TF_DOTS:
00885             tabsWriter.addAttribute( "style:leader-type", "single" );
00886             tabsWriter.addAttribute( "style:leader-style", "dotted" );
00887             // Give OOo a chance to show something, since it doesn't support lines here.
00888             tabsWriter.addAttribute( "style:leader-text", "." );
00889             break;
00890         case TF_DASH:
00891             tabsWriter.addAttribute( "style:leader-type", "single" );
00892             tabsWriter.addAttribute( "style:leader-style", "dash" );
00893             // Give OOo a chance to show something, since it doesn't support lines here.
00894             tabsWriter.addAttribute( "style:leader-text", "_" );
00895             break;
00896         case TF_DASH_DOT:
00897             tabsWriter.addAttribute( "style:leader-type", "single" );
00898             tabsWriter.addAttribute( "style:leader-style", "dot-dash" );
00899             // Give OOo a chance to show something, since it doesn't support lines here.
00900             tabsWriter.addAttribute( "style:leader-text", "." );
00901             break;
00902         case TF_DASH_DOT_DOT:
00903             tabsWriter.addAttribute( "style:leader-type", "single" );
00904             tabsWriter.addAttribute( "style:leader-style", "dot-dot-dash" );
00905             // Give OOo a chance to show something, since it doesn't support lines here.
00906             tabsWriter.addAttribute( "style:leader-text", "." );
00907             break;
00908         }
00909         if ( (*it).filling != TF_BLANK )
00910             tabsWriter.addAttributePt( "style:leader-width", (*it).ptWidth );
00911         // If we want to support it, oasis also defines style:leader-color
00912         tabsWriter.endElement();
00913     }
00914     tabsWriter.endElement();
00915     buffer.close();
00916     QString elementContents = QString::fromUtf8( buffer.buffer(), buffer.buffer().size() );
00917     gs.addChildElement( "style:tab-stops", elementContents );
00918 
00919     if ( !joinBorder )
00920         gs.addProperty( "style:join-border", "false" );
00921     bool fourBordersEqual = leftBorder.penWidth() > 0 &&
00922                leftBorder == rightBorder && rightBorder == topBorder && topBorder == bottomBorder;
00923     if ( fourBordersEqual ) {
00924         gs.addProperty( "fo:border", leftBorder.saveFoBorder() );
00925     } else {
00926         if ( leftBorder.penWidth() > 0 )
00927             gs.addProperty( "fo:border-left", leftBorder.saveFoBorder() );
00928         if ( rightBorder.penWidth() > 0 )
00929             gs.addProperty( "fo:border-right", rightBorder.saveFoBorder() );
00930         if ( topBorder.penWidth() > 0 )
00931             gs.addProperty( "fo:border-top", topBorder.saveFoBorder() );
00932         if ( bottomBorder.penWidth() > 0 )
00933             gs.addProperty( "fo:border-bottom", bottomBorder.saveFoBorder() );
00934     }
00935 
00936     if ( pageBreaking & KoParagLayout::HardFrameBreakBefore )
00937         gs.addProperty( "fo:break-before", context.hasColumns() ? "column" : "page" );
00938     else if ( pageBreaking & KoParagLayout::HardFrameBreakAfter )
00939         gs.addProperty( "fo:break-after", context.hasColumns() ? "column" : "page" );
00940     if ( pageBreaking & KoParagLayout::KeepLinesTogether )
00941         gs.addProperty( "fo:keep-together", "always" );
00942     if ( pageBreaking & KoParagLayout::KeepWithNext )
00943         gs.addProperty( "fo:keep-with-next", "always" );
00944 
00945     gs.addProperty( "fo:background-color",
00946                     backgroundColor.isValid() ?
00947                         backgroundColor.name() : "transparent");
00948 }
KDE Home | KDE Accessibility Home | Description of Access Keys