sw/inc/unoprnms.hxx                                         |    1 
 sw/inc/unotextcursor.hxx                                    |    1 
 sw/qa/core/unocore/data/paragraph-marker-formatted-run.docx |binary
 sw/qa/core/unocore/unocore.cxx                              |   19 ++
 sw/source/core/text/xmldump.cxx                             |    5 
 sw/source/core/unocore/unoobj.cxx                           |   81 +++++++++++-
 xmloff/source/text/txtparai.cxx                             |   17 ++
 7 files changed, 121 insertions(+), 3 deletions(-)

New commits:
commit 21c8dc9a87a811fef0bc11da3f3448397a396d79
Author:     Miklos Vajna <vmik...@collabora.com>
AuthorDate: Tue Dec 20 12:14:16 2022 +0100
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Tue Jan 3 12:37:25 2023 +0000

    sw: fix ODT import of paragraph marker formatting
    
    The DOCX bugdoc had a numbering portion which was not bold, even if all
    characters in the paragrpah was bold. This was rendered fine, but once
    it was saved to ODT + reloaded, the numbering portion became bold as
    well, which is buggy.
    
    What happens here is that there is one span that covers the entire
    paragraph (and is bold) and there is an empty span at paragraph end
    (which is not bold), so the ODT export is fine. But once we import it
    back, the first span gets "upgraded" to paragraph-level formatting
    (because SetAttrMode::NOFORMATATTR is not used when inserting the hints)
    and the second span is not mapped back to the original
    RES_PARATR_LIST_AUTOFMT in the text node.
    
    Fix the problem by 1) improving SwXTextCursor::setPropertyValue() to
    work with SetAttrMode::NOFORMATATTR when multiple spans are inserted and
    by 2) extending SwUnoCursorHelper::SetCursorPropertyValue() to create
    RES_PARATR_LIST_AUTOFMT for empty spans at paragraph end. This way the
    original doc model and the one created after ODT export + import is much
    closer to each other.
    
    This builds on top of 6249858a8972aef077e0249bd93cfe8f01bce4d6 (sw: ODT
    import/export of DOCX's paragraph marker formatting, 2022-12-19),
    previously the ODT export and import of paragraph marker formatting was
    completely missing.
    
    (cherry picked from commit 1feb1aa08421f9d0934ab65ce94cf6054818c0f3)
    
    Also includes commit de235fe13a2e5a4db043f44e6d5636e308f2b979 (sw layout
    xml dump: show numbering portion weight, 2022-12-19).
    
    Change-Id: I139e11217dcbc18744aeeb80638090781aa74933
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/144676
    Tested-by: Jenkins
    Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com>
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/144961
    Tested-by: Miklos Vajna <vmik...@collabora.com>
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>

diff --git a/sw/inc/unoprnms.hxx b/sw/inc/unoprnms.hxx
index 12f839c2b16a..3c6b5d8464ab 100644
--- a/sw/inc/unoprnms.hxx
+++ b/sw/inc/unoprnms.hxx
@@ -502,6 +502,7 @@
 #define UNO_NAME_IS_SKIP_HIDDEN_TEXT "IsSkipHiddenText"
 #define UNO_NAME_IS_SKIP_PROTECTED_TEXT "IsSkipProtectedText"
 #define UNO_NAME_RESET_PARAGRAPH_LIST_ATTRIBUTES "ResetParagraphListAttributes"
+#define UNO_NAME_NO_FORMAT_ATTR "NoFormatAttr"
 #define UNO_NAME_DOCUMENT_INDEX_MARKS "DocumentIndexMarks"
 #define UNO_NAME_FOOTNOTE_IS_COLLECT_AT_TEXT_END "FootnoteIsCollectAtTextEnd"
 #define UNO_NAME_FOOTNOTE_IS_RESTART_NUMBERING "FootnoteIsRestartNumbering"
diff --git a/sw/inc/unotextcursor.hxx b/sw/inc/unotextcursor.hxx
index b055f2d64504..25c71effa57c 100644
--- a/sw/inc/unotextcursor.hxx
+++ b/sw/inc/unotextcursor.hxx
@@ -76,6 +76,7 @@ private:
     const CursorType            m_eType;
     const css::uno::Reference< css::text::XText > m_xParentText;
     sw::UnoCursorPointer m_pUnoCursor;
+    SetAttrMode m_nAttrMode = SetAttrMode::DEFAULT;
 
     SwUnoCursor& GetCursorOrThrow() {
         if(!m_pUnoCursor)
diff --git a/sw/qa/core/unocore/data/paragraph-marker-formatted-run.docx 
b/sw/qa/core/unocore/data/paragraph-marker-formatted-run.docx
new file mode 100644
index 000000000000..c0635c157cc9
Binary files /dev/null and 
b/sw/qa/core/unocore/data/paragraph-marker-formatted-run.docx differ
diff --git a/sw/qa/core/unocore/unocore.cxx b/sw/qa/core/unocore/unocore.cxx
index 58723f829439..1566481157e8 100644
--- a/sw/qa/core/unocore/unocore.cxx
+++ b/sw/qa/core/unocore/unocore.cxx
@@ -781,6 +781,25 @@ CPPUNIT_TEST_FIXTURE(SwCoreUnocoreTest, 
testParagraphMarkerODFExport)
         getXPath(pXmlDoc, "//Special[@nType='PortionType::Number']/SwFont", 
"color"));
 }
 
