sc/qa/unit/data/xlsx/tdf166724_cellAnchor.xlsx |binary
 sc/qa/unit/subsequent_export_test2.cxx         |   48 +++++++++++++++++++++++++
 sc/source/filter/oox/drawingbase.cxx           |   14 ++++++-
 3 files changed, 61 insertions(+), 1 deletion(-)

New commits:
commit facc43db1e89a2fd5378e93c79c30f28ae5f2233
Author:     Justin Luth <[email protected]>
AuthorDate: Wed Nov 19 11:29:31 2025 -0500
Commit:     Miklos Vajna <[email protected]>
CommitDate: Mon Dec 1 08:55:56 2025 +0100

    tdf#166724 sc oox: limit x:anchor offsets to inside of the cell
    
    In the customer's test document, the anchor was defined like
        <x:Anchor>  1, 80, 1, 104, 3, 41, 6, 18</x:Anchor>
    which means:
    -starting in column B offset by 80 pixels
    -            row 2 offset by 104 pixels
    -ending in column D offset by 41 pixels
    -          row 7 offset by 18 pixels.
    
    But row 2 was only 10 pixels tall,
    so the effective offset had to be limited to 10 pixels.
    
    make CppunitTest_sc_subsequent_export_test2 \
        CPPUNIT_TEST_NAME=testTdf166724_cellAnchor
    
    Change-Id: Id90a0839b20e340676f70b62336b24a593a4f491
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/194218
    Reviewed-by: Justin Luth <[email protected]>
    Code-Style: Justin Luth <[email protected]>
    Tested-by: Jenkins
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/194709
    Reviewed-by: Miklos Vajna <[email protected]>
    Tested-by: Jenkins CollaboraOffice <[email protected]>

diff --git a/sc/qa/unit/data/xlsx/tdf166724_cellAnchor.xlsx 
b/sc/qa/unit/data/xlsx/tdf166724_cellAnchor.xlsx
new file mode 100644
index 000000000000..3b7004b39d50
Binary files /dev/null and b/sc/qa/unit/data/xlsx/tdf166724_cellAnchor.xlsx 
differ
diff --git a/sc/qa/unit/subsequent_export_test2.cxx 
b/sc/qa/unit/subsequent_export_test2.cxx
index 0729d513d34d..99720149f067 100644
--- a/sc/qa/unit/subsequent_export_test2.cxx
+++ b/sc/qa/unit/subsequent_export_test2.cxx
@@ -23,6 +23,8 @@
 #include <formula/grammar.hxx>
 #include <svl/numformat.hxx>
 #include <svl/zformat.hxx>
+#include <svx/svdograf.hxx>
+#include <svx/svdpage.hxx>
 
 #include <com/sun/star/sheet/XHeaderFooterContent.hpp>
 
@@ -50,6 +52,52 @@ CPPUNIT_TEST_FIXTURE(ScExportTest2, testGroupShape)
     assertXPath(pDoc, "/xdr:wsDr/xdr:twoCellAnchor/xdr:grpSp/xdr:grpSpPr");
 }
 
