sw/qa/filter/md/data/image-and-link.md |    1 
 sw/qa/filter/md/md.cxx                 |   61 +++++++++++++++++++++++++++++++++
 sw/source/filter/md/mdcallbcks.cxx     |   12 +++++-
 sw/source/filter/md/swmd.cxx           |   15 ++++++--
 sw/source/filter/md/swmd.hxx           |    3 +
 sw/source/filter/md/wrtmd.cxx          |   33 ++++++++++++++++-
 sw/source/uibase/utlui/uitool.cxx      |    2 -
 7 files changed, 118 insertions(+), 9 deletions(-)

New commits:
commit 3afb246da376076c7d05d282c48f274b3d96d029
Author:     Xisco Fauli <[email protected]>
AuthorDate: Thu Sep 11 22:38:26 2025 +0200
Commit:     Xisco Fauli <[email protected]>
CommitDate: Fri Sep 12 08:52:40 2025 +0200

    sw: silence warning C6011: Dereferencing NULL pointer 'pFormat'
    
    Change-Id: I2c075588d6efff4f51832ccefc241e8e135954c0
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/190840
    Tested-by: Jenkins
    Reviewed-by: Xisco Fauli <[email protected]>

diff --git a/sw/source/uibase/utlui/uitool.cxx 
b/sw/source/uibase/utlui/uitool.cxx
index 5f98464af353..986fc9d45c7b 100644
--- a/sw/source/uibase/utlui/uitool.cxx
+++ b/sw/source/uibase/utlui/uitool.cxx
@@ -237,7 +237,7 @@ static void FillHdFt(SfxPoolItem& rFormat, const 
SfxItemSet& rSet, SwPageDesc& r
         pFormat = pHeader->GetHeaderFormat();
     else
         pFormat = pFooter->GetFooterFormat();
-    OSL_ENSURE(pFormat != nullptr, "no header or footer format");
+    assert(pFormat && "no header or footer format");
 
     SwAttrSet aSet(pFormat->GetAttrSet());
     aSet.Put(rSet);
commit b7bc0e5f3999950d6b5f0d2bdcda2c6cc2f04e61
Author:     Miklos Vajna <[email protected]>
AuthorDate: Thu Sep 11 08:36:04 2025 +0200
Commit:     Miklos Vajna <[email protected]>
CommitDate: Fri Sep 12 08:52:33 2025 +0200

    tdf#168341 sw markdown filter: handle links on images
    
    The bugdoc has an image which has a link: the link is lost on both
    import and export.
    
    The doc model for links is different for text an images: the text is
    covered by an SwFormatINetFormat hint, but images have their
    SwFlyFrameFormat, and that can contain an SwFormatURL.
    
    Fix the problem by looking at the currently pending attributes stack
    while inserting images: if a link is open, also set that on the image.
    The original link will be discarded, because it covers no text.
    
    Also fix the export to write markup when the fly format has an
    SwFormatURL.
    
    Change-Id: Ib94bcd0903835d6eb197ef2077075390305f37c8
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/190813
    Tested-by: Jenkins
    Reviewed-by: Miklos Vajna <[email protected]>

diff --git a/sw/qa/filter/md/data/image-and-link.md 
b/sw/qa/filter/md/data/image-and-link.md
new file mode 100644
index 000000000000..75111ca945d5
--- /dev/null
+++ b/sw/qa/filter/md/data/image-and-link.md
@@ -0,0 +1 @@
+A [![alt](./myimage.png)](https://www.example.com) Z
diff --git a/sw/qa/filter/md/md.cxx b/sw/qa/filter/md/md.cxx
index 824b80d3874d..1de912e55ed8 100644
--- a/sw/qa/filter/md/md.cxx
+++ b/sw/qa/filter/md/md.cxx
@@ -25,6 +25,7 @@
 #include <ndgrf.hxx>
 #include <itabenum.hxx>
 #include <ndtxt.hxx>
+#include <fmturl.hxx>
 
 namespace
 {
@@ -505,6 +506,66 @@ CPPUNIT_TEST_FIXTURE(Test, testTableColumnAdjustMdExport)
     CPPUNIT_ASSERT_EQUAL(aExpected, aActual);
 }
 
+CPPUNIT_TEST_FIXTURE(Test, testImageLinkMdImport)
+{
+    // Given a document with an image which has a link on it:
+    // When importing that document:
+    setImportFilterName("Markdown");
+    createSwDoc("image-and-link.md");
+
+    // Then make sure the link is not lost:
+    SwDocShell* pDocShell = getSwDocShell();
+    SwDoc* pDoc = pDocShell->GetDoc();
+    sw::FrameFormats<sw::SpzFrameFormat*>& rFlys = *pDoc->GetSpzFrameFormats();
+    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rFlys.size());
+    sw::SpzFrameFormat& rFly = *rFlys[0];
+    const SwFormatURL& rURL = rFly.GetURL();
+    // Without the accompanying fix in place, this test would have failed with:
+    // - Expected: https://www.example.com/
+    // - Actual  :
+    // i.e. the image's item set didn't have a URL.
+    CPPUNIT_ASSERT_EQUAL(u"https://www.example.com/"_ustr, rURL.GetURL());
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testImageLinkMdExport)
+{
+    // Given a document with an inline, linked image + link on it:
+    createSwDoc();
+    SwDocShell* pDocShell = getSwDocShell();
+    SwDoc* pDoc = pDocShell->GetDoc();
+    SwWrtShell* pWrtShell = pDocShell->GetWrtShell();
+    pWrtShell->Insert(u"A "_ustr);
+    SfxItemSet aFrameSet(pDoc->GetAttrPool(), svl::Items<RES_FRMATR_BEGIN, 
RES_FRMATR_END - 1>);
+    SwFormatAnchor aAnchor(RndStdIds::FLY_AS_CHAR);
+    aFrameSet.Put(aAnchor);
+    Graphic aGraphic;
+    OUString aGraphicURL(u"./test.png"_ustr);
+    IDocumentContentOperations& rIDCO = pDoc->getIDocumentContentOperations();
+    SwCursor* pCursor = pWrtShell->GetCursor();
+    SwFlyFrameFormat* pFlyFormat
+        = rIDCO.InsertGraphic(*pCursor, aGraphicURL, OUString(), &aGraphic, 
&aFrameSet,
+                              /*pGrfAttrSet=*/nullptr, 
/*SwFrameFormat=*/nullptr);
+    SwNodeOffset nContentOffset = 
pFlyFormat->GetContent().GetContentIdx()->GetIndex();
+    SwGrfNode* pGrfNode = pDoc->GetNodes()[nContentOffset + 1]->GetGrfNode();
+    pGrfNode->SetTitle(u"mytitle"_ustr);
+    SwFormatURL aFormatURL;
+    aFormatURL.SetURL(u"https://x.com"_ustr, /*bServerMap=*/false);
+    pFlyFormat->SetFormatAttr(aFormatURL);
+    pWrtShell->Insert(u" B"_ustr);
+
+    // When saving that to markdown:
+    save(mpFilter);
+
+    // Then make sure the image is exported and the link is not lost:
+    std::string aActual = TempFileToString();
+    std::string aExpected("A [![mytitle](./test.png)](https://x.com) B" 
SAL_NEWLINE_STRING);
+    // Without the accompanying fix in place, this test would have failed with:
+    // - Expected: A [![mytitle](./test.png)](https://x.com) B
+    // - Actual  : A ![mytitle](./test.png) B
+    // i.e. the image link was lost.
+    CPPUNIT_ASSERT_EQUAL(aExpected, aActual);
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s 
cinkeys+=0=break: */
diff --git a/sw/source/filter/md/mdcallbcks.cxx 
b/sw/source/filter/md/mdcallbcks.cxx
index f8692993dc55..655f95bd344d 100644
--- a/sw/source/filter/md/mdcallbcks.cxx
+++ b/sw/source/filter/md/mdcallbcks.cxx
@@ -180,7 +180,17 @@ int SwMarkdownParser::enter_span_callback(MD_SPANTYPE 
type, void* detail, void*
                                                    RTL_TEXTENCODING_UTF8);
             OUString aTitle = 
rtl::OStringToOUString(std::string_view(rTitle.text, rTitle.size),
                                                      RTL_TEXTENCODING_UTF8);
-            parser->InsertImage(aURL, aTitle);
+
+            const SwFormatINetFormat* pINetFormat = nullptr;
+            if (!parser->m_aAttrStack.empty()
+                && parser->m_aAttrStack.back()->Which() == RES_TXTATR_INETFMT)
+            {
+                // Copy the link to the image, so it won't be lost for empty 
spans.
+                const SfxPoolItem* pTopItem = 
parser->m_aAttrStack.back().get();
+                pINetFormat = dynamic_cast<const 
SwFormatINetFormat*>(pTopItem);
+            }
+
+            parser->InsertImage(aURL, aTitle, pINetFormat);
             parser->m_bInsideImage = true;
             break;
         }
diff --git a/sw/source/filter/md/swmd.cxx b/sw/source/filter/md/swmd.cxx
index cf687d3a9ea6..1b46ae46494b 100644
--- a/sw/source/filter/md/swmd.cxx
+++ b/sw/source/filter/md/swmd.cxx
@@ -46,6 +46,7 @@
 #include <ndgrf.hxx>
 #include <fmtcntnt.hxx>
 #include <swtypes.hxx>
+#include <fmturl.hxx>
 
 #include "swmd.hxx"
 
@@ -592,7 +593,8 @@ void SwMarkdownParser::SetAttrs(SwPaM& rRange)
 
 void SwMarkdownParser::ClearAttrs() { m_xDoc->ResetAttrs(*m_pPam, true); }
 
-void SwMarkdownParser::InsertImage(const OUString& aURL, const OUString& 
rTitle)
+void SwMarkdownParser::InsertImage(const OUString& aURL, const OUString& 
rTitle,
+                                   const SwFormatINetFormat* pINetFormat)
 {
     OUString sGrfNm = INetURLObject::GetAbsURL(m_sBaseURL, aURL);
 
@@ -636,8 +638,7 @@ void SwMarkdownParser::InsertImage(const OUString& aURL, 
const OUString& rTitle)
     }
 
     SfxItemSet aFlySet(
-        SfxItemSet::makeFixedSfxItemSet<RES_FRM_SIZE, RES_VERT_ORIENT, 
RES_HORI_ORIENT, RES_ANCHOR>(
-            m_xDoc->GetAttrPool()));
+        SfxItemSet::makeFixedSfxItemSet<RES_FRMATR_BEGIN, 
RES_FRMATR_END>(m_xDoc->GetAttrPool()));
 
     aFlySet.Put(SwFormatAnchor(RndStdIds::FLY_AS_CHAR));
     aFlySet.Put(SwFormatFrameSize(SwFrameSize::Fixed, nWidth, nHeight));