+CPPUNIT_TEST_FIXTURE(SwCoreUnocoreTest, testParagraphMarkerFormattedRun)
+{
+    // Given a document with a bold run and non-bold paragraph marker:
+    load(DATA_DIRECTORY, "paragraph-marker-formatted-run.docx");
+
+    // When saving that as ODT + reload:
+    reload("writer8", nullptr);
+
+    // Then make sure that the numbering portion is still non-bold, matching 
Word:
+    xmlDocUniquePtr pXmlDoc = parseLayoutDump();
+    // Without the accompanying fix in place, this test would have failed with:
+    // - Expected: 5 (WEIGHT_NORMAL)
+    // - Actual  : 8 (WEIGHT_BOLD)
+    // i.e. the numbering portion was bold, while its weight should be normal.
+    CPPUNIT_ASSERT_EQUAL(
+        OUString("5"),
+        getXPath(pXmlDoc, "//Special[@nType='PortionType::Number']/SwFont", 
"weight"));
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/xmldump.cxx b/sw/source/core/text/xmldump.cxx
index 5e1ac0eb002c..7bac72593d6d 100644
--- a/sw/source/core/text/xmldump.cxx
+++ b/sw/source/core/text/xmldump.cxx
@@ -615,6 +615,11 @@ void SwFont::dumpAsXml(xmlTextWriterPtr writer) const
     (void)xmlTextWriterWriteFormatAttribute(writer, BAD_CAST("ptr"), "%p", 
this);
     // do not use Color::AsRGBHexString() as that omits the transparency
     (void)xmlTextWriterWriteFormatAttribute(writer, BAD_CAST("color"), "%08" 
SAL_PRIxUINT32, sal_uInt32(GetColor()));
+    {
+        std::stringstream ss;
+        ss << GetWeight();
+        (void)xmlTextWriterWriteAttribute(writer, BAD_CAST("weight"), 
BAD_CAST(ss.str().c_str()));
+    }
     (void)xmlTextWriterEndElement(writer);
 }
 
diff --git a/sw/source/core/unocore/unoobj.cxx 
b/sw/source/core/unocore/unoobj.cxx
index 94b168768ba9..252226ca80cc 100644
--- a/sw/source/core/unocore/unoobj.cxx
+++ b/sw/source/core/unocore/unoobj.cxx
@@ -227,6 +227,57 @@ lcl_setAutoStyle(IStyleAccess & rStyleAccess, const 
uno::Any & rValue,
     rSet.Put(aFormat);
 };
 
