offapi/com/sun/star/text/TextGraphicObject.idl            |    5 ++
 sw/inc/hintids.hxx                                        |    2 -
 sw/inc/unoprnms.hxx                                       |    1 
 sw/qa/extras/ooxmlexport/data/tdf162527_hidden_image.docx |binary
 sw/qa/extras/ooxmlexport/data/tdf169802_hidden_shape.docx |binary
 sw/qa/extras/ooxmlexport/ooxmlexport13.cxx                |   28 ++++++++++++++
 sw/source/core/bastyp/init.cxx                            |    2 -
 sw/source/core/layout/anchoredobject.cxx                  |    5 ++
 sw/source/core/layout/fly.cxx                             |    8 ++++
 sw/source/core/unocore/unoframe.cxx                       |   19 ++++++++-
 sw/source/core/unocore/unomap1.cxx                        |    1 
 sw/source/filter/html/css1atr.cxx                         |    2 -
 sw/source/filter/html/htmlatr.cxx                         |    2 -
 sw/source/filter/ww8/docxattributeoutput.cxx              |    2 +
 sw/source/writerfilter/dmapper/GraphicImport.cxx          |    3 +
 15 files changed, 74 insertions(+), 6 deletions(-)

New commits:
commit 0fe7cbd55ef43718e1e189e7bf5443abf239c138
Author:     Aron Budea <[email protected]>
AuthorDate: Mon Jan 12 15:04:20 2026 +1030
Commit:     Mike Kaganski <[email protected]>
CommitDate: Thu Jan 15 05:19:47 2026 +0100

    tdf#162527 tdf#169802 hidden image/shape in DOCX should be invisible
    
    ...and remain invisible after save.
    
    After 0b9e4f6085d147c43a86d107303eea9b86e7f34c shapes did get
    hidden, but in case of wrap-around shapes text still flowed
    around their supposed placement.
    Hidden images were simply shown before and after.
    
    Add a new Visible property to SwXTextGraphicObject, and let
    it set SwFlyDrawObj's Visible member as SdrObject.
    Import and export wp:docPr's hidden attribute in OOXML based
    on these properties.
    To avoid showing their area, let
    SwAnchoredObject::GetObjRectWithSpaces() return an empty
    rectangle (during opening this is only called from
    SwTextFly's IsAnyObj(...) and InitAnchoredObjList(), though).
    
    Note that Writer lacks support of changing visibility of
    images and shapes. This change adds minimal support for
    hiding them and roundtripping the setting in DOCX, but
    doesn't add ODF or UI support.
    
    Change-Id: I6e9d062628006a7128e380d1af06508625aa3d06
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197056
    Tested-by: Jenkins
    Reviewed-by: Aron Budea <[email protected]>
    (cherry picked from commit c1f7ea0db134d63c54b581f11e843ebd5bb83b54)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197286
    Reviewed-by: Mike Kaganski <[email protected]>

diff --git a/offapi/com/sun/star/text/TextGraphicObject.idl 
b/offapi/com/sun/star/text/TextGraphicObject.idl
index 423a41778992..7a425f70ea27 100644
--- a/offapi/com/sun/star/text/TextGraphicObject.idl
+++ b/offapi/com/sun/star/text/TextGraphicObject.idl
@@ -123,6 +123,11 @@ published service TextGraphicObject
      */
     [optional, property] com::sun::star::graphic::XGraphic Graphic;
 
+    /** if this is `FALSE`, the graphic is not visible.
+
+        @since LibreOffice 26.2
+     */
+    [ optional, property ] boolean Visible;
 };
 
 
diff --git a/sw/inc/hintids.hxx b/sw/inc/hintids.hxx
index 39676270559c..423dafdcea08 100644
--- a/sw/inc/hintids.hxx
+++ b/sw/inc/hintids.hxx
@@ -411,8 +411,8 @@ inline constexpr TypedWhichId<SwGammaGrf> 
RES_GRFATR_GAMMA(RES_GRFATR_BEGIN + 8)
 inline constexpr TypedWhichId<SwInvertGrf> RES_GRFATR_INVERT(RES_GRFATR_BEGIN 
+ 9);
 inline constexpr TypedWhichId<SwTransparencyGrf> 
