sw/qa/core/text/data/redline-image-inline.docx |binary
 sw/qa/core/text/itrpaint.cxx                   |   97 ++++++++++++++++---------
 sw/source/core/text/porfly.cxx                 |   10 ++
 sw/source/core/text/porlay.cxx                 |   14 +++
 4 files changed, 84 insertions(+), 37 deletions(-)

New commits:
commit b9433b5c9f9741bcfab0d0283ebed3391303ced4
Author:     Miklos Vajna <[email protected]>
AuthorDate: Thu Jan 22 08:37:58 2026 +0100
Commit:     Caolán McNamara <[email protected]>
CommitDate: Thu Jan 22 10:59:49 2026 +0100

    cool#13988 sw redline render mode: handle inline images
    
    Load the bugdoc, dispatch .uno:RedlineRenderMode, anchored images in
    deletions are grey, but this doesn't work with inline images.
    
    There is some infrastructure for this added in commit
    d845b91bcc6eb885c55494d4d4fab4ec09577e1d (tdf#78864 sw track changes:
    cross out deleted images, 2021-04-30), but that crosses out images
    instead of shading.
    
    Fix this by checking for the usual SwRedlineRenderMode flags in
    sw::FlyContentPortion::Paint() to avoid the unwanted cross. Also extend
    SwLineLayout::CalcLine() to set the inserted/deleted flags on the fly
    frame for redlines, so SwGrfNode::GetGraphicAttr() can do its shading as
    usual.
    
    And add a GetMetaFileImages() in the test suite to reduce some
    duplication.
    
    Change-Id: I97f2311ad7e9a6ffc70d76c1811faa2c13e509fe
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197789
    Reviewed-by: Caolán McNamara <[email protected]>
    Tested-by: Jenkins CollaboraOffice <[email protected]>

diff --git a/sw/qa/core/text/data/redline-image-inline.docx 
b/sw/qa/core/text/data/redline-image-inline.docx
new file mode 100644
index 000000000000..beaa9bbad87f
Binary files /dev/null and b/sw/qa/core/text/data/redline-image-inline.docx 
differ
diff --git a/sw/qa/core/text/itrpaint.cxx b/sw/qa/core/text/itrpaint.cxx
index 6b395efcef24..47aed19bb7ea 100644
--- a/sw/qa/core/text/itrpaint.cxx
+++ b/sw/qa/core/text/itrpaint.cxx
@@ -159,20 +159,12 @@ bool IsGrayScale(const BitmapEx& rBitmap)
     return aColor.GetRed() == aColor.GetGreen() && aColor.GetRed() == 
aColor.GetBlue();
 }
 
-CPPUNIT_TEST_FIXTURE(Test, testAnchoredImageRedlineRenderModeOmitInsertDelete)
+std::vector<BitmapEx> GetMetaFileImages(const GDIMetaFile& rMetaFile)
 {
-    // Given a document with a normal, a deleted and an inserted image:
-    createSwDoc("redline-image-anchored.docx");
-
-    // When using the standard redline render mode:
-    SwDocShell* pDocShell = getSwDocShell();
-    std::shared_ptr<GDIMetaFile> xMetaFile = pDocShell->GetPreviewMetaFile();
-
-    // Then make sure none of the images are grayscale:
     std::vector<BitmapEx> aImages;
-    for (size_t nAction = 0; nAction < xMetaFile->GetActionSize(); ++nAction)
+    for (size_t nAction = 0; nAction < rMetaFile.GetActionSize(); ++nAction)
     {
-        MetaAction* pAction = xMetaFile->GetAction(nAction);
+        MetaAction* pAction = rMetaFile.GetAction(nAction);
         if (pAction->GetType() != MetaActionType::BMPEXSCALE)
         {
             continue;
@@ -181,6 +173,20 @@ CPPUNIT_TEST_FIXTURE(Test, 
testAnchoredImageRedlineRenderModeOmitInsertDelete)
         auto pAct = static_cast<MetaBmpExScaleAction*>(pAction);
         aImages.push_back(pAct->GetBitmapEx());
     }
+    return aImages;
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testAnchoredImageRedlineRenderModeOmitInsertDelete)
+{
+    // Given a document with a normal, a deleted and an inserted image:
+    createSwDoc("redline-image-anchored.docx");
+
+    // When using the standard redline render mode:
+    SwDocShell* pDocShell = getSwDocShell();
+    std::shared_ptr<GDIMetaFile> xMetaFile = pDocShell->GetPreviewMetaFile();
+
+    // Then make sure none of the images are grayscale:
+    std::vector<BitmapEx> aImages = GetMetaFileImages(*xMetaFile);
     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), aImages.size());
     CPPUNIT_ASSERT(!IsGrayScale(aImages[0]));
     CPPUNIT_ASSERT(!IsGrayScale(aImages[1]));
