summaryrefslogtreecommitdiff
path: root/writerfilter
diff options
context:
space:
mode:
authorAttila Szűcs <szucs.attila3@nisz.hu>2021-06-25 10:37:47 +0200
committerLászló Németh <nemeth@numbertext.org>2021-07-05 12:26:08 +0200
commit11c61e5f0544966f3901c8fb4270bb07424942f1 (patch)
tree91b010f91f4f80cfdabfe3a60481ebd5d09a998c /writerfilter
parent96f2a6d16e6a6bf070e6875776b6153a70e75a7a (diff)
tdf#119952 DOCX import: fix negative page margins
DOCX body text can overlap with header/footer, if top/bottom page margin is negative. To support this, convert header/footer text content to textbox anchored to header/footer, if needed. Note: possible improvements: 1) Skip this hack, if the header is small enough to not overlap with the body, calculate only the height of the header at the import time. 2) This hack does not fix the case when the top of the header is under the top of the body. (A problem in DOC import, too.) This could be achieved by repositioning the dummy header to the top, and lower the textbox by the same amount. (This would still not resolve the extreme situation, when the body start from 0 mm (in LibreOffice, header must be at least 1 mm). 3) Import of VertOrientation::BOTTOM property seems to be bad, or at least the footer loses this property after a DOCX round-trip, resulting bad footer position. 4) after a round-trip, the 1 mm height of the dummy header increases to 1 line height. Also the "Autofit height" and "Use dynamic spacing" settings are changed, likely related to their missing DOCX export. Co-authored-by: Tibor Nagy (NISZ) Change-Id: I8319c93c6c5a980878ee9956c8ab2953da60409e Reviewed-on: https://gerrit.libreoffice.org/c/core/+/117842 Tested-by: László Németh <nemeth@numbertext.org> Reviewed-by: László Németh <nemeth@numbertext.org> (cherry picked from commit d656191ec308d4280b93c7169372e543a255d108) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/118295 Tested-by: Jenkins
Diffstat (limited to 'writerfilter')
-rw-r--r--writerfilter/source/dmapper/DomainMapperTableHandler.cxx38
-rw-r--r--writerfilter/source/dmapper/DomainMapper_Impl.cxx63
-rw-r--r--writerfilter/source/dmapper/DomainMapper_Impl.hxx3
-rw-r--r--writerfilter/source/dmapper/PropertyMap.cxx78
-rw-r--r--writerfilter/source/dmapper/PropertyMap.hxx7
5 files changed, 107 insertions, 82 deletions
diff --git a/writerfilter/source/dmapper/DomainMapperTableHandler.cxx b/writerfilter/source/dmapper/DomainMapperTableHandler.cxx
index 55c48740f0ec..884a195df75a 100644
--- a/writerfilter/source/dmapper/DomainMapperTableHandler.cxx
+++ b/writerfilter/source/dmapper/DomainMapperTableHandler.cxx
@@ -340,38 +340,6 @@ void lcl_adjustBorderDistance(TableInfo& rInfo, const table::BorderLine2& rLeftB
rInfo.nRightBorderDistance = nActualR;
}
-void lcl_fillEmptyFrameProperties(std::vector<beans::PropertyValue>& rFrameProperties)
-{
- // fill empty frame properties to create an invisible frame around the table:
- // hide frame borders and zero inner and outer frame margins
- beans::PropertyValue aValue;
- aValue.Name = getPropertyName( PROP_ANCHOR_TYPE );
- aValue.Value <<= text::TextContentAnchorType_AS_CHARACTER;
- rFrameProperties.push_back(aValue);
-
- table::BorderLine2 aEmptyBorder;
- static const std::vector<std::u16string_view> aBorderNames
- = { u"TopBorder", u"LeftBorder", u"BottomBorder", u"RightBorder" };
- for (size_t i = 0; i < aBorderNames.size(); ++i)
- {
- beans::PropertyValue aBorderValue;
- aBorderValue.Name = aBorderNames[i];
- aBorderValue.Value <<= aEmptyBorder;
- rFrameProperties.push_back(aBorderValue);
- }
- static const std::vector<std::u16string_view> aMarginNames
- = { u"TopBorderDistance", u"LeftBorderDistance",
- u"BottomBorderDistance", u"RightBorderDistance",
- u"TopMargin", u"LeftMargin", u"BottomMargin", u"RightMargin" };
- for (size_t i = 0; i < aMarginNames.size(); ++i)
- {
- beans::PropertyValue aMarginValue;
- aMarginValue.Name = aMarginNames[i];
- aMarginValue.Value <<= sal_Int32(10);
- rFrameProperties.push_back(aMarginValue);
- }
-}
-
}
TableStyleSheetEntry * DomainMapperTableHandler::endTableGetTableStyle(TableInfo & rInfo,
@@ -1440,8 +1408,10 @@ void DomainMapperTableHandler::endTable(unsigned int nestedTableLevel, bool bTab
uno::Reference<text::XTextRange> xStart;
uno::Reference<text::XTextRange> xEnd;
- if ( bConvertToFloating )
- lcl_fillEmptyFrameProperties(aFrameProperties);
+ // fill empty frame properties to create an invisible frame around the table:
+ // hide frame borders and zero inner and outer frame margins
+ if (bConvertToFloating)
+ DomainMapper_Impl::fillEmptyFrameProperties(aFrameProperties, true);
// OOXML table style may contain paragraph properties, apply these on cell paragraphs
if ( m_aTableRanges[0].hasElements() && m_aTableRanges[0][0].hasElements() )
diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.cxx b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
index e02549daf430..9f12d3a7de4f 100644
--- a/writerfilter/source/dmapper/DomainMapper_Impl.cxx
+++ b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
@@ -2600,6 +2600,64 @@ void DomainMapper_Impl::appendGlossaryEntry()
appendTextSectionAfter(m_xGlossaryEntryStart);
}
+void DomainMapper_Impl::fillEmptyFrameProperties(std::vector<beans::PropertyValue>& rFrameProperties, bool bSetAnchorToChar)
+{
+ if (bSetAnchorToChar)
+ rFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_ANCHOR_TYPE), text::TextContentAnchorType_AS_CHARACTER));
+
+ uno::Any aEmptyBorder = uno::makeAny(table::BorderLine2());
+ static const std::vector<PropertyIds> aBorderIds
+ = { PROP_BOTTOM_BORDER, PROP_LEFT_BORDER, PROP_RIGHT_BORDER, PROP_TOP_BORDER };
+ for (size_t i = 0; i < aBorderIds.size(); ++i)
+ rFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(aBorderIds[i]), aEmptyBorder));
+
+ static const std::vector<PropertyIds> aMarginIds
+ = { PROP_BOTTOM_MARGIN, PROP_BOTTOM_BORDER_DISTANCE,
+ PROP_LEFT_MARGIN, PROP_LEFT_BORDER_DISTANCE,
+ PROP_RIGHT_MARGIN, PROP_RIGHT_BORDER_DISTANCE,
+ PROP_TOP_MARGIN, PROP_TOP_BORDER_DISTANCE };
+ for (size_t i = 0; i < aMarginIds.size(); ++i)
+ rFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(aMarginIds[i]), static_cast<sal_Int32>(0)));
+}
+
+void DomainMapper_Impl::ConvertHeaderFooterToTextFrame(bool bDynamicHeightTop, bool bDynamicHeightBottom)
+{
+ while (!m_aHeaderFooterTextAppendStack.empty())
+ {
+ auto aFooterHeader = m_aHeaderFooterTextAppendStack.top();
+ if ((aFooterHeader.second && !bDynamicHeightTop) || (!aFooterHeader.second && !bDynamicHeightBottom))
+ {
+ uno::Reference< text::XTextAppend > xTextAppend = aFooterHeader.first.xTextAppend;
+ uno::Reference< text::XTextCursor > xCursor = xTextAppend->createTextCursor();
+ uno::Reference< text::XTextRange > xRangeStart, xRangeEnd;
+
+ xRangeStart = xCursor->getStart();
+ xCursor->gotoEnd(false);
+ xRangeEnd = xCursor->getStart();
+
+ std::vector<beans::PropertyValue> aFrameProperties;
+
+ aFrameProperties.push_back(comphelper::makePropertyValue("TextWrap", css::text::WrapTextMode_THROUGH));
+ aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_HORI_ORIENT), text::HoriOrientation::LEFT));
+ aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_OPAQUE), false));
+ aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_WIDTH_TYPE), text::SizeType::MIN));
+ aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_SIZE_TYPE), text::SizeType::MIN));
+
+ fillEmptyFrameProperties(aFrameProperties, false);
+
+ // If it is a footer, then orient the frame to the bottom
+ if (!aFooterHeader.second)
+ aFrameProperties.push_back(comphelper::makePropertyValue(getPropertyName(PROP_VERT_ORIENT), text::VertOrientation::BOTTOM));
+
+ uno::Reference<text::XTextAppendAndConvert> xBodyText(
+ xRangeStart->getText(), uno::UNO_QUERY);
+ xBodyText->convertToTextFrame(xRangeStart, xRangeEnd,
+ comphelper::containerToSequence(aFrameProperties));
+ }
+ m_aHeaderFooterTextAppendStack.pop();
+ }
+}
+
void DomainMapper_Impl::PushPageHeaderFooter(bool bHeader, SectionPropertyMap::PageType eType)
{
m_bSaveParaHadField = m_bParaHadField;
@@ -2662,6 +2720,11 @@ void DomainMapper_Impl::PushPageHeaderFooter(bool bHeader, SectionPropertyMap::P
m_bIsNewDoc
? uno::Reference<text::XTextCursor>()
: xText->createTextCursorByRange(xText->getStart())));
+ m_aHeaderFooterTextAppendStack.push(std::make_pair(TextAppendContext(uno::Reference< text::XTextAppend >(xText, uno::UNO_QUERY_THROW),
+ m_bIsNewDoc
+ ? uno::Reference<text::XTextCursor>()
+ : xText->createTextCursorByRange(xText->getStart())),
+ bHeader));
m_bDiscardHeaderFooter = false; // set only on success!
}
// If we have *hidden* header footer
diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.hxx b/writerfilter/source/dmapper/DomainMapper_Impl.hxx
index 5212653dcd47..845e902a9ee3 100644
--- a/writerfilter/source/dmapper/DomainMapper_Impl.hxx
+++ b/writerfilter/source/dmapper/DomainMapper_Impl.hxx
@@ -467,6 +467,7 @@ private:
std::stack<TextAppendContext> m_aTextAppendStack;
std::stack<AnchoredContext> m_aAnchoredStack;
std::stack<HeaderFooterContext> m_aHeaderFooterStack;
+ std::stack<std::pair<TextAppendContext, bool>> m_aHeaderFooterTextAppendStack;
std::deque<FieldContextPtr> m_aFieldStack;
bool m_bForceGenericFields;
bool m_bSetUserFieldContent;
@@ -820,6 +821,8 @@ public:
void PopPageHeaderFooter();
bool IsInHeaderFooter() const { return m_eInHeaderFooterImport != HeaderFooterImportState::none; }
+ void ConvertHeaderFooterToTextFrame(bool, bool);
+ static void fillEmptyFrameProperties(std::vector<css::beans::PropertyValue>& rFrameProperties, bool bSetAnchorToChar);
bool IsInTOC() const { return m_bStartTOC; }
diff --git a/writerfilter/source/dmapper/PropertyMap.cxx b/writerfilter/source/dmapper/PropertyMap.cxx
index 9b4c819e35cb..cacadef477d4 100644
--- a/writerfilter/source/dmapper/PropertyMap.cxx
+++ b/writerfilter/source/dmapper/PropertyMap.cxx
@@ -425,6 +425,8 @@ SectionPropertyMap::SectionPropertyMap( bool bIsFirstSection )
, 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 )
@@ -982,28 +984,25 @@ void SectionPropertyMap::PrepareHeaderFooterProperties( bool bFirstPage )
bool bCopyFirstToFollow = bFirstPage && m_bTitlePage && m_aFollowPageStyle.is();
sal_Int32 nTopMargin = m_nTopMargin;
- sal_Int32 nHeaderTop = m_nHeaderTop;
+ sal_Int32 nHeaderHeight = m_nHeaderTop;
if ( HasHeader( bFirstPage ) )
{
- nTopMargin = nHeaderTop;
- if ( m_nTopMargin > 0 && m_nTopMargin > nHeaderTop )
- nHeaderTop = m_nTopMargin - nHeaderTop;
- else
- nHeaderTop = 0;
+ nTopMargin = m_nHeaderTop;
+ nHeaderHeight = m_nTopMargin - m_nHeaderTop;
// minimum header height 1mm
- if ( nHeaderTop < MIN_HEAD_FOOT_HEIGHT )
- nHeaderTop = MIN_HEAD_FOOT_HEIGHT;
+ if ( nHeaderHeight < MIN_HEAD_FOOT_HEIGHT )
+ nHeaderHeight = MIN_HEAD_FOOT_HEIGHT;
}
+ Insert(PROP_HEADER_IS_DYNAMIC_HEIGHT, uno::makeAny(m_bDynamicHeightTop));
+ Insert(PROP_HEADER_DYNAMIC_SPACING, uno::makeAny(m_bDynamicHeightTop));
+ Insert(PROP_HEADER_BODY_DISTANCE, uno::makeAny(nHeaderHeight - MIN_HEAD_FOOT_HEIGHT));
+ Insert(PROP_HEADER_HEIGHT, uno::makeAny(nHeaderHeight));
+ // looks like PROP_HEADER_HEIGHT = height of the header + space between the header, and the body
- if ( m_nTopMargin >= 0 ) //fixed height header -> see WW8Par6.hxx
+ if ( m_bDynamicHeightTop ) //fixed height header -> see WW8Par6.hxx
{
- Insert( PROP_HEADER_IS_DYNAMIC_HEIGHT, uno::makeAny( true ) );
- Insert( PROP_HEADER_DYNAMIC_SPACING, uno::makeAny( true ) );
- Insert( PROP_HEADER_BODY_DISTANCE, uno::makeAny( nHeaderTop - MIN_HEAD_FOOT_HEIGHT ) );// ULSpace.Top()
- Insert( PROP_HEADER_HEIGHT, uno::makeAny( nHeaderTop ) );
-
if (bCopyFirstToFollow && HasHeader(/*bFirstPage=*/true))
{
m_aFollowPageStyle->setPropertyValue("HeaderDynamicSpacing",
@@ -1012,36 +1011,25 @@ void SectionPropertyMap::PrepareHeaderFooterProperties( bool bFirstPage )
getProperty(PROP_HEADER_HEIGHT)->second);
}
}
- else
- {
- //todo: old filter fakes a frame into the header/footer to support overlapping
- //current setting is completely wrong!
- Insert( PROP_HEADER_HEIGHT, uno::makeAny( nHeaderTop ) );
- Insert( PROP_HEADER_BODY_DISTANCE, uno::makeAny( m_nTopMargin - nHeaderTop ) );
- Insert( PROP_HEADER_IS_DYNAMIC_HEIGHT, uno::makeAny( false ) );
- Insert( PROP_HEADER_DYNAMIC_SPACING, uno::makeAny( false ) );
- }
sal_Int32 nBottomMargin = m_nBottomMargin;
- sal_Int32 nHeaderBottom = m_nHeaderBottom;
+ sal_Int32 nFooterHeight = m_nHeaderBottom;
if ( HasFooter( bFirstPage ) )
{
- nBottomMargin = nHeaderBottom;
- if ( m_nBottomMargin > 0 && m_nBottomMargin > nHeaderBottom )
- nHeaderBottom = m_nBottomMargin - nHeaderBottom;
- else
- nHeaderBottom = 0;
- if ( nHeaderBottom < MIN_HEAD_FOOT_HEIGHT )
- nHeaderBottom = MIN_HEAD_FOOT_HEIGHT;
+ nBottomMargin = m_nHeaderBottom;
+ nFooterHeight = m_nBottomMargin - m_nHeaderBottom;
+
+ // minimum footer height 1mm
+ if ( nFooterHeight < MIN_HEAD_FOOT_HEIGHT )
+ nFooterHeight = MIN_HEAD_FOOT_HEIGHT;
}
- if ( m_nBottomMargin >= 0 ) //fixed height footer -> see WW8Par6.hxx
+ Insert(PROP_FOOTER_IS_DYNAMIC_HEIGHT, uno::makeAny(m_bDynamicHeightBottom));
+ Insert(PROP_FOOTER_DYNAMIC_SPACING, uno::makeAny(m_bDynamicHeightBottom));
+ Insert(PROP_FOOTER_BODY_DISTANCE, uno::makeAny(nFooterHeight - MIN_HEAD_FOOT_HEIGHT));
+ Insert(PROP_FOOTER_HEIGHT, uno::makeAny(nFooterHeight));
+ if (m_bDynamicHeightBottom) //fixed height footer -> see WW8Par6.hxx
{
- Insert( PROP_FOOTER_IS_DYNAMIC_HEIGHT, uno::makeAny( true ) );
- Insert( PROP_FOOTER_DYNAMIC_SPACING, uno::makeAny( true ) );
- Insert( PROP_FOOTER_BODY_DISTANCE, uno::makeAny( nHeaderBottom - MIN_HEAD_FOOT_HEIGHT ) );
- Insert( PROP_FOOTER_HEIGHT, uno::makeAny( nHeaderBottom ) );
-
if (bCopyFirstToFollow && HasFooter(/*bFirstPage=*/true))
{
m_aFollowPageStyle->setPropertyValue("FooterDynamicSpacing",
@@ -1050,15 +1038,6 @@ void SectionPropertyMap::PrepareHeaderFooterProperties( bool bFirstPage )
getProperty(PROP_FOOTER_HEIGHT)->second);
}
}
- else
- {
- //todo: old filter fakes a frame into the header/footer to support overlapping
- //current setting is completely wrong!
- Insert( PROP_FOOTER_IS_DYNAMIC_HEIGHT, uno::makeAny( false ) );
- Insert( PROP_FOOTER_DYNAMIC_SPACING, uno::makeAny( false ) );
- Insert( PROP_FOOTER_HEIGHT, uno::makeAny( m_nBottomMargin - nHeaderBottom ) );
- Insert( PROP_FOOTER_BODY_DISTANCE, uno::makeAny( nHeaderBottom ) );
- }
//now set the top/bottom margin for the follow page style
Insert( PROP_TOP_MARGIN, uno::makeAny( std::max<sal_Int32>(nTopMargin, 0) ) );
@@ -1134,6 +1113,13 @@ void SectionPropertyMap::HandleMarginsHeaderFooter( bool bFirstPage, DomainMappe
*/
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 )
diff --git a/writerfilter/source/dmapper/PropertyMap.hxx b/writerfilter/source/dmapper/PropertyMap.hxx
index d4f981c5db91..917849957ce0 100644
--- a/writerfilter/source/dmapper/PropertyMap.hxx
+++ b/writerfilter/source/dmapper/PropertyMap.hxx
@@ -270,6 +270,9 @@ private:
sal_Int32 m_ndxaLnn;
sal_Int32 m_nLnnMin;
+ bool m_bDynamicHeightTop;
+ bool m_bDynamicHeightBottom;
+
std::vector<css::uno::Reference<css::drawing::XShape>> m_xRelativeWidthShapes;
// The "Link To Previous" flag indicates whether the header/footer
@@ -378,8 +381,8 @@ public:
sal_Int32 GetLeftMargin() const { return m_nLeftMargin; }
void SetRightMargin( sal_Int32 nSet ) { m_nRightMargin = nSet; }
sal_Int32 GetRightMargin() const { return m_nRightMargin; }
- void SetTopMargin( sal_Int32 nSet ) { m_nTopMargin = nSet; }
- void SetBottomMargin( sal_Int32 nSet ) { m_nBottomMargin = nSet; }
+ void SetTopMargin(sal_Int32 nSet) { m_bDynamicHeightTop = nSet >= 0; m_nTopMargin = std::abs(nSet); }
+ void SetBottomMargin( sal_Int32 nSet ) { m_bDynamicHeightBottom = nSet >= 0; m_nBottomMargin = std::abs(nSet); }
void SetHeaderTop( sal_Int32 nSet ) { m_nHeaderTop = nSet; }
void SetHeaderBottom( sal_Int32 nSet ) { m_nHeaderBottom = nSet; }
void SetGutterMargin( sal_Int32 nGutterMargin ) { m_nGutterMargin = nGutterMargin; }