sd/qa/unit/data/odp/tdf168109.fodp |   65 +++++++++++++++++++++++++++++++++++++
 sd/qa/unit/import-tests2.cxx       |   31 +++++++++++++++++
 vcl/win/gdi/salprn.cxx             |   17 +++++----
 xmloff/source/draw/ximpbody.cxx    |    5 +-
 xmloff/source/draw/ximpnote.cxx    |   17 +++++++++
 xmloff/source/draw/ximpnote.hxx    |   11 +++++-
 xmloff/source/draw/ximpshap.cxx    |    2 -
 xmloff/source/draw/ximpshap.hxx    |    5 ++
 8 files changed, 140 insertions(+), 13 deletions(-)

New commits:
commit 5a0d14eb62dcccfd8f6185b9f493d6e1ff3f4bf1
Author:     Mike Kaganski <[email protected]>
AuthorDate: Thu Feb 12 19:11:29 2026 +0500
Commit:     Mike Kaganski <[email protected]>
CommitDate: Thu Feb 12 18:45:46 2026 +0100

    tdf#168109: Ignore draw:page-number attribute for notes' thumbnails
    
    ODF 1.4 Part 3 sect. 19.203 draw:page-number tells:
    
    > For thumbnails on notes pages, the value of this attribute is fixed
    > to the drawing page of a notes page.
    
    As further clarified in OASIS ODF TC call from 2025-11-24 for issue
    OFFICE-4178, that wording shall be improved to unambiguously point to
    exactly the drawing page of the respective notes page. This means,
    that for thumbnail of DrawingPage's notes page, the actual value of
    the attribute is unimportant. As the bugdoc shows, the attribute may
    be missing. It may also possibly have wrong number. In any case, we
    should not break the default correct reference established between
    the thumbnail object and respective DrawingPage, when thumbnail was
    created.
    
    This change implements just that. For SdXMLPageShapeContext, it adds
    a new method ignorePageNumber, which sets mnPageNumber to -1. Given
    that this value corresponds to draw:page-number, having XML type
    positiveInteger, this special value is safe and cannot coincide with
    any valid value there. Its meaning is "do not set the shape's page
    number", which will keep the mentioned original reference.
    
    ignorePageNumber is only called in the context of DrawingPage/notes
    page: the "ignore" flag is passed through SdXMLNotesContext from
    SdXMLDrawPageContext::createFastChildContext. For notes of master
    page, handled in SdXMLMasterPageContext::createFastChildContext, no
    change is done.
    
    Change-Id: I39ef16d4e0ceda89b38ee994c908157e93c119a2
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/199283
    Reviewed-by: Mike Kaganski <[email protected]>
    Tested-by: Jenkins

