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; }