+CPPUNIT_TEST_FIXTURE(ScExportTest2, testTdf166724_cellAnchor)
+{
+    // given a hand-modified document where the checkbox position was changed 
to not match anchor
+    // and the anchor was changed to some absurd values,
+    // and row 2 was given a much larger height than what optimal-height needs
+    // (which means that all imported row heights and positions are 
meaningless)...
+
+    // vmlDrawing1.vml: <x:Anchor> 1, 11, 1, 904, 3, 41, 3, 1</x:Anchor>
+    // From: Col B pixel offset 11, Row 2 offset 904
+    // To: Col D offset 41, Row 4 offset 1
+
+    createScDoc("xlsx/tdf166724_cellAnchor.xlsx");
+
+    ScDocument& rDoc = *getScDoc();
+    ScDrawLayer* pDrawLayer = rDoc.GetDrawLayer();
+    const SdrPage* pPage = pDrawLayer->GetPage(0);
+
+    ScAnchorType anchorType = ScDrawLayer::GetAnchorType(*pPage->GetObj(0));
+    // VML's x:anchor indicates cell-anchor
+    CPPUNIT_ASSERT_EQUAL(SCA_CELL, anchorType); // probably should be 
SCA_CELL_RESIZE
+
+    // VML's x:anchor has too large Offsets: limit to cell B3's edges.
+    // Without the fixes, this was X[9040] Y[10257] W[2823] H[742]
+    tools::Rectangle aRect = pPage->GetObj(0)->GetSnapRect();
+    CPPUNIT_ASSERT_EQUAL(tools::Long(1990), aRect.Left());
+    CPPUNIT_ASSERT_EQUAL(tools::Long(3462), aRect.Top()); // really should be 
1058 (bottom of row 2)
+    CPPUNIT_ASSERT_EQUAL(tools::Long(4192), aRect.GetWidth());
+    CPPUNIT_ASSERT_EQUAL(tools::Long(560), aRect.GetHeight());
+
+    // I did not focus on (minor) round-trip concerns. Just documenting the 
current results...
+    saveAndReload(TestFilter::XLSX);
+
+    ScDocument& rRTDoc = *getScDoc();
+    pDrawLayer = rRTDoc.GetDrawLayer();
+    pPage = pDrawLayer->GetPage(0);
+
+    anchorType = ScDrawLayer::GetAnchorType(*pPage->GetObj(0));
+    CPPUNIT_ASSERT_EQUAL(SCA_CELL_RESIZE, anchorType);
+
+    aRect = pPage->GetObj(0)->GetSnapRect();
+    CPPUNIT_ASSERT_EQUAL(tools::Long(1990), aRect.Left());
+    CPPUNIT_ASSERT_EQUAL(tools::Long(3466), aRect.Top());
+    CPPUNIT_ASSERT_EQUAL(tools::Long(4192), aRect.GetWidth());
+    CPPUNIT_ASSERT_EQUAL(tools::Long(556), aRect.GetHeight());
+};
+
 CPPUNIT_TEST_FIXTURE(ScExportTest2, testFreezePaneStartCellXLSX)
 {
     // given a hand-mangled document with a newly-invalid topLeftCell for the 
active pane
diff --git a/sc/source/filter/oox/drawingbase.cxx 
b/sc/source/filter/oox/drawingbase.cxx
index 0f1ee8690c35..127cb73d4c7e 100644
--- a/sc/source/filter/oox/drawingbase.cxx
+++ b/sc/source/filter/oox/drawingbase.cxx
@@ -266,9 +266,19 @@ css::awt::Rectangle ShapeAnchor::calcAnchorRectHmm( const 
css::awt::Size& rPageS
 
 EmuPoint ShapeAnchor::calcCellAnchorEmu( const CellAnchorModel& rModel ) const
 {
+    const UnitConverter& rUnitConv = getUnitConverter();
+
     // calculate position of top-left edge of the cell
     css::awt::Point aPoint = getCellPosition( rModel.mnCol, rModel.mnRow );
+    css::awt::Point aNextCell = getCellPosition(rModel.mnCol + 1, rModel.mnRow 
+ 1);
     EmuPoint aEmuPoint( lclHmmToEmu( aPoint.X ), lclHmmToEmu( aPoint.Y ) );
+    // It is easily possible that the provided Offset is invalid (too large).
+    // Excel seems to limit the offsets to the bottom/left edge of the cell.
+    // Because most calculations are rounded down to TWIPs, reduce cell's 
right edge by a full twip.
+    // Reduce by another twip because of the way GetRange calculates which 
cell this point is in...
+    sal_Int64 n2TwipInEmu = std::ceil(rUnitConv.scaleValue(2, Unit::Twip, 
Unit::Emu));
+    EmuPoint aEmuMaxOffset(
+        lclHmmToEmu(aNextCell.X) - n2TwipInEmu, lclHmmToEmu(aNextCell.Y) - 
n2TwipInEmu);
 
     // add the offset inside the cell
     switch( meCellAnchorType )
@@ -280,13 +290,15 @@ EmuPoint ShapeAnchor::calcCellAnchorEmu( const 
CellAnchorModel& rModel ) const
 
         case CellAnchorType::Pixel:
         {
-            const UnitConverter& rUnitConv = getUnitConverter();
             aEmuPoint.X += std::round( rUnitConv.scaleValue( 
rModel.mnColOffset, Unit::ScreenX, Unit::Emu ) );
             aEmuPoint.Y += std::round( rUnitConv.scaleValue( 
rModel.mnRowOffset, Unit::ScreenY, Unit::Emu ) );
         }
         break;
     }
 
+    aEmuPoint.X = std::min(aEmuPoint.X, aEmuMaxOffset.X);
+    aEmuPoint.Y = std::min(aEmuPoint.Y, aEmuMaxOffset.Y);
+
     return aEmuPoint;
 }
 

Reply via email to