diff --git a/sd/qa/unit/data/odp/tdf168109.fodp 
b/sd/qa/unit/data/odp/tdf168109.fodp
new file mode 100644
index 000000000000..9a38821214a3
--- /dev/null
+++ b/sd/qa/unit/data/odp/tdf168109.fodp
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<office:document 
xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" 
xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" 
xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" 
xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" 
xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" 
xmlns:presentation="urn:oasis:names:tc:opendocument:xmlns:presentation:1.0" 
office:version="1.4" 
office:mimetype="application/vnd.oasis.opendocument.presentation">
+ <office:styles>
+  <style:style style:name="standard" style:family="graphic">
+   <style:graphic-properties draw:fill="none"/>
+  </style:style>
+ </office:styles>
+ <office:body>
+  <office:presentation>
+   <draw:page>
+    <draw:frame svg:width="20cm" svg:height="3cm" svg:x="5mm" svg:y="5mm" 
presentation:class="title">
+     <draw:text-box>
+      <text:p>Slide One</text:p>
+     </draw:text-box>
+    </draw:frame>
+    <draw:frame svg:width="20cm" svg:height="9cm" svg:x="5mm" svg:y="4cm" 
presentation:class="outline">
+     <draw:text-box>
+      <text:list>
+       <text:list-item>
+        <text:p>One</text:p>
+       </text:list-item>
+       <text:list-item>
+        <text:p>Two</text:p>
+       </text:list-item>
+      </text:list>
+     </draw:text-box>
+    </draw:frame>
+    <presentation:notes>
+     <!-- First case: the 'draw:page-number' attribute is missing -->
+     <draw:page-thumbnail svg:width="20cm" svg:height="11cm" svg:x="5mm" 
svg:y="5mm" presentation:class="page"/>
+     <draw:frame svg:width="20cm" svg:height="13cm" svg:x="5mm" svg:y="12cm">
+      <draw:text-box/>
+     </draw:frame>
+    </presentation:notes>
+   </draw:page>
+   <draw:page>
+    <draw:frame svg:width="20cm" svg:height="3cm" svg:x="5mm" svg:y="5mm" 
presentation:class="title">
+     <draw:text-box>
+      <text:p>Slide Two</text:p>
+     </draw:text-box>
+    </draw:frame>
+    <draw:frame svg:width="20cm" svg:height="9cm" svg:x="5mm" svg:y="4cm" 
presentation:class="outline">
+     <draw:text-box>
+      <text:list>
+       <text:list-item>
+        <text:p>Foo</text:p>
+       </text:list-item>
+       <text:list-item>
+        <text:p>Bar</text:p>
+       </text:list-item>
+      </text:list>
+     </draw:text-box>
+    </draw:frame>
+    <presentation:notes>
+     <!-- Second case: the 'draw:page-number' attribute is wrong -->
+     <draw:page-thumbnail svg:width="20cm" svg:height="11cm" svg:x="5mm" 
svg:y="5mm" draw:page-number="1" presentation:class="page"/>
+     <draw:frame svg:width="20cm" svg:height="13cm" svg:x="5mm" svg:y="12cm">
+      <draw:text-box/>
+     </draw:frame>
+    </presentation:notes>
+   </draw:page>
+  </office:presentation>
+ </office:body>
+</office:document>
\ No newline at end of file
diff --git a/sd/qa/unit/import-tests2.cxx b/sd/qa/unit/import-tests2.cxx
index 570e2b9690cb..6fe0cf439d65 100644
--- a/sd/qa/unit/import-tests2.cxx
+++ b/sd/qa/unit/import-tests2.cxx
@@ -2314,6 +2314,37 @@ CPPUNIT_TEST_FIXTURE(SdImportTest2, testTdf169524)
     CPPUNIT_ASSERT_EQUAL(sal_Int32(0), nLeftMargin);
 }
 
+CPPUNIT_TEST_FIXTURE(SdImportTest2, testTdf168109)
+{
+    createSdImpressDoc("odp/tdf168109.fodp");
+
+    // Slide 1
+    {
+        auto xPage = getPage(0).queryThrow<presentation::XPresentationPage>();
+        auto xNotesPage = xPage->getNotesPage();
+        auto xThumbnail = getShape(0, xNotesPage);
+        auto xDescriptor = xThumbnail.queryThrow<drawing::XShapeDescriptor>();
+
+        CPPUNIT_ASSERT_EQUAL(u"com.sun.star.presentation.PageShape"_ustr,
+                             xDescriptor->getShapeType());
+        CPPUNIT_ASSERT_EQUAL(uno::Any(sal_Int32(1)),
+                             xThumbnail->getPropertyValue(u"PageNumber"_ustr));
+    }
+
+    // Slide 2
+    {
+        auto xPage = getPage(1).queryThrow<presentation::XPresentationPage>();
+        auto xNotesPage = xPage->getNotesPage();
+        auto xThumbnail = getShape(0, xNotesPage);
+        auto xDescriptor = xThumbnail.queryThrow<drawing::XShapeDescriptor>();
+
+        CPPUNIT_ASSERT_EQUAL(u"com.sun.star.presentation.PageShape"_ustr,
+                             xDescriptor->getShapeType());
+        CPPUNIT_ASSERT_EQUAL(uno::Any(sal_Int32(2)),
+                             xThumbnail->getPropertyValue(u"PageNumber"_ustr));
+    }
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/xmloff/source/draw/ximpbody.cxx b/xmloff/source/draw/ximpbody.cxx
index f1f19a156ffa..591832735414 100644
--- a/xmloff/source/draw/ximpbody.cxx
+++ b/xmloff/source/draw/ximpbody.cxx
@@ -247,8 +247,9 @@ css::uno::Reference< css::xml::sax::XFastContextHandler >  
SdXMLDrawPageContext:
                     uno::Reference< drawing::XDrawPage > xNotesDrawPage = 
xPresPage->getNotesPage();
                     if(xNotesDrawPage.is())
                     {
-                        // presentation:notes inside draw:page context
-                        return new SdXMLNotesContext( GetSdImport(), 
xAttrList, xNotesDrawPage);
+                        // presentation:notes inside draw:page context. Inside 
notes, thumbnails
+                        // are fixed to this page, and actual page number 
attribute must be ignored
+                        return new SdXMLNotesContext(GetSdImport(), xAttrList, 
xNotesDrawPage, true);
                     }
                 }
             }