RES_GRFATR_TRANSPARENCY(RES_GRFATR_BEGIN + 10);
 inline constexpr TypedWhichId<SwDrawModeGrf> 
RES_GRFATR_DRAWMODE(RES_GRFATR_BEGIN + 11);
+inline constexpr TypedWhichId<SfxBoolItem> RES_GRFATR_VISIBLE(RES_GRFATR_BEGIN 
+ 12);
 
-inline constexpr TypedWhichId<SfxBoolItem> RES_GRFATR_DUMMY4(RES_GRFATR_BEGIN 
+ 12);
 inline constexpr TypedWhichId<SfxBoolItem> RES_GRFATR_DUMMY5(RES_GRFATR_BEGIN 
+ 13);
 inline constexpr sal_uInt16 RES_GRFATR_END(RES_GRFATR_BEGIN + 14);
 
diff --git a/sw/inc/unoprnms.hxx b/sw/inc/unoprnms.hxx
index 27981e05db01..e4b865b3f6de 100644
--- a/sw/inc/unoprnms.hxx
+++ b/sw/inc/unoprnms.hxx
@@ -601,6 +601,7 @@ inline constexpr OUString UNO_NAME_ADJUST_BLUE = 
u"AdjustBlue"_ustr;
 inline constexpr OUString UNO_NAME_GAMMA = u"Gamma"_ustr;
 inline constexpr OUString UNO_NAME_GRAPHIC_IS_INVERTED = 
u"GraphicIsInverted"_ustr;
 inline constexpr OUString UNO_NAME_TRANSPARENCY = u"Transparency"_ustr;
+inline constexpr OUString UNO_NAME_VISIBLE = u"Visible"_ustr;
 inline constexpr OUString UNO_NAME_REDLINE_AUTHOR = u"RedlineAuthor"_ustr;
 inline constexpr OUString UNO_NAME_REDLINE_DATE_TIME = u"RedlineDateTime"_ustr;
 inline constexpr OUString UNO_NAME_REDLINE_MOVED_ID = u"RedlineMovedID"_ustr;
diff --git a/sw/qa/extras/ooxmlexport/data/tdf162527_hidden_image.docx 
b/sw/qa/extras/ooxmlexport/data/tdf162527_hidden_image.docx
new file mode 100644
index 000000000000..5196bd67d772
Binary files /dev/null and 
b/sw/qa/extras/ooxmlexport/data/tdf162527_hidden_image.docx differ
diff --git a/sw/qa/extras/ooxmlexport/data/tdf169802_hidden_shape.docx 
b/sw/qa/extras/ooxmlexport/data/tdf169802_hidden_shape.docx
new file mode 100644
index 000000000000..b72fab170a69
Binary files /dev/null and 
b/sw/qa/extras/ooxmlexport/data/tdf169802_hidden_shape.docx differ
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport13.cxx 
b/sw/qa/extras/ooxmlexport/ooxmlexport13.cxx
index bf8776fc6c41..43c16d0c912d 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport13.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport13.cxx
@@ -724,6 +724,34 @@ DECLARE_OOXMLEXPORT_TEST(testTdf156484, "tdf156484.docx")
     CPPUNIT_ASSERT_MESSAGE("Third shape should not be printable.", 
!getProperty<bool>(xShape, u"Printable"_ustr));
 }
 
