diff options
Diffstat (limited to 'lib/kotext/KoParagLayout.cpp')
-rw-r--r-- | lib/kotext/KoParagLayout.cpp | 948 |
1 files changed, 948 insertions, 0 deletions
diff --git a/lib/kotext/KoParagLayout.cpp b/lib/kotext/KoParagLayout.cpp new file mode 100644 index 000000000..b3c3cf2c5 --- /dev/null +++ b/lib/kotext/KoParagLayout.cpp @@ -0,0 +1,948 @@ +/* This file is part of the KDE project + Copyright (C) 2001 David Faure <faure@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "KoParagLayout.h" +#include "KoRichText.h" +#include "KoParagCounter.h" +#include "KoStyleCollection.h" +#include "KoOasisContext.h" +#include <KoXmlWriter.h> +#include <KoXmlNS.h> +#include <KoDom.h> +#include <KoGenStyles.h> + +#include <kglobal.h> +#include <klocale.h> +#include <kdebug.h> +#include <qdom.h> +#include <qbuffer.h> +#include <qcolor.h> + +#include <float.h> + +QString* KoParagLayout::shadowCssCompat = 0L; + +// Create a default KoParagLayout. +KoParagLayout::KoParagLayout() +{ + initialise(); +} + +void KoParagLayout::operator=( const KoParagLayout &layout ) +{ + alignment = layout.alignment; + for ( int i = 0 ; i < 5 ; ++i ) + margins[i] = layout.margins[i]; + pageBreaking = layout.pageBreaking; + leftBorder = layout.leftBorder; + rightBorder = layout.rightBorder; + topBorder = layout.topBorder; + bottomBorder = layout.bottomBorder; + joinBorder = layout.joinBorder; + backgroundColor = layout.backgroundColor; + if ( layout.counter ) + counter = new KoParagCounter( *layout.counter ); + else + counter = 0L; + lineSpacing = layout.lineSpacing; + lineSpacingType = layout.lineSpacingType; + style = layout.style; + direction = layout.direction; + setTabList( layout.tabList() ); +} + +int KoParagLayout::compare( const KoParagLayout & layout ) const +{ + int flags = 0; + if ( alignment != layout.alignment ) + flags |= Alignment; + for ( int i = 0 ; i < 5 ; ++i ) + if ( margins[i] != layout.margins[i] ) + { + flags |= Margins; + break; + } + if ( pageBreaking != layout.pageBreaking ) + flags |= PageBreaking; + if ( leftBorder != layout.leftBorder + || rightBorder != layout.rightBorder + || topBorder != layout.topBorder + || bottomBorder != layout.bottomBorder + || joinBorder != layout.joinBorder ) + flags |= Borders; + + if ( layout.counter ) + { + if ( counter ) + { + if ( ! ( *layout.counter == *counter ) ) + flags |= BulletNumber; + } else + if ( layout.counter->numbering() != KoParagCounter::NUM_NONE ) + flags |= BulletNumber; + } + else + if ( counter && counter->numbering() != KoParagCounter::NUM_NONE ) + flags |= BulletNumber; + + if ( lineSpacing != layout.lineSpacing + || lineSpacingType != layout.lineSpacingType ) + flags |= LineSpacing; + //if ( style != layout.style ) + // flags |= Style; + if ( m_tabList != layout.m_tabList ) + flags |= Tabulator; + + if ( backgroundColor != layout.backgroundColor) + flags |= BackgroundColor; + + // This method is used for the GUI stuff only, so we don't have a flag + // for the Direction value. + return flags; +} + +void KoParagLayout::initialise() +{ + alignment = Qt::AlignAuto; + for ( int i = 0 ; i < 5 ; ++i ) // use memset ? + margins[i] = 0; + lineSpacingType = LS_SINGLE; + lineSpacing = 0; + counter = 0L; + leftBorder.setPenWidth( 0); + rightBorder.setPenWidth( 0); + topBorder.setPenWidth( 0); + bottomBorder.setPenWidth( 0); + joinBorder = true; + pageBreaking = 0; + style = 0L; + direction = QChar::DirON; + m_tabList.clear(); +} + +KoParagLayout::~KoParagLayout() +{ + delete counter; +} + +void KoParagLayout::loadParagLayout( KoParagLayout& layout, const QDomElement& parentElem, int docVersion ) +{ + // layout is an input and output parameter + // It can have been initialized already, e.g. by copying from a style + // (we don't do that anymore though). + + // Load the paragraph tabs - we load into a clean list, not mixing with those already in "layout" + // We can't apply the 'default comes from the style' in this case, because + // there is no way to differentiate between "I want no tabs in the parag" + // and "use default from style". + KoTabulatorList tabList; + QDomElement element = parentElem.firstChild().toElement(); + for ( ; !element.isNull() ; element = element.nextSibling().toElement() ) + { + if ( element.tagName() == "TABULATOR" ) + { + KoTabulator tab; + tab.type = static_cast<KoTabulators>( getAttribute( element, "type", T_LEFT ) ); + tab.ptPos = getAttribute( element, "ptpos", 0.0 ); + tab.filling = static_cast<KoTabulatorFilling>( getAttribute( element, "filling", TF_BLANK ) ); + tab.ptWidth = getAttribute( element, "width", 0.5 ); + QString alignCharStr = element.attribute("alignchar"); + if ( alignCharStr.isEmpty() ) + tab.alignChar = KGlobal::locale()->decimalSymbol()[0]; + else + tab.alignChar = alignCharStr[0]; + tabList.append( tab ); + } + } + qHeapSort( tabList ); + layout.setTabList( tabList ); + layout.alignment = Qt::AlignAuto; + element = parentElem.namedItem( "FLOW" ).toElement(); // Flow is what is now called alignment internally + if ( !element.isNull() ) + { + QString flow = element.attribute( "align" ); // KWord-1.0 DTD + if ( !flow.isEmpty() ) + { + layout.alignment = flow=="right" ? Qt::AlignRight : + flow=="center" ? Qt::AlignHCenter : + flow=="justify" ? Qt::AlignJustify : + flow=="left" ? Qt::AlignLeft : Qt::AlignAuto; + + QString dir = element.attribute( "dir" ); // KWord-1.2 + if ( !dir.isEmpty() ) { + if ( dir == "L" ) + layout.direction = QChar::DirL; + else if ( dir == "R" ) + layout.direction = QChar::DirR; + else + kdWarning() << "Unexpected value for paragraph direction: " << dir << endl; + } + } else { + flow = element.attribute( "value" ); // KWord-0.8 + static const int flow2align[] = { Qt::AlignAuto, Qt::AlignRight, Qt::AlignHCenter, Qt::AlignJustify }; + if ( !flow.isEmpty() && flow.toInt() < 4 ) + layout.alignment = flow2align[flow.toInt()]; + } + } + + if ( docVersion < 2 ) + { + element = parentElem.namedItem( "OHEAD" ).toElement(); // used by KWord-0.8 + if ( !element.isNull() ) + layout.margins[QStyleSheetItem::MarginTop] = getAttribute( element, "pt", 0.0 ); + + element = parentElem.namedItem( "OFOOT" ).toElement(); // used by KWord-0.8 + if ( !element.isNull() ) + layout.margins[QStyleSheetItem::MarginBottom] = getAttribute( element, "pt", 0.0 ); + + element = parentElem.namedItem( "IFIRST" ).toElement(); // used by KWord-0.8 + if ( !element.isNull() ) + layout.margins[QStyleSheetItem::MarginFirstLine] = getAttribute( element, "pt", 0.0 ); + + element = parentElem.namedItem( "ILEFT" ).toElement(); // used by KWord-0.8 + if ( !element.isNull() ) + layout.margins[QStyleSheetItem::MarginLeft] = getAttribute( element, "pt", 0.0 ); + } + + // KWord-1.0 DTD + element = parentElem.namedItem( "INDENTS" ).toElement(); + if ( !element.isNull() ) + { + layout.margins[QStyleSheetItem::MarginFirstLine] = getAttribute( element, "first", 0.0 ); + layout.margins[QStyleSheetItem::MarginLeft] = getAttribute( element, "left", 0.0 ); + layout.margins[QStyleSheetItem::MarginRight] = getAttribute( element, "right", 0.0 ); + } + element = parentElem.namedItem( "OFFSETS" ).toElement(); + if ( !element.isNull() ) + { + layout.margins[QStyleSheetItem::MarginTop] = getAttribute( element, "before", 0.0 ); + layout.margins[QStyleSheetItem::MarginBottom] = getAttribute( element, "after", 0.0 ); + } + + if ( docVersion < 2 ) + { + element = parentElem.namedItem( "LINESPACE" ).toElement(); // used by KWord-0.8 + if ( !element.isNull() ) + { + layout.lineSpacingType = KoParagLayout::LS_CUSTOM; + layout.lineSpacing = getAttribute( element, "pt", 0.0 ); + } + } + + element = parentElem.namedItem( "LINESPACING" ).toElement(); // KWord-1.0 DTD + if ( !element.isNull() ) + { + //compatibility with koffice 1.1 + if ( element.hasAttribute( "value" )) + { + QString value = element.attribute( "value" ); + if ( value == "oneandhalf" ) + { + layout.lineSpacingType = KoParagLayout::LS_ONEANDHALF; + layout.lineSpacing = 0; + } + else if ( value == "double" ) + { + layout.lineSpacingType = KoParagLayout::LS_DOUBLE; + layout.lineSpacing = 0; + } + else + { + layout.lineSpacingType = KoParagLayout::LS_CUSTOM; + layout.lineSpacing = value.toDouble(); + } + } + else + { + QString type = element.attribute( "type" ); + if ( type == "oneandhalf" ) + { + layout.lineSpacingType = KoParagLayout::LS_ONEANDHALF; + layout.lineSpacing = 0; + } + else if ( type == "double" ) + { + layout.lineSpacingType = KoParagLayout::LS_DOUBLE; + layout.lineSpacing = 0; + } + else if ( type == "custom" ) + { + layout.lineSpacingType = KoParagLayout::LS_CUSTOM; + layout.lineSpacing = element.attribute( "spacingvalue" ).toDouble(); + } + else if ( type == "atleast" ) + { + layout.lineSpacingType = KoParagLayout::LS_AT_LEAST; + layout.lineSpacing = element.attribute( "spacingvalue" ).toDouble(); + } + else if ( type == "multiple" ) + { + layout.lineSpacingType = KoParagLayout::LS_MULTIPLE; + layout.lineSpacing = element.attribute( "spacingvalue" ).toDouble(); + } + else if ( type == "fixed" ) + { + layout.lineSpacingType = KoParagLayout::LS_FIXED; + layout.lineSpacing = element.attribute( "spacingvalue" ).toDouble(); + } + else if ( type == "single" ) // not used; just in case future versions use it. + layout.lineSpacingType = KoParagLayout::LS_SINGLE; + } + } + + int pageBreaking = 0; + element = parentElem.namedItem( "PAGEBREAKING" ).toElement(); + if ( !element.isNull() ) + { + if ( element.attribute( "linesTogether" ) == "true" ) + pageBreaking |= KoParagLayout::KeepLinesTogether; + if ( element.attribute( "hardFrameBreak" ) == "true" ) + pageBreaking |= KoParagLayout::HardFrameBreakBefore; + if ( element.attribute( "hardFrameBreakAfter" ) == "true" ) + pageBreaking |= KoParagLayout::HardFrameBreakAfter; + } + if ( docVersion < 2 ) + { + element = parentElem.namedItem( "HARDBRK" ).toElement(); // KWord-0.8 + if ( !element.isNull() ) + pageBreaking |= KoParagLayout::HardFrameBreakBefore; + } + layout.pageBreaking = pageBreaking; + + element = parentElem.namedItem( "LEFTBORDER" ).toElement(); + if ( !element.isNull() ) + layout.leftBorder = KoBorder::loadBorder( element ); + else + layout.leftBorder.setPenWidth(0); + + element = parentElem.namedItem( "RIGHTBORDER" ).toElement(); + if ( !element.isNull() ) + layout.rightBorder = KoBorder::loadBorder( element ); + else + layout.rightBorder.setPenWidth(0); + + element = parentElem.namedItem( "TOPBORDER" ).toElement(); + if ( !element.isNull() ) + layout.topBorder = KoBorder::loadBorder( element ); + else + layout.topBorder.setPenWidth(0); + + element = parentElem.namedItem( "BOTTOMBORDER" ).toElement(); + if ( !element.isNull() ) + layout.bottomBorder = KoBorder::loadBorder( element ); + else + layout.bottomBorder.setPenWidth(0); + + element = parentElem.namedItem( "COUNTER" ).toElement(); + if ( !element.isNull() ) + { + layout.counter = new KoParagCounter; + layout.counter->load( element ); + } + + // Compatibility with KOffice-1.2 + element = parentElem.namedItem( "SHADOW" ).toElement(); + if ( !element.isNull() && element.hasAttribute("direction") ) + { + int shadowDistance = element.attribute("distance").toInt(); + int shadowDirection = element.attribute("direction").toInt(); + QColor shadowColor; + if ( element.hasAttribute("red") ) + { + int r = element.attribute("red").toInt(); + int g = element.attribute("green").toInt(); + int b = element.attribute("blue").toInt(); + shadowColor.setRgb( r, g, b ); + } + int distanceX = 0; + int distanceY = 0; + switch ( shadowDirection ) + { + case 1: // KoParagLayout::SD_LEFT_UP: + case 2: // KoParagLayout::SD_UP: + case 3: // KoParagLayout::SD_RIGHT_UP: + distanceX = - shadowDistance; + break; + case 7: // KoParagLayout::SD_LEFT_BOTTOM: + case 6: // KoParagLayout::SD_BOTTOM: + case 5: // KoParagLayout::SD_RIGHT_BOTTOM: + distanceX = shadowDistance; + break; + } + switch ( shadowDirection ) + { + case 7: // KoParagLayout::SD_LEFT_BOTTOM: + case 8: // KoParagLayout::SD_LEFT: + case 1: //KoParagLayout::SD_LEFT_UP: + distanceY = - shadowDistance; + break; + case 3: // KoParagLayout::SD_RIGHT_UP: + case 4: // KoParagLayout::SD_RIGHT: + case 5: // KoParagLayout::SD_RIGHT_BOTTOM: + distanceY = shadowDistance; + break; + } + if ( !shadowCssCompat ) + shadowCssCompat = new QString; + *shadowCssCompat = KoTextFormat::shadowAsCss( distanceX, distanceY, shadowColor ); + kdDebug(32500) << "setting shadow compat to " << ( *shadowCssCompat ) << endl; + } + else + { + delete shadowCssCompat; + shadowCssCompat = 0L; + } +} + +//static +Qt::AlignmentFlags KoParagLayout::loadOasisAlignment( const QCString& str ) +{ + return + str == "left" ? Qt::AlignLeft : + str == "right" ? Qt::AlignRight : + str == "start" ? Qt::AlignLeft : + str == "end" ? Qt::AlignRight : + str == "center" ? Qt::AlignHCenter : + str == "justify" ? Qt::AlignJustify : + str == "start" ? Qt::AlignAuto // i.e. direction-dependent + : Qt::AlignAuto; // default (can't happen unless spec is extended) +} + +//static +QCString KoParagLayout::saveOasisAlignment( Qt::AlignmentFlags alignment ) +{ + return alignment == Qt::AlignLeft ? "left" : + alignment == Qt::AlignRight ? "right" : + alignment == Qt::AlignHCenter ? "center" : + alignment == Qt::AlignJustify ? "justify" : + "start"; // i.e. direction-dependent +} + +void KoParagLayout::loadOasisParagLayout( KoParagLayout& layout, KoOasisContext& context ) +{ + context.styleStack().setTypeProperties( "paragraph" ); + // layout is an input and output parameter + // It can have been initialized already, e.g. by copying from a style + + // code from OoWriterImport::writeLayout + if ( context.styleStack().hasAttributeNS( KoXmlNS::fo, "text-align" ) ) { + QCString align = context.styleStack().attributeNS( KoXmlNS::fo, "text-align" ).latin1(); + layout.alignment = loadOasisAlignment( align ); + } + + if ( context.styleStack().hasAttributeNS( KoXmlNS::style, "writing-mode" ) ) { // http://web4.w3.org/TR/xsl/slice7.html#writing-mode + // LTR is lr-tb. RTL is rl-tb + QString writingMode = context.styleStack().attributeNS( KoXmlNS::style, "writing-mode" ); + layout.direction = ( writingMode=="rl-tb" || writingMode=="rl" ) ? QChar::DirR : QChar::DirL; + } + + // Indentation (margins) + if ( context.styleStack().hasAttributeNS( KoXmlNS::fo, "margin-left" ) || // 3.11.19 + context.styleStack().hasAttributeNS( KoXmlNS::fo, "margin-right" ) ) { + layout.margins[QStyleSheetItem::MarginLeft] = KoUnit::parseValue( context.styleStack().attributeNS( KoXmlNS::fo, "margin-left" ) ); + layout.margins[QStyleSheetItem::MarginRight] = KoUnit::parseValue( context.styleStack().attributeNS( KoXmlNS::fo, "margin-right" ) ); + // *text-indent must always be bound to either margin-left or margin-right + double first = 0; + if ( context.styleStack().attributeNS( KoXmlNS::style, "auto-text-indent") == "true" ) // style:auto-text-indent takes precedence + // ### "indented by a value that is based on the current font size" + // ### and "requires margin-left and margin-right + // ### but how much is the indent? + first = 10; + else if ( context.styleStack().hasAttributeNS( KoXmlNS::fo, "text-indent") ) + first = KoUnit::parseValue( context.styleStack().attributeNS( KoXmlNS::fo, "text-indent") ); + + layout.margins[QStyleSheetItem::MarginFirstLine] = first; + } + + // Offset before and after paragraph + if( context.styleStack().hasAttributeNS( KoXmlNS::fo, "margin-top") || // 3.11.22 + context.styleStack().hasAttributeNS( KoXmlNS::fo, "margin-bottom")) { + layout.margins[QStyleSheetItem::MarginTop] = KoUnit::parseValue( context.styleStack().attributeNS( KoXmlNS::fo, "margin-top" ) ); + layout.margins[QStyleSheetItem::MarginBottom] = KoUnit::parseValue( context.styleStack().attributeNS( KoXmlNS::fo, "margin-bottom" ) ); + } + + // Line spacing + if( context.styleStack().hasAttributeNS( KoXmlNS::fo, "line-height") ) { // 3.11.1 + // Fixed line height + QString value = context.styleStack().attributeNS( KoXmlNS::fo, "line-height" ); + if ( value != "normal" ) { + if ( value == "100%" ) + layout.lineSpacingType = KoParagLayout::LS_SINGLE; + else if( value=="150%") + layout.lineSpacingType = KoParagLayout::LS_ONEANDHALF; + else if( value=="200%") + layout.lineSpacingType = KoParagLayout::LS_DOUBLE; + else if ( value.find('%') > -1 ) + { + value = value.remove( '%' ); + double percent = value.toDouble(); + layout.lineSpacingType = KoParagLayout::LS_MULTIPLE; + layout.lineSpacing = percent / 100.0; + kdDebug(33001) << "line-height =" << percent << ", " << layout.lineSpacing << ", " << percent/100 << endl; + } + else // fixed value + { + layout.lineSpacingType = KoParagLayout::LS_FIXED; + layout.lineSpacing = KoUnit::parseValue( value ); + } + } + } + // Line-height-at-least is mutually exclusive with line-height + else if ( context.styleStack().hasAttributeNS( KoXmlNS::style, "line-height-at-least") ) // 3.11.2 + { + QString value = context.styleStack().attributeNS( KoXmlNS::style, "line-height-at-least" ); + // kotext has "at least" but that's for the linespacing, not for the entire line height! + // Strange. kotext also has "at least" for the whole line height.... + // Did we make the wrong choice in kotext? + //kdWarning() << "Unimplemented support for style:line-height-at-least: " << value << endl; + // Well let's see if this makes a big difference. + layout.lineSpacingType = KoParagLayout::LS_AT_LEAST; + layout.lineSpacing = KoUnit::parseValue( value ); + } + // Line-spacing is mutually exclusive with line-height and line-height-at-least + else if ( context.styleStack().hasAttributeNS( KoXmlNS::style, "line-spacing") ) // 3.11.3 + { + double value = KoUnit::parseValue( context.styleStack().attributeNS( KoXmlNS::style, "line-spacing" ) ); + if ( value != 0.0 ) + { + layout.lineSpacingType = KoParagLayout::LS_CUSTOM; + layout.lineSpacing = value; + } + } + + // Tabulators + KoTabulatorList tabList; + if ( context.styleStack().hasChildNodeNS( KoXmlNS::style, "tab-stops" ) ) { // 3.11.10 + QDomElement tabStops = context.styleStack().childNodeNS( KoXmlNS::style, "tab-stops" ); + //kdDebug(30519) << k_funcinfo << tabStops.childNodes().count() << " tab stops in layout." << endl; + QDomElement tabStop; + forEachElement( tabStop, tabStops ) + { + Q_ASSERT( tabStop.localName() == "tab-stop" ); + const QString type = tabStop.attributeNS( KoXmlNS::style, "type", QString::null ); // left, right, center or char + + KoTabulator tab; + tab.ptPos = KoUnit::parseValue( tabStop.attributeNS( KoXmlNS::style, "position", QString::null ) ); + // Tab stop positions in the XML are relative to the left-margin + tab.ptPos += layout.margins[QStyleSheetItem::MarginLeft]; + if ( type == "center" ) + tab.type = T_CENTER; + else if ( type == "right" ) + tab.type = T_RIGHT; + else if ( type == "char" ) { + QString delimiterChar = tabStop.attributeNS( KoXmlNS::style, "char", QString::null ); // single character + if ( !delimiterChar.isEmpty() ) + tab.alignChar = delimiterChar[0]; + tab.type = T_DEC_PNT; // "alignment on decimal point" + } + else //if ( type == "left" ) + tab.type = T_LEFT; + + tab.ptWidth = KoUnit::parseValue( tabStop.attributeNS( KoXmlNS::style, "leader-width", QString::null ), 0.5 ); + + tab.filling = TF_BLANK; + if ( tabStop.attributeNS( KoXmlNS::style, "leader-type", QString::null ) == "single" ) + { + QString leaderStyle = tabStop.attributeNS( KoXmlNS::style, "leader-style", QString::null ); + if ( leaderStyle == "solid" ) + tab.filling = TF_LINE; + else if ( leaderStyle == "dotted" ) + tab.filling = TF_DOTS; + else if ( leaderStyle == "dash" ) + tab.filling = TF_DASH; + else if ( leaderStyle == "dot-dash" ) + tab.filling = TF_DASH_DOT; + else if ( leaderStyle == "dot-dot-dash" ) + tab.filling = TF_DASH_DOT_DOT; + } + else + { + // Fallback: convert leaderChar's unicode value + QString leaderChar = tabStop.attributeNS( KoXmlNS::style, "leader-text", QString::null ); + if ( !leaderChar.isEmpty() ) + { + QChar ch = leaderChar[0]; + switch (ch.latin1()) { + case '.': + tab.filling = TF_DOTS; break; + case '-': + case '_': // TODO in KWord: differentiate --- and ___ + tab.filling = TF_LINE; break; + default: + // KWord doesn't have support for "any char" as filling. + break; + } + } + } + tabList.append( tab ); + } //for + } + qHeapSort( tabList ); + layout.setTabList( tabList ); + + layout.joinBorder = !( context.styleStack().attributeNS( KoXmlNS::style, "join-border") == "false" ); + + // Borders + if ( context.styleStack().hasAttributeNS( KoXmlNS::fo, "border","left") ) + layout.leftBorder.loadFoBorder( context.styleStack().attributeNS( KoXmlNS::fo, "border","left") ); + else + layout.leftBorder.setPenWidth(0); + if ( context.styleStack().hasAttributeNS( KoXmlNS::fo, "border","right") ) + layout.rightBorder.loadFoBorder( context.styleStack().attributeNS( KoXmlNS::fo, "border","right") ); + else + layout.rightBorder.setPenWidth(0); + if ( context.styleStack().hasAttributeNS( KoXmlNS::fo, "border","top") ) + layout.topBorder.loadFoBorder( context.styleStack().attributeNS( KoXmlNS::fo, "border","top") ); + else + layout.topBorder.setPenWidth(0); + if ( context.styleStack().hasAttributeNS( KoXmlNS::fo, "border","bottom") ) + layout.bottomBorder.loadFoBorder( context.styleStack().attributeNS( KoXmlNS::fo, "border","bottom") ); + else + layout.bottomBorder.setPenWidth(0); + + + // Page breaking + int pageBreaking = 0; + if( context.styleStack().hasAttributeNS( KoXmlNS::fo, "break-before") || + context.styleStack().hasAttributeNS( KoXmlNS::fo, "break-after") || + context.styleStack().hasAttributeNS( KoXmlNS::fo, "keep-together") || + context.styleStack().hasAttributeNS( KoXmlNS::style, "keep-with-next") || + context.styleStack().hasAttributeNS( KoXmlNS::fo, "keep-with-next") ) + { + if ( context.styleStack().hasAttributeNS( KoXmlNS::fo, "break-before") ) { // 3.11.24 + // TODO in KWord: implement difference between "column" and "page" + if ( context.styleStack().attributeNS( KoXmlNS::fo, "break-before" ) != "auto" ) + pageBreaking |= KoParagLayout::HardFrameBreakBefore; + } + else if ( context.styleStack().hasAttributeNS( KoXmlNS::fo, "break-after") ) { // 3.11.24 + // TODO in KWord: implement difference between "column" and "page" + if ( context.styleStack().attributeNS( KoXmlNS::fo, "break-after" ) != "auto" ) + pageBreaking |= KoParagLayout::HardFrameBreakAfter; + } + + if ( context.styleStack().hasAttributeNS( KoXmlNS::fo, "keep-together" ) ) { // was style:break-inside in OOo-1.1, renamed in OASIS + if ( context.styleStack().attributeNS( KoXmlNS::fo, "keep-together" ) != "auto" ) + pageBreaking |= KoParagLayout::KeepLinesTogether; + } + if ( context.styleStack().hasAttributeNS( KoXmlNS::fo, "keep-with-next" ) ) { + // OASIS spec says it's "auto"/"always", not a boolean. + QString val = context.styleStack().attributeNS( KoXmlNS::fo, "keep-with-next" ); + if ( val == "true" || val == "always" ) + pageBreaking |= KoParagLayout::KeepWithNext; + } + } + layout.pageBreaking = pageBreaking; + + // Paragraph background color - fo:background-color + // The background color for parts of a paragraph that have no text underneath + if ( context.styleStack().hasAttributeNS( KoXmlNS::fo, "background-color" ) ) { + QString bgColor = context.styleStack().attributeNS( KoXmlNS::fo, "background-color"); + if (bgColor != "transparent") + layout.backgroundColor.setNamedColor( bgColor ); + } +} + +void KoParagLayout::saveParagLayout( QDomElement & parentElem, int alignment ) const +{ + const KoParagLayout& layout = *this; // code moved from somewhere else;) + QDomDocument doc = parentElem.ownerDocument(); + QDomElement element = doc.createElement( "NAME" ); + parentElem.appendChild( element ); + if ( layout.style ) + element.setAttribute( "value", layout.style->displayName() ); + //else + // kdWarning() << "KoParagLayout::saveParagLayout: style==0!" << endl; + + element = doc.createElement( "FLOW" ); + parentElem.appendChild( element ); + + element.setAttribute( "align", alignment==Qt::AlignRight ? "right" : + alignment==Qt::AlignHCenter ? "center" : + alignment==Qt::AlignJustify ? "justify" : + alignment==Qt::AlignAuto ? "auto" : "left" ); // Note: styles can have AlignAuto. Not paragraphs. + + if ( static_cast<QChar::Direction>(layout.direction) == QChar::DirR ) + element.setAttribute( "dir", "R" ); + else + if ( static_cast<QChar::Direction>(layout.direction) == QChar::DirL ) + element.setAttribute( "dir", "L" ); + + if ( layout.margins[QStyleSheetItem::MarginFirstLine] != 0 || + layout.margins[QStyleSheetItem::MarginLeft] != 0 || + layout.margins[QStyleSheetItem::MarginRight] != 0 ) + { + element = doc.createElement( "INDENTS" ); + parentElem.appendChild( element ); + if ( layout.margins[QStyleSheetItem::MarginFirstLine] != 0 ) + element.setAttribute( "first", layout.margins[QStyleSheetItem::MarginFirstLine] ); + if ( layout.margins[QStyleSheetItem::MarginLeft] != 0 ) + element.setAttribute( "left", layout.margins[QStyleSheetItem::MarginLeft] ); + if ( layout.margins[QStyleSheetItem::MarginRight] != 0 ) + element.setAttribute( "right", layout.margins[QStyleSheetItem::MarginRight] ); + } + + if ( layout.margins[QStyleSheetItem::MarginTop] != 0 || + layout.margins[QStyleSheetItem::MarginBottom] != 0 ) + { + element = doc.createElement( "OFFSETS" ); + parentElem.appendChild( element ); + if ( layout.margins[QStyleSheetItem::MarginTop] != 0 ) + element.setAttribute( "before", layout.margins[QStyleSheetItem::MarginTop] ); + if ( layout.margins[QStyleSheetItem::MarginBottom] != 0 ) + element.setAttribute( "after", layout.margins[QStyleSheetItem::MarginBottom] ); + } + if ( layout.lineSpacingType != LS_SINGLE ) + { + element = doc.createElement( "LINESPACING" ); + parentElem.appendChild( element ); + if ( layout.lineSpacingType == KoParagLayout::LS_ONEANDHALF ) { + element.setAttribute( "type", "oneandhalf" ); + element.setAttribute( "value", "oneandhalf" ); //compatibility with koffice 1.2 + } + else if ( layout.lineSpacingType == KoParagLayout::LS_DOUBLE ) { + element.setAttribute( "type", "double" ); + element.setAttribute( "value", "double" ); //compatibility with koffice 1.2 + } + else if ( layout.lineSpacingType == KoParagLayout::LS_CUSTOM ) + { + element.setAttribute( "type", "custom" ); + element.setAttribute( "spacingvalue", layout.lineSpacing); + element.setAttribute( "value", layout.lineSpacing ); //compatibility with koffice 1.2 + } + else if ( layout.lineSpacingType == KoParagLayout::LS_AT_LEAST ) + { + element.setAttribute( "type", "atleast" ); + element.setAttribute( "spacingvalue", layout.lineSpacing); + } + else if ( layout.lineSpacingType == KoParagLayout::LS_MULTIPLE ) + { + element.setAttribute( "type", "multiple" ); + element.setAttribute( "spacingvalue", layout.lineSpacing); + } + else if ( layout.lineSpacingType == KoParagLayout::LS_FIXED ) + { + element.setAttribute( "type", "fixed" ); + element.setAttribute( "spacingvalue", layout.lineSpacing); + } + else + kdDebug()<<" error in lineSpacing Type\n"; + } + + if ( layout.pageBreaking != 0 ) + { + element = doc.createElement( "PAGEBREAKING" ); + parentElem.appendChild( element ); + if ( layout.pageBreaking & KoParagLayout::KeepLinesTogether ) + element.setAttribute( "linesTogether", "true" ); + if ( layout.pageBreaking & KoParagLayout::HardFrameBreakBefore ) + element.setAttribute( "hardFrameBreak", "true" ); + if ( layout.pageBreaking & KoParagLayout::HardFrameBreakAfter ) + element.setAttribute( "hardFrameBreakAfter", "true" ); + } + + if ( layout.leftBorder.penWidth() > 0 ) + { + element = doc.createElement( "LEFTBORDER" ); + parentElem.appendChild( element ); + layout.leftBorder.save( element ); + } + if ( layout.rightBorder.penWidth() > 0 ) + { + element = doc.createElement( "RIGHTBORDER" ); + parentElem.appendChild( element ); + layout.rightBorder.save( element ); + } + if ( layout.topBorder.penWidth() > 0 ) + { + element = doc.createElement( "TOPBORDER" ); + parentElem.appendChild( element ); + layout.topBorder.save( element ); + } + if ( layout.bottomBorder.penWidth() > 0 ) + { + element = doc.createElement( "BOTTOMBORDER" ); + parentElem.appendChild( element ); + layout.bottomBorder.save( element ); + } + if ( layout.counter && layout.counter->numbering() != KoParagCounter::NUM_NONE ) + { + element = doc.createElement( "COUNTER" ); + parentElem.appendChild( element ); + if ( layout.counter ) + layout.counter->save( element ); + } + + KoTabulatorList tabList = layout.tabList(); + KoTabulatorList::ConstIterator it = tabList.begin(); + for ( ; it != tabList.end() ; it++ ) + { + element = doc.createElement( "TABULATOR" ); + parentElem.appendChild( element ); + element.setAttribute( "type", (*it).type ); + element.setAttribute( "ptpos", (*it).ptPos ); + element.setAttribute( "filling", (*it).filling ); + if ( (*it).filling != TF_BLANK ) + element.setAttribute( "width", QString::number( (*it).ptWidth, 'g', DBL_DIG ) ); + if ( (*it).type == T_DEC_PNT && !(*it).alignChar.isNull() ) + element.setAttribute( "alignchar", QString((*it).alignChar) ); + } +} + +void KoParagLayout::saveOasis( KoGenStyle& gs, KoSavingContext& context, bool savingStyle ) const +{ + gs.addProperty( "fo:text-align", saveOasisAlignment( (Qt::AlignmentFlags)alignment ).data() ); + // Don't save the direction for a style, if "auto", so that the + // auto-determination of direction based on first char, works. + if ( !savingStyle || (QChar::Direction) direction != QChar::DirON ) + gs.addProperty( "style:writing-mode", (QChar::Direction)direction == QChar::DirR ? "rl-tb" : "lr-tb" ); + gs.addPropertyPt( "fo:margin-left", margins[QStyleSheetItem::MarginLeft] ); + gs.addPropertyPt( "fo:margin-right", margins[QStyleSheetItem::MarginRight] ); + gs.addPropertyPt( "fo:text-indent", margins[QStyleSheetItem::MarginFirstLine] ); + gs.addPropertyPt( "fo:margin-top", margins[QStyleSheetItem::MarginTop] ); + gs.addPropertyPt( "fo:margin-bottom", margins[QStyleSheetItem::MarginBottom] ); + + switch ( lineSpacingType ) { + case KoParagLayout::LS_SINGLE: + gs.addProperty( "fo:line-height", "100%" ); + break; + case KoParagLayout::LS_ONEANDHALF: + gs.addProperty( "fo:line-height", "150%" ); + break; + case KoParagLayout::LS_DOUBLE: + gs.addProperty( "fo:line-height", "200%" ); + break; + case KoParagLayout::LS_MULTIPLE: + gs.addProperty( "fo:line-height", QString::number( lineSpacing * 100.0 ) + '%' ); + break; + case KoParagLayout::LS_FIXED: + gs.addPropertyPt( "fo:line-height", lineSpacing ); + break; + case KoParagLayout::LS_CUSTOM: + gs.addPropertyPt( "style:line-spacing", lineSpacing ); + break; + case KoParagLayout::LS_AT_LEAST: + gs.addPropertyPt( "style:line-height-at-least", lineSpacing ); + break; + } + + QBuffer buffer; + buffer.open( IO_WriteOnly ); + KoXmlWriter tabsWriter( &buffer, 4 ); // indent==4: root,autostyle,style,parag-props + tabsWriter.startElement( "style:tab-stops" ); + KoTabulatorList::ConstIterator it = m_tabList.begin(); + for ( ; it != m_tabList.end() ; it++ ) + { + tabsWriter.startElement( "style:tab-stop" ); + // Tab stop positions in the XML are relative to the left-margin + double pos = (*it).ptPos - margins[QStyleSheetItem::MarginLeft]; + tabsWriter.addAttributePt( "style:position", pos ); + + switch ( (*it).type ) { + case T_LEFT: + tabsWriter.addAttribute( "style:type", "left" ); + break; + case T_CENTER: + tabsWriter.addAttribute( "style:type", "center" ); + break; + case T_RIGHT: + tabsWriter.addAttribute( "style:type", "right" ); + break; + case T_DEC_PNT: // "alignment on decimal point" + tabsWriter.addAttribute( "style:type", "char" ); + if ( !(*it).alignChar.isNull() ) + tabsWriter.addAttribute( "style:char", QString( (*it).alignChar ) ); + break; + case T_INVALID: // keep compiler happy, this can't happen + break; + } + switch( (*it).filling ) { + case TF_BLANK: + tabsWriter.addAttribute( "style:leader-type", "none" ); + break; + case TF_LINE: + tabsWriter.addAttribute( "style:leader-type", "single" ); + tabsWriter.addAttribute( "style:leader-style", "solid" ); + // Give OOo a chance to show something, since it doesn't support lines here. + tabsWriter.addAttribute( "style:leader-text", "_" ); + break; + case TF_DOTS: + tabsWriter.addAttribute( "style:leader-type", "single" ); + tabsWriter.addAttribute( "style:leader-style", "dotted" ); + // Give OOo a chance to show something, since it doesn't support lines here. + tabsWriter.addAttribute( "style:leader-text", "." ); + break; + case TF_DASH: + tabsWriter.addAttribute( "style:leader-type", "single" ); + tabsWriter.addAttribute( "style:leader-style", "dash" ); + // Give OOo a chance to show something, since it doesn't support lines here. + tabsWriter.addAttribute( "style:leader-text", "_" ); + break; + case TF_DASH_DOT: + tabsWriter.addAttribute( "style:leader-type", "single" ); + tabsWriter.addAttribute( "style:leader-style", "dot-dash" ); + // Give OOo a chance to show something, since it doesn't support lines here. + tabsWriter.addAttribute( "style:leader-text", "." ); + break; + case TF_DASH_DOT_DOT: + tabsWriter.addAttribute( "style:leader-type", "single" ); + tabsWriter.addAttribute( "style:leader-style", "dot-dot-dash" ); + // Give OOo a chance to show something, since it doesn't support lines here. + tabsWriter.addAttribute( "style:leader-text", "." ); + break; + } + if ( (*it).filling != TF_BLANK ) + tabsWriter.addAttributePt( "style:leader-width", (*it).ptWidth ); + // If we want to support it, oasis also defines style:leader-color + tabsWriter.endElement(); + } + tabsWriter.endElement(); + buffer.close(); + QString elementContents = QString::fromUtf8( buffer.buffer(), buffer.buffer().size() ); + gs.addChildElement( "style:tab-stops", elementContents ); + + if ( !joinBorder ) + gs.addProperty( "style:join-border", "false" ); + bool fourBordersEqual = leftBorder.penWidth() > 0 && + leftBorder == rightBorder && rightBorder == topBorder && topBorder == bottomBorder; + if ( fourBordersEqual ) { + gs.addProperty( "fo:border", leftBorder.saveFoBorder() ); + } else { + if ( leftBorder.penWidth() > 0 ) + gs.addProperty( "fo:border-left", leftBorder.saveFoBorder() ); + if ( rightBorder.penWidth() > 0 ) + gs.addProperty( "fo:border-right", rightBorder.saveFoBorder() ); + if ( topBorder.penWidth() > 0 ) + gs.addProperty( "fo:border-top", topBorder.saveFoBorder() ); + if ( bottomBorder.penWidth() > 0 ) + gs.addProperty( "fo:border-bottom", bottomBorder.saveFoBorder() ); + } + + if ( pageBreaking & KoParagLayout::HardFrameBreakBefore ) + gs.addProperty( "fo:break-before", context.hasColumns() ? "column" : "page" ); + else if ( pageBreaking & KoParagLayout::HardFrameBreakAfter ) + gs.addProperty( "fo:break-after", context.hasColumns() ? "column" : "page" ); + if ( pageBreaking & KoParagLayout::KeepLinesTogether ) + gs.addProperty( "fo:keep-together", "always" ); + if ( pageBreaking & KoParagLayout::KeepWithNext ) + gs.addProperty( "fo:keep-with-next", "always" ); + + gs.addProperty( "fo:background-color", + backgroundColor.isValid() ? + backgroundColor.name() : "transparent"); +} |