diff --git a/xmloff/source/draw/ximpnote.cxx b/xmloff/source/draw/ximpnote.cxx
index d855e85bf4ad..9035648cb9ed 100644
--- a/xmloff/source/draw/ximpnote.cxx
+++ b/xmloff/source/draw/ximpnote.cxx
@@ -18,6 +18,7 @@
  */
 
 #include "ximpnote.hxx"
+#include "ximpshap.hxx"
 #include <xmloff/xmlnamespace.hxx>
 
 using namespace ::com::sun::star;
@@ -25,8 +26,9 @@ using namespace ::xmloff::token;
 
 SdXMLNotesContext::SdXMLNotesContext(
     SdXMLImport& rImport, const 
css::uno::Reference<css::xml::sax::XFastAttributeList>& xAttrList,
-    uno::Reference<drawing::XShapes> const& rShapes)
+    uno::Reference<drawing::XShapes> const& rShapes, bool 
ignorePageNumberInThumbnail)
     : SdXMLGenericPageContext(rImport, xAttrList, rShapes)
+    , mbIgnorePageNumberInThumbnail(ignorePageNumberInThumbnail)
 {
     OUString sStyleName, sPageMasterName;
 
@@ -89,4 +91,17 @@ SdXMLNotesContext::SdXMLNotesContext(
 
 SdXMLNotesContext::~SdXMLNotesContext() {}
 
+uno::Reference<xml::sax::XFastContextHandler> 
SdXMLNotesContext::createFastChildContext(
+    sal_Int32 nElement, const uno::Reference<xml::sax::XFastAttributeList>& 
xAttrList)
+{
+    auto pContext = SdXMLGenericPageContext::createFastChildContext(nElement, 
xAttrList);
+    if (nElement == XML_ELEMENT(DRAW, XML_PAGE_THUMBNAIL) && 
mbIgnorePageNumberInThumbnail)
+    {
+        assert(dynamic_cast<SdXMLPageShapeContext*>(pContext.get()));
+        auto pPageShapeContext = 
static_cast<SdXMLPageShapeContext*>(pContext.get());
+        pPageShapeContext->ignorePageNumber();
+    }
+    return pContext;
+}
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/xmloff/source/draw/ximpnote.hxx b/xmloff/source/draw/ximpnote.hxx
index d7fe276b84eb..2048fc36ea62 100644
--- a/xmloff/source/draw/ximpnote.hxx
+++ b/xmloff/source/draw/ximpnote.hxx
@@ -29,8 +29,17 @@ class SdXMLNotesContext : public SdXMLGenericPageContext
 public:
     SdXMLNotesContext( SdXMLImport& rImport,
         const css::uno::Reference< css::xml::sax::XFastAttributeList>& 
xAttrList,
-        css::uno::Reference< css::drawing::XShapes > const & rShapes);
+        css::uno::Reference< css::drawing::XShapes > const & rShapes,
+        bool ignorePageNumberInThumbnail = false);
     virtual ~SdXMLNotesContext() override;
+
+    // XFastContextHandler
+    virtual css::uno::Reference<css::xml::sax::XFastContextHandler> SAL_CALL 
createFastChildContext(
+        sal_Int32 nElement,
+        const css::uno::Reference<css::xml::sax::XFastAttributeList>& 
xAttrList) override;
+
+private:
+    bool mbIgnorePageNumberInThumbnail;
 };
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/xmloff/source/draw/ximpshap.cxx b/xmloff/source/draw/ximpshap.cxx
index 3c840b6d4bc7..5caef558f8a7 100644
--- a/xmloff/source/draw/ximpshap.cxx
+++ b/xmloff/source/draw/ximpshap.cxx
@@ -2244,7 +2244,7 @@ void SdXMLPageShapeContext::startFastElement (sal_Int32 
nElement,
     SetTransformation();
 
     uno::Reference< beans::XPropertySet > xPropSet(mxShape, uno::UNO_QUERY);
-    if(xPropSet.is())
+    if (xPropSet.is() && mnPageNumber >= 0) // mnPageNumber < 0 means "ignore"
     {
         uno::Reference< beans::XPropertySetInfo > xPropSetInfo( 
xPropSet->getPropertySetInfo() );
         static constexpr OUString aPageNumberStr(u"PageNumber"_ustr);
diff --git a/xmloff/source/draw/ximpshap.hxx b/xmloff/source/draw/ximpshap.hxx
index 6a00cb252e5f..640cf26fdb5b 100644
--- a/xmloff/source/draw/ximpshap.hxx
+++ b/xmloff/source/draw/ximpshap.hxx
@@ -340,7 +340,7 @@ public:
     virtual bool processAttribute( const 
sax_fastparser::FastAttributeList::FastAttributeIter & ) override;
 };
 
-// draw:page context
+// draw:thumbnail context
 
 class SdXMLPageShapeContext : public SdXMLShapeContext
 {
@@ -359,6 +359,9 @@ public:
 
     // this is called from the parent group for each unparsed attribute in the 
attribute list
     virtual bool processAttribute( const 
sax_fastparser::FastAttributeList::FastAttributeIter & ) override;
+
+    // Do not try to set page number to the thumbnail (keep the default 
association)
+    void ignorePageNumber() { mnPageNumber = -1; }
 };
 
 // draw:caption context