@@ -194,18 +200,7 @@ CPPUNIT_TEST_FIXTURE(Test, 
testAnchoredImageRedlineRenderModeOmitInsertDelete)
 
     xMetaFile = pDocShell->GetPreviewMetaFile();
 
-    aImages.clear();
-    for (size_t nAction = 0; nAction < xMetaFile->GetActionSize(); ++nAction)
-    {
-        MetaAction* pAction = xMetaFile->GetAction(nAction);
-        if (pAction->GetType() != MetaActionType::BMPEXSCALE)
-        {
-            continue;
-        }
-
-        auto pAct = static_cast<MetaBmpExScaleAction*>(pAction);
-        aImages.push_back(pAct->GetBitmapEx());
-    }
+    aImages = GetMetaFileImages(*xMetaFile);
     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), aImages.size());
     CPPUNIT_ASSERT(!IsGrayScale(aImages[0]));
     CPPUNIT_ASSERT(!IsGrayScale(aImages[1]));
@@ -219,18 +214,52 @@ CPPUNIT_TEST_FIXTURE(Test, 
testAnchoredImageRedlineRenderModeOmitInsertDelete)
 
     xMetaFile = pDocShell->GetPreviewMetaFile();
 
-    aImages.clear();
-    for (size_t nAction = 0; nAction < xMetaFile->GetActionSize(); ++nAction)
-    {
-        MetaAction* pAction = xMetaFile->GetAction(nAction);
-        if (pAction->GetType() != MetaActionType::BMPEXSCALE)
-        {
-            continue;
-        }
+    aImages = GetMetaFileImages(*xMetaFile);
+    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), aImages.size());
+    CPPUNIT_ASSERT(!IsGrayScale(aImages[0]));
+    CPPUNIT_ASSERT(IsGrayScale(aImages[1]));
+    CPPUNIT_ASSERT(!IsGrayScale(aImages[2]));
+}
 
-        auto pAct = static_cast<MetaBmpExScaleAction*>(pAction);
-        aImages.push_back(pAct->GetBitmapEx());
-    }
+CPPUNIT_TEST_FIXTURE(Test, testInlineImageRedlineRenderModeOmitInsertDelete)
+{
+    // Given a document with a normal, a deleted and an inserted image:
+    createSwDoc("redline-image-inline.docx");
+
+    // When using the standard redline render mode:
+    SwDocShell* pDocShell = getSwDocShell();
+    std::shared_ptr<GDIMetaFile> xMetaFile = pDocShell->GetPreviewMetaFile();
+
+    // Then make sure none of the images are grayscale:
+    std::vector<BitmapEx> aImages = GetMetaFileImages(*xMetaFile);
+    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), aImages.size());
+    CPPUNIT_ASSERT(!IsGrayScale(aImages[0]));
+    CPPUNIT_ASSERT(!IsGrayScale(aImages[1]));
+    CPPUNIT_ASSERT(!IsGrayScale(aImages[2]));
+
+    // Omit insert: default, default, grayscale.
+    SwWrtShell* pWrtShell = pDocShell->GetWrtShell();
+    SwViewOption aOpt(*pWrtShell->GetViewOptions());
+    aOpt.SetRedlineRenderMode(SwRedlineRenderMode::OmitInserts);
+    pWrtShell->ApplyViewOptions(aOpt);
+
+    xMetaFile = pDocShell->GetPreviewMetaFile();
+
+    aImages = GetMetaFileImages(*xMetaFile);
+    CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), aImages.size());
+    CPPUNIT_ASSERT(!IsGrayScale(aImages[0]));
+    CPPUNIT_ASSERT(!IsGrayScale(aImages[1]));
+    // Without the accompanying fix in place, this test would have failed, the 
image's center pixel
+    // wasn't gray.
+    CPPUNIT_ASSERT(IsGrayScale(aImages[2]));
+
+    // Omit deletes: default, grayscale, default.
+    aOpt.SetRedlineRenderMode(SwRedlineRenderMode::OmitDeletes);
+    pWrtShell->ApplyViewOptions(aOpt);
+
+    xMetaFile = pDocShell->GetPreviewMetaFile();
+
+    aImages = GetMetaFileImages(*xMetaFile);
     CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), aImages.size());
     CPPUNIT_ASSERT(!IsGrayScale(aImages[0]));
     CPPUNIT_ASSERT(IsGrayScale(aImages[1]));