@@ -645,6 +646,14 @@ void SwMarkdownParser::InsertImage(const OUString& aURL, 
const OUString& rTitle)
     aFlySet.Put(
         SwFormatVertOrient(0, text::VertOrientation::CHAR_CENTER, 
text::RelOrientation::CHAR));
 
+    if (pINetFormat)
+    {
+        // Have a link, set that on the image.
+        SwFormatURL aFormatURL;
+        aFormatURL.SetURL(pINetFormat->GetValue(), /*bServerMap=*/false);
+        aFlySet.Put(aFormatURL);
+    }
+
     SanitizeAnchor(aFlySet);
 
     SwFlyFrameFormat* pFlyFormat = 
m_xDoc->getIDocumentContentOperations().InsertGraphic(
diff --git a/sw/source/filter/md/swmd.hxx b/sw/source/filter/md/swmd.hxx
index 30e96a626990..d4646ccb507c 100644
--- a/sw/source/filter/md/swmd.hxx
+++ b/sw/source/filter/md/swmd.hxx
@@ -118,7 +118,8 @@ class SwMarkdownParser
     void SetAttrs(SwPaM& rRange);
     void ClearAttrs();
 
-    void InsertImage(const OUString& aURL, const OUString& rTitle);
+    void InsertImage(const OUString& aURL, const OUString& rTitle,
+                     const SwFormatINetFormat* pINetFormat);
 
     void StartTable(sal_Int32 nRow, sal_Int32 nCol);
     void EndTable();
diff --git a/sw/source/filter/md/wrtmd.cxx b/sw/source/filter/md/wrtmd.cxx
index 7430aa389447..d8764a9aa283 100644
--- a/sw/source/filter/md/wrtmd.cxx
+++ b/sw/source/filter/md/wrtmd.cxx
@@ -47,6 +47,7 @@
 #include <charatr.hxx>
 #include <fmtcntnt.hxx>
 #include <ndgrf.hxx>
+#include <fmturl.hxx>
 #include "wrtmd.hxx"
 
 #include <algorithm>
@@ -60,10 +61,12 @@ struct SwMDImageInfo
 {
     OUString aURL;
     OUString aTitle;
+    OUString aLink;
 
-    SwMDImageInfo(const OUString& rURL, const OUString& rTitle)
+    SwMDImageInfo(const OUString& rURL, const OUString& rTitle, const 
OUString& rLink)
         : aURL(rURL)
         , aTitle(rTitle)
+        , aLink(rLink)
     {
     }
 
@@ -73,7 +76,11 @@ struct SwMDImageInfo
             return true;
         if (rOther.aURL < aURL)
             return false;
-        return aTitle < rOther.aTitle;
+        if (aLink < rOther.aLink)
+            return true;
+        if (rOther.aLink < aLink)
+            return false;
+        return aLink < rOther.aLink;
     }
 };
 