+/// Tries to map rValue to RES_PARATR_LIST_AUTOFMT on the current paragraph, 
returns true on
+/// success.
+static bool lcl_setListAutoStyle(SwPaM& rPam, const uno::Any& rValue, 
SfxItemSet& rItemSet)
+{
+    // See if this is an empty range at the end of a paragraph.
+    if (rPam.Start()->nNode.GetIndex() != rPam.End()->nNode.GetIndex())
+    {
+        return false;
+    }
+
+    if (rPam.Start()->nContent.GetIndex() != rPam.End()->nContent.GetIndex())
+    {
+        return false;
+    }
+
+    SwTextNode* pTextNode = rPam.GetNode().GetTextNode();
+    if (!pTextNode)
+    {
+        return false;
+    }
+
+    if (rPam.Start()->nContent.GetIndex() != pTextNode->Len())
+    {
+        return false;
+    }
+
+    // Look up the style content based on the name.
+    OUString sStyle;
+    if (!(rValue >>= sStyle))
+    {
+        return false;
+    }
+
+    IStyleAccess& rStyleAccess = rPam.GetDoc().GetIStyleAccess();
+    std::shared_ptr<SfxItemSet> pStyle
+        = rStyleAccess.getByName(sStyle, IStyleAccess::AUTO_STYLE_CHAR);
+    if (!pStyle)
+    {
+        return false;
+    }
+
+    // Set the style on the text node.
+    SwFormatAutoFormat aItem(RES_PARATR_LIST_AUTOFMT);
+    aItem.SetStyleHandle(pStyle);
+    pTextNode->SetAttr(aItem);
+    // Clear the style from the hints array. Without clearing, it would 
contain some style which
+    // happened to be there previously.
+    rItemSet.ClearItem(RES_TXTATR_AUTOFMT);
+    return true;
+}
+
 void
 SwUnoCursorHelper::SetTextFormatColl(const uno::Any & rAny, SwPaM & rPaM)
 {
@@ -451,6 +502,11 @@ SwUnoCursorHelper::SetCursorPropertyValue(
             lcl_setCharStyle(rPam.GetDoc(), rValue, rItemSet);
         break;
         case RES_TXTATR_AUTOFMT:
+            if (lcl_setListAutoStyle(rPam, rValue, rItemSet))
+            {
+                break;
+            }
+
             lcl_setAutoStyle(rPam.GetDoc().GetIStyleAccess(),
                     rValue, rItemSet, false);
         break;
@@ -2019,7 +2075,8 @@ SwUnoCursorHelper::GetPropertyStates(
         if(!pEntry)
         {
             if (pNames[i] == UNO_NAME_IS_SKIP_HIDDEN_TEXT ||
-                pNames[i] == UNO_NAME_IS_SKIP_PROTECTED_TEXT)
+                pNames[i] == UNO_NAME_IS_SKIP_PROTECTED_TEXT ||
+                pNames[i] == UNO_NAME_NO_FORMAT_ATTR)
             {
                 pStates[i] = beans::PropertyState_DEFAULT_VALUE;
                 continue;
@@ -2212,6 +2269,7 @@ SwXTextCursor::getPropertySetInfo()
         {
             { u"" UNO_NAME_IS_SKIP_HIDDEN_TEXT, FN_SKIP_HIDDEN_TEXT, 
cppu::UnoType<bool>::get(), PROPERTY_NONE,     0},
             { u"" UNO_NAME_IS_SKIP_PROTECTED_TEXT, FN_SKIP_PROTECTED_TEXT, 
cppu::UnoType<bool>::get(), PROPERTY_NONE,     0},
+            { u"" UNO_NAME_NO_FORMAT_ATTR, 0, cppu::UnoType<bool>::get(), 
PROPERTY_NONE,     0},
             { u"", 0, css::uno::Type(), 0, 0 }
         };
         const uno::Reference< beans::XPropertySetInfo >  xInfo =
@@ -2260,10 +2318,26 @@ SwXTextCursor::setPropertyValue(
             pTextNode->ResetAttr(RES_PARATR_LIST_BEGIN, RES_PARATR_LIST_END);
         }
     }
+    else if (rPropertyName == UNO_NAME_NO_FORMAT_ATTR)
+    {
+        bool bSet(false);
+        if (!(rValue >>= bSet))
+        {
+            throw lang::IllegalArgumentException();
+        }
+        if (bSet)
+        {
+            m_nAttrMode = SetAttrMode::NOFORMATATTR;
+        }
+        else
+        {
+            m_nAttrMode = SetAttrMode::DEFAULT;
+        }
+    }
     else
     {
         SwUnoCursorHelper::SetPropertyValue(rUnoCursor,
-                m_rPropSet, rPropertyName, rValue);
+                m_rPropSet, rPropertyName, rValue, m_nAttrMode);
     }
 }
 
@@ -2576,7 +2650,8 @@ SwXTextCursor::getPropertyDefaults(
             if (!pEntry)
             {
                 if (pNames[i] == UNO_NAME_IS_SKIP_HIDDEN_TEXT ||
-                    pNames[i] == UNO_NAME_IS_SKIP_PROTECTED_TEXT)
+                    pNames[i] == UNO_NAME_IS_SKIP_PROTECTED_TEXT ||
+                    pNames[i] == UNO_NAME_NO_FORMAT_ATTR)
                 {
                     continue;
                 }
diff --git a/xmloff/source/text/txtparai.cxx b/xmloff/source/text/txtparai.cxx
index be40c2c34ea6..d7308f8f4c18 100644
--- a/xmloff/source/text/txtparai.cxx
+++ b/xmloff/source/text/txtparai.cxx
@@ -1820,6 +1820,19 @@ void XMLParaContext::endFastElement(sal_Int32 )
 
     if (m_xHints)
     {
+        bool bSetNoFormatAttr = false;
+        uno::Reference<beans::XPropertySet> xCursorProps(xAttrCursor, 
uno::UNO_QUERY);
+        if (m_xHints->GetHints().size() > 1)
+        {
+            // We have multiple hints, then make try to ask the cursor to not 
upgrade our character
+            // attributes to paragraph-level formatting, which would lead to 
incorrect rendering.
+            uno::Reference<beans::XPropertySetInfo> xCursorPropsInfo = 
xCursorProps->getPropertySetInfo();
+            bSetNoFormatAttr = 
xCursorPropsInfo->hasPropertyByName("NoFormatAttr");
+        }
+        if (bSetNoFormatAttr)
+        {
+            xCursorProps->setPropertyValue("NoFormatAttr", uno::Any(true));
+        }
         for (const auto & i : m_xHints->GetHints())
         {
             XMLHint_Impl *const pHint = i.get();
@@ -1959,6 +1972,10 @@ void XMLParaContext::endFastElement(sal_Int32 )
                 break;
             }
         }
+        if (bSetNoFormatAttr)
+        {
+            xCursorProps->setPropertyValue("NoFormatAttr", uno::Any(false));
+        }
     }
     m_xHints.reset();
 }

Reply via email to