diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /writerfilter/source/dmapper/PropertyMap.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'writerfilter/source/dmapper/PropertyMap.cxx')
-rw-r--r-- | writerfilter/source/dmapper/PropertyMap.cxx | 2219 |
1 files changed, 2219 insertions, 0 deletions
diff --git a/writerfilter/source/dmapper/PropertyMap.cxx b/writerfilter/source/dmapper/PropertyMap.cxx new file mode 100644 index 000000000..1ae28759d --- /dev/null +++ b/writerfilter/source/dmapper/PropertyMap.cxx @@ -0,0 +1,2219 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <string_view> + +#include "PropertyMap.hxx" +#include "TagLogger.hxx" +#include <ooxml/resourceids.hxx> +#include "DomainMapper_Impl.hxx" +#include "ConversionHelper.hxx" +#include <editeng/boxitem.hxx> +#include <i18nutil/paper.hxx> +#include <osl/diagnose.h> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/XMultiPropertySet.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/table/BorderLine2.hpp> +#include <com/sun/star/container/XEnumeration.hpp> +#include <com/sun/star/container/XEnumerationAccess.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/style/BreakType.hpp> +#include <com/sun/star/style/PageStyleLayout.hpp> +#include <com/sun/star/style/XStyleFamiliesSupplier.hpp> +#include <com/sun/star/table/ShadowFormat.hpp> +#include <com/sun/star/text/RelOrientation.hpp> +#include <com/sun/star/text/HoriOrientation.hpp> +#include <com/sun/star/text/HorizontalAdjust.hpp> +#include <com/sun/star/text/SizeType.hpp> +#include <com/sun/star/text/VertOrientation.hpp> +#include <com/sun/star/text/WritingMode.hpp> +#include <com/sun/star/text/WritingMode2.hpp> +#include <com/sun/star/text/XRedline.hpp> +#include <com/sun/star/text/XTextColumns.hpp> +#include <com/sun/star/text/XText.hpp> +#include <com/sun/star/text/XTextFrame.hpp> +#include <com/sun/star/text/XTextTablesSupplier.hpp> +#include <com/sun/star/text/TextGridMode.hpp> +#include <com/sun/star/text/XTextCopy.hpp> +#include <com/sun/star/style/VerticalAlignment.hpp> +#include <comphelper/sequence.hxx> +#include <comphelper/propertyvalue.hxx> +#include <tools/diagnose_ex.h> +#include "PropertyMapHelper.hxx" +#include <o3tl/sorted_vector.hxx> +#include <o3tl/unit_conversion.hxx> + +using namespace com::sun::star; + +namespace writerfilter::dmapper { + +uno::Sequence< beans::PropertyValue > PropertyMap::GetPropertyValues( bool bCharGrabBag ) +{ + using comphelper::makePropertyValue; + + if ( !m_aValues.empty() || m_vMap.empty() ) + return comphelper::containerToSequence( m_aValues ); + + size_t nCharGrabBag = 0; + size_t nParaGrabBag = 0; + size_t nCellGrabBag = 0; + size_t nRowGrabBag = 0; + + const PropValue* pParaStyleProp = nullptr; + const PropValue* pCharStyleProp = nullptr; + const PropValue* pNumRuleProp = nullptr; + + m_aValues.reserve( m_vMap.size() ); + for ( const auto& rPropPair : m_vMap ) + { + if ( rPropPair.second.getGrabBagType() == CHAR_GRAB_BAG ) + nCharGrabBag++; + else if ( rPropPair.second.getGrabBagType() == PARA_GRAB_BAG ) + nParaGrabBag++; + else if ( rPropPair.second.getGrabBagType() == CELL_GRAB_BAG ) + nCellGrabBag++; + else if ( rPropPair.first == PROP_CELL_INTEROP_GRAB_BAG ) + { + uno::Sequence< beans::PropertyValue > aSeq; + rPropPair.second.getValue() >>= aSeq; + nCellGrabBag += aSeq.getLength(); + } + else if ( rPropPair.second.getGrabBagType() == ROW_GRAB_BAG ) + nRowGrabBag++; + + if ( rPropPair.first == PROP_PARA_STYLE_NAME ) pParaStyleProp = &rPropPair.second; + if ( rPropPair.first == PROP_CHAR_STYLE_NAME ) pCharStyleProp = &rPropPair.second; + if ( rPropPair.first == PROP_NUMBERING_RULES ) pNumRuleProp = &rPropPair.second; + } + + // Style names have to be the first elements within the property sequence + // otherwise they will overwrite 'hard' attributes + if ( pParaStyleProp != nullptr ) + m_aValues.push_back( makePropertyValue( getPropertyName( PROP_PARA_STYLE_NAME ), pParaStyleProp->getValue() ) ); + if ( pCharStyleProp != nullptr ) + m_aValues.push_back( makePropertyValue( getPropertyName( PROP_CHAR_STYLE_NAME ), pCharStyleProp->getValue() ) ); + if ( pNumRuleProp != nullptr ) + m_aValues.push_back( makePropertyValue(getPropertyName( PROP_NUMBERING_RULES ), pNumRuleProp->getValue() ) ); + + // If there are any grab bag properties, we need one slot for them. + uno::Sequence< beans::PropertyValue > aCharGrabBagValues( nCharGrabBag ); + uno::Sequence< beans::PropertyValue > aParaGrabBagValues( nParaGrabBag ); + uno::Sequence< beans::PropertyValue > aCellGrabBagValues( nCellGrabBag ); + uno::Sequence< beans::PropertyValue > aRowGrabBagValues ( nRowGrabBag ); + beans::PropertyValue* pCharGrabBagValues = aCharGrabBagValues.getArray(); + beans::PropertyValue* pParaGrabBagValues = aParaGrabBagValues.getArray(); + beans::PropertyValue* pCellGrabBagValues = aCellGrabBagValues.getArray(); + beans::PropertyValue* pRowGrabBagValues = aRowGrabBagValues.getArray(); + // Record index for the next property to be added in each grab bag. + sal_Int32 nRowGrabBagValue = 0; + sal_Int32 nCellGrabBagValue = 0; + sal_Int32 nParaGrabBagValue = 0; + sal_Int32 nCharGrabBagValue = 0; + + for ( const auto& rPropPair : m_vMap ) + { + if ( rPropPair.first != PROP_PARA_STYLE_NAME && + rPropPair.first != PROP_CHAR_STYLE_NAME && + rPropPair.first != PROP_NUMBERING_RULES ) + { + if ( rPropPair.second.getGrabBagType() == CHAR_GRAB_BAG ) + { + if ( bCharGrabBag ) + { + pCharGrabBagValues[nCharGrabBagValue].Name = getPropertyName( rPropPair.first ); + pCharGrabBagValues[nCharGrabBagValue].Value = rPropPair.second.getValue(); + ++nCharGrabBagValue; + } + } + else if ( rPropPair.second.getGrabBagType() == PARA_GRAB_BAG ) + { + pParaGrabBagValues[nParaGrabBagValue].Name = getPropertyName( rPropPair.first ); + pParaGrabBagValues[nParaGrabBagValue].Value = rPropPair.second.getValue(); + ++nParaGrabBagValue; + } + else if ( rPropPair.second.getGrabBagType() == CELL_GRAB_BAG ) + { + pCellGrabBagValues[nCellGrabBagValue].Name = getPropertyName( rPropPair.first ); + pCellGrabBagValues[nCellGrabBagValue].Value = rPropPair.second.getValue(); + ++nCellGrabBagValue; + } + else if ( rPropPair.second.getGrabBagType() == ROW_GRAB_BAG ) + { + pRowGrabBagValues[nRowGrabBagValue].Name = getPropertyName( rPropPair.first ); + pRowGrabBagValues[nRowGrabBagValue].Value = rPropPair.second.getValue(); + ++nRowGrabBagValue; + } + else if ( rPropPair.first == PROP_CELL_INTEROP_GRAB_BAG ) + { + uno::Sequence< beans::PropertyValue > aSeq; + rPropPair.second.getValue() >>= aSeq; + std::copy(std::cbegin(aSeq), std::cend(aSeq), pCellGrabBagValues + nCellGrabBagValue); + nCellGrabBagValue += aSeq.getLength(); + } + else + { + m_aValues.push_back( makePropertyValue( getPropertyName( rPropPair.first ), rPropPair.second.getValue() ) ); + } + } + } + + if ( nCharGrabBag && bCharGrabBag ) + m_aValues.push_back( makePropertyValue( "CharInteropGrabBag", uno::Any( aCharGrabBagValues ) ) ); + + if ( nParaGrabBag ) + m_aValues.push_back( makePropertyValue( "ParaInteropGrabBag", uno::Any( aParaGrabBagValues ) ) ); + + if ( nCellGrabBag ) + m_aValues.push_back( makePropertyValue( "CellInteropGrabBag", uno::Any( aCellGrabBagValues ) ) ); + + if ( nRowGrabBag ) + m_aValues.push_back( makePropertyValue( "RowInteropGrabBag", uno::Any( aRowGrabBagValues ) ) ); + + return comphelper::containerToSequence( m_aValues ); +} + +std::vector< PropertyIds > PropertyMap::GetPropertyIds() +{ + std::vector< PropertyIds > aRet; + for ( const auto& rPropPair : m_vMap ) + aRet.push_back( rPropPair.first ); + return aRet; +} + +#ifdef DBG_UTIL +static void lcl_AnyToTag( const uno::Any& rAny ) +{ + try { + sal_Int32 aInt = 0; + if ( rAny >>= aInt ) + { + TagLogger::getInstance().attribute( "value", rAny ); + } + else + { + TagLogger::getInstance().attribute( "unsignedValue", 0 ); + } + + sal_uInt32 auInt = 0; + rAny >>= auInt; + TagLogger::getInstance().attribute( "unsignedValue", auInt ); + + float aFloat = 0.0f; + if ( rAny >>= aFloat ) + { + TagLogger::getInstance().attribute( "floatValue", rAny ); + } + else + { + TagLogger::getInstance().attribute( "unsignedValue", 0 ); + } + + OUString aStr; + rAny >>= aStr; + TagLogger::getInstance().attribute( "stringValue", aStr ); + } + catch ( ... ) + { + } +} +#endif + +void PropertyMap::Insert( PropertyIds eId, const uno::Any& rAny, bool bOverwrite, GrabBagType i_GrabBagType, bool bDocDefault ) +{ +#ifdef DBG_UTIL + const OUString& rInsert = getPropertyName(eId); + + TagLogger::getInstance().startElement("propertyMap.insert"); + TagLogger::getInstance().attribute("name", rInsert); + lcl_AnyToTag(rAny); + TagLogger::getInstance().endElement(); +#endif + + if ( !bOverwrite ) + m_vMap.insert(std::make_pair(eId, PropValue(rAny, i_GrabBagType, bDocDefault))); + else + m_vMap[eId] = PropValue(rAny, i_GrabBagType); + + Invalidate(); +} + +void PropertyMap::Erase( PropertyIds eId ) +{ + // Safe call to erase, it throws no exceptions, even if eId is not in m_vMap + m_vMap.erase(eId); + + Invalidate(); +} + +std::optional< PropertyMap::Property > PropertyMap::getProperty( PropertyIds eId ) const +{ + std::map< PropertyIds, PropValue >::const_iterator aIter = m_vMap.find( eId ); + if ( aIter == m_vMap.end() ) + return std::optional<Property>(); + else + return std::make_pair( eId, aIter->second.getValue() ); +} + +bool PropertyMap::isSet( PropertyIds eId) const +{ + return m_vMap.find( eId ) != m_vMap.end(); +} + +bool PropertyMap::isDocDefault( PropertyIds eId ) const +{ + std::map< PropertyIds, PropValue >::const_iterator aIter = m_vMap.find( eId ); + if ( aIter == m_vMap.end() ) + return false; + else + return aIter->second.getIsDocDefault(); +} + +#ifdef DBG_UTIL +void PropertyMap::dumpXml() const +{ + TagLogger::getInstance().startElement( "PropertyMap" ); + + for ( const auto& rPropPair : m_vMap ) + { + TagLogger::getInstance().startElement( "property" ); + + TagLogger::getInstance().attribute( "name", getPropertyName( rPropPair.first ) ); + + switch ( rPropPair.first ) + { + case PROP_TABLE_COLUMN_SEPARATORS: + lcl_DumpTableColumnSeparators( rPropPair.second.getValue() ); + break; + default: + { + try + { + sal_Int32 aInt = 0; + rPropPair.second.getValue() >>= aInt; + TagLogger::getInstance().attribute( "value", aInt ); + + sal_uInt32 auInt = 0; + rPropPair.second.getValue() >>= auInt; + TagLogger::getInstance().attribute( "unsignedValue", auInt ); + + float aFloat = 0.0; + rPropPair.second.getValue() >>= aFloat; + TagLogger::getInstance().attribute( "floatValue", aFloat ); + + rPropPair.second.getValue() >>= auInt; + TagLogger::getInstance().attribute( "stringValue", std::u16string_view() ); + } + catch ( ... ) + { + } + } + break; + } + + TagLogger::getInstance().endElement(); + } + + TagLogger::getInstance().endElement(); +} +#endif + +void PropertyMap::InsertProps( const PropertyMapPtr& rMap, const bool bOverwrite ) +{ + if ( !rMap ) + return; + + for ( const auto& rPropPair : rMap->m_vMap ) + { + if ( bOverwrite || !m_vMap.count(rPropPair.first) ) + { + if ( !bOverwrite && !rPropPair.second.getIsDocDefault() ) + m_vMap.insert(std::make_pair(rPropPair.first, PropValue(rPropPair.second.getValue(), rPropPair.second.getGrabBagType(), true))); + else + m_vMap[rPropPair.first] = rPropPair.second; + } + } + + insertTableProperties( rMap.get(), bOverwrite ); + + Invalidate(); +} + +void PropertyMap::insertTableProperties( const PropertyMap*, const bool ) +{ +#ifdef DBG_UTIL + TagLogger::getInstance().element( "PropertyMap.insertTableProperties" ); +#endif +} + +void PropertyMap::printProperties() +{ +#ifdef DBG_UTIL + TagLogger::getInstance().startElement( "properties" ); + + for ( const auto& rPropPair : m_vMap ) + { + SAL_INFO( "writerfilter", getPropertyName( rPropPair.first ) ); + + table::BorderLine2 aLine; + sal_Int32 nColor; + if ( rPropPair.second.getValue() >>= aLine ) + { + TagLogger::getInstance().startElement( "borderline" ); + TagLogger::getInstance().attribute( "color", aLine.Color ); + TagLogger::getInstance().attribute( "inner", aLine.InnerLineWidth ); + TagLogger::getInstance().attribute( "outer", aLine.OuterLineWidth ); + TagLogger::getInstance().endElement(); + } + else if ( rPropPair.second.getValue() >>= nColor ) + { + TagLogger::getInstance().startElement( "color" ); + TagLogger::getInstance().attribute( "number", nColor ); + TagLogger::getInstance().endElement(); + } + } + + TagLogger::getInstance().endElement(); +#else + (void) this; // avoid loplugin:staticmethods +#endif +} + +SectionPropertyMap::SectionPropertyMap( bool bIsFirstSection ) + : m_bIsFirstSection( bIsFirstSection ) + , m_eBorderApply( BorderApply::ToAllInSection ) + , m_eBorderOffsetFrom( BorderOffsetFrom::Text ) + , m_bTitlePage( false ) + , m_nColumnCount( 0 ) + , m_nColumnDistance( 1249 ) + , m_bSeparatorLineIsOn( false ) + , m_bEvenlySpaced( false ) + , m_nPageNumber( -1 ) + , m_nPageNumberType( -1 ) + , m_nBreakType( -1 ) + , m_nLeftMargin( o3tl::convert(1, o3tl::Length::in, o3tl::Length::mm100) ) + , m_nRightMargin( o3tl::convert(1, o3tl::Length::in, o3tl::Length::mm100) ) + , m_nGutterMargin(0) + , m_nTopMargin( o3tl::convert(1, o3tl::Length::in, o3tl::Length::mm100) ) + , m_nBottomMargin( o3tl::convert(1, o3tl::Length::in, o3tl::Length::mm100) ) + , m_nHeaderTop( o3tl::convert(0.5, o3tl::Length::in, o3tl::Length::mm100) ) + , m_nHeaderBottom( o3tl::convert(0.5, o3tl::Length::in, o3tl::Length::mm100) ) + , m_nGridType( 0 ) + , m_nGridLinePitch( 1 ) + , m_nDxtCharSpace( 0 ) + , m_bGridSnapToChars( true ) + , m_nLnnMod( 0 ) + , m_nLnc(NS_ooxml::LN_Value_ST_LineNumberRestart_newPage) + , m_ndxaLnn( 0 ) + , m_nLnnMin( 0 ) + , m_bDynamicHeightTop( true ) + , m_bDynamicHeightBottom( true ) + , m_bDefaultHeaderLinkToPrevious( true ) + , m_bEvenPageHeaderLinkToPrevious( true ) + , m_bFirstPageHeaderLinkToPrevious( true ) + , m_bDefaultFooterLinkToPrevious( true ) + , m_bEvenPageFooterLinkToPrevious( true ) + , m_bFirstPageFooterLinkToPrevious( true ) +{ +#ifdef DBG_UTIL + static sal_Int32 nNumber = 0; + m_nDebugSectionNumber = nNumber++; +#endif + + for ( sal_Int32 nBorder = 0; nBorder < 4; ++nBorder ) + { + m_nBorderDistances[nBorder] = -1; + m_bBorderShadows[nBorder] = false; + } + // todo: set defaults in ApplyPropertiesToPageStyles + // initialize defaults + PaperInfo aLetter( PAPER_LETTER ); + // page height, 1/100mm + Insert( PROP_HEIGHT, uno::Any( static_cast<sal_Int32>(aLetter.getHeight()) ) ); + // page width, 1/100mm + Insert( PROP_WIDTH, uno::Any( static_cast<sal_Int32>(aLetter.getWidth()) ) ); + // page left margin, 1/100 mm + Insert( PROP_LEFT_MARGIN, uno::Any( sal_Int32(o3tl::convert(1, o3tl::Length::in, o3tl::Length::mm100)) ) ); + // page right margin, 1/100 mm + Insert( PROP_RIGHT_MARGIN, uno::Any( sal_Int32(o3tl::convert(1, o3tl::Length::in, o3tl::Length::mm100)) ) ); + // page top margin, 1/100 mm + Insert( PROP_TOP_MARGIN, uno::Any( sal_Int32(o3tl::convert(1, o3tl::Length::in, o3tl::Length::mm100)) ) ); + // page bottom margin, 1/100 mm + Insert( PROP_BOTTOM_MARGIN, uno::Any( sal_Int32(o3tl::convert(1, o3tl::Length::in, o3tl::Length::mm100)) ) ); + // page style layout + Insert( PROP_PAGE_STYLE_LAYOUT, uno::Any( style::PageStyleLayout_ALL ) ); + uno::Any aFalse( uno::Any( false ) ); + Insert( PROP_GRID_DISPLAY, aFalse ); + Insert( PROP_GRID_PRINT, aFalse ); + Insert( PROP_GRID_MODE, uno::Any( text::TextGridMode::NONE ) ); + + if ( m_bIsFirstSection ) + { + m_sFirstPageStyleName = getPropertyName( PROP_FIRST_PAGE ); + m_sFollowPageStyleName = getPropertyName( PROP_STANDARD ); + } +} + +uno::Reference< beans::XPropertySet > SectionPropertyMap::GetPageStyle( DomainMapper_Impl& rDM_Impl, + bool bFirst ) +{ + const uno::Reference< container::XNameContainer >& xPageStyles = rDM_Impl.GetPageStyles(); + const uno::Reference < lang::XMultiServiceFactory >& xTextFactory = rDM_Impl.GetTextFactory(); + uno::Reference< beans::XPropertySet > xRet; + try + { + if ( bFirst ) + { + if ( m_sFirstPageStyleName.isEmpty() && xPageStyles.is() ) + { + assert( !rDM_Impl.IsInFootOrEndnote() && "Don't create useless page styles" ); + m_sFirstPageStyleName = rDM_Impl.GetUnusedPageStyleName(); + m_aFirstPageStyle.set( xTextFactory->createInstance( "com.sun.star.style.PageStyle" ), + uno::UNO_QUERY ); + + // Call insertByName() before GetPageStyle(), otherwise the + // first and the follow page style will have the same name, and + // insertByName() will fail. + if ( xPageStyles.is() ) + xPageStyles->insertByName( m_sFirstPageStyleName, uno::Any( m_aFirstPageStyle ) ); + + // Ensure that m_aFollowPageStyle has been created + GetPageStyle( rDM_Impl, false ); + // Chain m_aFollowPageStyle to be after m_aFirstPageStyle + m_aFirstPageStyle->setPropertyValue( "FollowStyle", + uno::Any( m_sFollowPageStyleName ) ); + } + else if ( !m_aFirstPageStyle.is() && xPageStyles.is() ) + { + xPageStyles->getByName( m_sFirstPageStyleName ) >>= m_aFirstPageStyle; + } + xRet = m_aFirstPageStyle; + } + else + { + if ( m_sFollowPageStyleName.isEmpty() && xPageStyles.is() ) + { + assert( !rDM_Impl.IsInFootOrEndnote() && "Don't create useless page styles" ); + m_sFollowPageStyleName = rDM_Impl.GetUnusedPageStyleName(); + m_aFollowPageStyle.set( xTextFactory->createInstance( "com.sun.star.style.PageStyle" ), + uno::UNO_QUERY ); + xPageStyles->insertByName( m_sFollowPageStyleName, uno::Any( m_aFollowPageStyle ) ); + } + else if ( !m_aFollowPageStyle.is() && xPageStyles.is() ) + { + xPageStyles->getByName( m_sFollowPageStyleName ) >>= m_aFollowPageStyle; + } + xRet = m_aFollowPageStyle; + } + + } + catch ( const uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION( "writerfilter" ); + } + + return xRet; +} + +void SectionPropertyMap::SetBorder( BorderPosition ePos, sal_Int32 nLineDistance, const table::BorderLine2& rBorderLine, bool bShadow ) +{ + m_oBorderLines[ePos] = rBorderLine; + m_nBorderDistances[ePos] = nLineDistance; + m_bBorderShadows[ePos] = bShadow; +} + +void SectionPropertyMap::ApplyBorderToPageStyles( DomainMapper_Impl& rDM_Impl, + BorderApply eBorderApply, BorderOffsetFrom eOffsetFrom ) +{ + /* + page border applies to: + nIntValue & 0x07 -> + 0 all pages in this section + 1 first page in this section + 2 all pages in this section but first + 3 whole document (all sections) + nIntValue & 0x18 -> page border depth 0 - in front 1- in back + nIntValue & 0xe0 -> + page border offset from: + 0 offset from text + 1 offset from edge of page + */ + uno::Reference< beans::XPropertySet > xFirst; + uno::Reference< beans::XPropertySet > xSecond; + // todo: negative spacing (from ww8par6.cxx) + switch ( eBorderApply ) + { + case BorderApply::ToAllInSection: // all styles + if ( !m_sFollowPageStyleName.isEmpty() ) + xFirst = GetPageStyle( rDM_Impl, false ); + if ( !m_sFirstPageStyleName.isEmpty() ) + xSecond = GetPageStyle( rDM_Impl, true ); + break; + case BorderApply::ToFirstPageInSection: // first page + if ( !m_sFirstPageStyleName.isEmpty() ) + xFirst = GetPageStyle( rDM_Impl, true ); + break; + case BorderApply::ToAllButFirstInSection: // left and right + if ( !m_sFollowPageStyleName.isEmpty() ) + xFirst = GetPageStyle( rDM_Impl, false ); + break; + default: + return; + } + + // has to be sorted like enum BorderPosition: l-r-t-b + const PropertyIds aBorderIds[4] = + { + PROP_LEFT_BORDER, + PROP_RIGHT_BORDER, + PROP_TOP_BORDER, + PROP_BOTTOM_BORDER + }; + + const PropertyIds aBorderDistanceIds[4] = + { + PROP_LEFT_BORDER_DISTANCE, + PROP_RIGHT_BORDER_DISTANCE, + PROP_TOP_BORDER_DISTANCE, + PROP_BOTTOM_BORDER_DISTANCE + }; + + const PropertyIds aMarginIds[4] = + { + PROP_LEFT_MARGIN, + PROP_RIGHT_MARGIN, + PROP_TOP_MARGIN, + PROP_BOTTOM_MARGIN + }; + + for ( sal_Int32 nBorder = 0; nBorder < 4; ++nBorder ) + { + if ( m_oBorderLines[nBorder] ) + { + const OUString sBorderName = getPropertyName( aBorderIds[nBorder] ); + if ( xFirst.is() ) + xFirst->setPropertyValue( sBorderName, uno::Any( *m_oBorderLines[nBorder] ) ); + if ( xSecond.is() ) + xSecond->setPropertyValue( sBorderName, uno::Any( *m_oBorderLines[nBorder] ) ); + } + if ( m_nBorderDistances[nBorder] >= 0 ) + { + sal_uInt32 nLineWidth = 0; + if ( m_oBorderLines[nBorder] ) + nLineWidth = m_oBorderLines[nBorder]->LineWidth; + if ( xFirst.is() ) + SetBorderDistance( xFirst, aMarginIds[nBorder], aBorderDistanceIds[nBorder], + m_nBorderDistances[nBorder], eOffsetFrom, nLineWidth, rDM_Impl ); + if ( xSecond.is() ) + SetBorderDistance( xSecond, aMarginIds[nBorder], aBorderDistanceIds[nBorder], + m_nBorderDistances[nBorder], eOffsetFrom, nLineWidth, rDM_Impl ); + } + } + + if ( m_bBorderShadows[BORDER_RIGHT] ) + { + table::ShadowFormat aFormat = getShadowFromBorder( *m_oBorderLines[BORDER_RIGHT] ); + if ( xFirst.is() ) + xFirst->setPropertyValue( getPropertyName( PROP_SHADOW_FORMAT ), uno::Any( aFormat ) ); + if ( xSecond.is() ) + xSecond->setPropertyValue( getPropertyName( PROP_SHADOW_FORMAT ), uno::Any( aFormat ) ); + } +} + +table::ShadowFormat PropertyMap::getShadowFromBorder( const table::BorderLine2& rBorder ) +{ + // In Word UI, shadow is a boolean property, in OOXML, it's a boolean + // property of each 4 border type, finally in Writer the border is a + // property of the page style, with shadow location, distance and + // color. See SwWW8ImplReader::SetShadow(). + table::ShadowFormat aFormat; + aFormat.Color = sal_Int32(COL_BLACK); + aFormat.Location = table::ShadowLocation_BOTTOM_RIGHT; + aFormat.ShadowWidth = rBorder.LineWidth; + return aFormat; +} + +void SectionPropertyMap::SetBorderDistance( const uno::Reference< beans::XPropertySet >& xStyle, + PropertyIds eMarginId, + PropertyIds eDistId, + sal_Int32 nDistance, + BorderOffsetFrom eOffsetFrom, + sal_uInt32 nLineWidth, + DomainMapper_Impl& rDM_Impl ) +{ + if (!xStyle.is()) + return; + const OUString sMarginName = getPropertyName( eMarginId ); + const OUString sBorderDistanceName = getPropertyName( eDistId ); + uno::Any aMargin = xStyle->getPropertyValue( sMarginName ); + sal_Int32 nMargin = 0; + aMargin >>= nMargin; + editeng::BorderDistanceFromWord(eOffsetFrom == BorderOffsetFrom::Edge, nMargin, nDistance, + nLineWidth); + + if (eOffsetFrom == BorderOffsetFrom::Edge) + { + uno::Any aGutterMargin = xStyle->getPropertyValue( "GutterMargin" ); + sal_Int32 nGutterMargin = 0; + aGutterMargin >>= nGutterMargin; + + if (eMarginId == PROP_LEFT_MARGIN && !rDM_Impl.GetSettingsTable()->GetGutterAtTop()) + { + nMargin -= nGutterMargin; + nDistance += nGutterMargin; + } + + if (eMarginId == PROP_TOP_MARGIN && rDM_Impl.GetSettingsTable()->GetGutterAtTop()) + { + nMargin -= nGutterMargin; + nDistance += nGutterMargin; + } + } + + // Change the margins with the border distance + uno::Reference< beans::XMultiPropertySet > xMultiSet( xStyle, uno::UNO_QUERY_THROW ); + uno::Sequence<OUString> aProperties { sMarginName, sBorderDistanceName }; + uno::Sequence<uno::Any> aValues { uno::Any( nMargin ), uno::Any( nDistance ) }; + xMultiSet->setPropertyValues( aProperties, aValues ); +} + +void SectionPropertyMap::DontBalanceTextColumns() +{ + try + { + if ( m_xColumnContainer.is() ) + m_xColumnContainer->setPropertyValue( "DontBalanceTextColumns", uno::Any( true ) ); + } + catch ( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "writerfilter", "SectionPropertyMap::DontBalanceTextColumns" ); + } +} + +void SectionPropertyMap::ApplySectionProperties( const uno::Reference< beans::XPropertySet >& xSection, DomainMapper_Impl& /*rDM_Impl*/ ) +{ + try + { + if ( xSection.is() ) + { + std::optional< PropertyMap::Property > pProp = getProperty( PROP_WRITING_MODE ); + if ( pProp ) + xSection->setPropertyValue( "WritingMode", pProp->second ); + } + } + catch ( uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("writerfilter", "Exception in SectionPropertyMap::ApplySectionProperties"); + } +} + +void SectionPropertyMap::ApplyProtectionProperties( uno::Reference< beans::XPropertySet >& xSection, DomainMapper_Impl& rDM_Impl ) +{ + try + { + // Word implements section protection differently than LO. + // PROP_IS_PROTECTED only applies if global setting GetProtectForm is enabled. + bool bIsProtected = rDM_Impl.GetSettingsTable()->GetProtectForm(); + if ( bIsProtected ) + { + // If form protection is enabled then section protection is enabled, unless explicitly disabled + if ( isSet(PROP_IS_PROTECTED) ) + getProperty(PROP_IS_PROTECTED)->second >>= bIsProtected; + if ( !xSection.is() ) + xSection = rDM_Impl.appendTextSectionAfter( m_xStartingRange ); + if ( xSection.is() ) + xSection->setPropertyValue( getPropertyName(PROP_IS_PROTECTED), uno::Any(bIsProtected) ); + } + } + catch ( uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("writerfilter", "ApplyProtectionProperties failed setting PROP_IS_PROTECTED"); + } +} + +uno::Reference< text::XTextColumns > SectionPropertyMap::ApplyColumnProperties( const uno::Reference< beans::XPropertySet >& xColumnContainer, + DomainMapper_Impl& rDM_Impl ) +{ + uno::Reference< text::XTextColumns > xColumns; + assert( m_nColumnCount > 1 && "ApplyColumnProperties called without any columns" ); + try + { + const OUString sTextColumns = getPropertyName( PROP_TEXT_COLUMNS ); + if ( xColumnContainer.is() ) + xColumnContainer->getPropertyValue( sTextColumns ) >>= xColumns; + uno::Reference< beans::XPropertySet > xColumnPropSet( xColumns, uno::UNO_QUERY_THROW ); + if ( !m_bEvenlySpaced && + ( sal_Int32(m_aColWidth.size()) == m_nColumnCount ) && + ( (sal_Int32(m_aColDistance.size()) == m_nColumnCount - 1) || (sal_Int32(m_aColDistance.size()) == m_nColumnCount) ) ) + { + // the column width in word is an absolute value, in OOo it's relative + // the distances are both absolute + sal_Int32 nColSum = 0; + for ( sal_Int32 nCol = 0; nCol < m_nColumnCount; ++nCol ) + { + nColSum += m_aColWidth[nCol]; + if ( nCol ) + nColSum += m_aColDistance[nCol - 1]; + } + + sal_Int32 nRefValue = xColumns->getReferenceValue(); + double fRel = nColSum ? double( nRefValue ) / double( nColSum ) : 0.0; + uno::Sequence< text::TextColumn > aColumns( m_nColumnCount ); + text::TextColumn* pColumn = aColumns.getArray(); + + nColSum = 0; + for ( sal_Int32 nCol = 0; nCol < m_nColumnCount; ++nCol ) + { + const double fLeft = nCol ? m_aColDistance[nCol - 1] / 2 : 0; + pColumn[nCol].LeftMargin = fLeft; + const double fRight = (nCol == m_nColumnCount - 1) ? 0 : m_aColDistance[nCol] / 2; + pColumn[nCol].RightMargin = fRight; + const double fWidth = m_aColWidth[nCol]; + pColumn[nCol].Width = (fWidth + fLeft + fRight) * fRel; + nColSum += pColumn[nCol].Width; + } + if ( nColSum != nRefValue ) + pColumn[m_nColumnCount - 1].Width += (nRefValue - nColSum); + assert( pColumn[m_nColumnCount - 1].Width >= 0 ); + + xColumns->setColumns( aColumns ); + } + else + { + xColumns->setColumnCount( m_nColumnCount ); + xColumnPropSet->setPropertyValue( getPropertyName( PROP_AUTOMATIC_DISTANCE ), uno::Any( m_nColumnDistance ) ); + } + + if ( m_bSeparatorLineIsOn ) + { + xColumnPropSet->setPropertyValue( "SeparatorLineIsOn", uno::Any( true ) ); + xColumnPropSet->setPropertyValue( "SeparatorLineVerticalAlignment", uno::Any( style::VerticalAlignment_TOP ) ); + xColumnPropSet->setPropertyValue( "SeparatorLineRelativeHeight", uno::Any( static_cast<sal_Int8>(100) ) ); + xColumnPropSet->setPropertyValue( "SeparatorLineColor", uno::Any( static_cast<sal_Int32>(COL_BLACK) ) ); + // 1 twip -> 2 mm100. + xColumnPropSet->setPropertyValue( "SeparatorLineWidth", uno::Any( static_cast<sal_Int32>(2) ) ); + } + xColumnContainer->setPropertyValue( sTextColumns, uno::Any( xColumns ) ); + // Set the columns to be unbalanced if that compatibility option is set or this is the last section. + m_xColumnContainer = xColumnContainer; + if ( rDM_Impl.GetSettingsTable()->GetNoColumnBalance() || rDM_Impl.GetIsLastSectionGroup() ) + DontBalanceTextColumns(); + } + catch ( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "writerfilter", "SectionPropertyMap::ApplyColumnProperties" ); + } + return xColumns; +} + +bool SectionPropertyMap::HasHeader( bool bFirstPage ) const +{ + bool bRet = false; + if ( (bFirstPage && m_aFirstPageStyle.is()) || (!bFirstPage && m_aFollowPageStyle.is()) ) + { + if ( bFirstPage ) + m_aFirstPageStyle->getPropertyValue( + getPropertyName( PROP_HEADER_IS_ON ) ) >>= bRet; + else + m_aFollowPageStyle->getPropertyValue( + getPropertyName( PROP_HEADER_IS_ON ) ) >>= bRet; + } + return bRet; +} + +bool SectionPropertyMap::HasFooter( bool bFirstPage ) const +{ + bool bRet = false; + if ( (bFirstPage && m_aFirstPageStyle.is()) || (!bFirstPage && m_aFollowPageStyle.is()) ) + { + if ( bFirstPage ) + m_aFirstPageStyle->getPropertyValue( getPropertyName( PROP_FOOTER_IS_ON ) ) >>= bRet; + else + m_aFollowPageStyle->getPropertyValue( getPropertyName( PROP_FOOTER_IS_ON ) ) >>= bRet; + } + return bRet; +} + +#define MIN_HEAD_FOOT_HEIGHT 100 // minimum header/footer height + +void SectionPropertyMap::CopyHeaderFooterTextProperty( const uno::Reference< beans::XPropertySet >& xPrevStyle, + const uno::Reference< beans::XPropertySet >& xStyle, + PropertyIds ePropId ) +{ + try { + OUString sName = getPropertyName( ePropId ); + + SAL_INFO( "writerfilter", "Copying " << sName ); + uno::Reference< text::XTextCopy > xTxt; + if ( xStyle.is() ) + xTxt.set( xStyle->getPropertyValue( sName ), uno::UNO_QUERY_THROW ); + + uno::Reference< text::XTextCopy > xPrevTxt; + if ( xPrevStyle.is() ) + xPrevTxt.set( xPrevStyle->getPropertyValue( sName ), uno::UNO_QUERY_THROW ); + + xTxt->copyText( xPrevTxt ); + } + catch ( const uno::Exception& ) + { + TOOLS_INFO_EXCEPTION( "writerfilter", "An exception occurred in SectionPropertyMap::CopyHeaderFooterTextProperty( )" ); + } +} + +// Copy headers and footers from the previous page style. +void SectionPropertyMap::CopyHeaderFooter( const DomainMapper_Impl& rDM_Impl, + const uno::Reference< beans::XPropertySet >& xPrevStyle, + const uno::Reference< beans::XPropertySet >& xStyle, + bool bOmitRightHeader, + bool bOmitLeftHeader, + bool bOmitRightFooter, + bool bOmitLeftFooter ) +{ + if (!rDM_Impl.IsNewDoc()) + { // see also DomainMapper_Impl::PushPageHeaderFooter() + return; // tdf#139737 SwUndoInserts cannot deal with new header/footer + } + bool bHasPrevHeader = false; + bool bHeaderIsShared = true; + OUString sHeaderIsOn = getPropertyName( PROP_HEADER_IS_ON ); + OUString sHeaderIsShared = getPropertyName( PROP_HEADER_IS_SHARED ); + if ( xPrevStyle.is() ) + { + xPrevStyle->getPropertyValue( sHeaderIsOn ) >>= bHasPrevHeader; + xPrevStyle->getPropertyValue( sHeaderIsShared ) >>= bHeaderIsShared; + } + + if ( bHasPrevHeader ) + { + uno::Reference< beans::XMultiPropertySet > xMultiSet( xStyle, uno::UNO_QUERY_THROW ); + uno::Sequence<OUString> aProperties { sHeaderIsOn, sHeaderIsShared }; + uno::Sequence<uno::Any> aValues { uno::Any( true ), uno::Any( bHeaderIsShared ) }; + xMultiSet->setPropertyValues( aProperties, aValues ); + if ( !bOmitRightHeader ) + { + CopyHeaderFooterTextProperty( xPrevStyle, xStyle, + PROP_HEADER_TEXT ); + } + if ( !bHeaderIsShared && !bOmitLeftHeader ) + { + CopyHeaderFooterTextProperty( xPrevStyle, xStyle, + PROP_HEADER_TEXT_LEFT ); + } + } + + bool bHasPrevFooter = false; + bool bFooterIsShared = true; + OUString sFooterIsOn = getPropertyName( PROP_FOOTER_IS_ON ); + OUString sFooterIsShared = getPropertyName( PROP_FOOTER_IS_SHARED ); + if ( xPrevStyle.is() ) + { + xPrevStyle->getPropertyValue( sFooterIsOn ) >>= bHasPrevFooter; + xPrevStyle->getPropertyValue( sFooterIsShared ) >>= bFooterIsShared; + } + + if ( !bHasPrevFooter ) + return; + + uno::Reference< beans::XMultiPropertySet > xMultiSet( xStyle, uno::UNO_QUERY_THROW ); + uno::Sequence<OUString> aProperties { sFooterIsOn, sFooterIsShared }; + uno::Sequence<uno::Any> aValues { uno::Any( true ), uno::Any( bFooterIsShared ) }; + xMultiSet->setPropertyValues( aProperties, aValues ); + if ( !bOmitRightFooter ) + { + CopyHeaderFooterTextProperty( xPrevStyle, xStyle, + PROP_FOOTER_TEXT ); + } + if ( !bFooterIsShared && !bOmitLeftFooter ) + { + CopyHeaderFooterTextProperty( xPrevStyle, xStyle, + PROP_FOOTER_TEXT_LEFT ); + } +} + +// Copy header and footer content from the previous docx section as needed. +// +// Any headers and footers which were not defined in this docx section +// should be "linked" with the corresponding header or footer from the +// previous section. LO does not support linking of header/footer content +// across page styles so we just copy the content from the previous section. +void SectionPropertyMap::CopyLastHeaderFooter( bool bFirstPage, DomainMapper_Impl& rDM_Impl ) +{ + SAL_INFO( "writerfilter", "START>>> SectionPropertyMap::CopyLastHeaderFooter()" ); + SectionPropertyMap* pLastContext = rDM_Impl.GetLastSectionContext(); + if ( pLastContext ) + { + const bool bUseEvenPages = rDM_Impl.GetSettingsTable()->GetEvenAndOddHeaders(); + uno::Reference< beans::XPropertySet > xPrevStyle = pLastContext->GetPageStyle( rDM_Impl, + bFirstPage ); + uno::Reference< beans::XPropertySet > xStyle = GetPageStyle( rDM_Impl, + bFirstPage ); + + if ( bFirstPage ) + { + CopyHeaderFooter(rDM_Impl, xPrevStyle, xStyle, + !m_bFirstPageHeaderLinkToPrevious, true, + !m_bFirstPageFooterLinkToPrevious, true ); + } + else + { + CopyHeaderFooter(rDM_Impl, xPrevStyle, xStyle, + !m_bDefaultHeaderLinkToPrevious, + !(m_bEvenPageHeaderLinkToPrevious && bUseEvenPages), + !m_bDefaultFooterLinkToPrevious, + !(m_bEvenPageFooterLinkToPrevious && bUseEvenPages)); + } + } + SAL_INFO( "writerfilter", "END>>> SectionPropertyMap::CopyLastHeaderFooter()" ); +} + +void SectionPropertyMap::PrepareHeaderFooterProperties( bool bFirstPage ) +{ + bool bCopyFirstToFollow = bFirstPage && m_bTitlePage && m_aFollowPageStyle.is(); + + sal_Int32 nTopMargin = m_nTopMargin; + sal_Int32 nHeaderHeight = m_nHeaderTop; + if ( HasHeader( bFirstPage ) ) + { + nTopMargin = m_nHeaderTop; + nHeaderHeight = m_nTopMargin - m_nHeaderTop; + + // minimum header height 1mm + if ( nHeaderHeight < MIN_HEAD_FOOT_HEIGHT ) + nHeaderHeight = MIN_HEAD_FOOT_HEIGHT; + } + + Insert(PROP_HEADER_IS_DYNAMIC_HEIGHT, uno::Any(m_bDynamicHeightTop)); + Insert(PROP_HEADER_DYNAMIC_SPACING, uno::Any(m_bDynamicHeightTop)); + Insert(PROP_HEADER_BODY_DISTANCE, uno::Any(nHeaderHeight - MIN_HEAD_FOOT_HEIGHT)); + Insert(PROP_HEADER_HEIGHT, uno::Any(nHeaderHeight)); + // looks like PROP_HEADER_HEIGHT = height of the header + space between the header, and the body + + if ( m_bDynamicHeightTop ) //fixed height header -> see WW8Par6.hxx + { + if (bCopyFirstToFollow && HasHeader(/*bFirstPage=*/true)) + { + m_aFollowPageStyle->setPropertyValue("HeaderDynamicSpacing", + getProperty(PROP_HEADER_DYNAMIC_SPACING)->second); + m_aFollowPageStyle->setPropertyValue("HeaderHeight", + getProperty(PROP_HEADER_HEIGHT)->second); + } + } + + sal_Int32 nBottomMargin = m_nBottomMargin; + sal_Int32 nFooterHeight = m_nHeaderBottom; + if ( HasFooter( bFirstPage ) ) + { + nBottomMargin = m_nHeaderBottom; + nFooterHeight = m_nBottomMargin - m_nHeaderBottom; + + // minimum footer height 1mm + if ( nFooterHeight < MIN_HEAD_FOOT_HEIGHT ) + nFooterHeight = MIN_HEAD_FOOT_HEIGHT; + } + + Insert(PROP_FOOTER_IS_DYNAMIC_HEIGHT, uno::Any(m_bDynamicHeightBottom)); + Insert(PROP_FOOTER_DYNAMIC_SPACING, uno::Any(m_bDynamicHeightBottom)); + Insert(PROP_FOOTER_BODY_DISTANCE, uno::Any(nFooterHeight - MIN_HEAD_FOOT_HEIGHT)); + Insert(PROP_FOOTER_HEIGHT, uno::Any(nFooterHeight)); + if (m_bDynamicHeightBottom) //fixed height footer -> see WW8Par6.hxx + { + if (bCopyFirstToFollow && HasFooter(/*bFirstPage=*/true)) + { + m_aFollowPageStyle->setPropertyValue("FooterDynamicSpacing", + getProperty(PROP_FOOTER_DYNAMIC_SPACING)->second); + m_aFollowPageStyle->setPropertyValue("FooterHeight", + getProperty(PROP_FOOTER_HEIGHT)->second); + } + } + + //now set the top/bottom margin for the follow page style + Insert( PROP_TOP_MARGIN, uno::Any( std::max<sal_Int32>(nTopMargin, 0) ) ); + Insert( PROP_BOTTOM_MARGIN, uno::Any( std::max<sal_Int32>(nBottomMargin, 0) ) ); +} + +static uno::Reference< beans::XPropertySet > lcl_GetRangeProperties( bool bIsFirstSection, + DomainMapper_Impl& rDM_Impl, + const uno::Reference< text::XTextRange >& xStartingRange ) +{ + uno::Reference< beans::XPropertySet > xRangeProperties; + if ( bIsFirstSection && rDM_Impl.GetBodyText().is() ) + { + uno::Reference< container::XEnumerationAccess > xEnumAccess( rDM_Impl.GetBodyText(), uno::UNO_QUERY_THROW ); + uno::Reference< container::XEnumeration > xEnum = xEnumAccess->createEnumeration(); + xRangeProperties.set( xEnum->nextElement(), uno::UNO_QUERY_THROW ); + if ( rDM_Impl.GetIsDummyParaAddedForTableInSection() && xEnum->hasMoreElements() ) + xRangeProperties.set( xEnum->nextElement(), uno::UNO_QUERY_THROW ); + } + else if ( xStartingRange.is() ) + xRangeProperties.set( xStartingRange, uno::UNO_QUERY_THROW ); + return xRangeProperties; +} + +void SectionPropertyMap::HandleMarginsHeaderFooter( bool bFirstPage, DomainMapper_Impl& rDM_Impl ) +{ + Insert( PROP_LEFT_MARGIN, uno::Any( m_nLeftMargin ) ); + Insert( PROP_RIGHT_MARGIN, uno::Any( m_nRightMargin ) ); + Insert(PROP_GUTTER_MARGIN, uno::Any(m_nGutterMargin)); + + if ( rDM_Impl.m_oBackgroundColor ) + Insert( PROP_BACK_COLOR, uno::Any( *rDM_Impl.m_oBackgroundColor ) ); + + // Check for missing footnote separator only in case there is at least + // one footnote. + if (rDM_Impl.m_bHasFtn && !rDM_Impl.m_bHasFtnSep) + { + // Set footnote line width to zero, document has no footnote separator. + Insert(PROP_FOOTNOTE_LINE_RELATIVE_WIDTH, uno::Any(sal_Int32(0))); + } + if ( rDM_Impl.m_bHasFtnSep ) + { + //If default paragraph style is RTL, footnote separator should be right aligned + //and for RTL locales, LTR default paragraph style should present a left aligned footnote separator + try + { + uno::Reference<style::XStyleFamiliesSupplier> xStylesSupplier(rDM_Impl.GetTextDocument(), uno::UNO_QUERY); + if ( xStylesSupplier.is() ) + { + uno::Reference<container::XNameAccess> xStyleFamilies = xStylesSupplier->getStyleFamilies(); + uno::Reference<container::XNameAccess> xParagraphStyles; + if ( xStyleFamilies.is() ) + xStyleFamilies->getByName("ParagraphStyles") >>= xParagraphStyles; + uno::Reference<beans::XPropertySet> xStandard; + if ( xParagraphStyles.is() ) + xParagraphStyles->getByName("Standard") >>= xStandard; + if ( xStandard.is() ) + { + sal_Int16 aWritingMode(0); + xStandard->getPropertyValue( getPropertyName(PROP_WRITING_MODE) ) >>= aWritingMode; + if( aWritingMode == text::WritingMode2::RL_TB ) + Insert( PROP_FOOTNOTE_LINE_ADJUST, uno::Any( sal_Int16(text::HorizontalAdjust_RIGHT) ), false ); + else + Insert( PROP_FOOTNOTE_LINE_ADJUST, uno::Any( sal_Int16(text::HorizontalAdjust_LEFT) ), false ); + } + } + } + catch ( const uno::Exception& ) {} + } + + /*** if headers/footers are available then the top/bottom margins of the + header/footer are copied to the top/bottom margin of the page + */ + CopyLastHeaderFooter( bFirstPage, rDM_Impl ); + PrepareHeaderFooterProperties( bFirstPage ); + + // tdf#119952: If top/bottom margin was negative during docx import, + // then the header/footer and the body could be on top of each other + // writer is unable to display both of them in the same position, but can be simulated + // by moving the header/footer text into a flyframe anchored to the header/footer, + // leaving an empty dummy header/footer. + rDM_Impl.ConvertHeaderFooterToTextFrame(m_bDynamicHeightTop, m_bDynamicHeightBottom); +} + +bool SectionPropertyMap::FloatingTableConversion( const DomainMapper_Impl& rDM_Impl, FloatingTableInfo& rInfo ) +{ + // always convert non-floating tables to floating ones in footnotes and endnotes + if ( rInfo.m_bConvertToFloatingInFootnote ) + return true; + // This is OOXML version of the code deciding if the table needs to be + // in a floating frame. + // For ww8 code, see SwWW8ImplReader::FloatingTableConversion in + // sw/source/filter/ww8/ww8par.cxx + // The two should do the same, so if you make changes here, please check + // that the other is in sync. + + // Note that this is just a list of heuristics till sw core can have a + // table that is floating and can span over multiple pages at the same + // time. + + // If there is an explicit section break right after a table, then there + // will be no wrapping anyway. + if (rDM_Impl.m_bConvertedTable && !rDM_Impl.GetIsLastSectionGroup() && rInfo.m_nBreakType == NS_ooxml::LN_Value_ST_SectionMark_nextPage) + return false; + + sal_Int32 nVertOrientPosition = rInfo.getPropertyValue(u"VertOrientPosition").get<sal_Int32>(); + sal_Int16 nHoriOrientRelation = rInfo.getPropertyValue( u"HoriOrientRelation" ).get<sal_Int16>(); + if (nVertOrientPosition < 0 && nHoriOrientRelation != text::RelOrientation::PAGE_FRAME) + { + // Negative vertical position: then need a floating table, as normal tables can't have + // negative top margins. + return true; + } + + sal_Int32 nPageWidth = GetPageWidth(); + sal_Int32 nTextAreaWidth = nPageWidth - GetLeftMargin() - GetRightMargin(); + // Count the layout width of the table. + sal_Int32 nTableWidth = rInfo.m_nTableWidth; + if (rInfo.m_nTableWidthType == text::SizeType::VARIABLE) + { + nTableWidth *= nTextAreaWidth / 100.0; + } + sal_Int32 nLeftMargin = 0; + if ( rInfo.getPropertyValue( u"LeftMargin" ) >>= nLeftMargin ) + nTableWidth += nLeftMargin; + sal_Int32 nRightMargin = 0; + if ( rInfo.getPropertyValue( u"RightMargin" ) >>= nRightMargin ) + nTableWidth += nRightMargin; + + sal_Int16 nVertOrientRelation = rInfo.getPropertyValue( u"VertOrientRelation" ).get<sal_Int16>(); + if ( nHoriOrientRelation == text::RelOrientation::PAGE_FRAME && nVertOrientRelation == text::RelOrientation::PAGE_FRAME ) + { + sal_Int16 nHoriOrient = rInfo.getPropertyValue( u"HoriOrient" ).get<sal_Int16>(); + sal_Int16 nVertOrient = rInfo.getPropertyValue( u"VertOrient" ).get<sal_Int16>(); + if ( nHoriOrient == text::HoriOrientation::NONE && nVertOrient == text::VertOrientation::NONE ) + { + // Anchor position is relative to the page horizontally and vertically as well and is an absolute position. + // The more close we are to the left edge, the less likely there will be any wrapping. + // The more close we are to the bottom, the more likely the table will span over to the next page + // So if we're in the bottom left quarter, don't do any conversion. + sal_Int32 nHoriOrientPosition = rInfo.getPropertyValue( u"HoriOrientPosition" ).get<sal_Int32>(); + sal_Int32 nPageHeight = getProperty( PROP_HEIGHT )->second.get<sal_Int32>(); + if ( nHoriOrientPosition < (nPageWidth / 2) && nVertOrientPosition >( nPageHeight / 2 ) ) + return false; + } + } + + // It seems Word has a limit here, so that in case the table width is quite + // close to the text area width, then it won't perform a wrapping, even in + // case the content (e.g. an empty paragraph) would fit. The magic constant + // here represents this limit. + const sal_Int32 nMagicNumber = 469; + + // If the table's width is smaller than the text area width, text might + // be next to the table and so it should behave as a floating table. + if ( (nTableWidth + nMagicNumber) < nTextAreaWidth ) + return true; + + // If the position is relative to the edge of the page, then we need to check the whole + // page width to see whether text can fit next to the table. + if ( nHoriOrientRelation == text::RelOrientation::PAGE_FRAME ) + { + // If the table is wide enough so that no text fits next to it, then don't create a fly + // for the table: no wrapping will be performed anyway, but multi-page + // tables will be broken. + if ((nTableWidth + nMagicNumber) < (nPageWidth - std::min(GetLeftMargin(), GetRightMargin()))) + return true; + } + + // If there are columns, always create the fly, otherwise the columns would + // restrict geometry of the table. + if ( ColumnCount() > 1 ) + return true; + + return false; +} + +void SectionPropertyMap::InheritOrFinalizePageStyles( DomainMapper_Impl& rDM_Impl ) +{ + // if no new styles have been created for this section, inherit from the previous section, + // otherwise apply this section's settings to the new style. + // Ensure that FollowPage is inherited first - otherwise GetPageStyle may auto-create a follow when checking FirstPage. + SectionPropertyMap* pLastContext = rDM_Impl.GetLastSectionContext(); + //tdf124637 TODO: identify and skip special sections (like footnotes/endnotes) + if ( pLastContext && m_sFollowPageStyleName.isEmpty() ) + m_sFollowPageStyleName = pLastContext->GetPageStyleName(); + else + { + HandleMarginsHeaderFooter( /*bFirst=*/false, rDM_Impl ); + GetPageStyle( rDM_Impl, /*bFirst=*/false ); + if ( rDM_Impl.IsNewDoc() && m_aFollowPageStyle.is() ) + ApplyProperties_( m_aFollowPageStyle ); + } + + // FirstPageStyle may only be inherited if it will not be used or re-linked to a different follow + if ( !m_bTitlePage && pLastContext && m_sFirstPageStyleName.isEmpty() ) + m_sFirstPageStyleName = pLastContext->GetPageStyleName( /*bFirst=*/true ); + else + { + HandleMarginsHeaderFooter( /*bFirst=*/true, rDM_Impl ); + GetPageStyle( rDM_Impl, /*bFirst=*/true ); + if ( rDM_Impl.IsNewDoc() && m_aFirstPageStyle.is() ) + ApplyProperties_( m_aFirstPageStyle ); + + // Chain m_aFollowPageStyle to be after m_aFirstPageStyle + m_aFirstPageStyle->setPropertyValue( "FollowStyle", uno::Any( m_sFollowPageStyleName ) ); + } +} + +void SectionPropertyMap::HandleIncreasedAnchoredObjectSpacing(DomainMapper_Impl& rDM_Impl) +{ + // Ignore Word 2010 and older. + if (rDM_Impl.GetSettingsTable()->GetWordCompatibilityMode() < 15) + return; + + sal_Int32 nPageWidth = GetPageWidth(); + sal_Int32 nTextAreaWidth = nPageWidth - GetLeftMargin() - GetRightMargin(); + + std::vector<AnchoredObjectsInfo>& rAnchoredObjectAnchors = rDM_Impl.m_aAnchoredObjectAnchors; + for (const auto& rAnchor : rAnchoredObjectAnchors) + { + // Ignore this paragraph when there are not enough shapes to trigger the Word bug we + // emulate. + if (rAnchor.m_aAnchoredObjects.size() < 4) + continue; + + // Ignore this paragraph if none of the objects are wrapped in the background. + sal_Int32 nOpaqueCount = 0; + for (const auto& rAnchored : rAnchor.m_aAnchoredObjects) + { + // Ignore inline objects stored only for redlining. + if (rAnchored.m_xRedlineForInline) + continue; + + uno::Reference<beans::XPropertySet> xShape(rAnchored.m_xAnchoredObject, uno::UNO_QUERY); + if (!xShape.is()) + { + continue; + } + + bool bOpaque = true; + xShape->getPropertyValue("Opaque") >>= bOpaque; + if (!bOpaque) + { + ++nOpaqueCount; + } + } + if (nOpaqueCount < 1) + { + continue; + } + + // Analyze the anchored objects of this paragraph, now that we know the + // page width. + sal_Int32 nShapesWidth = 0; + for (const auto& rAnchored : rAnchor.m_aAnchoredObjects) + { + uno::Reference<drawing::XShape> xShape(rAnchored.m_xAnchoredObject, uno::UNO_QUERY); + if (!xShape.is()) + continue; + + uno::Reference<beans::XPropertySet> xPropertySet(xShape, uno::UNO_QUERY); + if (!xPropertySet.is()) + continue; + + // Ignore objects with no wrapping. + text::WrapTextMode eWrap = text::WrapTextMode_THROUGH; + xPropertySet->getPropertyValue("Surround") >>= eWrap; + if (eWrap == text::WrapTextMode_THROUGH) + continue; + + // Use the original left margin, in case GraphicImport::lcl_sprm() reduced the doc model + // one to 0. + sal_Int32 nLeftMargin = rAnchored.m_nLeftMargin; + sal_Int32 nRightMargin = 0; + xPropertySet->getPropertyValue("RightMargin") >>= nRightMargin; + nShapesWidth += xShape->getSize().Width + nLeftMargin + nRightMargin; + } + + // Ignore cases when we have enough horizontal space for the shapes. + if (nTextAreaWidth > nShapesWidth) + continue; + + sal_Int32 nHeight = 0; + for (const auto& rAnchored : rAnchor.m_aAnchoredObjects) + { + uno::Reference<drawing::XShape> xShape(rAnchored.m_xAnchoredObject, uno::UNO_QUERY); + if (!xShape.is()) + continue; + + nHeight += xShape->getSize().Height; + } + + uno::Reference<beans::XPropertySet> xParagraph(rAnchor.m_xParagraph, uno::UNO_QUERY); + if (xParagraph.is()) + { + sal_Int32 nTopMargin = 0; + xParagraph->getPropertyValue("ParaTopMargin") >>= nTopMargin; + // Increase top spacing of the paragraph to match Word layout + // behavior. + nTopMargin = std::max(nTopMargin, nHeight); + xParagraph->setPropertyValue("ParaTopMargin", uno::Any(nTopMargin)); + } + } + rAnchoredObjectAnchors.clear(); +} + +void SectionPropertyMap::CloseSectionGroup( DomainMapper_Impl& rDM_Impl ) +{ + SectionPropertyMap* pPrevSection = rDM_Impl.GetLastSectionContext(); + + // The default section type is nextPage. + if ( m_nBreakType == -1 ) + m_nBreakType = NS_ooxml::LN_Value_ST_SectionMark_nextPage; + else if ( m_nBreakType == NS_ooxml::LN_Value_ST_SectionMark_nextColumn ) + { + // Word 2013+ seems to treat a section column break as a page break all the time. + // It always acts like a page break if there are no columns, or a different number of columns. + // Also, if this is the first section, the break type is basically irrelevant - works best as nextPage. + if ( rDM_Impl.GetSettingsTable()->GetWordCompatibilityMode() > 14 + || !pPrevSection + || m_nColumnCount < 2 + || m_nColumnCount != pPrevSection->ColumnCount() + ) + { + m_nBreakType = NS_ooxml::LN_Value_ST_SectionMark_nextPage; + } + } + else if ( m_nBreakType == NS_ooxml::LN_Value_ST_SectionMark_continuous ) + { + // if page orientation differs from previous section, it can't be treated as continuous + if ( pPrevSection ) + { + bool bIsLandscape = false; + std::optional< PropertyMap::Property > pProp = getProperty( PROP_IS_LANDSCAPE ); + if ( pProp ) + pProp->second >>= bIsLandscape; + + bool bPrevIsLandscape = false; + pProp = pPrevSection->getProperty( PROP_IS_LANDSCAPE ); + if ( pProp ) + pProp->second >>= bPrevIsLandscape; + + if ( bIsLandscape != bPrevIsLandscape ) + m_nBreakType = NS_ooxml::LN_Value_ST_SectionMark_nextPage; + } + } + + // Text area width is known at the end of a section: decide if tables should be converted or not. + std::vector<FloatingTableInfo>& rPendingFloatingTables = rDM_Impl.m_aPendingFloatingTables; + for ( FloatingTableInfo & rInfo : rPendingFloatingTables ) + { + rInfo.m_nBreakType = m_nBreakType; + if ( FloatingTableConversion( rDM_Impl, rInfo ) ) + { + uno::Reference<text::XTextAppendAndConvert> xBodyText( + rInfo.m_bConvertToFloatingInFootnote + ? rInfo.m_xStart->getText() + : rDM_Impl.GetBodyText(), uno::UNO_QUERY ); + std::deque<css::uno::Any> aFramedRedlines = rDM_Impl.m_aStoredRedlines[StoredRedlines::FRAME]; + try + { + // convert redline ranges to cursor movement and character length + std::vector<sal_Int32> redPos, redLen; + std::vector<OUString> redCell; + std::vector<OUString> redTable; + for( size_t i = 0; i < aFramedRedlines.size(); i+=3) + { + uno::Reference<text::XText> xCell; + uno::Reference< text::XTextRange > xRange; + aFramedRedlines[i] >>= xRange; + uno::Reference< beans::XPropertySet > xRangeProperties; + if ( xRange.is() ) + { + OUString sTableName; + OUString sCellName; + xRangeProperties.set( xRange, uno::UNO_QUERY_THROW ); + if (xRangeProperties->getPropertySetInfo()->hasPropertyByName("TextTable")) + { + uno::Any aTable = xRangeProperties->getPropertyValue("TextTable"); + if ( aTable != uno::Any() ) + { + uno::Reference<text::XTextTable> xTable; + aTable >>= xTable; + uno::Reference<beans::XPropertySet> xTableProperties(xTable, uno::UNO_QUERY); + xTableProperties->getPropertyValue("TableName") >>= sTableName; + } + if (xRangeProperties->getPropertySetInfo()->hasPropertyByName("Cell")) + { + uno::Any aCell = xRangeProperties->getPropertyValue("Cell"); + if ( aCell != uno::Any() ) + { + aCell >>= xCell; + uno::Reference<beans::XPropertySet> xCellProperties(xCell, uno::UNO_QUERY); + xCellProperties->getPropertyValue("CellName") >>= sCellName; + } + } + } + redTable.push_back(sTableName); + redCell.push_back(sCellName); + bool bOk = false; + if (!sTableName.isEmpty() && !sCellName.isEmpty()) + { + uno::Reference<text::XTextCursor> xRangeCursor = xCell->createTextCursorByRange( xRange ); + if ( xRangeCursor.is() ) + { + bOk = true; + sal_Int32 nLen = xRange->getString().getLength(); + redLen.push_back(nLen); + xRangeCursor->gotoStart(true); + redPos.push_back(xRangeCursor->getString().getLength() - nLen); + } + } + if (!bOk) + { + // missing cell or failed createTextCursorByRange() + redLen.push_back(-1); + redPos.push_back(-1); + } + } + } + + const uno::Reference< text::XTextContent >& xTextContent = + xBodyText->convertToTextFrame(rInfo.m_xStart, rInfo.m_xEnd, + rInfo.m_aFrameProperties); + + // paragraph of the anchoring point of the floating table needs zero top and bottom + // margins, if the table was a not floating table in the footnote, otherwise + // docDefault margins could result bigger vertical spaces around the table + if ( rInfo.m_bConvertToFloatingInFootnote && xTextContent.is() ) + { + uno::Reference<beans::XPropertySet> xParagraph( + xTextContent->getAnchor(), uno::UNO_QUERY); + if ( xParagraph.is() ) + { + xParagraph->setPropertyValue("ParaTopMargin", + uno::Any(static_cast<sal_Int32>(0))); + xParagraph->setPropertyValue("ParaBottomMargin", + uno::Any(static_cast<sal_Int32>(0))); + } + } + + uno::Reference<text::XTextTablesSupplier> xTextDocument(rDM_Impl.GetTextDocument(), uno::UNO_QUERY); + uno::Reference<container::XNameAccess> xTables = xTextDocument->getTextTables(); + for( size_t i = 0; i < aFramedRedlines.size(); i+=3) + { + OUString sType; + beans::PropertyValues aRedlineProperties( 3 ); + // skip failed createTextCursorByRange() + if (redPos[i/3] == -1) + continue; + aFramedRedlines[i+1] >>= sType; + aFramedRedlines[i+2] >>= aRedlineProperties; + uno::Reference<text::XTextTable> xTable(xTables->getByName(redTable[i/3]), uno::UNO_QUERY); + uno::Reference<text::XText> xCell(xTable->getCellByName(redCell[i/3]), uno::UNO_QUERY); + uno::Reference<text::XTextCursor> xCrsr = xCell->createTextCursor(); + xCrsr->goRight(redPos[i/3], false); + xCrsr->goRight(redLen[i/3], true); + uno::Reference < text::XRedline > xRedline( xCrsr, uno::UNO_QUERY_THROW ); + try + { + xRedline->makeRedline( sType, aRedlineProperties ); + } + catch (const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("writerfilter", "makeRedline() failed"); + } + } + } + catch (const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("writerfilter", "convertToTextFrame() failed"); + } + + aFramedRedlines.clear(); + } + } + rPendingFloatingTables.clear(); + + try + { + HandleIncreasedAnchoredObjectSpacing(rDM_Impl); + } + catch (const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("writerfilter", "HandleIncreasedAnchoredObjectSpacing() failed"); + } + + if ( m_nLnnMod ) + { + bool bFirst = rDM_Impl.IsLineNumberingSet(); + rDM_Impl.SetLineNumbering( m_nLnnMod, m_nLnc, m_ndxaLnn ); + if ( m_nLnnMin > 0 || (bFirst && m_nLnc == NS_ooxml::LN_Value_ST_LineNumberRestart_newSection) ) + { + //set the starting value at the beginning of the section + try + { + uno::Reference< beans::XPropertySet > xRangeProperties; + if ( m_xStartingRange.is() ) + { + xRangeProperties.set( m_xStartingRange, uno::UNO_QUERY_THROW ); + } + else + { + //set the start value at the beginning of the document + xRangeProperties.set( rDM_Impl.GetTextDocument()->getText()->getStart(), uno::UNO_QUERY_THROW ); + } + // Writer is 1-based, Word is 0-based. + xRangeProperties->setPropertyValue( + getPropertyName(PROP_PARA_LINE_NUMBER_START_VALUE), + uno::Any(m_nLnnMin + 1)); + } + catch ( const uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("writerfilter.dmapper", "Exception in SectionPropertyMap::CloseSectionGroup"); + } + } + } + + if (m_nBreakType == NS_ooxml::LN_Value_ST_SectionMark_continuous + && !rDM_Impl.IsInComments()) + { + //todo: insert a section or access the already inserted section + uno::Reference< beans::XPropertySet > xSection = + rDM_Impl.appendTextSectionAfter( m_xStartingRange ); + if ( xSection.is() ) + { + if ( m_nColumnCount > 1 ) + ApplyColumnProperties( xSection, rDM_Impl ); + + ApplyProtectionProperties( xSection, rDM_Impl ); + } + + try + { + InheritOrFinalizePageStyles( rDM_Impl ); + ApplySectionProperties( xSection, rDM_Impl ); //depends on InheritOrFinalizePageStyles + OUString aName = m_bTitlePage ? m_sFirstPageStyleName : m_sFollowPageStyleName; + uno::Reference< beans::XPropertySet > xRangeProperties( lcl_GetRangeProperties( m_bIsFirstSection, rDM_Impl, m_xStartingRange ) ); + if ( m_bIsFirstSection && !aName.isEmpty() && xRangeProperties.is() ) + { + xRangeProperties->setPropertyValue( getPropertyName( PROP_PAGE_DESC_NAME ), uno::Any( aName ) ); + } + else if ((!m_bFirstPageHeaderLinkToPrevious || + !m_bFirstPageFooterLinkToPrevious || + !m_bDefaultHeaderLinkToPrevious || + !m_bDefaultFooterLinkToPrevious || + !m_bEvenPageHeaderLinkToPrevious || + !m_bEvenPageFooterLinkToPrevious) + && rDM_Impl.GetCurrentXText()) + { // find a node in the section that has a page break and change + // it to apply the page style; see "nightmare scenario" in + // wwSectionManager::InsertSegments() + auto xTextAppend = rDM_Impl.GetCurrentXText(); + uno::Reference<container::XEnumerationAccess> const xCursor( + xTextAppend->createTextCursorByRange( + uno::Reference<text::XTextContent>(xSection, uno::UNO_QUERY_THROW)->getAnchor()), + uno::UNO_QUERY_THROW); + uno::Reference<container::XEnumeration> const xEnum( + xCursor->createEnumeration()); + bool isFound = false; + while (xEnum->hasMoreElements()) + { + uno::Reference<beans::XPropertySet> xElem; + xEnum->nextElement() >>= xElem; + if (xElem->getPropertySetInfo()->hasPropertyByName("BreakType")) + { + style::BreakType bt; + if ((xElem->getPropertyValue("BreakType") >>= bt) + && bt == style::BreakType_PAGE_BEFORE) + { + // tdf#112201: do *not* use m_sFirstPageStyleName here! + xElem->setPropertyValue(getPropertyName(PROP_PAGE_DESC_NAME), + uno::Any(m_sFollowPageStyleName)); + isFound = true; + break; + } + } + } + uno::Reference<text::XParagraphCursor> const xPCursor(xCursor, + uno::UNO_QUERY_THROW); + float fCharHeight = 0; + if (!isFound) + { // HACK: try the last paragraph of the previous section + xPCursor->gotoPreviousParagraph(false); + uno::Reference<beans::XPropertySet> const xPSCursor(xCursor, uno::UNO_QUERY_THROW); + style::BreakType bt; + if ((xPSCursor->getPropertyValue("BreakType") >>= bt) + && bt == style::BreakType_PAGE_BEFORE) + { + xPSCursor->setPropertyValue(getPropertyName(PROP_PAGE_DESC_NAME), + uno::Any(m_sFollowPageStyleName)); + isFound = true; + } + else + { + xPSCursor->getPropertyValue("CharHeight") >>= fCharHeight; + } + } + if (!isFound && fCharHeight <= 1.0) + { + // If still not found, see if the last paragraph is ~invisible, and work with + // the last-in-practice paragraph. + xPCursor->gotoPreviousParagraph(false); + uno::Reference<beans::XPropertySet> xPropertySet(xCursor, uno::UNO_QUERY_THROW); + OUString aPageDescName; + if ((xPropertySet->getPropertyValue("PageDescName") >>= aPageDescName) + && !aPageDescName.isEmpty()) + { + uno::Reference<beans::XPropertySet> xPageStyle( + rDM_Impl.GetPageStyles()->getByName(aPageDescName), uno::UNO_QUERY); + xPageStyle->setPropertyValue("FollowStyle", + uno::Any(m_sFollowPageStyleName)); + } + } + } + } + catch ( const uno::Exception& ) + { + SAL_WARN( "writerfilter", "failed to set PageDescName!" ); + } + } + // If the section is of type "New column" (0x01), then simply insert a column break. + // But only if there actually are columns on the page, otherwise a column break + // seems to be handled like a page break by MSO. + else if (m_nBreakType == NS_ooxml::LN_Value_ST_SectionMark_nextColumn + && m_nColumnCount > 1 && !rDM_Impl.IsInComments()) + { + try + { + InheritOrFinalizePageStyles( rDM_Impl ); + /*TODO tdf#135343: Just inserting a column break sounds like the right idea, but the implementation is wrong. + * Somehow, the previous column section needs to be extended to cover this new text. + * Currently, it is completely broken, producing a no-column section that starts on a new page. + */ + uno::Reference< beans::XPropertySet > xRangeProperties; + if ( m_xStartingRange.is() ) + { + xRangeProperties.set( m_xStartingRange, uno::UNO_QUERY_THROW ); + } + else + { + //set the start value at the beginning of the document + xRangeProperties.set( rDM_Impl.GetTextDocument()->getText()->getStart(), uno::UNO_QUERY_THROW ); + } + xRangeProperties->setPropertyValue( getPropertyName( PROP_BREAK_TYPE ), uno::Any( style::BreakType_COLUMN_BEFORE ) ); + } + catch ( const uno::Exception& ) {} + } + else if (!rDM_Impl.IsInComments()) + { + uno::Reference< beans::XPropertySet > xSection; + ApplyProtectionProperties( xSection, rDM_Impl ); + + //get the properties and create appropriate page styles + uno::Reference< beans::XPropertySet > xFollowPageStyle; + //This part certainly is not needed for footnotes, so don't create unused page styles. + if ( !rDM_Impl.IsInFootOrEndnote() ) + { + xFollowPageStyle.set( GetPageStyle( rDM_Impl, false ) ); + + HandleMarginsHeaderFooter(/*bFirstPage=*/false, rDM_Impl ); + } + + if ( rDM_Impl.GetSettingsTable()->GetMirrorMarginSettings() ) + { + Insert( PROP_PAGE_STYLE_LAYOUT, uno::Any( style::PageStyleLayout_MIRRORED ) ); + } + uno::Reference< text::XTextColumns > xColumns; + if ( m_nColumnCount > 1 ) + { + // prefer setting column properties into a section, not a page style if at all possible. + if ( !xSection.is() ) + xSection = rDM_Impl.appendTextSectionAfter( m_xStartingRange ); + if ( xSection.is() ) + ApplyColumnProperties( xSection, rDM_Impl ); + else if ( xFollowPageStyle.is() ) + xColumns = ApplyColumnProperties( xFollowPageStyle, rDM_Impl ); + } + + // these BreakTypes are effectively page-breaks: don't evenly distribute text in columns before a page break; + if ( pPrevSection && pPrevSection->ColumnCount() ) + pPrevSection->DontBalanceTextColumns(); + + //prepare text grid properties + sal_Int32 nHeight = 1; + std::optional< PropertyMap::Property > pProp = getProperty( PROP_HEIGHT ); + if ( pProp ) + pProp->second >>= nHeight; + + sal_Int32 nWidth = 1; + pProp = getProperty( PROP_WIDTH ); + if ( pProp ) + pProp->second >>= nWidth; + + sal_Int16 nWritingMode = text::WritingMode2::LR_TB; + pProp = getProperty( PROP_WRITING_MODE ); + if ( pProp ) + pProp->second >>= nWritingMode; + + sal_Int32 nTextAreaHeight = nWritingMode == text::WritingMode2::LR_TB ? + nHeight - m_nTopMargin - m_nBottomMargin : + nWidth - m_nLeftMargin - m_nRightMargin; + + sal_Int32 nGridLinePitch = m_nGridLinePitch; + //sep.dyaLinePitch + if ( nGridLinePitch < 1 || nGridLinePitch > 31680 ) + { + SAL_WARN( "writerfilter", "sep.dyaLinePitch outside legal range: " << nGridLinePitch ); + nGridLinePitch = 1; + } + + const sal_Int32 nGridLines = nTextAreaHeight / nGridLinePitch; + sal_Int16 nGridType = m_nGridType; + if ( nGridLines >= 0 && nGridLines <= SAL_MAX_INT16 ) + Insert( PROP_GRID_LINES, uno::Any( sal_Int16(nGridLines) ) ); + else + nGridType = text::TextGridMode::NONE; + + // PROP_GRID_MODE + if ( nGridType == text::TextGridMode::LINES_AND_CHARS ) + { + if (!m_nDxtCharSpace) + nGridType = text::TextGridMode::LINES; + else + Insert( PROP_GRID_SNAP_TO_CHARS, uno::Any( m_bGridSnapToChars ) ); + } + + Insert( PROP_GRID_MODE, uno::Any( nGridType ) ); + + sal_Int32 nCharWidth = 423; //240 twip/ 12 pt + const StyleSheetEntryPtr pEntry = rDM_Impl.GetStyleSheetTable()->FindStyleSheetByConvertedStyleName( u"Standard" ); + if ( pEntry ) + { + std::optional< PropertyMap::Property > pPropHeight = pEntry->pProperties->getProperty( PROP_CHAR_HEIGHT_ASIAN ); + if ( pPropHeight ) + { + double fHeight = 0; + if ( pPropHeight->second >>= fHeight ) + nCharWidth = ConversionHelper::convertTwipToMM100( static_cast<tools::Long>(fHeight * 20.0 + 0.5) ); + } + } + + //dxtCharSpace + if ( m_nDxtCharSpace ) + { + sal_Int32 nCharSpace = m_nDxtCharSpace; + //main lives in top 20 bits, and is signed. + sal_Int32 nMain = (nCharSpace & 0xFFFFF000); + nMain /= 0x1000; + nCharWidth += ConversionHelper::convertTwipToMM100( nMain * 20 ); + + sal_Int32 nFraction = (nCharSpace & 0x00000FFF); + nFraction = (nFraction * 20) / 0xFFF; + nCharWidth += ConversionHelper::convertTwipToMM100( nFraction ); + } + + if ( m_nPageNumberType >= 0 ) + Insert( PROP_NUMBERING_TYPE, uno::Any( m_nPageNumberType ) ); + + // #i119558#, force to set document as standard page mode, + // refer to ww8 import process function "SwWW8ImplReader::SetDocumentGrid" + try + { + uno::Reference< beans::XPropertySet > xDocProperties; + xDocProperties.set( rDM_Impl.GetTextDocument(), uno::UNO_QUERY_THROW ); + Insert(PROP_GRID_STANDARD_MODE, uno::Any(true)); + xDocProperties->setPropertyValue("DefaultPageMode", uno::Any(false)); + } + catch ( const uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("writerfilter.dmapper", "Exception in SectionPropertyMap::CloseSectionGroup"); + } + + Insert( PROP_GRID_BASE_HEIGHT, uno::Any( nGridLinePitch ) ); + Insert( PROP_GRID_BASE_WIDTH, uno::Any( nCharWidth ) ); + Insert( PROP_GRID_RUBY_HEIGHT, uno::Any( sal_Int32( 0 ) ) ); + + if ( rDM_Impl.IsNewDoc() && xFollowPageStyle.is() ) + ApplyProperties_( xFollowPageStyle ); + + //todo: creating a "First Page" style depends on HasTitlePage and _fFacingPage_ + if ( m_bTitlePage ) + { + CopyLastHeaderFooter( true, rDM_Impl ); + PrepareHeaderFooterProperties( true ); + uno::Reference< beans::XPropertySet > xFirstPageStyle = GetPageStyle( + rDM_Impl, true ); + if ( rDM_Impl.IsNewDoc() ) + ApplyProperties_( xFirstPageStyle ); + + if ( xColumns.is() ) + xFirstPageStyle->setPropertyValue( + getPropertyName( PROP_TEXT_COLUMNS ), uno::Any( xColumns ) ); + } + + ApplyBorderToPageStyles( rDM_Impl, m_eBorderApply, m_eBorderOffsetFrom ); + + try + { + //now apply this break at the first paragraph of this section + uno::Reference< beans::XPropertySet > xRangeProperties( lcl_GetRangeProperties( m_bIsFirstSection, rDM_Impl, m_xStartingRange ) ); + + // Handle page breaks with odd/even page numbering. We need to use an extra page style for setting the page style + // to left/right, because if we set it to the normal style, we'd set it to "First Page"/"Default Style", which would + // break them (all default pages would be only left or right). + if ( m_nBreakType == NS_ooxml::LN_Value_ST_SectionMark_evenPage || m_nBreakType == NS_ooxml::LN_Value_ST_SectionMark_oddPage ) + { + OUString* pageStyle = m_bTitlePage ? &m_sFirstPageStyleName : &m_sFollowPageStyleName; + OUString evenOddStyleName = rDM_Impl.GetUnusedPageStyleName(); + uno::Reference< beans::XPropertySet > evenOddStyle( + rDM_Impl.GetTextFactory()->createInstance( "com.sun.star.style.PageStyle" ), + uno::UNO_QUERY ); + // Unfortunately using setParent() does not work for page styles, so make a deep copy of the page style. + uno::Reference< beans::XPropertySet > pageProperties( m_bTitlePage ? m_aFirstPageStyle : m_aFollowPageStyle ); + uno::Reference< beans::XPropertySetInfo > pagePropertiesInfo( pageProperties->getPropertySetInfo() ); + const uno::Sequence< beans::Property > propertyList( pagePropertiesInfo->getProperties() ); + // Ignore write-only properties. + static const o3tl::sorted_vector<OUString> aDenylist + = { "FooterBackGraphicURL", "BackGraphicURL", "HeaderBackGraphicURL" }; + for ( const auto& rProperty : propertyList ) + { + if ( (rProperty.Attributes & beans::PropertyAttribute::READONLY) == 0 ) + { + if (aDenylist.find(rProperty.Name) == aDenylist.end()) + evenOddStyle->setPropertyValue( + rProperty.Name, + pageProperties->getPropertyValue(rProperty.Name)); + } + } + evenOddStyle->setPropertyValue( "FollowStyle", uno::Any( *pageStyle ) ); + rDM_Impl.GetPageStyles()->insertByName( evenOddStyleName, uno::Any( evenOddStyle ) ); + evenOddStyle->setPropertyValue( "HeaderIsOn", uno::Any( false ) ); + evenOddStyle->setPropertyValue( "FooterIsOn", uno::Any( false ) ); + CopyHeaderFooter(rDM_Impl, pageProperties, evenOddStyle); + *pageStyle = evenOddStyleName; // And use it instead of the original one (which is set as follow of this one). + if ( m_nBreakType == NS_ooxml::LN_Value_ST_SectionMark_evenPage ) + evenOddStyle->setPropertyValue( getPropertyName( PROP_PAGE_STYLE_LAYOUT ), uno::Any( style::PageStyleLayout_LEFT ) ); + else if ( m_nBreakType == NS_ooxml::LN_Value_ST_SectionMark_oddPage ) + evenOddStyle->setPropertyValue( getPropertyName( PROP_PAGE_STYLE_LAYOUT ), uno::Any( style::PageStyleLayout_RIGHT ) ); + } + + if (rDM_Impl.m_xAltChunkStartingRange.is()) + { + xRangeProperties.set(rDM_Impl.m_xAltChunkStartingRange, uno::UNO_QUERY); + } + if (xRangeProperties.is() && (rDM_Impl.IsNewDoc() || rDM_Impl.IsAltChunk())) + { + // Avoid setting page style in case of autotext: so inserting the autotext at the + // end of the document does not introduce an unwanted page break. + // Also avoid setting the page style at the very beginning if it still is the default page style. + const OUString sPageStyle = m_bTitlePage ? m_sFirstPageStyleName : m_sFollowPageStyleName; + if (!rDM_Impl.IsReadGlossaries() + && !rDM_Impl.IsInFootOrEndnote() + && !(m_bIsFirstSection && sPageStyle == getPropertyName( PROP_STANDARD ) && m_nPageNumber < 0) + ) + { + xRangeProperties->setPropertyValue( + getPropertyName( PROP_PAGE_DESC_NAME ), + uno::Any(sPageStyle) ); + } + + if (0 <= m_nPageNumber) + { + sal_Int16 nPageNumber = static_cast< sal_Int16 >(m_nPageNumber); + xRangeProperties->setPropertyValue(getPropertyName(PROP_PAGE_NUMBER_OFFSET), + uno::Any(nPageNumber)); + } + } + } + catch ( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "writerfilter", "SectionPropertyMap::CloseSectionGroup" ); + } + } + + // Now that the margins are known, resize relative width shapes because some shapes in LO do not support percentage-sizes + sal_Int32 nParagraphWidth = GetPageWidth() - m_nLeftMargin - m_nRightMargin; + if ( m_nColumnCount > 1 ) + { + // skip custom-width columns since we don't know what column the shape is in. + if ( !m_aColWidth.empty() ) + nParagraphWidth = 0; + else + nParagraphWidth = (nParagraphWidth - (m_nColumnDistance * (m_nColumnCount - 1))) / m_nColumnCount; + } + if ( nParagraphWidth > 0 ) + { + const OUString sPropRelativeWidth = getPropertyName(PROP_RELATIVE_WIDTH); + for ( const auto& xShape : m_xRelativeWidthShapes ) + { + const uno::Reference<beans::XPropertySet> xShapePropertySet( xShape, uno::UNO_QUERY ); + if ( xShapePropertySet->getPropertySetInfo()->hasPropertyByName(sPropRelativeWidth) ) + { + sal_uInt16 nPercent = 0; + try + { + xShapePropertySet->getPropertyValue(sPropRelativeWidth) >>= nPercent; + } + catch (const css::uno::RuntimeException& e) + { + // May happen e.g. when text frame has no frame format + // See sw/qa/extras/ooxmlimport/data/n779627.docx + SAL_WARN("writerfilter", "Getting relative width failed. " << e.Message); + } + if ( nPercent ) + { + const sal_Int32 nWidth = nParagraphWidth * nPercent / 100; + xShape->setSize( awt::Size( nWidth, xShape->getSize().Height ) ); + } + } + } + } + m_xRelativeWidthShapes.clear(); + + rDM_Impl.SetIsLastSectionGroup( false ); + rDM_Impl.SetIsFirstParagraphInSection( true ); + + if ( !rDM_Impl.IsInFootOrEndnote() && !rDM_Impl.IsInComments() ) + { + rDM_Impl.m_bHasFtn = false; + rDM_Impl.m_bHasFtnSep = false; + } +} + +// Clear the flag that says we should take the header/footer content from +// the previous section. This should be called when we encounter a header +// or footer definition for this section. +void SectionPropertyMap::ClearHeaderFooterLinkToPrevious( bool bHeader, PageType eType ) +{ + if ( bHeader ) + { + switch ( eType ) + { + case PAGE_FIRST: m_bFirstPageHeaderLinkToPrevious = false; break; + case PAGE_LEFT: m_bEvenPageHeaderLinkToPrevious = false; break; + case PAGE_RIGHT: m_bDefaultHeaderLinkToPrevious = false; break; + // no default case as all enumeration values have been covered + } + } + else + { + switch ( eType ) + { + case PAGE_FIRST: m_bFirstPageFooterLinkToPrevious = false; break; + case PAGE_LEFT: m_bEvenPageFooterLinkToPrevious = false; break; + case PAGE_RIGHT: m_bDefaultFooterLinkToPrevious = false; break; + } + } +} + +namespace { + +class NamedPropertyValue +{ +private: + OUString m_aName; + +public: + explicit NamedPropertyValue( const OUString& i_aStr ) + : m_aName( i_aStr ) + { + } + + bool operator() ( beans::PropertyValue const & aVal ) + { + return aVal.Name == m_aName; + } +}; + +} + +void SectionPropertyMap::ApplyProperties_( const uno::Reference< beans::XPropertySet >& xStyle ) +{ + uno::Reference< beans::XMultiPropertySet > const xMultiSet( xStyle, uno::UNO_QUERY ); + + std::vector< OUString > vNames; + std::vector< uno::Any > vValues; + { + // Convert GetPropertyValues() value into something useful + const uno::Sequence< beans::PropertyValue > vPropVals = GetPropertyValues(); + + //Temporarily store the items that are in grab bags + uno::Sequence< beans::PropertyValue > vCharVals; + uno::Sequence< beans::PropertyValue > vParaVals; + const beans::PropertyValue* pCharGrabBag = std::find_if( vPropVals.begin(), vPropVals.end(), NamedPropertyValue( "CharInteropGrabBag" ) ); + if ( pCharGrabBag != vPropVals.end() ) + (pCharGrabBag->Value) >>= vCharVals; + const beans::PropertyValue* pParaGrabBag = std::find_if( vPropVals.begin(), vPropVals.end(), NamedPropertyValue( "ParaInteropGrabBag" ) ); + if ( pParaGrabBag != vPropVals.end() ) + (pParaGrabBag->Value) >>= vParaVals; + + for ( const beans::PropertyValue* pIter = vPropVals.begin(); pIter != vPropVals.end(); ++pIter ) + { + if ( pIter != pCharGrabBag && pIter != pParaGrabBag + && pIter->Name != "IsProtected" //section-only property + ) + { + vNames.push_back( pIter->Name ); + vValues.push_back( pIter->Value ); + } + } + for ( const beans::PropertyValue & v : std::as_const(vCharVals) ) + { + vNames.push_back( v.Name ); + vValues.push_back( v.Value ); + } + for ( const beans::PropertyValue & v : std::as_const(vParaVals) ) + { + vNames.push_back( v.Name ); + vValues.push_back( v.Value ); + } + } + if ( xMultiSet.is() ) + { + try + { + xMultiSet->setPropertyValues( comphelper::containerToSequence( vNames ), comphelper::containerToSequence( vValues ) ); + return; + } + catch ( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "writerfilter", "SectionPropertyMap::ApplyProperties_" ); + } + } + for ( size_t i = 0; i < vNames.size(); ++i ) + { + try + { + if ( xStyle.is() ) + xStyle->setPropertyValue( vNames[i], vValues[i] ); + } + catch ( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "writerfilter", "SectionPropertyMap::ApplyProperties_" ); + } + } +} + +sal_Int32 SectionPropertyMap::GetPageWidth() const +{ + return getProperty( PROP_WIDTH )->second.get<sal_Int32>(); +} + +StyleSheetPropertyMap::StyleSheetPropertyMap() + : mnListLevel( -1 ) + , mnOutlineLevel( -1 ) +{ +} + +ParagraphProperties::ParagraphProperties() + : m_bFrameMode( false ) + , m_nDropCap( NS_ooxml::LN_Value_doc_ST_DropCap_none ) + , m_nLines( 0 ) + , m_w( -1 ) + , m_h( -1 ) + , m_nWrap( text::WrapTextMode::WrapTextMode_MAKE_FIXED_SIZE ) + , m_hAnchor( -1 ) + , m_vAnchor( -1 ) + , m_x( -1 ) + , m_bxValid( false ) + , m_y( -1 ) + , m_byValid( false ) + , m_hSpace( -1 ) + , m_vSpace( -1 ) + , m_hRule( -1 ) + , m_xAlign( -1 ) + , m_yAlign( -1 ) + , m_nDropCapLength( 0 ) +{ +} + +bool ParagraphProperties::operator==( const ParagraphProperties& rCompare ) +{ + return ( m_bFrameMode == rCompare.m_bFrameMode && + m_nDropCap == rCompare.m_nDropCap && + m_nLines == rCompare.m_nLines && + m_w == rCompare.m_w && + m_h == rCompare.m_h && + m_nWrap == rCompare.m_nWrap && + m_hAnchor == rCompare.m_hAnchor && + m_vAnchor == rCompare.m_vAnchor && + m_x == rCompare.m_x && + m_bxValid == rCompare.m_bxValid && + m_y == rCompare.m_y && + m_byValid == rCompare.m_byValid && + m_hSpace == rCompare.m_hSpace && + m_vSpace == rCompare.m_vSpace && + m_hRule == rCompare.m_hRule && + m_xAlign == rCompare.m_xAlign && + m_yAlign == rCompare.m_yAlign ); +} + +void ParagraphProperties::ResetFrameProperties() +{ + m_bFrameMode = false; + m_nDropCap = NS_ooxml::LN_Value_doc_ST_DropCap_none; + m_nLines = 0; + m_w = -1; + m_h = -1; + m_nWrap = text::WrapTextMode::WrapTextMode_MAKE_FIXED_SIZE; + m_hAnchor = -1; + m_vAnchor = -1; + m_x = -1; + m_bxValid = false; + m_y = -1; + m_byValid = false; + m_hSpace = -1; + m_vSpace = -1; + m_hRule = -1; + m_xAlign = -1; + m_yAlign = -1; + m_nDropCapLength = 0; +} + +bool TablePropertyMap::getValue( TablePropertyMapTarget eWhich, sal_Int32& nFill ) +{ + if ( eWhich < TablePropertyMapTarget_MAX ) + { + if ( m_aValidValues[eWhich].bValid ) + nFill = m_aValidValues[eWhich].nValue; + return m_aValidValues[eWhich].bValid; + } + else + { + OSL_FAIL( "invalid TablePropertyMapTarget" ); + return false; + } +} + +void TablePropertyMap::setValue( TablePropertyMapTarget eWhich, sal_Int32 nSet ) +{ + if ( eWhich < TablePropertyMapTarget_MAX ) + { + m_aValidValues[eWhich].bValid = true; + m_aValidValues[eWhich].nValue = nSet; + } + else + OSL_FAIL( "invalid TablePropertyMapTarget" ); +} + +void TablePropertyMap::insertTableProperties( const PropertyMap* pMap, const bool bOverwrite ) +{ +#ifdef DBG_UTIL + TagLogger::getInstance().startElement( "TablePropertyMap.insertTableProperties" ); + pMap->dumpXml(); +#endif + + const TablePropertyMap* pSource = dynamic_cast< const TablePropertyMap* >(pMap); + if ( pSource ) + { + for ( sal_Int32 eTarget = TablePropertyMapTarget_START; + eTarget < TablePropertyMapTarget_MAX; ++eTarget ) + { + if ( pSource->m_aValidValues[eTarget].bValid && (bOverwrite || !m_aValidValues[eTarget].bValid) ) + { + m_aValidValues[eTarget].bValid = true; + m_aValidValues[eTarget].nValue = pSource->m_aValidValues[eTarget].nValue; + } + } + } + +#ifdef DBG_UTIL + dumpXml(); + TagLogger::getInstance().endElement(); +#endif +} + +} // namespace writerfilter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |