DOCX filter: improve handling of negative <w:position> in paragraph styles
The bugdoc has a <w:position w:val="-1"> in its Normal paragraph style,
which is almost not visible in Word, but we mapped this to default
subscript text in Writer, leading to very visible bad font height in
practice.
The root of the problem is that <w:position> works with an absolute
offset in half-points, while Writer works in percentages, so the
import/export code can only do a correct mapping in case the font size
is known. This initial mapping was added in commit
e70df84352d3670508a4666c97df44f82c1ce934 (try somewhat harder to read
w:position (bnc#773061), 2012-08-07), and later commit
d71cf6390a89ea6a4fab724e3a7996f28ca33661 (tdf#99602 writerfilter: import
subscript into character style, 2019-10-04) gave up on this for
character styles.
Fix the problem by working with paragraph styles similar to what the
binary DOC filter already does, just assuming that the font height from
the style won't be overwritten, or will be overwritten together with a
matching <w:position>. Do this only for negative <w:position> for now,
as that's good enough for our needs. Do the opposite of this at export
time.
It would be still possible in the future to add native handling for
absolute escapements, and then this mapping would not be needed at all.
Change-Id: I771c7bed27fa2596153aa77c472c91b819fa4cb1
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/152962
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins
diff --git a/sw/qa/extras/ooxmlexport/data/para-style-char-position.docx b/sw/qa/extras/ooxmlexport/data/para-style-char-position.docx
new file mode 100644
index 0000000..946ca0b
--- /dev/null
+++ b/sw/qa/extras/ooxmlexport/data/para-style-char-position.docx
Binary files differ
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport18.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport18.cxx
index 50a058d..fbb8791 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport18.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport18.cxx
@@ -615,6 +615,24 @@ CPPUNIT_TEST_FIXTURE(Test, testNumberPortionFormatFromODT)
assertXPath(pXmlDoc, "//w:pPr/w:rPr/w:sz", "val", "48");
}
CPPUNIT_TEST_FIXTURE(Test, testParaStyleCharPosition)
{
// Given a loaded document where the Normal paragraph style has <w:position w:val="-1">:
createSwDoc("para-style-char-position.docx");
// When saving it back to DOCX:
save("Office Open XML Text");
// Then make sure that is not turned into a normal subscript text:
xmlDocUniquePtr pXmlDoc = parseExport("word/styles.xml");
// Without the accompanying fix in place, this test would have failed with:
// - Expected: 1
// - Actual : 0
// - XPath '/w:styles/w:style[@w:styleId='Normal']/w:rPr/w:position' number of nodes is incorrect
// i.e. we wrote <w:vertAlign w:val="subscript"> instead of <w:position>.
assertXPath(pXmlDoc, "/w:styles/w:style[@w:styleId='Normal']/w:rPr/w:position", "val", "-1");
}
CPPUNIT_TEST_FIXTURE(Test, testTdf150966_regularInset)
{
// Given a docx document with a rectangular shape with height cy="900000" (EMU), tIns="180000"
diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx b/sw/source/filter/ww8/docxattributeoutput.cxx
index a756827..ba5d14b 100644
--- a/sw/source/filter/ww8/docxattributeoutput.cxx
+++ b/sw/source/filter/ww8/docxattributeoutput.cxx
@@ -7791,8 +7791,14 @@ void DocxAttributeOutput::CharEscapement( const SvxEscapementItem& rEscapement )
OString sIss;
short nEsc = rEscapement.GetEsc(), nProp = rEscapement.GetProportionalHeight();
bool bParaStyle = false;
if (m_rExport.m_bStyDef && m_rExport.m_pCurrentStyle)
{
bParaStyle = m_rExport.m_pCurrentStyle->Which() == RES_TXTFMTCOLL;
}
// Simplify styles to avoid impossible complexity. Import and export as defaults only
if ( m_rExport.m_bStyDef && nEsc )
if ( m_rExport.m_bStyDef && nEsc && !(bParaStyle && nEsc < 0))
{
nProp = DFLT_ESC_PROP;
nEsc = (nEsc > 0) ? DFLT_ESC_AUTO_SUPER : DFLT_ESC_AUTO_SUB;
diff --git a/writerfilter/source/dmapper/DomainMapper.cxx b/writerfilter/source/dmapper/DomainMapper.cxx
index aa131a62..b1d3b71 100644
--- a/writerfilter/source/dmapper/DomainMapper.cxx
+++ b/writerfilter/source/dmapper/DomainMapper.cxx
@@ -2061,6 +2061,13 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext )
{
// For some undocumented reason, MS Word seems to ignore this in docDefaults
const StyleSheetEntryPtr pCurrStyle = GetStyleSheetTable()->GetCurrentEntry();
if (pCurrStyle && pCurrStyle->m_nStyleTypeCode == STYLE_TYPE_PARA && nIntValue < 0)
{
m_pImpl->deferCharacterProperty(nSprmId, uno::Any(nIntValue));
break;
}
// DON'T FIXME: Truly calculating this for Character Styles will be tricky,
// because it depends on the final fontsize - regardless of
// where it is set. So at the style level,
@@ -3534,9 +3541,19 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext )
}
}
void DomainMapper::processDeferredCharacterProperties( const std::map< sal_Int32, uno::Any >& deferredCharacterProperties )
void DomainMapper::ProcessDeferredStyleCharacterProperties()
{
assert( m_pImpl->GetTopContextType() == CONTEXT_CHARACTER );
assert(m_pImpl->GetTopContextType() == CONTEXT_STYLESHEET);
m_pImpl->processDeferredCharacterProperties(false);
}
void DomainMapper::processDeferredCharacterProperties(
const std::map<sal_Int32, uno::Any>& deferredCharacterProperties, bool bCharContext)
{
if (bCharContext)
{
assert(m_pImpl->GetTopContextType() == CONTEXT_CHARACTER);
}
PropertyMapPtr rContext = m_pImpl->GetTopContext();
for( const auto& rProp : deferredCharacterProperties )
{
diff --git a/writerfilter/source/dmapper/DomainMapper.hxx b/writerfilter/source/dmapper/DomainMapper.hxx
index b9771b8..a452e2e 100644
--- a/writerfilter/source/dmapper/DomainMapper.hxx
+++ b/writerfilter/source/dmapper/DomainMapper.hxx
@@ -123,7 +123,11 @@ public:
/**
@see DomainMapper_Impl::processDeferredCharacterProperties()
*/
void processDeferredCharacterProperties(const std::map<sal_Int32, css::uno::Any>& rDeferredCharacterProperties);
void processDeferredCharacterProperties(
const std::map<sal_Int32, css::uno::Any>& rDeferredCharacterProperties,
bool bCharContext = true);
void ProcessDeferredStyleCharacterProperties();
/// Enable storing of seen tokens in a named grab bag.
void enableInteropGrabBag(const OUString& aName);
diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.cxx b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
index 14cdc0a..41ca8bf 100644
--- a/writerfilter/source/dmapper/DomainMapper_Impl.cxx
+++ b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
@@ -9046,12 +9046,12 @@ void DomainMapper_Impl::deferCharacterProperty(sal_Int32 id, const css::uno::Any
m_deferredCharacterProperties[ id ] = value;
}
void DomainMapper_Impl::processDeferredCharacterProperties()
void DomainMapper_Impl::processDeferredCharacterProperties(bool bCharContext)
{
// Actually process in DomainMapper, so that it's the same source file like normal processing.
if( !m_deferredCharacterProperties.empty())
{
m_rDMapper.processDeferredCharacterProperties( m_deferredCharacterProperties );
m_rDMapper.processDeferredCharacterProperties(m_deferredCharacterProperties, bCharContext);
m_deferredCharacterProperties.clear();
}
}
diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.hxx b/writerfilter/source/dmapper/DomainMapper_Impl.hxx
index 1e02fd8..4668ebc 100644
--- a/writerfilter/source/dmapper/DomainMapper_Impl.hxx
+++ b/writerfilter/source/dmapper/DomainMapper_Impl.hxx
@@ -1074,7 +1074,7 @@ public:
Processes properties deferred using deferCharacterProperty(). To be called whenever the top
CONTEXT_CHARACTER is going to be used (e.g. by appendText()).
*/
void processDeferredCharacterProperties();
void processDeferredCharacterProperties(bool bCharContext = true);
sal_Int32 getNumberingProperty(const sal_Int32 nListId, sal_Int32 nListLevel, const OUString& aProp);
/// Get a property of the current numbering style's current level.
diff --git a/writerfilter/source/dmapper/StyleSheetTable.cxx b/writerfilter/source/dmapper/StyleSheetTable.cxx
index b0282e9..fa03d3f 100644
--- a/writerfilter/source/dmapper/StyleSheetTable.cxx
+++ b/writerfilter/source/dmapper/StyleSheetTable.cxx
@@ -808,6 +808,7 @@ void StyleSheetTable::lcl_entry(writerfilter::Reference<Properties>::Pointer_t r
m_pImpl->m_pCurrentEntry = pNewEntry;
m_pImpl->m_rDMapper.PushStyleSheetProperties( m_pImpl->m_pCurrentEntry->m_pProperties.get() );
ref->resolve(*this);
m_pImpl->m_rDMapper.ProcessDeferredStyleCharacterProperties();
//append it to the table
m_pImpl->m_rDMapper.PopStyleSheetProperties();
if( !m_pImpl->m_rDMapper.IsOOXMLImport() || !m_pImpl->m_pCurrentEntry->m_sStyleName.isEmpty())