diff --git a/sw/source/core/text/porfly.cxx b/sw/source/core/text/porfly.cxx
index d4612cbd26d0..5b5b64bfccd9 100644
--- a/sw/source/core/text/porfly.cxx
+++ b/sw/source/core/text/porfly.cxx
@@ -40,6 +40,7 @@
 #include <sortedobjs.hxx>
 #include <officecfg/Office/Common.hxx>
 #include <PostItMgr.hxx>
+#include <viewopt.hxx>
 
 /**
  * class SwFlyPortion => we expect a frame-locale SwRect!
@@ -223,9 +224,10 @@ void sw::FlyContentPortion::Paint(const SwTextPaintInfo& 
rInf) const
     if(rInf.GetTextFrame()->IsVertical())
         rInf.GetTextFrame()->SwitchHorizontalToVertical(aRepaintRect);
 
+    SwViewShell* pViewShell = m_pFly->getRootFrame()->GetCurrShell();
     if(!((m_pFly->IsCompletePaint() ||
             m_pFly->getFrameArea().Overlaps(aRepaintRect)) &&
-            SwFlyFrame::IsPaint(m_pFly->GetVirtDrawObj(), 
*m_pFly->getRootFrame()->GetCurrShell())))
+            SwFlyFrame::IsPaint(m_pFly->GetVirtDrawObj(), *pViewShell)))
         return;
 
     SwRect aRect(m_pFly->getFrameArea());
@@ -239,7 +241,11 @@ void sw::FlyContentPortion::Paint(const SwTextPaintInfo& 
rInf) const
 
         // track changes: cross out the image, if it is deleted
         const SwFrame *pFrame = m_pFly->Lower();
-        if ( GetAuthor() != std::string::npos && IsDeleted() && pFrame )
+        const SwViewOption* pViewOptions = pViewShell->GetViewOptions();
+        SwRedlineRenderMode eRedlineRenderMode
+            = pViewOptions ? pViewOptions->GetRedlineRenderMode() : 
SwRedlineRenderMode::Standard;
+        if (GetAuthor() != std::string::npos && IsDeleted() && pFrame
+            && eRedlineRenderMode == SwRedlineRenderMode::Standard)
         {
             SwRect aPaintRect( pFrame->GetPaintArea() );
 
diff --git a/sw/source/core/text/porlay.cxx b/sw/source/core/text/porlay.cxx
index 1206eebe4e3a..7c5b7b490e5c 100644
--- a/sw/source/core/text/porlay.cxx
+++ b/sw/source/core/text/porlay.cxx
@@ -671,6 +671,7 @@ void SwLineLayout::CalcLine( SwTextFormatter &rLine, 
SwTextFormatInfo &rInf )
         if( pPos->IsFlyCntPortion() )
         {
             bool bDeleted = false;
+            bool bInserted = false;
             size_t nAuthor = std::string::npos;
             if ( bHasRedline )
             {
@@ -682,10 +683,21 @@ void SwLineLayout::CalcLine( SwTextFormatter &rLine, 
SwTextFormatInfo &rInf )
                 bool bHasFlyRedline = 
rLine.GetRedln()->CheckLine(flyStart.first->GetIndex(),
                     flyStart.second, flyStart.first->GetIndex(), 
flyStart.second, sRedlineText,
                     bHasRedlineEnd, eRedlineEnd, /*pAuthorAtPos=*/&nAuthor);
-                bDeleted = bHasFlyRedline && eRedlineEnd == 
RedlineType::Delete;
+                if (bHasFlyRedline)
+                {
+                    bDeleted = eRedlineEnd == RedlineType::Delete;
+                    bInserted = eRedlineEnd == RedlineType::Insert;
+                }
             }
             static_cast<SwFlyCntPortion*>(pPos)->SetDeleted(bDeleted);
             static_cast<SwFlyCntPortion*>(pPos)->SetAuthor(nAuthor);
+
+            if (auto pFlyPortion = dynamic_cast<sw::FlyContentPortion*>(pPos))
+            {
+                SwFlyFrame* pFlyFrame = pFlyPortion->GetFlyFrame();
+                pFlyFrame->SetDeleted(bDeleted);
+                pFlyFrame->SetInserted(bInserted);
+            }
         }
         // anchored to characters
         else if ( pPos->IsFlyPortion() )

Reply via email to