@@ -219,7 +226,13 @@ void ApplyItem(SwMDWriter& rWrt, FormattingStatus& 
rChange, const SfxPoolItem& r
                 aGraphicURL = 
URIHelper::simpleNormalizedMakeRelative(rBaseURL, aGraphicURL);
             }
             OUString aTitle = pGrfNode->GetTitle();
-            rChange.aImages.emplace(aGraphicURL, aTitle);
+            OUString aLink;
+            if (rFrameFormat.GetAttrSet().HasItem(RES_URL))
+            {
+                const SwFormatURL& rLink = rFrameFormat.GetURL();
+                aLink = rLink.GetURL();
+            }
+            rChange.aImages.emplace(aGraphicURL, aTitle, aLink);
             break;
         }
     }
@@ -388,11 +401,25 @@ void OutFormattingChange(SwMDWriter& rWrt, NodePositions& 
positions, sal_Int32 p
             continue;
         }
 
+        if (!rImageInfo.aLink.isEmpty())
+        {
+            // Start image link.
+            rWrt.Strm().WriteUnicodeOrByteText(u"[");
+        }
+
         rWrt.Strm().WriteUnicodeOrByteText(u"![");
         OutEscapedChars(rWrt, rImageInfo.aTitle);
         rWrt.Strm().WriteUnicodeOrByteText(u"](");
         rWrt.Strm().WriteUnicodeOrByteText(rImageInfo.aURL);
         rWrt.Strm().WriteUnicodeOrByteText(u")");
+
+        if (!rImageInfo.aLink.isEmpty())
+        {
+            // End image link.
+            rWrt.Strm().WriteUnicodeOrByteText(u"](");
+            rWrt.Strm().WriteUnicodeOrByteText(rImageInfo.aLink);
+            rWrt.Strm().WriteUnicodeOrByteText(u")");
+        }
     }
 
     current = std::move(result);

Reply via email to