filter/qa/unit/data/dashedline.fodg | 28 + filter/qa/unit/svg.cxx | 14 filter/source/svg/svgwriter.cxx | 12 include/test/xmltesttools.hxx | 5 include/vcl/dndlistenercontainer.hxx | 3 include/vcl/seleng.hxx | 2 oox/source/export/shapes.cxx | 3 oox/source/export/vmlexport.cxx | 2 oox/source/ole/vbaexport.cxx | 8 oox/source/vml/vmlshapecontext.cxx | 7 sc/source/ui/miscdlgs/linkarea.cxx | 4 sw/qa/core/layout/flycnt.cxx | 10 sw/qa/extras/layout/data/tdf170381-split-float-table-in-float-table.docx |binary sw/qa/extras/layout/data/tdf170381-split-float-table-in-normal-table.docx |binary sw/qa/extras/layout/layout4.cxx | 7 sw/qa/extras/layout/layout6.cxx | 271 ++++++++++ sw/qa/extras/ooxmlexport/ooxmlexport11.cxx | 6 sw/qa/extras/ooxmlexport/ooxmlexport12.cxx | 15 sw/qa/extras/ooxmlexport/ooxmlexport13.cxx | 18 sw/qa/extras/ooxmlexport/ooxmlexport16.cxx | 2 sw/qa/extras/ooxmlexport/ooxmlexport17.cxx | 14 sw/source/core/inc/flyfrm.hxx | 2 sw/source/core/inc/frame.hxx | 2 sw/source/core/inc/tabfrm.hxx | 2 sw/source/core/layout/findfrm.cxx | 18 sw/source/core/layout/fly.cxx | 57 +- sw/source/core/layout/tabfrm.cxx | 12 sw/source/core/objectpositioning/tocntntanchoredobjectposition.cxx | 9 sw/source/core/text/itrform2.cxx | 13 sw/source/core/text/txtfly.cxx | 5 sw/source/filter/ww8/docxattributeoutput.cxx | 21 sw/source/filter/ww8/docxtableexport.cxx | 7 sw/source/writerfilter/dmapper/DomainMapper.cxx | 2 test/source/xmltesttools.cxx | 28 + vcl/source/window/dndlistenercontainer.cxx | 6 vcl/source/window/seleng.cxx | 24 36 files changed, 568 insertions(+), 71 deletions(-)
New commits: commit 12ccaa2c207a0500789d75fb2899f4ea1c76aafa Author: Mike Kaganski <[email protected]> AuthorDate: Tue Jan 20 12:11:45 2026 +0500 Commit: Andras Timar <[email protected]> CommitDate: Wed Jan 21 16:45:23 2026 +0100 tdf#170396: implement SVG export support for dashed lines Commit b71d9a6d15cfb8a50afdea5ac064f40d84c561f8 (do not apply line dashing in drawinglayer (tdf#136957), 2021-04-29) made sure that metafiles pass dashing information in LineInfo of MetaPolyLineAction, instead of altering the polyline itself. Since SVG export ignored dashing information in MetaPolyLineAction, the exported lines were solid since that patch. This patch introduces respective export support, emitting stroke-dasharray attribute. As far as I see, it has exactly the same semantics, and renders correctly in chrome and firefox. It also imports OK into Draw. Ref: https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/stroke-dasharray Change-Id: Ifb634a4f3608ee5c2c76727ef4416b80f8f90709 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197618 Tested-by: Jenkins Reviewed-by: Mike Kaganski <[email protected]> (cherry picked from commit c103d337bd41eaa44b65accabe978fe542e41d06) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197639 Reviewed-by: Xisco Fauli <[email protected]> diff --git a/filter/qa/unit/data/dashedline.fodg b/filter/qa/unit/data/dashedline.fodg new file mode 100644 index 000000000000..73ca01124945 --- /dev/null +++ b/filter/qa/unit/data/dashedline.fodg @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style: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" office:version="1.4" office:mimetype="application/vnd.oasis.opendocument.graphics"> + <office:styles> + <draw:stroke-dash draw:name="DoubleDashDotDot" draw:style="rect" draw:dots1="1" draw:dots1-length="800%" draw:dots2="2" draw:dots2-length="90%" draw:distance="300%"/> + <style:style style:name="standard" style:family="graphic"> + <style:graphic-properties draw:stroke="solid" svg:stroke-width="1mm" svg:stroke-color="#008000" draw:fill="none" draw:shadow="hidden"/> + </style:style> + </office:styles> + <office:automatic-styles> + <style:page-layout style:name="PM0"> + <style:page-layout-properties fo:margin-top="1cm" fo:margin-bottom="1cm" fo:margin-left="1cm" fo:margin-right="1cm" fo:page-width="14cm" fo:page-height="4cm"/> + </style:page-layout> + <style:style style:name="gr1" style:family="graphic" style:parent-style-name="standard"> + <style:graphic-properties draw:stroke="dash" draw:stroke-dash="DoubleDashDotDot" draw:textarea-vertical-align="middle"/> + </style:style> + </office:automatic-styles> + <office:master-styles> + <style:master-page style:name="Default" style:page-layout-name="PM0"/> + </office:master-styles> + <office:body> + <office:drawing> + <draw:page draw:name="page1" draw:master-page-name="Default"> + <draw:line draw:style-name="gr1" svg:x1="2cm" svg:y1="2cm" svg:x2="12cm" svg:y2="2cm"/> + </draw:page> + </office:drawing> + </office:body> +</office:document> \ No newline at end of file diff --git a/filter/qa/unit/svg.cxx b/filter/qa/unit/svg.cxx index 0faa7a59922e..17637d10de3c 100644 --- a/filter/qa/unit/svg.cxx +++ b/filter/qa/unit/svg.cxx @@ -397,6 +397,20 @@ CPPUNIT_TEST_FIXTURE(SvgFilterTest, testTdf166789) CPPUNIT_ASSERT_DOUBLES_EQUAL(7000, length.toInt32(), 70); // allow 1% for rounding errors } +CPPUNIT_TEST_FIXTURE(SvgFilterTest, testDashedLine) +{ + // A dashed line + loadFromFile(u"dashedline.fodg"); + + save(TestFilter::SVG_DRAW); + + xmlDocUniquePtr pXmlDoc = parseExportedFile(); + CPPUNIT_ASSERT(pXmlDoc); + + // The result must include 'stroke-dasharray' attribute + assertXPath(pXmlDoc, "/svg:svg/svg:g//svg:path", "stroke-dasharray", u"800,300,90,300,90,300"); +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/filter/source/svg/svgwriter.cxx b/filter/source/svg/svgwriter.cxx index 65e1a38e842f..f299b959bbe8 100644 --- a/filter/source/svg/svgwriter.cxx +++ b/filter/source/svg/svgwriter.cxx @@ -2141,6 +2141,18 @@ void SVGActionWriter::ImplAddLineAttr( const LineInfo &rAttrs ) } } + if (rAttrs.GetStyle() == LineStyle::Dash) + { + OUStringBuffer aDashArrayStr; + for (double x : rAttrs.GetDotDashArray()) + { + if (!aDashArrayStr.isEmpty()) + aDashArrayStr.append(","); + aDashArrayStr.append(x); + } + if (!aDashArrayStr.isEmpty()) + mrExport.AddAttribute(u"stroke-dasharray"_ustr, aDashArrayStr.makeStringAndClear()); + } } commit a7c351391a244a733bb41052ceee3bd66799b000 Author: Noel Grandin <[email protected]> AuthorDate: Tue Jan 20 07:39:59 2026 +0100 Commit: Andras Timar <[email protected]> CommitDate: Wed Jan 21 16:45:23 2026 +0100 Revert "mso-test: invalid xdr:twoCellAnchor" This reverts commit 6b9794ea1c6c24d7c48a1b43a9302c51ea607215, because With this change, now these files are corrupt after a RT -forum-mso-en3-23097.docx -forum-mso-en4-364124.docx -forum-mso-en4-428907.docx -tdf104181-3.docx -tdf104181-6.docx Change-Id: Ib3010fbf0868897fbe8d1f325f3ccdd33475cf3e Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197616 Tested-by: Jenkins Reviewed-by: Noel Grandin <[email protected]> (cherry picked from commit 2a7ee38519d9b1c3db6efe83ccff50a4af0593d2) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197640 Reviewed-by: Xisco Fauli <[email protected]> diff --git a/oox/source/export/shapes.cxx b/oox/source/export/shapes.cxx index 3ddf1c3fbd7f..4adb4f2a187c 100644 --- a/oox/source/export/shapes.cxx +++ b/oox/source/export/shapes.cxx @@ -1422,7 +1422,10 @@ void ShapeExport::WriteGraphicObjectShapePart( const Reference< XShape >& xShape && (xShapeProps->getPropertyValue(u"MediaURL"_ustr) >>= sMediaURL); if (!xGraphic.is() && !bHasMediaURL) + { SAL_INFO("oox.shape", "no graphic or media URL found"); + return; + } FSHelperPtr pFS = GetFS(); XmlFilterBase* pFB = GetFB(); diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport16.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport16.cxx index 4de1842e917c..9210da630603 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport16.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport16.cxx @@ -592,7 +592,7 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf141173_missingFrames) saveAndReload(TestFilter::DOCX); // Without the fix in place, almost all of the text and textboxes were missing. // Without the fix, there were only 2 shapes (mostly unseen). - CPPUNIT_ASSERT_EQUAL(14, getShapes()); + CPPUNIT_ASSERT_EQUAL(13, getShapes()); } DECLARE_OOXMLEXPORT_TEST(testTdf142404_tabSpacing, "tdf142404_tabSpacing.docx") commit 09b044758b1810a4065cfa9aa0094266cef7333e Author: Noel Grandin <[email protected]> AuthorDate: Thu Jan 15 15:06:13 2026 +0200 Commit: Andras Timar <[email protected]> CommitDate: Wed Jan 21 16:45:23 2026 +0100 officeotron: moveFromRangeStart must have a w:date attribute so just give it our null date Change-Id: I22bdd9ffd53dbe7d4608a759dfc9dd93c658f44a Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197426 Reviewed-by: Michael Stahl <[email protected]> Tested-by: Jenkins (cherry picked from commit 232b4ff3d530080efe28b8212c44952a625f8bc9) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197561 Reviewed-by: Xisco Fauli <[email protected]> diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport12.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport12.cxx index 9d6490d1e118..b724869b42bf 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport12.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport12.cxx @@ -1209,9 +1209,6 @@ DECLARE_OOXMLEXPORT_TEST(testTdf118521_marginsLR, "tdf118521_marginsLR.docx") DECLARE_OOXMLEXPORT_TEST(testTdf104797, "tdf104797.docx") { - // FIXME: validation error in OOXML export: Errors: 2 - skipValidation(); - // check moveFrom and moveTo CPPUNIT_ASSERT_EQUAL(u"Will this sentence be duplicated?"_ustr, getParagraph(1)->getString()); CPPUNIT_ASSERT_EQUAL(u""_ustr, getRun(getParagraph(1), 1)->getString()); @@ -1250,9 +1247,6 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf145720) // check moveFromRangeStart/End and moveToRangeStart/End (to keep tracked text moving) createSwDoc("tdf104797.docx"); - // FIXME: validation error in OOXML export: Errors: 2 - skipValidation(); - save(TestFilter::DOCX); xmlDocUniquePtr pXmlDoc = parseExport(u"word/document.xml"_ustr); // These were 0 (missing move*FromRange* elements) @@ -1269,10 +1263,11 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf145720) // mandatory authors and dates assertXPath(pXmlDoc, "/w:document/w:body/w:p[1]/w:moveFromRangeStart", "author", u"Tekijä"); assertXPath(pXmlDoc, "/w:document/w:body/w:p[2]/w:moveToRangeStart", "author", u"Tekijä"); - // no date (anonymized change) - // This failed, date was exported as w:date="0-00-00T00:00:00Z", and later "1970-01-01T00:00:00Z" - assertXPathNoAttribute(pXmlDoc, "/w:document/w:body/w:p[1]/w:moveFromRangeStart", "date"); - assertXPathNoAttribute(pXmlDoc, "/w:document/w:body/w:p[2]/w:moveToRangeStart", "date"); + // anonymized date + assertXPath(pXmlDoc, "/w:document/w:body/w:p[1]/w:moveFromRangeStart", "date", + u"1970-01-01T00:00:00Z"); + assertXPath(pXmlDoc, "/w:document/w:body/w:p[2]/w:moveToRangeStart", "date", + u"1970-01-01T00:00:00Z"); } CPPUNIT_TEST_FIXTURE(Test, testTdf150166) diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx b/sw/source/filter/ww8/docxattributeoutput.cxx index f0c0270d1ff0..fb29e28eea6e 100644 --- a/sw/source/filter/ww8/docxattributeoutput.cxx +++ b/sw/source/filter/ww8/docxattributeoutput.cxx @@ -2265,6 +2265,9 @@ void DocxAttributeOutput::DoWriteMoveRangeTagStart(std::u16string_view bookmarkN : OUStringToOString(rAuthor, RTL_TEXTENCODING_UTF8)); if (!bNoDate) pAttributeList->add(FSNS(XML_w, XML_date ), DateTimeToOString( aDateTime )); + else + // w:data is a required attribute, so just use a placeholder date + pAttributeList->add(FSNS(XML_w, XML_date ), "1970-01-01T00:00:00Z"); pAttributeList->add(FSNS(XML_w, XML_name), bookmarkName); m_pSerializer->singleElementNS( XML_w, bFrom ? XML_moveFromRangeStart : XML_moveToRangeStart, pAttributeList ); commit a5611c65bf2419241b65f122725403d2e9f399c0 Author: Noel Grandin <[email protected]> AuthorDate: Mon Jan 19 16:56:32 2026 +0200 Commit: Andras Timar <[email protected]> CommitDate: Wed Jan 21 16:45:23 2026 +0100 officeotron: w:shd must have val attribute Change-Id: Ia4ec654a6ba6c341a89994ac7a5f6e575ae1defa Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197591 Tested-by: Jenkins Reviewed-by: Noel Grandin <[email protected]> (cherry picked from commit 6ebb696bebb04e2296a6dfcba769ec4bd3f8ed88) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197636 Reviewed-by: Xisco Fauli <[email protected]> diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport11.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport11.cxx index 18abf65f0303..08a1a1443914 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport11.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport11.cxx @@ -36,9 +36,6 @@ public: DECLARE_OOXMLEXPORT_TEST(testTdf57589_hashColor, "tdf57589_hashColor.docx") { - // FIXME: validation error in OOXML export: Errors: 51 - skipValidation(); - CPPUNIT_ASSERT_EQUAL(drawing::FillStyle_SOLID, getProperty<drawing::FillStyle>(getParagraph(1), u"FillStyle"_ustr)); CPPUNIT_ASSERT_EQUAL(COL_LIGHTMAGENTA, getProperty<Color>(getParagraph(1), u"ParaBackColor"_ustr)); CPPUNIT_ASSERT_EQUAL(drawing::FillStyle_NONE, getProperty<drawing::FillStyle>(getParagraph(2), u"FillStyle"_ustr)); diff --git a/sw/source/filter/ww8/docxtableexport.cxx b/sw/source/filter/ww8/docxtableexport.cxx index 6be12ab131ef..ab76c65ea2d3 100644 --- a/sw/source/filter/ww8/docxtableexport.cxx +++ b/sw/source/filter/ww8/docxtableexport.cxx @@ -571,6 +571,7 @@ void DocxAttributeOutput::TableBackgrounds( } else { + bool bAddedValAttr = false; rtl::Reference<sax_fastparser::FastAttributeList> pAttrList; for (const auto & [ name, val ] : rGrabBag) @@ -595,8 +596,14 @@ void DocxAttributeOutput::TableBackgrounds( else if (name == "color") AddToAttrList(pAttrList, FSNS(XML_w, XML_color), val.get<OUString>()); else if (name == "val") + { AddToAttrList(pAttrList, FSNS(XML_w, XML_val), val.get<OUString>()); + bAddedValAttr = true; + } } + // w:val attribute is required + if (!bAddedValAttr) + AddToAttrList(pAttrList, FSNS(XML_w, XML_val), "clear"); m_pSerializer->singleElementNS(XML_w, XML_shd, pAttrList); } } commit fb1bdaaf2f8314c516757086ba08ba68fa83e6f1 Author: Mike Kaganski <[email protected]> AuthorDate: Sun Jan 18 20:41:01 2026 +0500 Commit: Andras Timar <[email protected]> CommitDate: Wed Jan 21 16:45:23 2026 +0100 tdf#170381: try to avoid handling of split-but-not-yet-moved floating tables When a floating table is being split, it creates a follow table. Later, still in Split, the follow gets reformatted, creating a follow fly and its anchor (which itself is a follow of a text frame). But at this point, the anchor is still next to its precede, and all of these haven't yet moved to the next page (see SwFrame::GetNextFlyLeaf); the follow fly is still registered in the precede's page. The actual move will happen only when the follow anchor will move to the next page as part of higher-level layout. In this intermediate state, after split but before move, the floating table and its fly should get specal handling. It must not be taken into account when calculating object intersections; it must not try to move forward itself (as part of its own re-layout). On the other hand, it can't be excluded from any handling. It must calculate its size and position (even though its position will eventually be changed) - without that, things fall apart badly. When working on it, it turned out, that SwTextFormatter::FormatLine had a problem when calculating its real height. The old code used to set the height of the last-on-page line to the full available space, plus one; and there was an exception for `HasNonLastSplitFlyDrawObj` case, where "plus one" was not used. But that wasn't enough; in case of a floating table in floating table, using spacing, the calculation created lines that were too high, causing oscillation. I attempted to find a better algorithm to calculate the height, e.g. using Grow with bTxt set to true; but all things I tried failed. Thus I disabled the call to SetRealHeight in case of HasNonLastSplitFlyDrawObj. It feels unsafe, and may require more work, if it turns out problematic. Another problem was found in SwToContentAnchoredObjectPosition::CalcPosition. It has a code that calls SwTabFrame::SetDoesObjsFit( false ), but this may force moving of objects without final size. The condition was improved. In GetFlyAnchorBottom, there is a code that decides if legacy behavior must be used, where the fly can overlap the bottom margin. It checked if anchor is in body. But it didn't consider the case when the anchor itself was in a fly (and in that case, thought that the anchor isn't in body); that made the floating-table-in-floating-table case calculate anchor bottom incorrectly (as in legacy mode), causing move forward and layout loop. Fixed here. One existing test (testSplitFlyInTextSection in sw/qa/core/layout/flycnt.cxx) started to hang with this change. That seems to not be a real regression per se: turns out, that floattable-in-text-section.docx already started to hang in master when opened in GUI; and the change only aligned that between the GUI and the unit test. I couldn't find a fix for that; the test was disabled. Change-Id: I738ba4def4a9ddae447b833acc46df1d20de93e4 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197524 Tested-by: Jenkins Reviewed-by: Mike Kaganski <[email protected]> (cherry picked from commit fb203eb57a1ba0accf1894672d35ddf9430d8b05) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197548 Reviewed-by: Xisco Fauli <[email protected]> diff --git a/sw/qa/core/layout/flycnt.cxx b/sw/qa/core/layout/flycnt.cxx index 48d816ba435b..e390f0a7e3c9 100644 --- a/sw/qa/core/layout/flycnt.cxx +++ b/sw/qa/core/layout/flycnt.cxx @@ -741,6 +741,15 @@ CPPUNIT_TEST_FIXTURE(Test, testSplitFlyThenTable) save(TestFilter::PDF_WRITER); } +/* FIXME: hangs indefinitely; prior to disabling, it passed the test, but hung interactively; and + even before that, it produced a wrong layout anyway. + The problem is somehow related to section. The looping sequence is: + 1. The table tries to split on page 1, with space less than minimal row height. + 2. It splits successfully between rows 1 and 2. The follow moves to page 2. + 3. The original table still doesn't fit page 1, and moves to page 2 (for some reason, into the + same follow fly that holds its follow). + 4. It detects that its next is its follow, and joins it. + 5. It moves pack to page 1. But all the generated follow flys get collected on page 3 (!). CPPUNIT_TEST_FIXTURE(Test, testSplitFlyInTextSection) { // The document contains a DOCX cont sect break, which is mapped to a TextSection. @@ -748,6 +757,7 @@ CPPUNIT_TEST_FIXTURE(Test, testSplitFlyInTextSection) // section frame, which is broken. createSwDoc("floattable-in-text-section.docx"); } +*/ CPPUNIT_TEST_FIXTURE(Test, testSplitFlyTableRowKeep) { diff --git a/sw/qa/extras/layout/data/tdf170381-split-float-table-in-float-table.docx b/sw/qa/extras/layout/data/tdf170381-split-float-table-in-float-table.docx new file mode 100644 index 000000000000..fc5b3a72ef50 Binary files /dev/null and b/sw/qa/extras/layout/data/tdf170381-split-float-table-in-float-table.docx differ diff --git a/sw/qa/extras/layout/data/tdf170381-split-float-table-in-normal-table.docx b/sw/qa/extras/layout/data/tdf170381-split-float-table-in-normal-table.docx new file mode 100644 index 000000000000..979a7618de7d Binary files /dev/null and b/sw/qa/extras/layout/data/tdf170381-split-float-table-in-normal-table.docx differ diff --git a/sw/qa/extras/layout/layout6.cxx b/sw/qa/extras/layout/layout6.cxx index 59571bcdf03f..e69f42a197ab 100644 --- a/sw/qa/extras/layout/layout6.cxx +++ b/sw/qa/extras/layout/layout6.cxx @@ -1771,6 +1771,277 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter6, testTdf155306) "expand", u"2"); } +CPPUNIT_TEST_FIXTURE(SwLayoutWriter6, testTdf170381_split_float_table_in_normal_table) +{ + // Given a document with a normal table containing a floating table which is split across + // pages: + createSwDoc("tdf170381-split-float-table-in-normal-table.docx"); + + // 1. It must not hang. + // 2. Check some correct layout aspects: + + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + CPPUNIT_ASSERT(pXmlDoc); + + // Exactly two pages: + assertXPath(pXmlDoc, "//page", 2); + + // Exactly one object anchored at each page: + assertXPath(pXmlDoc, "//page[1]/sorted_objs/fly", 1); + assertXPath(pXmlDoc, "//page[2]/sorted_objs/fly", 1); + + // Get the ids of the two flys (for both pages): + OString f1 = getXPath(pXmlDoc, "//page[1]/sorted_objs/fly", "ptr").toUtf8(); + OString f2 = getXPath(pXmlDoc, "//page[2]/sorted_objs/fly", "ptr").toUtf8(); + CPPUNIT_ASSERT(f1 != f2); + assertXPath(pXmlDoc, "//anchored/fly", 2); + OString aP1FlyTab = "//anchored/fly[@ptr='" + f1 + "']/tab"; + OString aP2FlyTab = "//anchored/fly[@ptr='" + f2 + "']/tab"; + + // Exactly one normal (master / follow) table on each page: + assertXPath(pXmlDoc, "//page[1]/body/tab", 1); + assertXPath(pXmlDoc, "//page[2]/body/tab", 1); + assertXPathNoAttribute(pXmlDoc, "//page[1]/body/tab", "precede"); + assertXPath(pXmlDoc, "//page[1]/body/tab", "follow", + getXPath(pXmlDoc, "//page[2]/body/tab", "id")); + assertXPathNoAttribute(pXmlDoc, "//page[2]/body/tab", "follow"); + assertXPath(pXmlDoc, "//page[2]/body/tab", "precede", + getXPath(pXmlDoc, "//page[1]/body/tab", "id")); + + // Exactly two rows in the first page's normal table: + assertXPath(pXmlDoc, "//page[1]/body/tab/row", 2); + + // Check the text of the first (repeating) row's cell text: + assertXPath(pXmlDoc, "//page[1]/body/tab/row[1]/cell", 1); + assertXPath(pXmlDoc, "//page[1]/body/tab/row[1]/cell/txt[1]//SwLineLayout", "portion", + u"elit ipsum lorem dolor"); + assertXPath(pXmlDoc, "//page[1]/body/tab/row[1]/cell/txt[2]//SwLineLayout", "portion", + u"amet elit amet sit adipiscing adipiscing consectetur consectetur elit dolor"); + assertXPath(pXmlDoc, "//page[1]/body/tab/row[1]/cell/txt[3]//SwLineLayout", "portion", u""); + assertXPath(pXmlDoc, "//page[1]/body/tab/row[1]/cell/txt[4]//SwLineLayout", "portion", u""); + + // The second row's cell has a single master paragraph with two anchored flys: + assertXPath(pXmlDoc, "//page[1]/body/tab/row[2]/cell", 1); + assertXPath(pXmlDoc, "//page[1]/body/tab/row[2]/cell/txt", 1); + OUString followId = getXPath(pXmlDoc, "//page[1]/body/tab/row[2]/cell/txt", "follow"); + CPPUNIT_ASSERT_GREATER(sal_Int32(0), followId.toInt32()); + assertXPath(pXmlDoc, "//page[1]/body/tab/row[2]/cell/txt/anchored/fly", 2); + + // Exactly four rows in the second page's normal table: + assertXPath(pXmlDoc, "//page[2]/body/tab/row", 4); + + // Check the text of the first (repeating) row's cell text: + assertXPath(pXmlDoc, "//page[2]/body/tab/row[1]/cell", 1); + assertXPath(pXmlDoc, "//page[2]/body/tab/row[1]/cell/txt[1]//SwLineLayout", "portion", + u"elit ipsum lorem dolor"); + assertXPath(pXmlDoc, "//page[2]/body/tab/row[1]/cell/txt[2]//SwLineLayout", "portion", + u"amet elit amet sit adipiscing adipiscing consectetur consectetur elit dolor"); + assertXPath(pXmlDoc, "//page[2]/body/tab/row[1]/cell/txt[3]//SwLineLayout", "portion", u""); + assertXPath(pXmlDoc, "//page[2]/body/tab/row[1]/cell/txt[4]//SwLineLayout", "portion", u""); + + // The second row's cell has a single follow paragraph: + assertXPath(pXmlDoc, "//page[2]/body/tab/row[2]/cell", 1); + assertXPath(pXmlDoc, "//page[1]/body/tab/row[2]/cell/txt", 1); + assertXPath(pXmlDoc, "//page[2]/body/tab/row[2]/cell/txt", "id", followId); + assertXPath(pXmlDoc, "//page[2]/body/tab/row[2]/cell/txt", "precede", + getXPath(pXmlDoc, "//page[1]/body/tab/row[2]/cell/txt", "id")); + + // Test floating tables' content (the line split must be correct). + + auto assertCellLines + = [&](int page, int row, std::initializer_list<std::u16string_view> lines) { + OString base = (page == 1 ? aP1FlyTab : aP2FlyTab) + "/row[" + OString::number(row) + + "]/cell/txt/SwParaPortion/SwLineLayout["; + int i = 1; + for (const auto& line : lines) + assertXPath(pXmlDoc, base + OString::number(i++) + "]", "portion", line); + }; + + // Page 1's floating table: + // NB: the *intended correct layout* is, when the first page's floating table has 5 rows! + // Currently asserting 6 rows on page 1, but row 6 must move to page 2, when fixed properly. + + std::initializer_list<std::u16string_view> page1cells[] = { + { u"amet sit consectetur ", u"elit" }, + { u"" }, + { u"dolor dolor dolor ", u"ipsum" }, + { u"amet ipsum amet dolor ", u"elit sit" }, + { u"ipsum consectetur ", u"consectetur amet ", u"adipiscing ipsum" }, + // NB: this must move to the follow! + { u"" }, + }; + + assertXPath(pXmlDoc, aP1FlyTab + "/row", std::size(page1cells)); + for (size_t r = 0; r < std::size(page1cells); ++r) + assertCellLines(1, r + 1, page1cells[r]); + + // Page 2's floating table: + + std::initializer_list<std::u16string_view> page2cells[] = { + { u"adipiscing ipsum elit ", u"lorem" }, + { u"lorem adipiscing sit sit ", u"lorem lorem" }, + { u"ipsum lorem ", u"consectetur amet amet ", u"ipsum" }, + { u"" }, + { u"sit consectetur ", u"adipiscing sit" }, + { u"elit consectetur lorem ", u"consectetur ", u"consectetur lorem sit ", + u"sit dolor elit adipiscing ", u"consectetur sit" }, + { u"consectetur dolor ", u"dolor sit elit lorem ", u"consectetur dolor ", u"lorem ipsum" }, + }; + + assertXPath(pXmlDoc, aP2FlyTab + "/row", std::size(page2cells)); + for (size_t r = 0; r < std::size(page2cells); ++r) + assertCellLines(2, r + 1, page2cells[r]); +} + +CPPUNIT_TEST_FIXTURE(SwLayoutWriter6, testTdf170381_split_float_table_in_float_table) +{ + // Given a document with a floating table containing another floating table which is split + // across pages: + createSwDoc("tdf170381-split-float-table-in-float-table.docx"); + + // 1. It must not hang. + // 2. Check some correct layout aspects: + + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + CPPUNIT_ASSERT(pXmlDoc); + + // Exactly two pages: + assertXPath(pXmlDoc, "//page", 2); + + // Exactly two objects anchored at each page: + assertXPath(pXmlDoc, "//page[1]/sorted_objs/fly", 2); + assertXPath(pXmlDoc, "//page[2]/sorted_objs/fly", 2); + + // Exactly one (master/follow) paragraph on each page: + assertXPath(pXmlDoc, "//page[1]/body/txt", 1); + assertXPath(pXmlDoc, "//page[2]/body/txt", 1); + assertXPath(pXmlDoc, "//page[1]/body/txt", "follow", + getXPath(pXmlDoc, "//page[2]/body/txt", "id")); + assertXPath(pXmlDoc, "//page[2]/body/txt", "precede", + getXPath(pXmlDoc, "//page[1]/body/txt", "id")); + + // Page 1's paragraph has two anchored flys: + assertXPath(pXmlDoc, "//page[1]/body/txt/anchored/fly", 2); + + // Get the ids of the two outer flys. + // Page 1: + OString f1 = getXPath(pXmlDoc, "//page[1]/sorted_objs/fly[1]", "ptr").toUtf8(); + OString f2 = getXPath(pXmlDoc, "//page[1]/sorted_objs/fly[2]", "ptr").toUtf8(); + CPPUNIT_ASSERT(f1 != f2); + OString filter1 = "@ptr='" + f1 + "' or @ptr='" + f2 + "'"; + OUString id = getXPath(pXmlDoc, "//page[1]/body/txt/anchored/fly[" + filter1 + "]", "id"); + OString aP1OuterFlyTab = "//anchored/fly[@id='" + id.toUtf8() + "']/tab"; + + // Page 2: + f1 = getXPath(pXmlDoc, "//page[2]/sorted_objs/fly[1]", "ptr").toUtf8(); + f2 = getXPath(pXmlDoc, "//page[2]/sorted_objs/fly[2]", "ptr").toUtf8(); + CPPUNIT_ASSERT(f1 != f2); + OString filter2 = "@ptr='" + f1 + "' or @ptr='" + f2 + "'"; + id = getXPath(pXmlDoc, "//page[1]/body/txt/anchored/fly[" + filter2 + "]", "id"); + OString aP2OuterFlyTab = "//anchored/fly[@id='" + id.toUtf8() + "']/tab"; + + // Exactly one row in both top-level floating tables: + assertXPath(pXmlDoc, aP1OuterFlyTab + "/row", 1); + assertXPath(pXmlDoc, aP2OuterFlyTab + "/row", 1); + + // Exactly one cell in both top-level floating tables: + assertXPath(pXmlDoc, aP1OuterFlyTab + "/row/cell", 1); + assertXPath(pXmlDoc, aP2OuterFlyTab + "/row/cell", 1); + + // First page's top-level floating table's cell has three paragraphs: + assertXPath(pXmlDoc, aP1OuterFlyTab + "/row/cell/txt", 3); + // Check text in the first two paragraphs: + assertXPath(pXmlDoc, aP1OuterFlyTab + "/row/cell/txt[1]//SwLineLayout", "portion", + u"Table1 A1 dolor elit"); + assertXPath(pXmlDoc, aP1OuterFlyTab + "/row/cell/txt[2]//SwLineLayout", "portion", + u"adipiscing dolor adipiscing amet ipsum elit sit elit lorem elit adipiscing " + "dolor ipsum"); + // The third paragraph has two attached inner floating tables: + assertXPath(pXmlDoc, aP1OuterFlyTab + "/row/cell/txt[3]/anchored/fly", 2); + + // Get the ids of the two inner flys. + // Page 1: + id = getXPath(pXmlDoc, aP1OuterFlyTab + "/row/cell/txt[3]/anchored/fly[" + filter1 + "]", "id"); + OString aP1InnerFlyTab = "//anchored/fly[@id='" + id.toUtf8() + "']/tab"; + + // Page 2: + id = getXPath(pXmlDoc, aP1OuterFlyTab + "/row/cell/txt[3]/anchored/fly[" + filter2 + "]", "id"); + OString aP2InnerFlyTab = "//anchored/fly[@id='" + id.toUtf8() + "']/tab"; + + // Check the layout of the inner tables (splitting of lines and rows). + + auto assertCellLines + = [&](int page, int row, std::initializer_list<std::u16string_view> lines) { + OString base = (page == 1 ? aP1InnerFlyTab : aP2InnerFlyTab) + "/row[" + + OString::number(row) + "]/cell[1]/txt/SwParaPortion/SwLineLayout["; + int i = 1; + for (const auto& line : lines) + assertXPath(pXmlDoc, base + OString::number(i++) + "]", "portion", line); + }; + + // Page 1's inner table: + // NB: the *intended correct layout* is, when the first page's inner floating table is split + // after row 21! Currently part of row 22 is on page 1, but must move to page 2, when fixed. + + std::initializer_list<std::u16string_view> page1cells[] = { + { u"Table2 A1 sit amet ", u"ipsum consectetur ", u"ipsum amet ", u"adipiscing amet elit ", + u"dolor consectetur" }, + { u"Table2 A2 ", u"consectetur ", u"adipiscing adipiscing ", u"consectetur dolor sit ", + u"amet lorem" }, + { u"Table2 A3 dolor elit ", u"amet ipsum ", u"adipiscing ipsum ", u"dolor lorem" }, + { u"Table2 A4" }, + { u"Table2 A5 amet dolor ", u"elit consectetur lorem ", u"dolor sit amet" }, + { u"Table2 A6 sit dolor ", u"elit consectetur elit sit ", u"dolor adipiscing" }, + { u"Table2 A7" }, + { u"Table2 A8 ", u"consectetur ipsum ", u"dolor adipiscing ", u"ipsum dolor dolor ", + u"sit elit consectetur ", u"adipiscing" }, + { u"Table2 A9 adipiscing ", u"amet dolor amet ", u"lorem elit sit amet" }, + { u"Table2 A10 amet ", u"lorem elit elit elit ", u"adipiscing elit sit" }, + { u"Table2 A11" }, + { u"Table2 A12 sit ", u"adipiscing adipiscing ", u"consectetur sit ipsum ", + u"consectetur ipsum" }, + { u"Table2 A13 amet ", u"dolor consectetur ", u"amet dolor ipsum sit ", u"sit" }, + { u"Table2 A14" }, + { u"Table2 A15 dolor ", u"dolor elit dolor ", u"dolor ipsum ", u"consectetur amet ", + u"elit sit" }, + { u"Table2 A16 ipsum ", u"lorem adipiscing sit ", u"sit dolor lorem elit" }, + { u"Table2 A17 sit dolor ", u"adipiscing ", u"consectetur elit ", u"ipsum lorem sit" }, + { u"Table2 A18" }, + { u"Table2 A19 sit ", u"adipiscing ", u"consectetur ", u"adipiscing lorem ", + u"ipsum amet elit" }, + { u"Table2 A20 ipsum ", u"amet consectetur elit ", u"amet amet sit sit ", u"adipiscing" }, + { u"Table2 A21" }, + // NB: this must merge to the first row of the follow! + { u"Table2 A22 elit ", u"ipsum elit elit sit elit " }, + }; + + assertXPath(pXmlDoc, aP1InnerFlyTab + "/row", std::size(page1cells)); + for (size_t r = 0; r < std::size(page1cells); ++r) + assertCellLines(1, r + 1, page1cells[r]); + + // Page 2's inner table: + + std::initializer_list<std::u16string_view> page2cells[] = { + { u"sit consectetur ", u"amet sit" }, + { u"Table2 A23 ", u"consectetur amet ", u"lorem consectetur elit ", u"dolor sit elit" }, + { u"Table2 A24 ipsum ", u"amet ipsum amet ", u"consectetur lorem ", u"amet sit" }, + { u"Table2 A25" }, + { u"Table2 A26 ", u"consectetur dolor ", u"consectetur ", u"adipiscing dolor dolor ", + u"lorem adipiscing" }, + { u"Table2 A27 dolor sit ", u"elit dolor ipsum lorem ", u"dolor elit" }, + { u"Table2 A28" }, + { u"Table2 A29 ipsum ", u"ipsum amet ipsum" }, + { u"Table2 A30 amet ", u"ipsum lorem ", u"consectetur ipsum ", u"ipsum lorem ipsum ", + u"ipsum sit consectetur ", u"consectetur" }, + { u"Table2 A31 ", u"consectetur elit sit ", u"ipsum adipiscing ", u"ipsum ipsum ", + u"consectetur" }, + }; + + assertXPath(pXmlDoc, aP2InnerFlyTab + "/row", std::size(page2cells)); + for (size_t r = 0; r < std::size(page2cells); ++r) + assertCellLines(2, r + 1, page2cells[r]); +} + } // end of anonymous namespace CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/sw/source/core/inc/flyfrm.hxx b/sw/source/core/inc/flyfrm.hxx index 30509dd0eb50..9dc03bc323af 100644 --- a/sw/source/core/inc/flyfrm.hxx +++ b/sw/source/core/inc/flyfrm.hxx @@ -308,6 +308,8 @@ public: SW_DLLPUBLIC SwFlyAtContentFrame* DynCastFlyAtContentFrame(); + bool IsSplitButNotYetMovedFollow() const; + private: void UpdateUnfloatButton(SwWrtShell* pWrtSh, bool bShow) const; void PaintDecorators() const; diff --git a/sw/source/core/inc/frame.hxx b/sw/source/core/inc/frame.hxx index 143ad395ee90..8c286f69a5e0 100644 --- a/sw/source/core/inc/frame.hxx +++ b/sw/source/core/inc/frame.hxx @@ -976,6 +976,8 @@ public: /// Determines if the upper margin of this frame should be ignored. bool IsCollapseUpper() const; + + bool IsInSplitButNotYetMovedFollow() const; }; inline void SwFrame::InvalidateInfFlags() diff --git a/sw/source/core/inc/tabfrm.hxx b/sw/source/core/inc/tabfrm.hxx index 07449b23da01..a7fdeefedafb 100644 --- a/sw/source/core/inc/tabfrm.hxx +++ b/sw/source/core/inc/tabfrm.hxx @@ -236,6 +236,8 @@ public: sal_uInt16 GetBottomLineSize() const; + bool IsSplitButNotYetMovedFloatingFollow() const; + void dumpAsXml(xmlTextWriterPtr writer = nullptr) const override; }; diff --git a/sw/source/core/layout/findfrm.cxx b/sw/source/core/layout/findfrm.cxx index f7a3bbd36d9d..0092b74542a4 100644 --- a/sw/source/core/layout/findfrm.cxx +++ b/sw/source/core/layout/findfrm.cxx @@ -1502,6 +1502,20 @@ static bool lcl_IsInSectionDirectly( const SwFrame *pUp ) return false; } +bool SwFrame::IsInSplitButNotYetMovedFollow() const +{ + const SwFrame* pFrame = this; + while (pFrame && pFrame->IsInTab() && pFrame->IsInFly()) + { + const SwTabFrame* pTabFrame = pFrame->FindTabFrame(); + assert(pTabFrame); + if (pTabFrame->IsSplitButNotYetMovedFloatingFollow()) + return true; + pFrame = pTabFrame->GetUpper(); + } + return false; +} + /** determine, if frame is moveable in given environment OD 08.08.2003 #110978# @@ -1512,6 +1526,10 @@ static bool lcl_IsInSectionDirectly( const SwFrame *pUp ) */ bool SwFrame::IsMoveable( const SwLayoutFrame* _pLayoutFrame ) const { + if (IsTabFrame()) + if (static_cast<const SwTabFrame*>(this)->IsSplitButNotYetMovedFloatingFollow()) + return false; + bool bRetVal = false; if ( !_pLayoutFrame ) diff --git a/sw/source/core/layout/fly.cxx b/sw/source/core/layout/fly.cxx index 380df804bac8..289d081f9445 100644 --- a/sw/source/core/layout/fly.cxx +++ b/sw/source/core/layout/fly.cxx @@ -84,6 +84,31 @@ using namespace ::com::sun::star; namespace { +// True if the anchor is (directly or indirectly) in the document body. +bool isAnchorInDocBody(const SwFrame& rAnchor) +{ + for (auto p = &rAnchor; p; p = p->FindFlyFrame()->GetAnchorFrame()) + { + if (p->IsInDocBody()) + return true; + if (!p->IsInFly()) + return false; + } + return false; +} + +// True means Word <= 2010 behavior +bool isLegacyBehavior(const SwFlyFrame& rFly, const SwFrame& rAnchor) +{ + const auto* pFrameFormat = rFly.GetFrameFormat(); + if (!pFrameFormat->getIDocumentSettingAccess().get(DocumentSettingId::TAB_OVER_MARGIN)) + return false; + // Allow overlap with bottom margin / footer only in case we're relative to the page frame. + bool bVertPageFrame + = pFrameFormat->GetVertOrient().GetRelationOrient() == text::RelOrientation::PAGE_FRAME; + return bVertPageFrame || !isAnchorInDocBody(rAnchor); +} + /// Gets the bottom position which is a deadline for a split fly. SwTwips GetFlyAnchorBottom(SwFlyFrame& rFly, const SwFrame& rAnchor) { @@ -101,13 +126,7 @@ SwTwips GetFlyAnchorBottom(SwFlyFrame& rFly, const SwFrame& rAnchor) return 0; } - const auto* pFrameFormat = rFly.GetFrameFormat(); - const IDocumentSettingAccess& rIDSA = pFrameFormat->getIDocumentSettingAccess(); - // Allow overlap with bottom margin / footer only in case we're relative to the page frame. - bool bVertPageFrame = pFrameFormat->GetVertOrient().GetRelationOrient() == text::RelOrientation::PAGE_FRAME; - bool bInBody = rAnchor.IsInDocBody(); - bool bLegacy = rIDSA.get(DocumentSettingId::TAB_OVER_MARGIN) && (bVertPageFrame || !bInBody); - if (bLegacy) + if (isLegacyBehavior(rFly, rAnchor)) { // Word <= 2010 style: the fly can overlap with the bottom margin / footer area in case the // fly height fits the body height and the fly bottom fits the page. @@ -2288,6 +2307,30 @@ SwFlyAtContentFrame* SwFlyFrame::DynCastFlyAtContentFrame() return IsFlyAtContentFrame() ? static_cast<SwFlyAtContentFrame*>(this) : nullptr; } +bool SwFlyFrame::IsSplitButNotYetMovedFollow() const +{ + if (IsFlySplitAllowed()) + { + auto& rFlyAtContentFrame = static_cast<SwFlyAtContentFrame&>(const_cast<SwFlyFrame&>(*this)); + if (rFlyAtContentFrame.IsFollow()) + { + auto pThisAnchor = rFlyAtContentFrame.FindAnchorCharFrame(); + if (!pThisAnchor) + return true; // no anchor frame has been created yet + auto pPrecedeAnchor = rFlyAtContentFrame.GetPrecede()->FindAnchorCharFrame(); + assert(pPrecedeAnchor); + if (pThisAnchor->GetUpper() == pPrecedeAnchor->GetUpper()) + { + // This is a just-split follow fly, and it is waiting to be moved to the next page + // together with its anchor. See SwFrame::GetNextFlyLeaf and its "nesting" case + // handling. + return true; + } + } + } + return false; +} + SwTwips SwFlyFrame::Grow_(SwTwips nDist, SwResizeLimitReason& reason, bool bTst) { if (!Lower()) diff --git a/sw/source/core/layout/tabfrm.cxx b/sw/source/core/layout/tabfrm.cxx index eb7d49360c8f..a5bbd2b3710b 100644 --- a/sw/source/core/layout/tabfrm.cxx +++ b/sw/source/core/layout/tabfrm.cxx @@ -1515,6 +1515,18 @@ bool SwTabFrame::Split(const SwTwips nCutPos, bool bTryToSplit, return bRet; } +bool SwTabFrame::IsSplitButNotYetMovedFloatingFollow() const +{ + if (IsFollow() && GetUpper() && GetUpper()->IsFlyFrame()) + { + // Test if this is a just-split follow nested floating table, and it is waiting to be moved + // to the next page together with its anchor. We get here while formatting all the outer + // cells' anchored objects, before the outer table splits eventually. + return static_cast<const SwFlyFrame*>(GetUpper())->IsSplitButNotYetMovedFollow(); + } + return false; +} + namespace { bool CanDeleteFollow(const SwTabFrame *pFoll) diff --git a/sw/source/core/objectpositioning/tocntntanchoredobjectposition.cxx b/sw/source/core/objectpositioning/tocntntanchoredobjectposition.cxx index 57b200c81d58..71c66cf1cfca 100644 --- a/sw/source/core/objectpositioning/tocntntanchoredobjectposition.cxx +++ b/sw/source/core/objectpositioning/tocntntanchoredobjectposition.cxx @@ -964,7 +964,8 @@ void SwToContentAnchoredObjectPosition::CalcPosition() nDist = aRectFnSet.BottomDist( GetAnchoredObj().GetObjRect(), aRectFnSet.GetPrtBottom(*pUpperOfOrientFrame) ); if ( nDist < 0 && - pOrientFrame == &rAnchorTextFrame && !pOrientFrame->GetIndPrev() ) + pOrientFrame == &rAnchorTextFrame && !pOrientFrame->GetIndPrev() && + pUpperOfOrientFrame->isFrameAreaDefinitionValid() ) { const_cast<SwTabFrame*>(pOrientFrame->FindTabFrame()) ->SetDoesObjsFit( false ); @@ -1224,6 +1225,9 @@ void SwToContentAnchoredObjectPosition::CalcOverlap(const SwTextFrame* pAnchorFr SwFlyFrame* pFlyFrame = GetAnchoredObj().DynCastFlyFrame(); if (pFlyFrame && pFlyFrame->IsFlySplitAllowed()) { + if (pFlyFrame->IsSplitButNotYetMovedFollow()) + return; // Don't check overlaps until the follow is moved. + // At least for split flys we need to consider objects on the same page, but anchored in // different text frames. bSplitFly = true; @@ -1270,6 +1274,9 @@ void SwToContentAnchoredObjectPosition::CalcOverlap(const SwTextFrame* pAnchorFr continue; } + if (pAnchoredObjFly->IsSplitButNotYetMovedFollow()) + continue; // Don't check overlaps with not-yet-moved objects. + if (pAnchoredObjFly->getRootFrame()->IsInFlyDelList(pAnchoredObjFly)) { // A fly overlapping with a to-be-deleted fly is fine. diff --git a/sw/source/core/text/itrform2.cxx b/sw/source/core/text/itrform2.cxx index 551c52ad4564..47fbfc4b4335 100644 --- a/sw/source/core/text/itrform2.cxx +++ b/sw/source/core/text/itrform2.cxx @@ -2104,15 +2104,10 @@ TextFrameIndex SwTextFormatter::FormatLine(TextFrameIndex const nStartPos) if( GetInfo().IsStop() ) { m_pCurr->SetLen(TextFrameIndex(0)); - m_pCurr->Height( GetFrameRstHeight() + 1, false ); - m_pCurr->SetRealHeight( GetFrameRstHeight() + 1 ); - - // Don't oversize the line in case of split flys, so we don't try to move the anchor - // of a precede fly forward, next to its follow. - if (m_pFrame->HasNonLastSplitFlyDrawObj()) - { - m_pCurr->SetRealHeight(GetFrameRstHeight()); - } + auto nFrameRstHeight = GetFrameRstHeight(); + m_pCurr->Height(nFrameRstHeight + 1, false); + if (!m_pFrame->HasNonLastSplitFlyDrawObj()) + m_pCurr->SetRealHeight(nFrameRstHeight + 1); m_pCurr->Width(0); m_pCurr->Truncate(); diff --git a/sw/source/core/text/txtfly.cxx b/sw/source/core/text/txtfly.cxx index b9e7229c5c76..45df77b30b25 100644 --- a/sw/source/core/text/txtfly.cxx +++ b/sw/source/core/text/txtfly.cxx @@ -900,7 +900,7 @@ SwAnchoredObjList& SwTextFly::InitAnchoredObjList() // #i68520# mpAnchoredObjList.reset(new SwAnchoredObjList); - if( nCount && bWrapAllowed ) + if (nCount && bWrapAllowed && !m_pCurrFrame->IsInSplitButNotYetMovedFollow()) { SwRect const aRect(GetFrameArea()); // Make ourselves a little smaller than we are, @@ -929,7 +929,8 @@ SwAnchoredObjList& SwTextFly::InitAnchoredObjList() !pAnchoredObj->ConsiderForTextWrap() || ( mbIgnoreObjsInHeaderFooter && !bFooterHeader && pAnchoredObj->GetAnchorFrame()->FindFooterOrHeader() ) || - ( bAllowCompatWrap && !pAnchoredObj->GetFrameFormat()->GetFollowTextFlow().GetValue() ) + ( bAllowCompatWrap && !pAnchoredObj->GetFrameFormat()->GetFollowTextFlow().GetValue() ) || + ( pAnchoredObj->DynCastFlyFrame() && pAnchoredObj->DynCastFlyFrame()->IsSplitButNotYetMovedFollow() ) ) { continue; commit 9ac800377c4e44532b936bd61f5c51213894a460 Author: Noel Grandin <[email protected]> AuthorDate: Wed Jan 14 15:28:06 2026 +0200 Commit: Andras Timar <[email protected]> CommitDate: Wed Jan 21 16:45:23 2026 +0100 officeotron: ordering of w:startOverride wrong we end up with <w:lvlOverride w:ilvl="0"> <w:lvl w:ilvl="0"> .... </w:lvl> <w:startOverride w:val="1"/> </w:lvlOverride> but startOverride needs to come before w:lvl Change-Id: Ide251945b312d48101e277970f1a53fc52c8b5a7 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197290 Reviewed-by: Michael Stahl <[email protected]> Tested-by: Jenkins Signed-off-by: Xisco Fauli <[email protected]> Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197430 diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx index bddf3715c7bd..3a42e7e3890d 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx @@ -755,9 +755,6 @@ DECLARE_OOXMLEXPORT_TEST(testTdf142407, "tdf142407.docx") DECLARE_OOXMLEXPORT_TEST(testWPGBodyPr, "WPGbodyPr.docx") { - // FIXME: validation error in OOXML export: Errors: 3 - skipValidation(); - // There are a WPG shape and a picture CPPUNIT_ASSERT_EQUAL(2, getShapes()); @@ -803,9 +800,6 @@ DECLARE_OOXMLEXPORT_TEST(testWPGBodyPr, "WPGbodyPr.docx") DECLARE_OOXMLEXPORT_TEST(testTdf146851_1, "tdf146851_1.docx") { - // FIXME: validation error in OOXML export: Errors: 1 - skipValidation(); - uno::Reference<beans::XPropertySet> xPara; xPara.set(getParagraph(1, u"qwerty"_ustr), uno::UNO_QUERY); @@ -990,9 +984,6 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf146955) { createSwDoc("tdf146955.odt"); - // FIXME: validation error in OOXML export: Errors: 9 - skipValidation(); - saveAndReload(TestFilter::DOCX); // import of a (broken?) DOCX export with dozens of frames raised a SAX exception, // when the code tried to access to a non-existent footnote diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx b/sw/source/filter/ww8/docxattributeoutput.cxx index 1e780c0a1658..f0c0270d1ff0 100644 --- a/sw/source/filter/ww8/docxattributeoutput.cxx +++ b/sw/source/filter/ww8/docxattributeoutput.cxx @@ -7798,16 +7798,16 @@ void DocxAttributeOutput::OverrideNumberingDefinition( m_pSerializer->startElementNS(XML_w, XML_lvlOverride, FSNS(XML_w, XML_ilvl), OString::number(nLevel)); - if (bListsAreDifferent) - { - GetExport().NumberingLevel(rRule, nLevel); - } if (levelOverride != rLevelOverrides.end()) { // list numbering restart override m_pSerializer->singleElementNS(XML_w, XML_startOverride, FSNS(XML_w, XML_val), OString::number(levelOverride->second)); } + if (bListsAreDifferent) + { + GetExport().NumberingLevel(rRule, nLevel); + } m_pSerializer->endElementNS(XML_w, XML_lvlOverride); } commit 920131875277129dfc52c459116d7b965c870163 Author: Noel Grandin <[email protected]> AuthorDate: Thu Jan 15 14:36:41 2026 +0200 Commit: Andras Timar <[email protected]> CommitDate: Wed Jan 21 16:45:23 2026 +0100 officeotron: w:r inside w:pPr is invalid we end with the following in word/header1.xml: <w:hdr> <w:p> <w:pPr> <w:pStyle w:val="Header"/> <w:r> <w:br w:type="page"/> </w:r> <w:rPr></w:rPr> </w:pPr> restrict the fix to the case of writing header data, just in case I missed some other case somewhere. Change-Id: I451480a075c55cafd016279d229dbf0698c01265 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197425 Tested-by: Jenkins Reviewed-by: Michael Stahl <[email protected]> (cherry picked from commit 821e808610a9710d4875bff65604f34aacf4a29c) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197590 Reviewed-by: Xisco Fauli <[email protected]> diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport13.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport13.cxx index 4ada85283bde..851ba1e29f45 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport13.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport13.cxx @@ -111,9 +111,6 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf121374_sectionHF) { createSwDoc("tdf121374_sectionHF.odt"); - // FIXME: validation error in OOXML export: Errors: 1 - skipValidation(); - saveAndReload(TestFilter::DOCX); uno::Reference<beans::XPropertySet> xPageStyle(getStyles(u"PageStyles"_ustr)->getByName(u"Standard"_ustr), uno::UNO_QUERY); uno::Reference<text::XTextRange> xFooterText = getProperty< uno::Reference<text::XTextRange> >(xPageStyle, u"FooterText"_ustr); diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx b/sw/source/filter/ww8/docxattributeoutput.cxx index 271cdc9b381b..1e780c0a1658 100644 --- a/sw/source/filter/ww8/docxattributeoutput.cxx +++ b/sw/source/filter/ww8/docxattributeoutput.cxx @@ -7094,6 +7094,8 @@ void DocxAttributeOutput::PageBreakBefore( bool bBreak ) void DocxAttributeOutput::SectionBreak( sal_uInt8 nC, bool bBreakAfter, const WW8_SepInfo* pSectionInfo, bool bExtraPageBreak) { + if (m_bWritingHeaderFooter && m_bOpenedParaPr) + return; // do not put a run inside <w:hdr>..<w:p>..<w:pPr> switch ( nC ) { case msword::ColumnBreak: commit 76c4125b159f5461a96ae2ce665cbd8e86b062a5 Author: Noel Grandin <[email protected]> AuthorDate: Thu Jan 15 16:03:07 2026 +0200 Commit: Andras Timar <[email protected]> CommitDate: Wed Jan 21 16:45:23 2026 +0100 officeotron: attribute signinginstructions should not have a namespace prefix we end up with <o:signatureline ... o:signinginstructions="Check the machines!" where the "o:" prefix on signinginstructions is incorrect. Just to be safe, when loading, try both the old and the new style. However, this does mean that creating such data on a new version of LO, and then sending it to someone with an old version of LO, this data will be lost. Change-Id: I3ec366a2de1ba4945aae1ff67656eda0868333e1 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197427 Tested-by: Jenkins Reviewed-by: Michael Stahl <[email protected]> (cherry picked from commit bf5cc0ae565fb3a11c4cb6b081f99f6daea26dfb) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197562 Reviewed-by: Xisco Fauli <[email protected]> diff --git a/oox/source/export/vmlexport.cxx b/oox/source/export/vmlexport.cxx index 0613426e6fe5..40fb4624fa5c 100644 --- a/oox/source/export/vmlexport.cxx +++ b/oox/source/export/vmlexport.cxx @@ -711,7 +711,7 @@ void VMLExport::Commit( EscherPropertyContainer& rProps, const tools::Rectangle& { pAttrListSignatureLine->add(XML_signinginstructionsset, "t"); pAttrListSignatureLine->add( - FSNS(XML_o, XML_signinginstructions), + XML_signinginstructions, pSdrGrafObj->getSignatureLineSigningInstructions()); } pAttrListSignatureLine->add( diff --git a/oox/source/vml/vmlshapecontext.cxx b/oox/source/vml/vmlshapecontext.cxx index 5839fa511116..01e2054bb078 100644 --- a/oox/source/vml/vmlshapecontext.cxx +++ b/oox/source/vml/vmlshapecontext.cxx @@ -649,7 +649,12 @@ ContextHandlerRef ShapeContext::onCreateContext( sal_Int32 nElement, const Attri mrShapeModel.maSignatureLineSuggestedSignerEmail = rAttribs.getStringDefaulted(O_TOKEN(suggestedsigneremail)); mrShapeModel.maSignatureLineSigningInstructions - = rAttribs.getStringDefaulted(O_TOKEN(signinginstructions)); + = rAttribs.getStringDefaulted(XML_signinginstructions); + // we used to save this with an "o:" prefix, which is incorrect, so to support older + // data, try the older way if the correct way is empty. + if (mrShapeModel.maSignatureLineSigningInstructions.isEmpty()) + mrShapeModel.maSignatureLineSigningInstructions + = rAttribs.getStringDefaulted(O_TOKEN(signinginstructions)); mrShapeModel.mbSignatureLineShowSignDate = ConversionHelper::decodeBool( rAttribs.getString(XML_showsigndate, u"t"_ustr)); // default is true mrShapeModel.mbSignatureLineCanAddComment = ConversionHelper::decodeBool( diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport11.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport11.cxx index 869fdb02eca1..18abf65f0303 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport11.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport11.cxx @@ -389,9 +389,6 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf44832_testSectionWithDifferentHeader) DECLARE_OOXMLEXPORT_TEST(testSignatureLineShape, "signature-line-all-props-set.docx") { - // FIXME: validation error in OOXML export: Errors: 1 - skipValidation(); - uno::Reference<drawing::XShape> xSignatureLineShape = getShape(1); uno::Reference<beans::XPropertySet> xPropSet(xSignatureLineShape, uno::UNO_QUERY); commit a5c9179d5cd47b829fb708177a75739a80ecfc86 Author: Xisco Fauli <[email protected]> AuthorDate: Fri Jan 16 10:43:40 2026 +0100 Commit: Andras Timar <[email protected]> CommitDate: Wed Jan 21 16:45:23 2026 +0100 sw_ooxmlexport13: Add test for b2dd08c6af51 Change-Id: I0cae335f075b785de907c4aa8ec47c9ca086cd12 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197436 Tested-by: Jenkins Reviewed-by: Xisco Fauli <[email protected]> (cherry picked from commit eaca892394d5206e8a7062068b03df57353ac99f) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197589 Tested-by: Aron Budea <[email protected]> Reviewed-by: Aron Budea <[email protected]> diff --git a/include/test/xmltesttools.hxx b/include/test/xmltesttools.hxx index 94f4cf18a816..76140972c85b 100644 --- a/include/test/xmltesttools.hxx +++ b/include/test/xmltesttools.hxx @@ -73,6 +73,11 @@ protected: * Useful for checking relative order of elements. */ int getXPathPosition(const xmlDocUniquePtr& pXmlDoc, const char* pXPath, const char* pChildName); + /** + * Get the position of the attribute named rName of the parent node specified by pXPath. + * Useful for checking relative order of elements. + */ + int getXPathAttributePosition(const xmlDocUniquePtr& pXmlDoc, const char* pXPath, const char* pAttributeName); /** * Get the number of the nodes returned by the pXPath. */ diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport13.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport13.cxx index 75e6ae776e66..4ada85283bde 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport13.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport13.cxx @@ -756,6 +756,21 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf169802_hidden_shape) // 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); + + save(TestFilter::DOCX); + + xmlDocUniquePtr pXmlDoc = parseExport(u"word/document.xml"_ustr); + const char* const sPath("/w:document/w:body/w:p[1]/w:r[1]/mc:AlternateContent/mc:Choice/w:drawing/wp:anchor"); + CPPUNIT_ASSERT_EQUAL(0, getXPathAttributePosition(pXmlDoc, sPath, "distT")); + CPPUNIT_ASSERT_EQUAL(1, getXPathAttributePosition(pXmlDoc, sPath, "distB")); + CPPUNIT_ASSERT_EQUAL(2, getXPathAttributePosition(pXmlDoc, sPath, "distL")); + CPPUNIT_ASSERT_EQUAL(3, getXPathAttributePosition(pXmlDoc, sPath, "distR")); + CPPUNIT_ASSERT_EQUAL(4, getXPathAttributePosition(pXmlDoc, sPath, "simplePos")); + CPPUNIT_ASSERT_EQUAL(5, getXPathAttributePosition(pXmlDoc, sPath, "relativeHeight")); + CPPUNIT_ASSERT_EQUAL(6, getXPathAttributePosition(pXmlDoc, sPath, "behindDoc")); + CPPUNIT_ASSERT_EQUAL(7, getXPathAttributePosition(pXmlDoc, sPath, "locked")); + CPPUNIT_ASSERT_EQUAL(8, getXPathAttributePosition(pXmlDoc, sPath, "layoutInCell")); + CPPUNIT_ASSERT_EQUAL(9, getXPathAttributePosition(pXmlDoc, sPath, "allowOverlap")); } DECLARE_OOXMLEXPORT_TEST(testTdf124594, "tdf124594.docx") diff --git a/test/source/xmltesttools.cxx b/test/source/xmltesttools.cxx index aac5425cff55..70bd853bc785 100644 --- a/test/source/xmltesttools.cxx +++ b/test/source/xmltesttools.cxx @@ -329,6 +329,34 @@ int XmlTestTools::getXPathPosition(const xmlDocUniquePtr& pXmlDoc, const char* p return nRet; } +int XmlTestTools::getXPathAttributePosition(const xmlDocUniquePtr& pXmlDoc, const char* pXPath, const char* pAttributeName) +{ + xmlXPathObjectPtr pXmlObj = getXPathNode(pXmlDoc, pXPath); + xmlNodeSetPtr pXmlNodes = pXmlObj->nodesetval; + CPPUNIT_ASSERT(pXmlNodes); + CPPUNIT_ASSERT_EQUAL_MESSAGE(OString(OString::Concat("In <") + pXmlDoc->name + ">, XPath '" + pXPath + "' number of nodes is incorrect").getStr(), + 1, + xmlXPathNodeSetGetLength(pXmlNodes)); + xmlNodePtr pXmlNode = pXmlNodes->nodeTab[0]; + int nRet = 0; + bool bFound = false; + for (xmlAttrPtr pAttribute = pXmlNode->properties; pAttribute; pAttribute = pAttribute->next) + { + if (oconvert(pAttribute->name) == pAttributeName) + { + bFound = true; + break; + } + ++nRet; + } + xmlXPathFreeObject(pXmlObj); + CPPUNIT_ASSERT_MESSAGE(OString(OString::Concat("In <") + pXmlDoc->name + ">, XPath '" + pXPath + + "' attribute '" + pAttributeName + "' not found") + .getStr(), + bFound); + return nRet; +} + void XmlTestTools::assertXPathNodeName(const xmlDocUniquePtr& pXmlDoc, const char* pXPath, std::string_view rExpectedName) { commit da3545181012c3546ba49d70a5ba8fabdeb641d2 Author: [email protected] <[email protected]> AuthorDate: Thu Jan 15 16:58:32 2026 -0500 Commit: Andras Timar <[email protected]> CommitDate: Wed Jan 21 16:45:23 2026 +0100 tdf#170322 docx export: close blockSDT when frame is done If a framePr frame contains (only) a blockSDT, then it was NOT closing the SDT element that it had started, and the popFromTableExportContext guard was resetting m_aParagraphSdt.m_bStartedSdt to false, and thus it was NEVER getting closed, creating invalid XML. make CppunitTest_sw_layoutwriter4 CPPUNIT_TEST_NAME=testTdf159259 Change-Id: I882686474346accc56b95322e39c0a2c4756d21f Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197410 Reviewed-by: Justin Luth <[email protected]> Tested-by: Jenkins Signed-off-by: Xisco Fauli <[email protected]> Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197556 diff --git a/sw/qa/extras/layout/layout4.cxx b/sw/qa/extras/layout/layout4.cxx index 37a32c3ab533..ebe72e405b53 100644 --- a/sw/qa/extras/layout/layout4.cxx +++ b/sw/qa/extras/layout/layout4.cxx @@ -1122,11 +1122,11 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter4, testTdf159259) CPPUNIT_ASSERT_EQUAL(paraHeight, flyHeight); // tdf#170322: MS Word considers the document corrupt if a plainText control contains a field - // save(TestFilter::DOCX); - // xmlDocUniquePtr pXmlDocument = parseExport(u"word/document.xml"_ustr); - // assertXPath(pXmlDocument, "//w:sdt/w:sdtPr", 1); - // // the sdtPr must be a richText control, not plainText - // assertXPath(pXmlDocument, "//w:sdt/w:sdtPr/w:text", 0); + save(TestFilter::DOCX); + xmlDocUniquePtr pXmlDocument = parseExport(u"word/document.xml"_ustr); + assertXPath(pXmlDocument, "//w:sdt/w:sdtPr", 1); + // the sdtPr must be a richText control, not plainText + assertXPath(pXmlDocument, "//w:sdt/w:sdtPr/w:text", 0); } CPPUNIT_TEST_FIXTURE(SwLayoutWriter4, testLargeTopParaMarginAfterHiddenSection) diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx b/sw/source/filter/ww8/docxattributeoutput.cxx index de9da3af3c80..271cdc9b381b 100644 --- a/sw/source/filter/ww8/docxattributeoutput.cxx +++ b/sw/source/filter/ww8/docxattributeoutput.cxx @@ -1292,7 +1292,10 @@ void DocxAttributeOutput::EndParagraph( const ww8::WW8TableNodeInfoInner::Pointe { DocxTableExportContext aTableExportContext(*this); m_aFramePr.SetFrame(pFrame.get(), !m_xTableWrt ? -1 : m_tableReference.m_nTableDepth); + const bool bOldStartedSdt = m_aParagraphSdt.m_bStartedSdt; m_rExport.SdrExporter().writeOnlyTextOfFrame(pFrame.get()); + if (!bOldStartedSdt && m_aParagraphSdt.m_bStartedSdt) + m_aParagraphSdt.EndSdtBlock(m_pSerializer); m_aFramePr.SetFrame(nullptr); } commit e8bc4c66cd89461d70b1733f6e6526c322bf3ed6 Author: Samuel Mehrbrodt <[email protected]> AuthorDate: Mon Jan 12 14:52:42 2026 +0100 Commit: Andras Timar <[email protected]> CommitDate: Wed Jan 21 16:45:22 2026 +0100 tdf#170340: Fix drag&drop with multiselection from extensions When registering drag gesture listeners via an extension, the selection handling code did not recognize that as "drag mode" being set. Dragging multiple elements did not work consequently. Change this and consider registered drag gesture listeners also as "drag mode" being set. Change-Id: I37de30ad3c385001f08988d462f6e7dfb3f0183c Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197117 Reviewed-by: Stephan Bergmann <[email protected]> Tested-by: Jenkins (cherry picked from commit 2173a79c691f793a6a6f55353d406e6c71c41e09) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197219 Reviewed-by: Thorsten Behrens <[email protected]> diff --git a/include/vcl/dndlistenercontainer.hxx b/include/vcl/dndlistenercontainer.hxx index a359796d1894..790c9b28aefc 100644 --- a/include/vcl/dndlistenercontainer.hxx +++ b/include/vcl/dndlistenercontainer.hxx @@ -79,6 +79,9 @@ public: virtual void SAL_CALL removeDragGestureListener( const css::uno::Reference< css::datatransfer::dnd::XDragGestureListener >& dgl ) override; virtual void SAL_CALL resetRecognizer( ) override; + // Helper method to check if there are any drag gesture listeners registered + bool hasDragGestureListeners() const; + /* * XDropTargetDragContext */ diff --git a/include/vcl/seleng.hxx b/include/vcl/seleng.hxx index a0f069e04c1e..5f5498a6caec 100644 --- a/include/vcl/seleng.hxx +++ b/include/vcl/seleng.hxx @@ -91,6 +91,8 @@ private: inline bool ShouldDeselect( bool bModifierKey1 ) const; // determines to deselect or not when Ctrl-key is pressed on CursorPosChanging + bool IsDragEnabled() const; + // checks if dragging is enabled via flag or drag gesture listeners public: SelectionEngine( vcl::Window* pWindow, diff --git a/vcl/source/window/dndlistenercontainer.cxx b/vcl/source/window/dndlistenercontainer.cxx index f3f5b6cda0e5..1970bfcb524f 100644 --- a/vcl/source/window/dndlistenercontainer.cxx +++ b/vcl/source/window/dndlistenercontainer.cxx @@ -50,6 +50,12 @@ void SAL_CALL DNDListenerContainer::resetRecognizer( ) { } +bool DNDListenerContainer::hasDragGestureListeners() const +{ + std::unique_lock g(m_aMutex); + return maDragGestureListeners.getLength(g) > 0; +} + void SAL_CALL DNDListenerContainer::addDropTargetListener( const Reference< XDropTargetListener >& dtl ) { std::unique_lock g(m_aMutex); diff --git a/vcl/source/window/seleng.cxx b/vcl/source/window/seleng.cxx index a22ecaa7c5dd..05a5859bdbda 100644 --- a/vcl/source/window/seleng.cxx +++ b/vcl/source/window/seleng.cxx @@ -20,6 +20,7 @@ #include <vcl/commandevent.hxx> #include <vcl/window.hxx> #include <vcl/seleng.hxx> +#include <vcl/dndlistenercontainer.hxx> #include <comphelper/lok.hxx> #include <sal/log.hxx> @@ -32,6 +33,22 @@ inline bool SelectionEngine::ShouldDeselect( bool bModifierKey1 ) const return eSelMode != SelectionMode::Multiple || !bModifierKey1; } +bool SelectionEngine::IsDragEnabled() const +{ + // Check if drag is enabled via flag + if (nFlags & SelectionEngineFlags::DRG_ENAB) + return true; + + // Extensions might have registered drag gesture listeners + // while the drag flag is not set - in this case we also + // want to allow drag operations. Otherwise D&D from + // extensions would not work properly (esp. with multiple selection). + if (!pWin) + return false; + rtl::Reference<DNDListenerContainer> rDropTarget = pWin->GetDropTarget(); + return rDropTarget.is() && rDropTarget->hasDragGestureListeners(); +} + // TODO: throw out FunctionSet::SelectAtPoint SelectionEngine::SelectionEngine( vcl::Window* pWindow, FunctionSet* pFuncSet ) : @@ -153,8 +170,9 @@ bool SelectionEngine::SelMouseButtonDown( const MouseEvent& rMEvt ) case 0: // KEY_NO_KEY { bool bSelAtPoint = pFunctionSet->IsSelectionAtPoint( aPos ); + bool bDragEnabled = IsDragEnabled(); nFlags &= ~SelectionEngineFlags::IN_ADD; - if ( (nFlags & SelectionEngineFlags::DRG_ENAB) && bSelAtPoint ) + if ( bDragEnabled && bSelAtPoint ) { nFlags |= SelectionEngineFlags::WAIT_UPEVT; nFlags &= ~SelectionEngineFlags::IN_SEL; @@ -171,7 +189,7 @@ bool SelectionEngine::SelMouseButtonDown( const MouseEvent& rMEvt ) } pFunctionSet->SetCursorAtPoint( aPos ); // special case Single-Selection, to enable simple Select+Drag - if (eSelMode == SelectionMode::Single && (nFlags & SelectionEngineFlags::DRG_ENAB)) + if (eSelMode == SelectionMode::Single && bDragEnabled) nFlags |= SelectionEngineFlags::WAIT_UPEVT; return true; } @@ -375,7 +393,7 @@ bool SelectionEngine::Command( const CommandEvent& rCEvt ) return false; nFlags |= SelectionEngineFlags::CMDEVT; - if ( nFlags & SelectionEngineFlags::DRG_ENAB ) + if ( IsDragEnabled() ) { SAL_WARN_IF( !rCEvt.IsMouseEvent(), "vcl", "STARTDRAG: Not a MouseEvent" ); if ( pFunctionSet->IsSelectionAtPoint( rCEvt.GetMousePosPixel() ) ) commit 8a9afc3fa143c70207ac4fed2182112638c4b98c Author: Justin Luth <[email protected]> AuthorDate: Wed Jan 14 15:16:10 2026 -0500 Commit: Andras Timar <[email protected]> CommitDate: Wed Jan 21 16:45:22 2026 +0100 tdf#170322 writerfilter: import fields into richText, not plainText MS Word complains that documents are corrupt if a <w:text/> w:sdt (plainText content control) contains a field. This failing was brought to our attention by 24.2.2 commit 13a11632014ccc27199667c6a1e313f8ff616d6d "tdf#159259: make sure to set FieldStartRange in sdt helper" One 'field' that is NOT adversely affected is a hyperlink, but I don't see how to identify that, since m_pImpl->GetTopFieldContext()->GetFieldId() was optional-has_no_value at this point as seen with ooxmlexport3's glossaryWithEmail.docx. But I can't really imagine a big problem turning a plainText into a richText by accident for the cases where it is not strictly necessary. In fact, I expect that ALL unknown types ought to be richText instead of plainText, since by definition not specifying anything is a richText control. make CppunitTest_sw_layoutwriter4 CPPUNIT_TEST_NAME=testTdf159259 except that this was not writing an ending </w:sdt>... Change-Id: Ib393f8eaf024d332be3f3603f72ef644fb5fe8c7 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197300 Tested-by: Jenkins Reviewed-by: Justin Luth <[email protected]> Signed-off-by: Xisco Fauli <[email protected]> Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197555 diff --git a/sw/qa/extras/layout/layout4.cxx b/sw/qa/extras/layout/layout4.cxx index b1e3897acd12..37a32c3ab533 100644 --- a/sw/qa/extras/layout/layout4.cxx +++ b/sw/qa/extras/layout/layout4.cxx @@ -1120,6 +1120,13 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter4, testTdf159259) CPPUNIT_ASSERT_EQUAL(paraRight, flyRight); // The fly is right-aligned CPPUNIT_ASSERT_EQUAL(paraHeight, flyHeight); + + // tdf#170322: MS Word considers the document corrupt if a plainText control contains a field + // save(TestFilter::DOCX); + // xmlDocUniquePtr pXmlDocument = parseExport(u"word/document.xml"_ustr); + // assertXPath(pXmlDocument, "//w:sdt/w:sdtPr", 1); + // // the sdtPr must be a richText control, not plainText + // assertXPath(pXmlDocument, "//w:sdt/w:sdtPr/w:text", 0); } CPPUNIT_TEST_FIXTURE(SwLayoutWriter4, testLargeTopParaMarginAfterHiddenSection) diff --git a/sw/source/writerfilter/dmapper/DomainMapper.cxx b/sw/source/writerfilter/dmapper/DomainMapper.cxx index c9598ebad25f..98ed01303c07 100644 --- a/sw/source/writerfilter/dmapper/DomainMapper.cxx +++ b/sw/source/writerfilter/dmapper/DomainMapper.cxx @@ -4884,6 +4884,8 @@ void DomainMapper::lcl_utext(const sal_Unicode *const data_, size_t len) m_pImpl->m_pSdtHelper->createPlainTextControl(); else if (!m_pImpl->m_pSdtHelper->isFieldStartRangeSet()) m_pImpl->m_pSdtHelper->setFieldStartRange(GetCurrentTextRange()->getEnd()); + // MS Word says plainText control containing a field is a corrupt file + m_pImpl->m_pSdtHelper->setControlType(SdtControlType::richText); } m_pImpl->AppendFieldCommand(sText); } commit 0bfc1ebdffb364cb570903cc091a9ad7fca10475 Author: Noel Grandin <[email protected]> AuthorDate: Wed Jan 14 19:02:11 2026 +0200 Commit: Andras Timar <[email protected]> CommitDate: Wed Jan 21 16:45:22 2026 +0100 officeotron: tabIndex=-1 is invalid since the value is constrained by the spec to be unsigned. Change-Id: I9b40b6fc9634cbe5c0e848ba15c9ce7918b2f067 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197291 Tested-by: Jenkins Reviewed-by: Michael Stahl <[email protected]> Signed-off-by: Xisco Fauli <[email protected]> Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197431 diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx index 28ed1b321bad..bddf3715c7bd 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport17.cxx @@ -427,14 +427,10 @@ CPPUNIT_TEST_FIXTURE(Test, testDateContentControlExport) xContentControlProps->setPropertyValue(u"Alias"_ustr, uno::Any(u"myalias"_ustr)); xContentControlProps->setPropertyValue(u"Tag"_ustr, uno::Any(u"mytag"_ustr)); xContentControlProps->setPropertyValue(u"Id"_ustr, uno::Any(static_cast<sal_Int32>(123))); - xContentControlProps->setPropertyValue(u"TabIndex"_ustr, uno::Any(sal_uInt32(4294967295))); // -1 xContentControlProps->setPropertyValue(u"Lock"_ustr, uno::Any(u"sdtLocked"_ustr)); xText->insertTextContent(xCursor, xContentControl, /*bAbsorb=*/true); - // FIXME: validation error in OOXML export: Errors: 2 - skipValidation(); - // When exporting to DOCX: save(TestFilter::DOCX); @@ -457,7 +453,6 @@ CPPUNIT_TEST_FIXTURE(Test, testDateContentControlExport) assertXPath(pXmlDoc, "//w:sdt/w:sdtPr/w:alias", "val", u"myalias"); assertXPath(pXmlDoc, "//w:sdt/w:sdtPr/w:tag", "val", u"mytag"); assertXPath(pXmlDoc, "//w:sdt/w:sdtPr/w:id", "val", u"123"); - assertXPath(pXmlDoc, "//w:sdt/w:sdtPr/w:tabIndex", "val", u"-1"); assertXPath(pXmlDoc, "//w:sdt/w:sdtPr/w:lock", "val", u"sdtLocked"); } diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx b/sw/source/filter/ww8/docxattributeoutput.cxx index 3668c9b1aef5..de9da3af3c80 100644 --- a/sw/source/filter/ww8/docxattributeoutput.cxx +++ b/sw/source/filter/ww8/docxattributeoutput.cxx @@ -2720,8 +2720,9 @@ void DocxAttributeOutput::WriteContentControlStart() { // write the unsigned value as if it were signed since that is all we can import const sal_Int32 nTabIndex = static_cast<sal_Int32>(m_pContentControl->GetTabIndex()); - m_pSerializer->singleElementNS(XML_w, XML_tabIndex, FSNS(XML_w, XML_val), - OString::number(nTabIndex)); + if (nTabIndex != -1) + m_pSerializer->singleElementNS(XML_w, XML_tabIndex, FSNS(XML_w, XML_val), + OString::number(nTabIndex)); } if (m_pContentControl->GetPicture()) commit c35f28cbbe31401112ce5635f2483126cb963123 Author: Caolán McNamara <[email protected]> AuthorDate: Fri Jan 16 11:39:28 2026 +0000 Commit: Andras Timar <[email protected]> CommitDate: Wed Jan 21 16:45:22 2026 +0100 reuse comphelper::rng::uniform_uint_distribution see also: commit 6d9228d6b14d968fa92df3ca018a555f8652e579 Date: Fri May 3 14:17:27 2024 +0100 lok: reseed comphelper's random number generator on fork. Also avoid std::random_device it doesn't work in a COOL kit process. Change-Id: I0ea3607a689632bf75861d367db70ac6c7f40a90 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197443 Tested-by: Jenkins Reviewed-by: Caolán McNamara <[email protected]> (cherry picked from commit 0e439c7289a3a0679929a9baf7ee41c8f31e6d96) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197471 diff --git a/oox/source/ole/vbaexport.cxx b/oox/source/ole/vbaexport.cxx index 049c8fc1e9a9..7131c8755d13 100644 --- a/oox/source/ole/vbaexport.cxx +++ b/oox/source/ole/vbaexport.cxx @@ -10,7 +10,6 @@ #include <sal/config.h> #include <cassert> -#include <random> #include <string_view> #include <oox/ole/vbaexport.hxx> @@ -30,6 +29,7 @@ #include <sot/storage.hxx> +#include <comphelper/random.hxx> #include <comphelper/xmltools.hxx> #include <utility> #include <rtl/tencinfo.h> @@ -376,14 +376,10 @@ VBAEncryption::VBAEncryption(const sal_uInt8* pData, const sal_uInt16 length, ,mnEncryptedByte2(0) ,mnProjKey(nProjKey) ,mnIgnoredLength(0) - ,mnSeed(0x00) + ,mnSeed(comphelper::rng::uniform_uint_distribution(0, 255)) ,mnVersionEnc(0) ,meTextEncoding(eTextEncoding) { - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_int_distribution<> dis(0, 255); - mnSeed = dis(gen); } void VBAEncryption::writeSeed() commit bfc56d2e40705287a497dcbd7273932ebdf01be4 Author: Ilmari Lauhakangas <[email protected]> AuthorDate: Sat Jan 17 19:06:34 2026 +0200 Commit: Andras Timar <[email protected]> CommitDate: Wed Jan 21 16:45:22 2026 +0100 tdf#168132 Make sure ranges do not show as disabled in Calc's External Data dialog Change-Id: I5cc64cea920f778e33f0a3e28e082839ceb8314a Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197509 Reviewed-by: Noel Grandin <[email protected]> Tested-by: Ilmari Lauhakangas <[email protected]> Reviewed-by: Ilmari Lauhakangas <[email protected]> Tested-by: Jenkins (cherry picked from commit c6028a9ce60f25027b2901e9cc03b3a8f8883810) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197520 diff --git a/sc/source/ui/miscdlgs/linkarea.cxx b/sc/source/ui/miscdlgs/linkarea.cxx index ca559e626aff..c02610c4fc30 100644 --- a/sc/source/ui/miscdlgs/linkarea.cxx +++ b/sc/source/ui/miscdlgs/linkarea.cxx @@ -266,8 +266,10 @@ void ScLinkedAreaDlg::UpdateSourceRanges() m_xLbRanges->thaw(); - if (m_xLbRanges->n_children() >= 1) + if (m_xLbRanges->n_children() >= 1) { m_xLbRanges->select(0); + m_xLbRanges->set_sensitive(true); + } else { m_xLbRanges->append_text(ScResId(STR_NO_NAMED_RANGES_AVAILABLE));