+CPPUNIT_TEST_FIXTURE(Test, testTdf162527_hidden_image)
+{
+    createSwDoc("tdf162527_hidden_image.docx");
+    save(TestFilter::DOCX);
+
+    xmlDocUniquePtr pXmlDoc = parseExport(u"word/document.xml"_ustr);
+    CPPUNIT_ASSERT(pXmlDoc);
+    // Without the fix this element would have no 'hidden' attribute
+    assertXPath(pXmlDoc, 
"/w:document/w:body/w:p/w:r/w:drawing/wp:anchor/wp:docPr", "hidden",
+                u"1");
+
+    auto xShape(getShape(1));
+    CPPUNIT_ASSERT_MESSAGE("Shape should not be visible.",
+                           !getProperty<bool>(xShape, u"Visible"_ustr));
+}
+
+CPPUNIT_TEST_FIXTURE(Test, testTdf169802_hidden_shape)
+{
+    createSwDoc("tdf169802_hidden_shape.docx");
+    xmlDocUniquePtr pDump = parseLayoutDump();
+    // Just to check that the layout has sane content
+    int nTextNodes = countXPathNodes(pDump, "//*[contains(@type, 
'PortionType::Text')]");
+    CPPUNIT_ASSERT_MESSAGE("Layout must contain text nodes", 0 < nTextNodes);
+    // Layout mustn't contain fly portion, without the fix it would contain 
several
+    int nFlyNodes = countXPathNodes(pDump, "//*[contains(@type, 
'PortionType::Fly')]");
+    CPPUNIT_ASSERT_EQUAL_MESSAGE("No fly portion nodes must exist in the 
layout", 0, nFlyNodes);
+}
+
 DECLARE_OOXMLEXPORT_TEST(testTdf124594, "tdf124594.docx")
 {
     xmlDocUniquePtr pDump = parseLayoutDump();
diff --git a/sw/source/core/bastyp/init.cxx b/sw/source/core/bastyp/init.cxx
index bfc29059a774..cb2471ca3683 100644
--- a/sw/source/core/bastyp/init.cxx
+++ b/sw/source/core/bastyp/init.cxx
@@ -454,9 +454,9 @@ std::unique_ptr<ItemInfoPackage> 
createItemInfoPackageSwAttributes()
             { RES_GRFATR_INVERT, new SwInvertGrf, 0, SFX_ITEMINFOFLAG_NONE },
             { RES_GRFATR_TRANSPARENCY, new SwTransparencyGrf, 0, 
SFX_ITEMINFOFLAG_NONE },
             { RES_GRFATR_DRAWMODE, new SwDrawModeGrf, 0, SFX_ITEMINFOFLAG_NONE 
},
+            { RES_GRFATR_VISIBLE, new SfxBoolItem(RES_GRFATR_VISIBLE, true), 
0, SFX_ITEMINFOFLAG_NONE },
 
             // GraphicAttr - Dummies
-            { RES_GRFATR_DUMMY4, new SfxBoolItem( RES_GRFATR_DUMMY4 ), 0, 
SFX_ITEMINFOFLAG_NONE },
             { RES_GRFATR_DUMMY5, new SfxBoolItem( RES_GRFATR_DUMMY5 ), 0, 
SFX_ITEMINFOFLAG_NONE },
             { RES_BOXATR_FORMAT, new SwTableBoxNumFormat, 0, 
SFX_ITEMINFOFLAG_NONE },
             { RES_BOXATR_FORMULA, new SwTableBoxFormula( OUString() ), 0, 
SFX_ITEMINFOFLAG_NONE },
diff --git a/sw/source/core/layout/anchoredobject.cxx 
b/sw/source/core/layout/anchoredobject.cxx
index c8c418fa310f..39429e8b209f 100644
--- a/sw/source/core/layout/anchoredobject.cxx
+++ b/sw/source/core/layout/anchoredobject.cxx
@@ -564,6 +564,11 @@ bool SwAnchoredObject::HasClearedEnvironment() const
 */
 const SwRect& SwAnchoredObject::GetObjRectWithSpaces() const
 {
+    static const SwRect aEmptyRect;
+    // invisible objects have no area
+    if (!GetDrawObj()->IsVisible())
+        return aEmptyRect;
+
     if ( mbObjRectWithSpacesValid &&
          maLastObjRect != GetObjRect() )
     {
diff --git a/sw/source/core/layout/fly.cxx b/sw/source/core/layout/fly.cxx
index e1c2a35e8a24..380df804bac8 100644
--- a/sw/source/core/layout/fly.cxx
+++ b/sw/source/core/layout/fly.cxx
@@ -1487,6 +1487,14 @@ void SwFlyFrame::Format( vcl::RenderContext* 
/*pRenderContext*/, const SwBorderA
 {
     OSL_ENSURE( pAttrs, "FlyFrame::Format, pAttrs is 0." );
 
+    if (GetDrawObj() && !GetDrawObj()->IsVisible())
+    {
+        SwFrameAreaDefinition::FrameAreaWriteAccess aFrm(*this);
+        aFrm.setSwRect(SwRect());
+        setFrameAreaSizeValid(true);
+        return;
+    }
+
     ColLock();
 
     if ( !isFrameAreaSizeValid() )
diff --git a/sw/source/core/unocore/unoframe.cxx 
b/sw/source/core/unocore/unoframe.cxx
index b87dcd7f29f2..4ea5dbcb08c6 100644
--- a/sw/source/core/unocore/unoframe.cxx
+++ b/sw/source/core/unocore/unoframe.cxx
@@ -1411,7 +1411,14 @@ void SwXFrame::setPropertyValue(const OUString& 
rPropertyName, const ::uno::Any&
             throw beans::PropertyVetoException("Property is read-only: " + 
rPropertyName, getXWeak() );
 
         SwDoc& rDoc = pFormat->GetDoc();
-        if ( ((m_eType == FLYCNTTYPE_GRF) && isGRFATR(pEntry->nWID)) ||
+        if (m_eType == FLYCNTTYPE_GRF && RES_GRFATR_VISIBLE == pEntry->nWID)
+        {
+            bool bVisible = true;
+            aValue >>= bVisible;
+            SdrObject* pObject = 
GetOrCreateSdrObject(static_cast<SwFlyFrameFormat&>(*pFormat));
+            pObject->SetVisible(bVisible);
+        }
+        else if ( ((m_eType == FLYCNTTYPE_GRF) && isGRFATR(pEntry->nWID)) ||
             (FN_PARAM_CONTOUR_PP         == pEntry->nWID) ||
             (FN_UNO_IS_AUTOMATIC_CONTOUR == pEntry->nWID) ||
             (FN_UNO_IS_PIXEL_CONTOUR     == pEntry->nWID) )
@@ -1962,7 +1969,12 @@ uno::Any SwXFrame::getPropertyValue(const OUString& 
rPropertyName)
     }
     else if(pFormat)
     {
-        if( ((m_eType == FLYCNTTYPE_GRF) || (m_eType == FLYCNTTYPE_OLE)) &&
+        if (m_eType == FLYCNTTYPE_GRF && RES_GRFATR_VISIBLE == pEntry->nWID)
+        {
+            SdrObject* pObject = 
GetOrCreateSdrObject(static_cast<SwFlyFrameFormat&>(*pFormat));
+            aAny <<= pObject->IsVisible();
+        }
+        else if( ((m_eType == FLYCNTTYPE_GRF) || (m_eType == FLYCNTTYPE_OLE)) 
&&
                 (isGRFATR(pEntry->nWID) ||
                         pEntry->nWID == FN_PARAM_CONTOUR_PP ||
                         pEntry->nWID == FN_UNO_IS_AUTOMATIC_CONTOUR ||
@@ -3046,6 +3058,9 @@ void 
SwXFrame::attachToRange(uno::Reference<text::XTextRange> const& xTextRange,
     {
         setPropertyValue(UNO_NAME_DESCRIPTION, *pDescription);
     }
+    if (const uno::Any* pVisible = m_pProps->GetProperty(RES_GRFATR_VISIBLE, 
0))
+        setPropertyValue(UNO_NAME_VISIBLE, *pVisible);
+
 
     // For grabbag
     if (const uno::Any* pFrameIntropgrabbagItem = 
m_pProps->GetProperty(RES_FRMATR_GRABBAG, 0))
diff --git a/sw/source/core/unocore/unomap1.cxx 
b/sw/source/core/unocore/unomap1.cxx
index b04688f57240..db0e32baaa34 100644
--- a/sw/source/core/unocore/unomap1.cxx
+++ b/sw/source/core/unocore/unomap1.cxx
@@ -851,6 +851,7 @@ std::span<const SfxItemPropertyMapEntry> 
SwUnoPropertyMapProvider::GetGraphicPro
         { UNO_NAME_GAMMA, RES_GRFATR_GAMMA,        
cppu::UnoType<double>::get(),     0,   0},
         { UNO_NAME_GRAPHIC_IS_INVERTED, RES_GRFATR_INVERT,         
cppu::UnoType<bool>::get(),    0,   0},
         { UNO_NAME_TRANSPARENCY, RES_GRFATR_TRANSPARENCY, 
cppu::UnoType<sal_Int16>::get(),   0,   0},
+        { UNO_NAME_VISIBLE, RES_GRFATR_VISIBLE,           
cppu::UnoType<bool>::get(), 0, 0 },
         { UNO_NAME_GRAPHIC_COLOR_MODE, RES_GRFATR_DRAWMODE,    
cppu::UnoType<css::drawing::ColorMode>::get(),      0,   0},
 
         // added FillProperties for SW, same as FILL_PROPERTIES in svx
diff --git a/sw/source/filter/html/css1atr.cxx 
b/sw/source/filter/html/css1atr.cxx
index fa4f4fec235e..892584bd0820 100644
--- a/sw/source/filter/html/css1atr.cxx
+++ b/sw/source/filter/html/css1atr.cxx
@@ -3562,7 +3562,7 @@ SwAttrFnTab const aCSS1AttrFnTab = {
 /* RES_GRFATR_TRANSPARENCY */       nullptr,
 /* RES_GRFATR_DRWAMODE */           nullptr,
 /* RES_GRFATR_DUMMY3 */             nullptr,
-/* RES_GRFATR_DUMMY4 */             nullptr,
+/* RES_GRFATR_VISIBLE */            nullptr,
 /* RES_GRFATR_DUMMY5 */             nullptr,
 
 /* RES_BOXATR_FORMAT */             nullptr,
diff --git a/sw/source/filter/html/htmlatr.cxx 
b/sw/source/filter/html/htmlatr.cxx
index 1fc954052c1e..2fc92408e46a 100644
--- a/sw/source/filter/html/htmlatr.cxx
+++ b/sw/source/filter/html/htmlatr.cxx
@@ -3430,7 +3430,7 @@ const SwAttrFnTab aHTMLAttrFnTab = {
 /* RES_GRFATR_TRANSPARENCY */       nullptr,
 /* RES_GRFATR_DRWAMODE */           nullptr,
 /* RES_GRFATR_DUMMY3 */             nullptr,
-/* RES_GRFATR_DUMMY4 */             nullptr,
+/* RES_GRFATR_VISIBLE */            nullptr,
 /* RES_GRFATR_DUMMY5 */             nullptr,
 
 /* RES_BOXATR_FORMAT */             nullptr,
diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx 
b/sw/source/filter/ww8/docxattributeoutput.cxx
index 3317e134824d..610a641c88b3 100644
--- a/sw/source/filter/ww8/docxattributeoutput.cxx
+++ b/sw/source/filter/ww8/docxattributeoutput.cxx
@@ -5514,6 +5514,8 @@ void DocxAttributeOutput::FlyFrameGraphic( const 
SwGrfNode* pGrfNode, const Size
     OUString const title(pGrfNode ? pGrfNode->GetTitle() : 
pOLEFrameFormat->GetObjTitle());
     auto const docPrattrList(CreateDocPrAttrList(
         GetExport(), pFrameFormat->GetName().toString(), title, descr));
+    if (pSdrObj && !pSdrObj->IsVisible())
+        docPrattrList->add(XML_hidden, "1");
     m_pSerializer->startElementNS( XML_wp, XML_docPr, docPrattrList );
 
     OUString sURL, sRelId;
diff --git a/sw/source/writerfilter/dmapper/GraphicImport.cxx 
b/sw/source/writerfilter/dmapper/GraphicImport.cxx
index 891f287741d0..b24f5dd7f2de 100644
--- a/sw/source/writerfilter/dmapper/GraphicImport.cxx
+++ b/sw/source/writerfilter/dmapper/GraphicImport.cxx
@@ -1812,6 +1812,9 @@ rtl::Reference<SwXTextGraphicObject> 
GraphicImport::createGraphicObject(uno::Ref
             xGraphicObject->setPropertyValue(getPropertyName(PROP_DECORATIVE), 
uno::Any(m_bDecorative));
             if (m_rGraphicImportType == IMPORT_AS_DETECTED_ANCHOR)
             {
+                if (m_bHidden)
+                    xGraphicObject->setPropertyValue(u"Visible"_ustr, 
uno::Any(false));
+
                 if (m_nHoriRelation == text::RelOrientation::FRAME
                     && (m_nHoriOrient == text::HoriOrientation::LEFT
                         || m_nHoriOrient == text::HoriOrientation::RIGHT

Reply via email to