commit 390eebddedd955ee7ce4b258fa9aaabbf9374a62
Author:     Mike Kaganski <[email protected]>
AuthorDate: Wed Feb 11 17:43:53 2026 +0500
Commit:     Mike Kaganski <[email protected]>
CommitDate: Thu Feb 12 18:45:39 2026 +0100

    rtl_allocateZeroMemory / std::free mismatch; use std::calloc
    
    Indeed, rtl_allocateZeroMemory is implemented itself using calloc;
    but to not rely on implementation details, we either should use
    rtl_FreeMemory, or one of std allocation functions.
    
    Even if it's not a problem here, rtl_allocateZeroMemory has signature
    that takes a single size for the whole block. If allocating an array,
    multiplying a size, that doesn't match fundamental alignment, by the
    number of elements might give wrong size of array.
    
    Change-Id: Icc45772113f4058155c1fccc7c4096958c46e3c4
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/199282
    Reviewed-by: Mike Kaganski <[email protected]>
    Tested-by: Jenkins

diff --git a/vcl/win/gdi/salprn.cxx b/vcl/win/gdi/salprn.cxx
index 8fa2a2adb16f..9520e33ba133 100644
--- a/vcl/win/gdi/salprn.cxx
+++ b/vcl/win/gdi/salprn.cxx
@@ -441,7 +441,8 @@ static void ImplDevModeToJobSetup( WinSalInfoPrinter const 
* pPrinter, ImplJobSe
 
         if ( nCount && (nCount != GDI_ERROR) )
         {
-            WORD* pBins = static_cast<WORD*>(rtl_allocateZeroMemory( 
nCount*sizeof(WORD) ));
+            WORD* pBins = static_cast<WORD*>(std::calloc(nCount, 
sizeof(WORD)));
+            assert(pBins);
             ImplDeviceCaps( pPrinter, DC_BINS, pBins, pSetupData );
             pSetupData->SetPaperBin( 0 );
 
@@ -475,12 +476,12 @@ static void ImplDevModeToJobSetup( WinSalInfoPrinter 
const * pPrinter, ImplJobSe
             POINT*  pPaperSizes = nullptr;
             if ( nPaperCount && (nPaperCount != GDI_ERROR) )
             {
-                pPapers = 
static_cast<WORD*>(rtl_allocateZeroMemory(nPaperCount*sizeof(WORD)));
+                pPapers = static_cast<WORD*>(std::calloc(nPaperCount, 
sizeof(WORD)));
                 ImplDeviceCaps( pPrinter, DC_PAPERS, pPapers, pSetupData );
             }
             if ( nPaperSizeCount && (nPaperSizeCount != GDI_ERROR) )
             {
-                pPaperSizes = 
static_cast<POINT*>(rtl_allocateZeroMemory(nPaperSizeCount*sizeof(POINT)));
+                pPaperSizes = static_cast<POINT*>(std::calloc(nPaperSizeCount, 
sizeof(POINT)));
                 ImplDeviceCaps( pPrinter, DC_PAPERSIZE, pPaperSizes, 
pSetupData );
             }
             if( nPaperSizeCount == nPaperCount && pPaperSizes && pPapers )
@@ -710,7 +711,8 @@ static void ImplJobSetupToDevMode( WinSalInfoPrinter const 
* pPrinter, const Imp
 
         if ( nCount && (nCount != GDI_ERROR) )
         {
-            WORD* pBins = 
static_cast<WORD*>(rtl_allocateZeroMemory(nCount*sizeof(WORD)));
+            WORD* pBins = static_cast<WORD*>(std::calloc(nCount, 
sizeof(WORD)));
+            assert(pBins);
             ImplDeviceCaps( pPrinter, DC_BINS, pBins, pSetupData );
             pDevModeW->dmFields |= DM_DEFAULTSOURCE;
             pDevModeW->dmDefaultSource = pBins[ pSetupData->GetPaperBin() ];
@@ -888,12 +890,12 @@ static void ImplJobSetupToDevMode( WinSalInfoPrinter 
const * pPrinter, const Imp
                 DWORD   nLandscapeAngle = ImplDeviceCaps( pPrinter, 
DC_ORIENTATION, nullptr, pSetupData );
                 if ( nPaperCount && (nPaperCount != GDI_ERROR) )
                 {
-                    pPapers = 
static_cast<WORD*>(rtl_allocateZeroMemory(nPaperCount*sizeof(WORD)));
+                    pPapers = static_cast<WORD*>(std::calloc(nPaperCount, 
sizeof(WORD)));
                     ImplDeviceCaps( pPrinter, DC_PAPERS, pPapers, pSetupData );
                 }
                 if ( nPaperSizeCount && (nPaperSizeCount != GDI_ERROR) )
                 {
-                    pPaperSizes = 
static_cast<POINT*>(rtl_allocateZeroMemory(nPaperSizeCount*sizeof(POINT)));
+                    pPaperSizes = 
static_cast<POINT*>(std::calloc(nPaperSizeCount, sizeof(POINT)));
                     ImplDeviceCaps( pPrinter, DC_PAPERSIZE, pPaperSizes, 
pSetupData );
                 }
                 if ( (nPaperSizeCount == nPaperCount) && pPapers && 
pPaperSizes )
@@ -1095,7 +1097,8 @@ void WinSalInfoPrinter::InitPaperFormats( const 
ImplJobSetup* pSetupData )
 
     if( nCount )
     {
-        POINT* pPaperSizes = 
static_cast<POINT*>(rtl_allocateZeroMemory(nCount*sizeof(POINT)));
+        POINT* pPaperSizes = static_cast<POINT*>(std::calloc(nCount, 
sizeof(POINT)));
+        assert(pPaperSizes);
         ImplDeviceCaps( this, DC_PAPERSIZE, pPaperSizes, pSetupData );
 
         SAL_INFO("vcl.print", "DC_PAPERSIZE sizes (mm) from printer: " << 
DC_PAPERSIZE_array_to_string(pPaperSizes, nCount));

Reply via email to